-- ============================================================
-- Phase 6: 选址/空间参数化函数
-- 替换 _april 后缀的选址/空间视图为参数化函数
-- 依赖: fn_store_area_efficiency(p_month) 已在 Phase 2 中创建
-- ============================================================

-- 1. fn_store_site_profile(p_month) — 替换 v_store_site_profile_april
-- 依赖: fn_store_area_efficiency
CREATE OR REPLACE FUNCTION analytics.fn_store_site_profile(p_month date)
RETURNS TABLE (
  store_code text, store_name text, bill_count bigint, active_days bigint,
  received numeric, avg_daily_received numeric, avg_bill_value numeric,
  discount_rate_pct numeric, theoretical_margin_pct numeric, member_bill_share_pct numeric,
  items_per_bill numeric, skus_per_bill numeric, delivery_bill_share_pct numeric,
  noodle_snack_attach_pct numeric, noodle_drink_attach_pct numeric, noodle_cold_attach_pct numeric,
  combo_bill_share_pct numeric, theoretical_cost numeric, actual_food_cost numeric,
  food_cost_variance numeric, theoretical_cost_rate_pct numeric, actual_food_cost_rate_pct numeric,
  variance_to_theoretical_pct numeric, comparison_status text, variance_level text,
  identified_members bigint, repeat_rate_pct numeric, repeat_revenue_share_pct numeric,
  benchmark_score numeric, meituan_received numeric, taobao_received numeric, jd_received numeric,
  combined_platform_cost_rate_pct numeric, business_type text, scale_tier text,
  problem_count bigint, problem_combination text, action_priority text,
  area_sqm numeric, business_address text, city text, district text,
  latitude_gcj02 double precision, longitude_gcj02 double precision,
  open_date date, lease_expiry_date date, store_age_years numeric,
  monthly_received_per_sqm numeric, daily_received_per_sqm numeric,
  estimated_inventory_days numeric, site_scene text, floor_type text,
  area_band text, age_band text
) AS $$
  SELECT
    a.store_code, a.store_name, a.bill_count, a.active_days,
    a.received, a.avg_daily_received, a.avg_bill_value,
    a.discount_rate_pct, a.theoretical_margin_pct, a.member_bill_share_pct,
    a.items_per_bill, a.skus_per_bill, a.delivery_bill_share_pct,
    a.noodle_snack_attach_pct, a.noodle_drink_attach_pct, a.noodle_cold_attach_pct,
    a.combo_bill_share_pct, a.theoretical_cost, a.actual_food_cost,
    a.food_cost_variance, a.theoretical_cost_rate_pct, a.actual_food_cost_rate_pct,
    a.variance_to_theoretical_pct, a.comparison_status, a.variance_level,
    a.identified_members, a.repeat_rate_pct, a.repeat_revenue_share_pct,
    a.benchmark_score, a.meituan_received, a.taobao_received, a.jd_received,
    a.combined_platform_cost_rate_pct, a.business_type, a.scale_tier,
    a.problem_count, a.problem_combination, a.action_priority,
    a.area_sqm, a.business_address, a.city, a.district,
    a.latitude_gcj02, a.longitude_gcj02,
    a.open_date, a.lease_expiry_date, a.store_age_years,
    a.monthly_received_per_sqm, a.daily_received_per_sqm,
    a.estimated_inventory_days,
    CASE
      WHEN a.business_type = '特殊业态' THEN '特殊业态'
      WHEN a.business_address ~ '机场|航站楼' THEN '交通枢纽'
      WHEN a.business_address ~ '大学|食堂|档口' THEN '校园档口'
      WHEN a.business_address ~ '总部|科技园|产业园|创业园|商务楼|写字楼|信息产业基地|生命科学园|自贸试验区|经海|荣华' THEN '办公园区'
      WHEN a.business_address ~ '商场|商城|购物|超市|万科|龙湖|大悦|搜秀|美食城|商业大厦|商铺' THEN '商场商业体'
      WHEN a.business_address ~ '社区|小区|家园|里|园一区|园东街' THEN '社区居民'
      ELSE '街边综合'
    END AS site_scene,
    CASE
      WHEN a.business_address ~ '地下一层|负一层|-1层|-1至|B1|b1' THEN '地下层'
      WHEN a.business_address ~ '二层|2层|四层|4层|4F|五层|5层|23层' THEN '非首层'
      WHEN a.business_address ~ '一层|1层|底商' THEN '首层'
      ELSE '楼层不明'
    END AS floor_type,
    CASE
      WHEN a.area_sqm IS NULL THEN '面积缺失'
      WHEN a.area_sqm <= 180 THEN '≤180㎡'
      WHEN a.area_sqm <= 250 THEN '181-250㎡'
      WHEN a.area_sqm <= 350 THEN '251-350㎡'
      WHEN a.area_sqm <= 500 THEN '351-500㎡'
      ELSE '>500㎡'
    END AS area_band,
    CASE
      WHEN a.store_age_years IS NULL THEN '店龄缺失'
      WHEN a.store_age_years < 1 THEN '新店<1年'
      WHEN a.store_age_years < 3 THEN '成长期1-3年'
      WHEN a.store_age_years < 8 THEN '成熟期3-8年'
      ELSE '老店≥8年'
    END AS age_band
  FROM analytics.fn_store_area_efficiency(p_month) a
