dalongxia/remote_sync/product-retention-decision
2026-03-04 13:25:48 +08:00

515 lines
21 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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())