515 lines
21 KiB
Python
Executable File
515 lines
21 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
import argparse
|
||
import subprocess
|
||
import sys
|
||
|
||
|
||
def _sql_escape(value: str) -> str:
|
||
return value.replace("'", "''")
|
||
|
||
|
||
def build_sql(
|
||
product_no: str,
|
||
province: str,
|
||
city: str,
|
||
district: str,
|
||
days: int,
|
||
warehouse_transfer_fee: float,
|
||
depreciation_rate: float,
|
||
limit: int,
|
||
) -> str:
|
||
pno = _sql_escape(product_no.strip())
|
||
prov = _sql_escape(province.strip())
|
||
city_v = _sql_escape((city or "").strip())
|
||
dist_v = _sql_escape((district or "").strip())
|
||
wh_fee = f"{warehouse_transfer_fee:.2f}"
|
||
dep_rate = f"{depreciation_rate:.4f}"
|
||
return f"""
|
||
USE [JSDataMiddlePlatform];
|
||
SET NOCOUNT ON;
|
||
|
||
DECLARE @product_no nvarchar(120) = N'{pno}';
|
||
DECLARE @province nvarchar(50) = N'{prov}';
|
||
DECLARE @city nvarchar(50) = N'{city_v}';
|
||
DECLARE @district nvarchar(50) = N'{dist_v}';
|
||
DECLARE @days int = {days};
|
||
DECLARE @limit int = {limit};
|
||
DECLARE @warehouse_transfer_fee decimal(18,2) = {wh_fee};
|
||
DECLARE @depreciation_rate decimal(10,4) = {dep_rate};
|
||
DECLARE @start_time datetime2 = DATEADD(day, -@days, GETDATE());
|
||
DECLARE @end_time datetime2 = GETDATE();
|
||
|
||
DECLARE @province_norm nvarchar(50) =
|
||
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(ISNULL(@province, ''), '省', ''), '市', ''), '壮族自治区', ''), '回族自治区', ''), '维吾尔自治区', ''), '自治区', ''), '特别行政区', ''), '地区', ''), ' ', '');
|
||
DECLARE @city_norm nvarchar(50) =
|
||
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(ISNULL(@city, ''), '省', ''), '市', ''), '壮族自治区', ''), '回族自治区', ''), '维吾尔自治区', ''), '自治区', ''), '特别行政区', ''), '地区', ''), ' ', '');
|
||
DECLARE @district_norm nvarchar(50) =
|
||
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(ISNULL(@district, ''), '省', ''), '市', ''), '壮族自治区', ''), '回族自治区', ''), '维吾尔自治区', ''), '自治区', ''), '特别行政区', ''), '地区', ''), ' ', '');
|
||
|
||
IF OBJECT_ID('tempdb..#sku_pool') IS NOT NULL
|
||
DROP TABLE #sku_pool;
|
||
|
||
SELECT DISTINCT
|
||
s.sku_id,
|
||
s.name AS sku_name,
|
||
s.other_7 AS product_no,
|
||
s.i_id AS style_code,
|
||
s.other_1 AS spu
|
||
INTO #sku_pool
|
||
FROM dbo.jst_sku AS s WITH (NOLOCK)
|
||
WHERE ISNULL(NULLIF(s.sku_id, ''), '') <> ''
|
||
AND (
|
||
NULLIF(s.other_7, '') = @product_no
|
||
OR NULLIF(s.i_id, '') = @product_no
|
||
OR NULLIF(s.other_1, '') = @product_no
|
||
OR NULLIF(s.sku_id, '') = @product_no
|
||
OR NULLIF(s.other_7, '') LIKE @product_no + '%'
|
||
);
|
||
|
||
INSERT INTO #sku_pool (sku_id, sku_name, product_no, style_code, spu)
|
||
SELECT DISTINCT
|
||
NULLIF(a.undefined10, '') AS sku_id,
|
||
NULLIF(a.undefined36, '') AS sku_name,
|
||
NULLIF(a.undefined96, '') AS product_no,
|
||
NULL,
|
||
NULL
|
||
FROM dbo.jst_goods_profit_kpi_items_afs AS a WITH (NOLOCK)
|
||
LEFT JOIN #sku_pool AS p
|
||
ON p.sku_id = NULLIF(a.undefined10, '')
|
||
WHERE p.sku_id IS NULL
|
||
AND ISNULL(NULLIF(a.undefined10, ''), '') <> ''
|
||
AND (
|
||
NULLIF(a.undefined96, '') = @product_no
|
||
OR NULLIF(a.undefined96, '') LIKE @product_no + '%'
|
||
);
|
||
|
||
IF OBJECT_ID('tempdb..#order_base') IS NOT NULL
|
||
DROP TABLE #order_base;
|
||
|
||
SELECT
|
||
NULLIF(a.undefined2, '') AS order_id,
|
||
NULLIF(a.undefined10, '') AS sku_id,
|
||
COALESCE(NULLIF(s.other_7, ''), NULLIF(a.undefined96, ''), @product_no) AS product_no,
|
||
ISNULL(NULLIF(a.undefined8, ''), 'unknown') AS shop_name,
|
||
ISNULL(NULLIF(a.undefined73, ''), ISNULL(NULLIF(e.logistic_company_name, ''), '未标注')) AS express_company,
|
||
COALESCE(NULLIF(a.undefined66, ''), NULLIF(s.other_3, ''), '未标注') AS ship_warehouse_name,
|
||
ISNULL(NULLIF(a.undefined74, ''), '') AS province_raw,
|
||
ISNULL(NULLIF(a.undefined75, ''), '') AS city_raw,
|
||
ISNULL(NULLIF(e.receiver_address, ''), '') AS receiver_address,
|
||
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(ISNULL(NULLIF(a.undefined74, ''), ''), '省', ''), '市', ''), '壮族自治区', ''), '回族自治区', ''), '维吾尔自治区', ''), '自治区', ''), '特别行政区', ''), '地区', ''), ' ', '') AS province_norm,
|
||
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(ISNULL(NULLIF(a.undefined75, ''), ''), '省', ''), '市', ''), '壮族自治区', ''), '回族自治区', ''), '维吾尔自治区', ''), '自治区', ''), '特别行政区', ''), '地区', ''), ' ', '') AS city_norm,
|
||
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(ISNULL(NULLIF(e.receiver_address, ''), ''), '省', ''), '市', ''), '壮族自治区', ''), '回族自治区', ''), '维吾尔自治区', ''), '自治区', ''), '特别行政区', ''), '地区', ''), ' ', '') AS receiver_address_norm,
|
||
COALESCE(
|
||
TRY_CONVERT(datetime2, NULLIF(a.undefined6, ''), 120),
|
||
TRY_CONVERT(datetime2, REPLACE(NULLIF(a.undefined6, ''), '/', '-'), 120),
|
||
TRY_CONVERT(datetime2, NULLIF(a.undefined6, ''), 111),
|
||
TRY_CONVERT(datetime2, NULLIF(e.send_time, ''), 120),
|
||
TRY_CONVERT(datetime2, a.createdAt)
|
||
) AS ship_dt,
|
||
COALESCE(
|
||
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined29, '')),
|
||
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined26, '')),
|
||
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined21, '')),
|
||
CAST(0 AS decimal(18, 2))
|
||
) AS sale_amount,
|
||
COALESCE(
|
||
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined31, '')),
|
||
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined22, '')),
|
||
CAST(0 AS decimal(18, 2))
|
||
) AS cost_amount,
|
||
COALESCE(
|
||
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined30, '')),
|
||
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined20, '')),
|
||
CAST(0 AS decimal(18, 2))
|
||
) AS shipped_qty,
|
||
COALESCE(
|
||
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined40, '')),
|
||
CAST(0 AS decimal(18, 2))
|
||
)
|
||
+ COALESCE(
|
||
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined49, '')),
|
||
CAST(0 AS decimal(18, 2))
|
||
) AS return_qty,
|
||
COALESCE(
|
||
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined90, '')),
|
||
CAST(0 AS decimal(18, 2))
|
||
) AS return_freight_fee,
|
||
COALESCE(
|
||
TRY_CONVERT(decimal(18, 2), NULLIF(a.undefined79, '')),
|
||
CAST(0 AS decimal(18, 2))
|
||
) AS warehouse_transfer_fee_obs
|
||
INTO #order_base
|
||
FROM dbo.jst_goods_profit_kpi_items_afs AS a WITH (NOLOCK)
|
||
INNER JOIN #sku_pool AS p
|
||
ON p.sku_id = NULLIF(a.undefined10, '')
|
||
LEFT JOIN dbo.jst_sku AS s WITH (NOLOCK)
|
||
ON s.sku_id = NULLIF(a.undefined10, '')
|
||
LEFT JOIN dbo.jst_order_express_details AS e WITH (NOLOCK)
|
||
ON NULLIF(e.so_id, '') = NULLIF(a.undefined2, '')
|
||
WHERE ISNULL(NULLIF(a.undefined2, ''), '') <> ''
|
||
AND ISNULL(NULLIF(a.undefined62, ''), '') NOT LIKE '%配件%'
|
||
AND ISNULL(NULLIF(a.undefined62, ''), '') NOT LIKE '%特殊单%'
|
||
AND a.createdAt >= DATEADD(day, -(@days + 7), GETDATE());
|
||
|
||
IF OBJECT_ID('tempdb..#region_line') IS NOT NULL
|
||
DROP TABLE #region_line;
|
||
|
||
SELECT
|
||
ob.*
|
||
INTO #region_line
|
||
FROM #order_base AS ob
|
||
WHERE ob.ship_dt >= @start_time
|
||
AND ob.ship_dt < @end_time
|
||
AND (
|
||
(@province_norm = '' AND ISNULL(NULLIF(ob.province_norm, ''), '') <> '')
|
||
OR ob.province_norm LIKE '%' + @province_norm + '%'
|
||
OR @province_norm LIKE '%' + ob.province_norm + '%'
|
||
OR ob.receiver_address_norm LIKE '%' + @province_norm + '%'
|
||
)
|
||
AND (
|
||
@city_norm = ''
|
||
OR ob.city_norm LIKE '%' + @city_norm + '%'
|
||
OR @city_norm LIKE '%' + ob.city_norm + '%'
|
||
OR ob.receiver_address_norm LIKE '%' + @city_norm + '%'
|
||
)
|
||
AND (
|
||
@district_norm = ''
|
||
OR ob.receiver_address LIKE '%' + @district + '%'
|
||
OR ob.receiver_address_norm LIKE '%' + @district_norm + '%'
|
||
);
|
||
|
||
IF OBJECT_ID('tempdb..#order_agg') IS NOT NULL
|
||
DROP TABLE #order_agg;
|
||
|
||
SELECT
|
||
rl.order_id,
|
||
MAX(rl.shop_name) AS shop_name,
|
||
MAX(rl.product_no) AS product_no,
|
||
MAX(rl.ship_dt) AS ship_dt,
|
||
SUM(ISNULL(rl.sale_amount, 0)) AS sale_amount_order,
|
||
SUM(ISNULL(rl.cost_amount, 0)) AS cost_amount_order,
|
||
SUM(ISNULL(rl.shipped_qty, 0)) AS shipped_qty_order,
|
||
SUM(ISNULL(rl.return_qty, 0)) AS return_qty_order,
|
||
SUM(ISNULL(rl.return_freight_fee, 0)) AS return_freight_fee_order,
|
||
SUM(ISNULL(rl.warehouse_transfer_fee_obs, 0)) AS warehouse_transfer_fee_order
|
||
INTO #order_agg
|
||
FROM #region_line AS rl
|
||
GROUP BY rl.order_id;
|
||
|
||
IF OBJECT_ID('tempdb..#freight_order') IS NOT NULL
|
||
DROP TABLE #freight_order;
|
||
|
||
SELECT
|
||
NULLIF(f.so_id, '') AS order_id,
|
||
SUM(COALESCE(TRY_CONVERT(decimal(18, 2), f.freightCost), 0)) AS outbound_freight_order
|
||
INTO #freight_order
|
||
FROM dbo.fys_jstFreightCostDetail AS f WITH (NOLOCK)
|
||
INNER JOIN #region_line AS rl
|
||
ON rl.order_id = NULLIF(f.so_id, '')
|
||
AND rl.sku_id = NULLIF(f.sku_id, '')
|
||
WHERE f.send_date >= @start_time
|
||
AND f.send_date < @end_time
|
||
GROUP BY NULLIF(f.so_id, '');
|
||
|
||
IF OBJECT_ID('tempdb..#order_fact') IS NOT NULL
|
||
DROP TABLE #order_fact;
|
||
|
||
SELECT
|
||
oa.order_id,
|
||
oa.shop_name,
|
||
oa.product_no,
|
||
oa.ship_dt,
|
||
oa.sale_amount_order,
|
||
oa.cost_amount_order,
|
||
oa.shipped_qty_order,
|
||
oa.return_qty_order,
|
||
oa.return_freight_fee_order,
|
||
oa.warehouse_transfer_fee_order,
|
||
COALESCE(fo.outbound_freight_order, 0) AS outbound_freight_order
|
||
INTO #order_fact
|
||
FROM #order_agg AS oa
|
||
LEFT JOIN #freight_order AS fo
|
||
ON fo.order_id = oa.order_id;
|
||
|
||
IF OBJECT_ID('tempdb..#kpi') IS NOT NULL
|
||
DROP TABLE #kpi;
|
||
|
||
SELECT
|
||
COUNT(*) AS sample_order_count,
|
||
SUM(ISNULL(ofa.shipped_qty_order, 0)) AS shipped_qty_sum,
|
||
SUM(ISNULL(ofa.return_qty_order, 0)) AS return_qty_sum,
|
||
SUM(ISNULL(ofa.sale_amount_order, 0)) AS sale_amount_sum,
|
||
SUM(ISNULL(ofa.cost_amount_order, 0)) AS cost_amount_sum,
|
||
AVG(NULLIF(ISNULL(ofa.outbound_freight_order, 0), 0)) AS avg_outbound_freight_nonzero,
|
||
AVG(ISNULL(ofa.outbound_freight_order, 0)) AS avg_outbound_freight_per_order,
|
||
AVG(NULLIF(ISNULL(ofa.return_freight_fee_order, 0), 0)) AS avg_return_freight_observed,
|
||
AVG(NULLIF(ISNULL(ofa.warehouse_transfer_fee_order, 0), 0)) AS avg_warehouse_transfer_observed,
|
||
MIN(ofa.ship_dt) AS first_ship_time,
|
||
MAX(ofa.ship_dt) AS last_ship_time
|
||
INTO #kpi
|
||
FROM #order_fact AS ofa;
|
||
|
||
-- 1) 样本概览
|
||
SELECT
|
||
@product_no AS input_product_no,
|
||
@province AS input_province,
|
||
@city AS input_city,
|
||
@district AS input_district,
|
||
@days AS lookback_days,
|
||
(SELECT COUNT(*) FROM #sku_pool) AS matched_sku_count,
|
||
k.sample_order_count,
|
||
k.shipped_qty_sum,
|
||
k.return_qty_sum,
|
||
CASE WHEN ISNULL(k.shipped_qty_sum, 0) = 0 THEN NULL ELSE CAST(k.return_qty_sum / NULLIF(k.shipped_qty_sum, 0) AS decimal(10,4)) END AS return_rate,
|
||
k.first_ship_time,
|
||
k.last_ship_time
|
||
FROM #kpi AS k;
|
||
|
||
-- 2) 利润估算因子(按订单均值)
|
||
SELECT
|
||
CAST(CASE WHEN k.sample_order_count = 0 THEN NULL ELSE k.sale_amount_sum / NULLIF(k.sample_order_count, 0) END AS decimal(18,2)) AS avg_sale_amount_per_order,
|
||
CAST(CASE WHEN k.sample_order_count = 0 THEN NULL ELSE k.cost_amount_sum / NULLIF(k.sample_order_count, 0) END AS decimal(18,2)) AS avg_cost_amount_per_order,
|
||
CAST(ISNULL(k.avg_outbound_freight_per_order, 0) AS decimal(18,2)) AS est_outbound_freight_per_order_15d,
|
||
CAST(
|
||
COALESCE(
|
||
k.avg_return_freight_observed,
|
||
ISNULL(k.avg_outbound_freight_per_order, 0)
|
||
* CASE WHEN ISNULL(k.shipped_qty_sum, 0) = 0 THEN 0 ELSE k.return_qty_sum / NULLIF(k.shipped_qty_sum, 0) END,
|
||
0
|
||
) AS decimal(18,2)
|
||
) AS est_return_freight_per_order_15d,
|
||
CAST(COALESCE(k.avg_warehouse_transfer_observed, @warehouse_transfer_fee, 0) AS decimal(18,2)) AS warehouse_transfer_fee_used,
|
||
CAST(
|
||
CASE WHEN k.sample_order_count = 0 THEN NULL ELSE
|
||
(k.sale_amount_sum / NULLIF(k.sample_order_count, 0))
|
||
- (k.cost_amount_sum / NULLIF(k.sample_order_count, 0))
|
||
- ISNULL(k.avg_outbound_freight_per_order, 0)
|
||
- COALESCE(
|
||
k.avg_return_freight_observed,
|
||
ISNULL(k.avg_outbound_freight_per_order, 0)
|
||
* CASE WHEN ISNULL(k.shipped_qty_sum, 0) = 0 THEN 0 ELSE k.return_qty_sum / NULLIF(k.shipped_qty_sum, 0) END,
|
||
0
|
||
)
|
||
- COALESCE(k.avg_warehouse_transfer_observed, @warehouse_transfer_fee, 0)
|
||
END AS decimal(18,2)
|
||
) AS est_profit_per_order,
|
||
CAST(
|
||
CASE WHEN k.sample_order_count = 0 THEN NULL
|
||
WHEN (k.sale_amount_sum / NULLIF(k.sample_order_count, 0)) = 0 THEN NULL
|
||
ELSE (
|
||
(
|
||
(k.sale_amount_sum / NULLIF(k.sample_order_count, 0))
|
||
- (k.cost_amount_sum / NULLIF(k.sample_order_count, 0))
|
||
- ISNULL(k.avg_outbound_freight_per_order, 0)
|
||
- COALESCE(
|
||
k.avg_return_freight_observed,
|
||
ISNULL(k.avg_outbound_freight_per_order, 0)
|
||
* CASE WHEN ISNULL(k.shipped_qty_sum, 0) = 0 THEN 0 ELSE k.return_qty_sum / NULLIF(k.shipped_qty_sum, 0) END,
|
||
0
|
||
)
|
||
- COALESCE(k.avg_warehouse_transfer_observed, @warehouse_transfer_fee, 0)
|
||
)
|
||
/ NULLIF((k.sale_amount_sum / NULLIF(k.sample_order_count, 0)), 0)
|
||
)
|
||
END AS decimal(10,4)
|
||
) AS est_profit_margin
|
||
FROM #kpi AS k;
|
||
|
||
-- 3) 处置建议(退货/补偿/放货,含退回折旧损失)
|
||
WITH decision_base AS (
|
||
SELECT
|
||
k.sample_order_count,
|
||
CAST(CASE WHEN k.sample_order_count = 0 THEN NULL ELSE k.sale_amount_sum / NULLIF(k.sample_order_count, 0) END AS decimal(18,2)) AS avg_sale,
|
||
CAST(CASE WHEN k.sample_order_count = 0 THEN NULL ELSE k.cost_amount_sum / NULLIF(k.sample_order_count, 0) END AS decimal(18,2)) AS avg_cost,
|
||
CAST(ISNULL(k.avg_outbound_freight_per_order, 0) AS decimal(18,2)) AS avg_outbound,
|
||
CAST(COALESCE(
|
||
k.avg_return_freight_observed,
|
||
ISNULL(k.avg_outbound_freight_per_order, 0) * CASE WHEN ISNULL(k.shipped_qty_sum, 0) = 0 THEN 0 ELSE k.return_qty_sum / NULLIF(k.shipped_qty_sum, 0) END,
|
||
0
|
||
) AS decimal(18,2)) AS est_return_freight,
|
||
CAST(COALESCE(k.avg_warehouse_transfer_observed, @warehouse_transfer_fee, 0) AS decimal(18,2)) AS warehouse_transfer
|
||
FROM #kpi AS k
|
||
),
|
||
calc AS (
|
||
SELECT
|
||
db.*,
|
||
CAST(ISNULL(db.avg_cost, 0) * @depreciation_rate AS decimal(18,2)) AS depreciation_loss_if_return,
|
||
CAST((ISNULL(db.avg_outbound, 0) + ISNULL(db.est_return_freight, 0) + ISNULL(db.warehouse_transfer, 0) + (ISNULL(db.avg_cost, 0) * @depreciation_rate)) AS decimal(18,2)) AS expected_loss_if_return,
|
||
CAST((ISNULL(db.avg_sale, 0) - ISNULL(db.avg_cost, 0) - ISNULL(db.avg_outbound, 0)) AS decimal(18,2)) AS keep_order_net_before_comp,
|
||
CAST((0 - ISNULL(db.avg_outbound, 0) - ISNULL(db.est_return_freight, 0) - ISNULL(db.warehouse_transfer, 0) - (ISNULL(db.avg_cost, 0) * @depreciation_rate)) AS decimal(18,2)) AS option_return_net_profit,
|
||
CAST((0 - ISNULL(db.avg_cost, 0) - ISNULL(db.avg_outbound, 0)) AS decimal(18,2)) AS option_release_net_profit
|
||
FROM decision_base AS db
|
||
),
|
||
range_calc AS (
|
||
SELECT
|
||
c.*,
|
||
CAST(
|
||
CASE
|
||
WHEN c.sample_order_count = 0 THEN 0
|
||
WHEN c.keep_order_net_before_comp <= 0 THEN 0
|
||
ELSE
|
||
CASE
|
||
WHEN (c.keep_order_net_before_comp * 0.70) < (c.expected_loss_if_return * 0.90) THEN (c.keep_order_net_before_comp * 0.70)
|
||
ELSE (c.expected_loss_if_return * 0.90)
|
||
END
|
||
END
|
||
AS decimal(18,2)
|
||
) AS comp_high_raw
|
||
FROM calc AS c
|
||
)
|
||
SELECT
|
||
rc.sample_order_count,
|
||
rc.keep_order_net_before_comp AS est_profit_per_order,
|
||
CAST(@depreciation_rate AS decimal(10,4)) AS depreciation_rate_used,
|
||
rc.depreciation_loss_if_return,
|
||
rc.expected_loss_if_return,
|
||
CAST(
|
||
CASE
|
||
WHEN rc.sample_order_count = 0 THEN 0
|
||
WHEN rc.comp_high_raw <= 0 THEN 0
|
||
ELSE rc.comp_high_raw * 0.50
|
||
END AS decimal(18,2)
|
||
) AS compensation_low,
|
||
CAST(
|
||
CASE
|
||
WHEN rc.sample_order_count = 0 THEN 0
|
||
WHEN rc.comp_high_raw <= 0 THEN 0
|
||
ELSE rc.comp_high_raw
|
||
END AS decimal(18,2)
|
||
) AS compensation_high,
|
||
rc.option_return_net_profit,
|
||
rc.option_release_net_profit,
|
||
CAST(
|
||
rc.keep_order_net_before_comp
|
||
- (CASE WHEN rc.comp_high_raw <= 0 THEN 0 ELSE rc.comp_high_raw * 0.50 END)
|
||
AS decimal(18,2)
|
||
) AS option_discount_net_profit_low,
|
||
CAST(
|
||
rc.keep_order_net_before_comp
|
||
- (CASE WHEN rc.comp_high_raw <= 0 THEN 0 ELSE rc.comp_high_raw END)
|
||
AS decimal(18,2)
|
||
) AS option_discount_net_profit_high,
|
||
CASE
|
||
WHEN rc.sample_order_count = 0 THEN N'样本不足'
|
||
WHEN (
|
||
rc.keep_order_net_before_comp
|
||
- (CASE WHEN rc.comp_high_raw <= 0 THEN 0 ELSE rc.comp_high_raw END)
|
||
) >= rc.option_return_net_profit
|
||
AND (
|
||
rc.keep_order_net_before_comp
|
||
- (CASE WHEN rc.comp_high_raw <= 0 THEN 0 ELSE rc.comp_high_raw END)
|
||
) >= rc.option_release_net_profit
|
||
THEN N'优惠留客'
|
||
WHEN rc.option_return_net_profit >= rc.option_release_net_profit THEN N'退回'
|
||
ELSE N'放货'
|
||
END AS recommended_option,
|
||
CASE
|
||
WHEN rc.sample_order_count = 0 THEN N'样本不足,暂不做退补决策(建议放宽省市区或延长到30天)。'
|
||
WHEN (
|
||
rc.keep_order_net_before_comp
|
||
- (CASE WHEN rc.comp_high_raw <= 0 THEN 0 ELSE rc.comp_high_raw END)
|
||
) >= rc.option_return_net_profit
|
||
AND (
|
||
rc.keep_order_net_before_comp
|
||
- (CASE WHEN rc.comp_high_raw <= 0 THEN 0 ELSE rc.comp_high_raw END)
|
||
) >= rc.option_release_net_profit
|
||
THEN N'建议优先优惠留客:在建议补偿区间内,净收益优于退回和放货。'
|
||
WHEN rc.option_return_net_profit >= rc.option_release_net_profit
|
||
THEN N'建议优先退回:按当前折旧率和运费估算,退回净损失低于放货。'
|
||
ELSE N'建议优先放货:退回综合损失(含20%折旧)高于放货损失。'
|
||
END AS recommended_action,
|
||
CASE
|
||
WHEN rc.sample_order_count >= 30 THEN 'high'
|
||
WHEN rc.sample_order_count >= 10 THEN 'medium'
|
||
WHEN rc.sample_order_count > 0 THEN 'low'
|
||
ELSE 'none'
|
||
END AS confidence_level,
|
||
CAST(
|
||
CASE
|
||
WHEN rc.sample_order_count = 0 THEN NULL
|
||
ELSE rc.expected_loss_if_return
|
||
- (CASE WHEN rc.comp_high_raw <= 0 THEN 0 ELSE rc.comp_high_raw * 0.75 END)
|
||
END AS decimal(18,2)
|
||
) AS est_saving_vs_return
|
||
FROM range_calc AS rc;
|
||
|
||
-- 4) 快递/仓库策略(近15天样本)
|
||
SELECT TOP (@limit)
|
||
rl.express_company,
|
||
COUNT(DISTINCT rl.order_id) AS order_count,
|
||
SUM(ISNULL(rl.shipped_qty, 0)) AS shipped_qty_sum,
|
||
CAST(
|
||
SUM(ISNULL(rl.shipped_qty, 0))
|
||
/ NULLIF((SELECT SUM(ISNULL(shipped_qty, 0)) FROM #region_line), 0)
|
||
AS decimal(10,4)
|
||
) AS shipped_qty_ratio
|
||
FROM #region_line AS rl
|
||
GROUP BY rl.express_company
|
||
ORDER BY shipped_qty_sum DESC, order_count DESC;
|
||
|
||
SELECT TOP (@limit)
|
||
rl.ship_warehouse_name,
|
||
COUNT(DISTINCT rl.order_id) AS order_count,
|
||
SUM(ISNULL(rl.shipped_qty, 0)) AS shipped_qty_sum,
|
||
CAST(
|
||
SUM(ISNULL(rl.shipped_qty, 0))
|
||
/ NULLIF((SELECT SUM(ISNULL(shipped_qty, 0)) FROM #region_line), 0)
|
||
AS decimal(10,4)
|
||
) AS shipped_qty_ratio
|
||
FROM #region_line AS rl
|
||
GROUP BY rl.ship_warehouse_name
|
||
ORDER BY shipped_qty_sum DESC, order_count DESC;
|
||
"""
|
||
|
||
|
||
def main() -> int:
|
||
parser = argparse.ArgumentParser(
|
||
description="Estimate profit-based aftersale decision (退回/优惠/放货) by product_no + region, including return depreciation loss"
|
||
)
|
||
parser.add_argument("--product-no", required=True, help="商品货号(product_no/style_code/spu/SKU)")
|
||
parser.add_argument("--province", required=True, help="省份")
|
||
parser.add_argument("--city", default="", help="城市(可选)")
|
||
parser.add_argument("--district", default="", help="区县(可选)")
|
||
parser.add_argument("--days", type=int, default=15, help="lookback window, default 15")
|
||
parser.add_argument("--warehouse-transfer-fee", type=float, default=0.0, help="仓储转运费兜底值(当样本无观测值时使用)")
|
||
parser.add_argument("--depreciation-rate", type=float, default=0.20, help="退回折旧损失率,默认0.20(20%%)")
|
||
parser.add_argument("--limit", type=int, default=10, help="max rows for strategy result sets")
|
||
args = parser.parse_args()
|
||
|
||
product_no = args.product_no.strip()
|
||
province = args.province.strip()
|
||
if not product_no:
|
||
print("--product-no is required", file=sys.stderr)
|
||
return 2
|
||
if not province:
|
||
print("--province is required", file=sys.stderr)
|
||
return 2
|
||
|
||
days = max(7, min(args.days, 90))
|
||
limit = max(1, min(args.limit, 100))
|
||
depreciation_rate = max(0.0, min(args.depreciation_rate, 1.0))
|
||
|
||
sql = build_sql(
|
||
product_no=product_no,
|
||
province=province,
|
||
city=args.city,
|
||
district=args.district,
|
||
days=days,
|
||
warehouse_transfer_fee=args.warehouse_transfer_fee,
|
||
depreciation_rate=depreciation_rate,
|
||
limit=limit,
|
||
)
|
||
cmd = ["company-db", "query", "--sql", sql]
|
||
proc = subprocess.run(cmd, capture_output=True, text=True)
|
||
if proc.returncode != 0:
|
||
err = (proc.stderr or proc.stdout or "company-db failed").strip()
|
||
print(err, file=sys.stderr)
|
||
return proc.returncode
|
||
|
||
print(proc.stdout.rstrip())
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|