$$ LANGUAGE SQL STABLE;

-- 2. fn_store_spatial_pairs(p_month) — 替换 v_store_spatial_pairs_april
CREATE OR REPLACE FUNCTION analytics.fn_store_spatial_pairs(p_month date)
RETURNS TABLE (
  store_code_a text, store_name_a text, store_code_b text, store_name_b text,
  district_a text, district_b text, scene_a text, scene_b text,
  priority_a text, priority_b text, received_a numeric, received_b numeric,
  sqm_efficiency_a numeric, sqm_efficiency_b numeric, distance_km double precision,
  proximity_level text
) AS $$
  WITH physical AS (
    SELECT store_code, store_name, district, site_scene, action_priority,
      received, monthly_received_per_sqm, latitude_gcj02 AS lat, longitude_gcj02 AS lon
    FROM analytics.fn_store_site_profile(p_month)
    WHERE latitude_gcj02 IS NOT NULL AND longitude_gcj02 IS NOT NULL
  ), pairs AS (
    SELECT
      a.store_code AS store_code_a, a.store_name AS store_name_a,
      b.store_code AS store_code_b, b.store_name AS store_name_b,
      a.district AS district_a, b.district AS district_b,
      a.site_scene AS scene_a, b.site_scene AS scene_b,
      a.action_priority AS priority_a, b.action_priority AS priority_b,
      a.received AS received_a, b.received AS received_b,
      a.monthly_received_per_sqm AS sqm_efficiency_a, b.monthly_received_per_sqm AS sqm_efficiency_b,
      6371.0 * acos(LEAST(1.0, GREATEST(-1.0,
        cos(radians(a.lat)) * cos(radians(b.lat)) * cos(radians(b.lon - a.lon))
        + sin(radians(a.lat)) * sin(radians(b.lat))
      ))) AS distance_km
    FROM physical a JOIN physical b ON a.store_code < b.store_code
  )
  SELECT
    pairs.store_code_a, pairs.store_name_a, pairs.store_code_b, pairs.store_name_b,
    pairs.district_a, pairs.district_b, pairs.scene_a, pairs.scene_b,
    pairs.priority_a, pairs.priority_b, pairs.received_a, pairs.received_b,
    pairs.sqm_efficiency_a, pairs.sqm_efficiency_b, pairs.distance_km,
    CASE
      WHEN pairs.distance_km < 1 THEN '高度重叠<1km'
      WHEN pairs.distance_km < 2 THEN '较高重叠1-2km'
      WHEN pairs.distance_km < 3 THEN '观察2-3km'
      ELSE '相对独立≥3km'
    END AS proximity_level
  FROM pairs
$$ LANGUAGE SQL STABLE;

