- 新增6个HTML可视化仪表盘组件用于数据展示 * 人效产值损耗三维模型仪表盘 * 指标趋势分析与拐点预警仪表盘 * 一页式决策简报仪表盘 * 订单延迟预警分析仪表盘 * 供应链风险预警仪表盘 * 工单执行进度与异常节点仪表盘 - 添加VSCode工作区配置文件 - 更新businessQueries.json业务查询配置 - 优化api_client.py API客户端实现 - 更新pyproject.toml项目依赖版本 - 重组SQL查询文件结构 - 删除v2版本冗余文档配置 - 添加v2版本技能清单文档 - 更新日志文件记录
417 lines
15 KiB
SQL
417 lines
15 KiB
SQL
-- =====================================================
|
||
-- 供应链风险预警SQL
|
||
-- Supply Chain Risk Warning
|
||
-- 数据库: PostgreSQL
|
||
-- =====================================================
|
||
|
||
-- =====================================================
|
||
-- 1. 供应商历史交期表现分析
|
||
-- =====================================================
|
||
WITH supplier_delivery AS (
|
||
SELECT
|
||
po.supplier_id,
|
||
COUNT(*) AS order_count,
|
||
COUNT(pr.purchase_receipt_id) AS receipt_count,
|
||
AVG(
|
||
CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL
|
||
THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp)
|
||
ELSE NULL END
|
||
) AS avg_delivery_days,
|
||
MAX(
|
||
CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL
|
||
THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp)
|
||
ELSE NULL END
|
||
) AS max_delivery_days,
|
||
STDDEV(
|
||
CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL
|
||
THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp)
|
||
ELSE NULL END
|
||
) AS stddev_delivery_days
|
||
FROM fact_purchase_order po
|
||
LEFT JOIN fact_purchase_receipt pr ON po.supplier_id = pr.supplier_id
|
||
GROUP BY po.supplier_id
|
||
),
|
||
|
||
supplier_quality AS (
|
||
SELECT
|
||
pr.supplier_id,
|
||
COUNT(pr.purchase_receipt_id) AS total_receipts,
|
||
SUM(pr.receipt_qty_total) AS total_qty,
|
||
SUM(pr.amount) AS total_amount
|
||
FROM fact_purchase_receipt pr
|
||
GROUP BY pr.supplier_id
|
||
),
|
||
|
||
supplier_returns AS (
|
||
SELECT
|
||
pret.supplier_id,
|
||
COUNT(*) AS return_count,
|
||
SUM(CASE WHEN pret.return_reason = '损坏' THEN 1 ELSE 0 END) AS damage_count
|
||
FROM fact_purchase_return pret
|
||
GROUP BY pret.supplier_id
|
||
),
|
||
|
||
supplier_quality_rate AS (
|
||
SELECT
|
||
sq.supplier_id,
|
||
sq.total_receipts,
|
||
sq.total_qty,
|
||
sq.total_amount,
|
||
COALESCE(sr.return_count, 0) AS return_count,
|
||
COALESCE(sr.damage_count, 0) AS damage_count,
|
||
CASE WHEN sq.total_receipts > 0
|
||
THEN (sq.total_receipts - COALESCE(sr.return_count, 0)) * 100.0 / sq.total_receipts
|
||
ELSE 100 END AS quality_rate
|
||
FROM supplier_quality sq
|
||
LEFT JOIN supplier_returns sr ON sq.supplier_id = sr.supplier_id
|
||
),
|
||
|
||
supplier_risk AS (
|
||
SELECT
|
||
s.supplier_id,
|
||
s.supplier_name,
|
||
s.supplier_category,
|
||
COALESCE(sd.order_count, 0) AS order_count,
|
||
COALESCE(sd.receipt_count, 0) AS receipt_count,
|
||
ROUND(COALESCE(sd.avg_delivery_days, 0), 1) AS avg_delivery_days,
|
||
ROUND(COALESCE(sd.max_delivery_days, 0), 1) AS max_delivery_days,
|
||
ROUND(COALESCE(sd.stddev_delivery_days, 0), 1) AS delivery_volatility,
|
||
COALESCE(sqr.total_receipts, 0) AS total_receipts,
|
||
COALESCE(sqr.return_count, 0) AS return_count,
|
||
ROUND(COALESCE(sqr.quality_rate, 100), 1) AS quality_rate,
|
||
CASE
|
||
WHEN COALESCE(sd.avg_delivery_days, 0) > 60 THEN 40
|
||
WHEN COALESCE(sd.avg_delivery_days, 0) > 45 THEN 30
|
||
WHEN COALESCE(sd.avg_delivery_days, 0) > 30 THEN 20
|
||
ELSE 10
|
||
END +
|
||
CASE
|
||
WHEN COALESCE(sd.stddev_delivery_days, 0) > 20 THEN 30
|
||
WHEN COALESCE(sd.stddev_delivery_days, 0) > 10 THEN 20
|
||
ELSE 10
|
||
END AS delivery_risk_score,
|
||
CASE
|
||
WHEN COALESCE(sqr.quality_rate, 100) < 80 THEN 50
|
||
WHEN COALESCE(sqr.quality_rate, 100) < 90 THEN 30
|
||
WHEN COALESCE(sqr.quality_rate, 100) < 95 THEN 15
|
||
ELSE 5
|
||
END AS quality_risk_score
|
||
FROM dim_supplier s
|
||
LEFT JOIN supplier_delivery sd ON s.supplier_id = sd.supplier_id
|
||
LEFT JOIN supplier_quality_rate sqr ON s.supplier_id = sqr.supplier_id
|
||
WHERE s.is_current = 't'
|
||
),
|
||
|
||
supplier_risk_level AS (
|
||
SELECT
|
||
*,
|
||
delivery_risk_score + quality_risk_score AS total_risk_score,
|
||
CASE
|
||
WHEN delivery_risk_score + quality_risk_score >= 80 THEN 'HIGH'
|
||
WHEN delivery_risk_score + quality_risk_score >= 50 THEN 'MEDIUM'
|
||
ELSE 'LOW'
|
||
END AS risk_level,
|
||
CASE
|
||
WHEN delivery_risk_score >= 50 AND quality_risk_score >= 30 THEN 'DELIVERY_AND_QUALITY'
|
||
WHEN delivery_risk_score >= 50 THEN 'DELIVERY_ISSUE'
|
||
WHEN quality_risk_score >= 30 THEN 'QUALITY_ISSUE'
|
||
ELSE 'NORMAL'
|
||
END AS risk_pattern
|
||
FROM supplier_risk
|
||
)
|
||
|
||
SELECT
|
||
supplier_name,
|
||
supplier_category,
|
||
order_count,
|
||
receipt_count,
|
||
avg_delivery_days,
|
||
max_delivery_days,
|
||
delivery_volatility,
|
||
total_receipts,
|
||
return_count,
|
||
quality_rate,
|
||
delivery_risk_score,
|
||
quality_risk_score,
|
||
total_risk_score,
|
||
risk_level,
|
||
risk_pattern
|
||
FROM supplier_risk_level
|
||
ORDER BY total_risk_score DESC, supplier_name;
|
||
|
||
|
||
-- =====================================================
|
||
-- 补充查询1:高风险订单预警清单
|
||
-- =====================================================
|
||
WITH supplier_risk_info AS (
|
||
SELECT
|
||
s.supplier_id,
|
||
s.supplier_name,
|
||
s.supplier_category,
|
||
COALESCE(
|
||
(SELECT AVG(EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po2.doc_date_utc::timestamp))
|
||
FROM fact_purchase_order po2
|
||
LEFT JOIN fact_purchase_receipt pr ON po2.supplier_id = pr.supplier_id
|
||
WHERE po2.supplier_id = s.supplier_id
|
||
AND pr.doc_date_utc IS NOT NULL), 0
|
||
) AS avg_delivery_days,
|
||
COALESCE(
|
||
(SELECT COUNT(*) FROM fact_purchase_return pret WHERE pret.supplier_id = s.supplier_id), 0
|
||
) AS return_count,
|
||
COALESCE(
|
||
(SELECT COUNT(*) FROM fact_purchase_receipt pr WHERE pr.supplier_id = s.supplier_id), 0
|
||
) AS receipt_count
|
||
FROM dim_supplier s
|
||
WHERE s.is_current = 't'
|
||
),
|
||
order_risk AS (
|
||
SELECT
|
||
po.purchase_order_number,
|
||
po.doc_date_utc::date AS order_date,
|
||
sri.supplier_name,
|
||
sri.supplier_category,
|
||
ROUND(sri.avg_delivery_days, 1) AS supplier_avg_days,
|
||
sri.return_count,
|
||
sri.receipt_count,
|
||
CASE WHEN sri.receipt_count > 0
|
||
THEN ROUND((sri.receipt_count - sri.return_count) * 100.0 / sri.receipt_count, 1)
|
||
ELSE 100 END AS quality_rate,
|
||
EXTRACT(DAY FROM CURRENT_TIMESTAMP - po.doc_date_utc::timestamp) AS days_since_order,
|
||
CASE
|
||
WHEN sri.avg_delivery_days > 45 AND sri.return_count > 0 THEN 'HIGH'
|
||
WHEN sri.avg_delivery_days > 45 OR sri.return_count > 0 THEN 'MEDIUM'
|
||
ELSE 'LOW'
|
||
END AS risk_level
|
||
FROM fact_purchase_order po
|
||
JOIN supplier_risk_info sri ON po.supplier_id = sri.supplier_id
|
||
)
|
||
SELECT
|
||
purchase_order_number,
|
||
order_date,
|
||
supplier_name,
|
||
supplier_category,
|
||
supplier_avg_days,
|
||
quality_rate,
|
||
days_since_order,
|
||
risk_level,
|
||
CASE
|
||
WHEN risk_level = 'HIGH' THEN 'IMMEDIATE_FOLLOWUP'
|
||
WHEN risk_level = 'MEDIUM' THEN 'MONITOR'
|
||
ELSE 'NORMAL'
|
||
END AS action_required
|
||
FROM order_risk
|
||
WHERE risk_level IN ('HIGH', 'MEDIUM')
|
||
ORDER BY
|
||
CASE risk_level WHEN 'HIGH' THEN 1 WHEN 'MEDIUM' THEN 2 ELSE 3 END,
|
||
days_since_order DESC;
|
||
|
||
|
||
-- =====================================================
|
||
-- 补充查询2:供应商类别风险分布
|
||
-- =====================================================
|
||
WITH supplier_stats AS (
|
||
SELECT
|
||
s.supplier_category,
|
||
COUNT(DISTINCT s.supplier_id) AS supplier_count,
|
||
COUNT(DISTINCT po.purchase_order_id) AS order_count,
|
||
COUNT(DISTINCT pr.purchase_receipt_id) AS receipt_count,
|
||
COUNT(DISTINCT pret.purchase_return_id) AS return_count,
|
||
SUM(pr.amount) AS total_amount
|
||
FROM dim_supplier s
|
||
LEFT JOIN fact_purchase_order po ON s.supplier_id = po.supplier_id
|
||
LEFT JOIN fact_purchase_receipt pr ON s.supplier_id = pr.supplier_id
|
||
LEFT JOIN fact_purchase_return pret ON s.supplier_id = pret.supplier_id
|
||
WHERE s.is_current = 't'
|
||
GROUP BY s.supplier_category
|
||
)
|
||
SELECT
|
||
supplier_category,
|
||
supplier_count,
|
||
order_count,
|
||
receipt_count,
|
||
return_count,
|
||
CASE WHEN receipt_count > 0
|
||
THEN ROUND((receipt_count - return_count) * 100.0 / receipt_count, 1)
|
||
ELSE 100 END AS quality_rate,
|
||
ROUND(COALESCE(total_amount, 0), 2) AS purchase_amount,
|
||
CASE
|
||
WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 10 THEN 'HIGH'
|
||
WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 5 THEN 'MEDIUM'
|
||
ELSE 'LOW'
|
||
END AS category_risk_level
|
||
FROM supplier_stats
|
||
ORDER BY return_count DESC;
|
||
|
||
|
||
-- =====================================================
|
||
-- 补充查询3:近期交期异常趋势
|
||
-- =====================================================
|
||
WITH monthly_delivery AS (
|
||
SELECT
|
||
DATE_TRUNC('month', pr.doc_date_utc::timestamp)::date AS month_start,
|
||
COUNT(*) AS receipt_count,
|
||
AVG(EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp)) AS avg_delivery_days
|
||
FROM fact_purchase_receipt pr
|
||
JOIN fact_purchase_order po ON pr.supplier_id = po.supplier_id
|
||
WHERE pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL
|
||
GROUP BY DATE_TRUNC('month', pr.doc_date_utc::timestamp)
|
||
),
|
||
monthly_returns AS (
|
||
SELECT
|
||
DATE_TRUNC('month', pret.doc_date_utc::timestamp)::date AS month_start,
|
||
COUNT(*) AS return_count
|
||
FROM fact_purchase_return pret
|
||
GROUP BY DATE_TRUNC('month', pret.doc_date_utc::timestamp)
|
||
),
|
||
monthly_combined AS (
|
||
SELECT
|
||
md.month_start,
|
||
md.receipt_count,
|
||
ROUND(md.avg_delivery_days, 1) AS avg_delivery_days,
|
||
COALESCE(mr.return_count, 0) AS return_count
|
||
FROM monthly_delivery md
|
||
LEFT JOIN monthly_returns mr ON md.month_start = mr.month_start
|
||
),
|
||
with_trend AS (
|
||
SELECT
|
||
*,
|
||
LAG(avg_delivery_days, 1) OVER (ORDER BY month_start) AS prev_avg_days,
|
||
LAG(return_count, 1) OVER (ORDER BY month_start) AS prev_return_count
|
||
FROM monthly_combined
|
||
)
|
||
SELECT
|
||
month_start,
|
||
receipt_count,
|
||
avg_delivery_days,
|
||
return_count,
|
||
CASE WHEN receipt_count > 0
|
||
THEN ROUND(return_count * 100.0 / receipt_count, 1)
|
||
ELSE 0 END AS return_rate,
|
||
CASE
|
||
WHEN prev_avg_days IS NULL THEN 'NONE'
|
||
WHEN avg_delivery_days > prev_avg_days * 1.1 THEN 'INCREASING'
|
||
WHEN avg_delivery_days < prev_avg_days * 0.9 THEN 'DECREASING'
|
||
ELSE 'STABLE'
|
||
END AS delivery_trend,
|
||
CASE
|
||
WHEN prev_return_count IS NULL THEN 'NONE'
|
||
WHEN return_count > prev_return_count THEN 'INCREASING'
|
||
WHEN return_count < prev_return_count THEN 'DECREASING'
|
||
ELSE 'STABLE'
|
||
END AS return_trend
|
||
FROM with_trend
|
||
ORDER BY month_start DESC;
|
||
|
||
|
||
-- =====================================================
|
||
-- 补充查询4:风险预警汇总看板
|
||
-- =====================================================
|
||
WITH risk_summary AS (
|
||
SELECT
|
||
s.supplier_id,
|
||
s.supplier_name,
|
||
COALESCE(
|
||
(SELECT COUNT(*) FROM fact_purchase_return pret WHERE pret.supplier_id = s.supplier_id), 0
|
||
) AS return_count,
|
||
COALESCE(
|
||
(SELECT COUNT(*) FROM fact_purchase_receipt pr WHERE pr.supplier_id = s.supplier_id), 0
|
||
) AS receipt_count
|
||
FROM dim_supplier s
|
||
WHERE s.is_current = 't'
|
||
),
|
||
risk_counts AS (
|
||
SELECT
|
||
SUM(CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count > 10 THEN 1 ELSE 0 END) AS high_risk_suppliers,
|
||
SUM(CASE WHEN receipt_count > 0 AND return_count * 100.0 / receipt_count BETWEEN 5 AND 10 THEN 1 ELSE 0 END) AS medium_risk_suppliers,
|
||
SUM(CASE WHEN receipt_count = 0 OR return_count * 100.0 / receipt_count < 5 THEN 1 ELSE 0 END) AS low_risk_suppliers,
|
||
COUNT(*) AS total_suppliers
|
||
FROM risk_summary
|
||
),
|
||
order_stats AS (
|
||
SELECT
|
||
COUNT(*) AS pending_orders,
|
||
COUNT(CASE WHEN EXTRACT(DAY FROM CURRENT_TIMESTAMP - doc_date_utc::timestamp) > 30 THEN 1 END) AS overdue_orders
|
||
FROM fact_purchase_order
|
||
),
|
||
return_stats AS (
|
||
SELECT
|
||
COUNT(*) AS total_returns,
|
||
COUNT(CASE WHEN doc_date_utc::timestamp >= CURRENT_DATE - INTERVAL '30 days' THEN 1 END) AS recent_returns
|
||
FROM fact_purchase_return
|
||
)
|
||
SELECT
|
||
'high_risk_suppliers' AS metric_name,
|
||
rc.high_risk_suppliers::text AS metric_value,
|
||
CASE WHEN rc.high_risk_suppliers > 0 THEN 'ATTENTION_NEEDED' ELSE 'NORMAL' END AS status
|
||
FROM risk_counts rc
|
||
UNION ALL
|
||
SELECT 'medium_risk_suppliers', rc.medium_risk_suppliers::text,
|
||
CASE WHEN rc.medium_risk_suppliers > 2 THEN 'MONITOR' ELSE 'NORMAL' END
|
||
FROM risk_counts rc
|
||
UNION ALL
|
||
SELECT 'low_risk_suppliers', rc.low_risk_suppliers::text, 'NORMAL'
|
||
FROM risk_counts rc
|
||
UNION ALL
|
||
SELECT 'pending_orders', os.pending_orders::text,
|
||
CASE WHEN os.pending_orders > 20 THEN 'BACKLOG' ELSE 'NORMAL' END
|
||
FROM order_stats os
|
||
UNION ALL
|
||
SELECT 'overdue_orders_30d', os.overdue_orders::text,
|
||
CASE WHEN os.overdue_orders > 5 THEN 'DELIVERY_WARNING' ELSE 'NORMAL' END
|
||
FROM order_stats os
|
||
UNION ALL
|
||
SELECT 'recent_returns_30d', rs.recent_returns::text,
|
||
CASE WHEN rs.recent_returns > 3 THEN 'QUALITY_WARNING' ELSE 'NORMAL' END
|
||
FROM return_stats rs;
|
||
|
||
|
||
-- =====================================================
|
||
-- 补充查询5:供应商风险排行榜
|
||
-- =====================================================
|
||
WITH supplier_metrics AS (
|
||
SELECT
|
||
s.supplier_id,
|
||
s.supplier_name,
|
||
s.supplier_category,
|
||
COUNT(DISTINCT po.purchase_order_id) AS order_count,
|
||
COUNT(DISTINCT pr.purchase_receipt_id) AS receipt_count,
|
||
COUNT(DISTINCT pret.purchase_return_id) AS return_count,
|
||
SUM(pr.amount) AS total_amount
|
||
FROM dim_supplier s
|
||
LEFT JOIN fact_purchase_order po ON s.supplier_id = po.supplier_id
|
||
LEFT JOIN fact_purchase_receipt pr ON s.supplier_id = pr.supplier_id
|
||
LEFT JOIN fact_purchase_return pret ON s.supplier_id = pret.supplier_id
|
||
WHERE s.is_current = 't'
|
||
GROUP BY s.supplier_id, s.supplier_name, s.supplier_category
|
||
),
|
||
ranked_suppliers AS (
|
||
SELECT
|
||
*,
|
||
CASE WHEN receipt_count > 0
|
||
THEN ROUND(return_count * 100.0 / receipt_count, 1)
|
||
ELSE 0 END AS return_rate,
|
||
ROW_NUMBER() OVER (ORDER BY
|
||
CASE WHEN receipt_count > 0 THEN return_count * 1.0 / receipt_count ELSE 0 END DESC,
|
||
return_count DESC
|
||
) AS risk_rank
|
||
FROM supplier_metrics
|
||
)
|
||
SELECT
|
||
risk_rank,
|
||
supplier_name,
|
||
supplier_category,
|
||
order_count,
|
||
receipt_count,
|
||
return_count,
|
||
return_rate,
|
||
ROUND(COALESCE(total_amount, 0), 2) AS purchase_amount,
|
||
CASE
|
||
WHEN return_rate > 10 THEN 'HIGH_RISK'
|
||
WHEN return_rate > 5 THEN 'MEDIUM_RISK'
|
||
WHEN return_count > 0 THEN 'LOW_RISK'
|
||
ELSE 'EXCELLENT'
|
||
END AS risk_assessment
|
||
FROM ranked_suppliers
|
||
ORDER BY risk_rank
|
||
LIMIT 20;
|