-- 3. fn_store_nearest_neighbor(p_month) — 替换 v_store_nearest_neighbor_april
CREATE OR REPLACE FUNCTION analytics.fn_store_nearest_neighbor(p_month date)
RETURNS TABLE (
  store_code text, store_name text, nearest_store_code text, nearest_store_name text,
  nearest_distance_km numeric, nearest_proximity_level text
) AS $$
  WITH directed AS (
    SELECT store_code_a AS store_code, store_name_a AS store_name,
      store_code_b AS nearest_store_code, store_name_b AS nearest_store_name, distance_km
    FROM analytics.fn_store_spatial_pairs(p_month)
    UNION ALL
    SELECT store_code_b, store_name_b, store_code_a, store_name_a, distance_km
    FROM analytics.fn_store_spatial_pairs(p_month)
  ), ranked AS (
    SELECT store_code, store_name, nearest_store_code, nearest_store_name, distance_km,
      row_number() OVER (PARTITION BY store_code ORDER BY distance_km) AS rn
    FROM directed
  )
  SELECT store_code, store_name, nearest_store_code, nearest_store_name,
    round(distance_km::numeric, 2) AS nearest_distance_km,
    CASE
      WHEN distance_km < 1 THEN '高度重叠<1km'
      WHEN distance_km < 2 THEN '较高重叠1-2km'
      WHEN distance_km < 3 THEN '观察2-3km'
      ELSE '相对独立≥3km'
    END AS nearest_proximity_level
  FROM ranked WHERE rn = 1
$$ LANGUAGE SQL STABLE;

-- 4. fn_store_site_replication(p_month) — 替换 v_store_site_replication_score_april
CREATE OR REPLACE FUNCTION analytics.fn_store_site_replication(p_month date)
RETURNS TABLE (
  store_code text, store_name text, bill_count bigint, active_days bigint,
  received numeric, avg_daily_received numeric, avg_bill_value numeric,
  discount_rate_pct numeric, theoretical_margin_pct numeric, member_bill_share_pct numeric,
  items_per_bill numeric, skus_per_bill numeric, delivery_bill_share_pct numeric,
  noodle_snack_attach_pct numeric, noodle_drink_attach_pct numeric, noodle_cold_attach_pct numeric,
  combo_bill_share_pct numeric, theoretical_cost numeric, actual_food_cost numeric,
  food_cost_variance numeric, theoretical_cost_rate_pct numeric, actual_food_cost_rate_pct numeric,
  variance_to_theoretical_pct numeric, comparison_status text, variance_level text,
  identified_members bigint, repeat_rate_pct numeric, repeat_revenue_share_pct numeric,
  benchmark_score numeric, meituan_received numeric, taobao_received numeric, jd_received numeric,
  combined_platform_cost_rate_pct numeric, business_type text, scale_tier text,
  problem_count bigint, problem_combination text, action_priority text,
  area_sqm numeric, business_address text, city text, district text,
  latitude_gcj02 double precision, longitude_gcj02 double precision,
  open_date date, lease_expiry_date date, store_age_years numeric,
  monthly_received_per_sqm numeric, daily_received_per_sqm numeric,
  estimated_inventory_days numeric, site_scene text, floor_type text,
  area_band text, age_band text,
  nearest_store_code text, nearest_store_name text, nearest_distance_km numeric,
  sqm_score double precision, daily_score double precision, repeat_score double precision,
  discount_score double precision, cost_score double precision, platform_score double precision,
  execution_score numeric, site_replication_score numeric,
  replication_recommendation text, spatial_recommendation text
) AS $$
  WITH eligible AS (
    SELECT
      p.*, n.nearest_store_code, n.nearest_store_name, n.nearest_distance_km,
      percent_rank() OVER (ORDER BY p.monthly_received_per_sqm) AS sqm_score,
      percent_rank() OVER (ORDER BY p.avg_daily_received) AS daily_score,
      percent_rank() OVER (ORDER BY p.repeat_rate_pct NULLS FIRST) AS repeat_score,
      1.0 - percent_rank() OVER (ORDER BY p.discount_rate_pct) AS discount_score,
      1.0 - percent_rank() OVER (ORDER BY p.actual_food_cost_rate_pct) AS cost_score,
      1.0 - percent_rank() OVER (ORDER BY p.combined_platform_cost_rate_pct) AS platform_score,
      GREATEST(0, 1 - p.problem_count / 6.0) AS execution_score
    FROM analytics.fn_store_site_profile(p_month) p
    LEFT JOIN analytics.fn_store_nearest_neighbor(p_month) n USING (store_code, store_name)
    WHERE p.business_type = '标准门店' AND p.received > 0 AND p.area_sqm IS NOT NULL
  ), scored AS (
    SELECT eligible.*,
      round((0.30 * sqm_score + 0.20 * daily_score + 0.15 * repeat_score
        + 0.10 * discount_score + 0.10 * cost_score + 0.10 * platform_score
        + 0.05 * execution_score::double precision)::numeric * 100, 2) AS site_replication_score
    FROM eligible
  )
  SELECT scored.*,
    CASE
      WHEN site_replication_score >= 75 AND problem_count <= 1 THEN '优先提炼选址原型'
      WHEN site_replication_score >= 60 THEN '可作为同类参考'
      WHEN site_replication_score < 40 THEN '不宜作为选址标杆'
      ELSE '观察验证'
    END AS replication_recommendation,
    CASE
      WHEN nearest_distance_km >= 3 AND site_replication_score >= 70 THEN '高表现且周边相对独立，可研究相似商圈扩张'
      WHEN nearest_distance_km < 1.5 THEN '邻店较近，新址需重点防止同店分流'
      ELSE '常规评估'
    END AS spatial_recommendation
  FROM scored
$$ LANGUAGE SQL STABLE;

-- 5. fn_store_overlap_risk(p_month) — 替换 v_store_location_overlap_risk_april
CREATE OR REPLACE FUNCTION analytics.fn_store_overlap_risk(p_month date)
RETURNS TABLE (
  store_code_a text, store_name_a text, store_code_b text, store_name_b text,
  district_a text, district_b text, scene_a text, scene_b text,
  priority_a text, priority_b text, received_a numeric, received_b numeric,
  sqm_efficiency_a numeric, sqm_efficiency_b numeric, distance_km double precision,
  proximity_level text, problem_count_a bigint, problem_count_b bigint,
  overlap_risk text
) AS $$
  SELECT
    p.store_code_a, p.store_name_a, p.store_code_b, p.store_name_b,
    p.district_a, p.district_b, p.scene_a, p.scene_b,
    p.priority_a, p.priority_b, p.received_a, p.received_b,
    p.sqm_efficiency_a, p.sqm_efficiency_b, p.distance_km, p.proximity_level,
    a.problem_count AS problem_count_a, b.problem_count AS problem_count_b,
    CASE
      WHEN p.distance_km < 1 AND (a.action_priority LIKE 'P0%' OR b.action_priority LIKE 'P0%'
        OR a.action_priority = 'P1-重点整改' OR b.action_priority = 'P1-重点整改') THEN '高风险：距离近且至少一家经营承压'
      WHEN p.distance_km < 1.5 THEN '中风险：需核查客群和配送圈重叠'
      ELSE '观察'
    END AS overlap_risk
  FROM analytics.fn_store_spatial_pairs(p_month) p
  JOIN analytics.fn_store_site_profile(p_month) a ON a.store_code = p.store_code_a
  JOIN analytics.fn_store_site_profile(p_month) b ON b.store_code = p.store_code_b
  WHERE p.distance_km < 3
$$ LANGUAGE SQL STABLE;

-- 6. fn_district_site_benchmark(p_month) — 替换 v_district_site_benchmark_april
CREATE OR REPLACE FUNCTION analytics.fn_district_site_benchmark(p_month date)
RETURNS TABLE (
  city text, district text, store_count bigint, avg_area_sqm numeric,
  total_received numeric, avg_received numeric, median_received numeric,
  avg_received_per_sqm numeric, avg_bill_value numeric, avg_discount_rate_pct numeric,
  avg_repeat_rate_pct numeric, avg_actual_cost_rate_pct numeric,
  avg_platform_cost_rate_pct numeric, p0_count bigint, p1_count bigint
) AS $$
  SELECT
    city, district,
    count(*) AS store_count,
    round(avg(area_sqm), 1) AS avg_area_sqm,
    round(sum(received), 2) AS total_received,
    round(avg(received), 2) AS avg_received,
    round(percentile_cont(0.5) WITHIN GROUP (ORDER BY received::double precision)::numeric, 2) AS median_received,
    round(avg(monthly_received_per_sqm), 2) AS avg_received_per_sqm,
    round(avg(avg_bill_value), 2) AS avg_bill_value,
    round(avg(discount_rate_pct), 2) AS avg_discount_rate_pct,
    round(avg(repeat_rate_pct), 2) AS avg_repeat_rate_pct,
    round(avg(actual_food_cost_rate_pct) FILTER (WHERE comparison_status = '可比'), 2) AS avg_actual_cost_rate_pct,
    round(avg(combined_platform_cost_rate_pct), 2) AS avg_platform_cost_rate_pct,
    count(*) FILTER (WHERE action_priority LIKE 'P0%') AS p0_count,
    count(*) FILTER (WHERE action_priority = 'P1-重点整改') AS p1_count
  FROM analytics.fn_store_site_profile(p_month)
  WHERE business_type = '标准门店' AND received > 0
  GROUP BY city, district
$$ LANGUAGE SQL STABLE;

-- 7. fn_site_segment_benchmark(p_month) — 替换 v_site_segment_benchmark_april
CREATE OR REPLACE FUNCTION analytics.fn_site_segment_benchmark(p_month date)
RETURNS TABLE (
  site_scene text, area_band text, store_count bigint, avg_area_sqm numeric,
  avg_received numeric, median_received numeric, avg_received_per_sqm numeric,
  median_received_per_sqm numeric, avg_bill_value numeric, avg_discount_rate_pct numeric,
  avg_repeat_rate_pct numeric, avg_actual_cost_rate_pct numeric,
  avg_delivery_share_pct numeric, avg_drink_attach_pct numeric
) AS $$
  SELECT
    site_scene, area_band,
    count(*) AS store_count,
    round(avg(area_sqm), 1) AS avg_area_sqm,
    round(avg(received), 2) AS avg_received,
    round(percentile_cont(0.5) WITHIN GROUP (ORDER BY received::double precision)::numeric, 2) AS median_received,
    round(avg(monthly_received_per_sqm), 2) AS avg_received_per_sqm,
    round(percentile_cont(0.5) WITHIN GROUP (ORDER BY monthly_received_per_sqm::double precision)::numeric, 2) AS median_received_per_sqm,
    round(avg(avg_bill_value), 2) AS avg_bill_value,
    round(avg(discount_rate_pct), 2) AS avg_discount_rate_pct,
    round(avg(repeat_rate_pct), 2) AS avg_repeat_rate_pct,
    round(avg(actual_food_cost_rate_pct) FILTER (WHERE comparison_status = '可比'), 2) AS avg_actual_cost_rate_pct,
    round(avg(delivery_bill_share_pct), 2) AS avg_delivery_share_pct,
    round(avg(noodle_drink_attach_pct), 2) AS avg_drink_attach_pct
  FROM analytics.fn_store_site_profile(p_month)
  WHERE business_type = '标准门店' AND received > 0 AND area_sqm IS NOT NULL
  GROUP BY site_scene, area_band
$$ LANGUAGE SQL STABLE;

-- 8. fn_dish_member_repeat(p_month) — 替换 v_dish_member_repeat_april
CREATE OR REPLACE FUNCTION analytics.fn_dish_member_repeat(p_month date)
RETURNS TABLE (
  dish_name text, purchasing_members bigint, repeat_members bigint,
  repeat_member_rate_pct numeric, avg_member_orders numeric, member_received_amount numeric
) AS $$
  SELECT
    dish_name,
    count(*) AS purchasing_members,
    count(*) FILTER (WHERE order_count >= 2) AS repeat_members,
    round(count(*) FILTER (WHERE order_count >= 2)::numeric / NULLIF(count(*), 0) * 100, 2) AS repeat_member_rate_pct,
    round(avg(order_count), 2) AS avg_member_orders,
    sum(received_amount) AS member_received_amount
  FROM analytics.fn_dish_member_sku(p_month)
  GROUP BY dish_name
$$ LANGUAGE SQL STABLE;
