3v324v23's picture
Enhances platform with robust monitoring and notifications
4e22b4d
Raw
History Blame Contribute Delete
3.49 kB
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from typing import List
from apps.api.dependencies.auth import get_api_key
from packages.core.olap import get_clickhouse_client
router = APIRouter(prefix="/bi", tags=["BI Dashboard"])
class TopologyNode(BaseModel):
name: str
type: str # country, port, or company
value: float
class TopologyEdge(BaseModel):
source: str
target: str
value: float
class FlowTopologyResponse(BaseModel):
nodes: List[TopologyNode]
edges: List[TopologyEdge]
@router.get("/supply-chain-flow", response_model=FlowTopologyResponse)
async def get_supply_chain_flow(
hs_code: str,
year: int,
api_key: str = Depends(get_api_key)
):
"""
提供“全球供应链流向拓扑图”底层数据聚合接口。
查询 ClickHouse 获取特定商品在全球范围内的流向(原产国 -> 目的国)。
"""
if not hs_code or not hs_code.isdigit():
raise HTTPException(status_code=400, detail="Invalid hs_code")
client = get_clickhouse_client()
# ClickHouse 查询示例: 聚合原产国和目的国的贸易额
# 使用参数化查询防止 SQL 注入
query = """
SELECT
origin_country,
destination_country,
sum(amount) as total_amount
FROM customs_data.trade_records
WHERE hs_code = %(hs_code)s
AND toYear(trade_date) = %(year)s
AND origin_country != ''
AND destination_country != ''
GROUP BY origin_country, destination_country
ORDER BY total_amount DESC
LIMIT 50
"""
try:
result = client.query(query, parameters={"hs_code": hs_code, "year": year})
rows = result.result_rows
nodes_dict = {}
edges = []
for row in rows:
orig = row[0]
dest = row[1]
amt = float(row[2])
# 添加节点
if orig not in nodes_dict:
nodes_dict[orig] = TopologyNode(name=orig, type="country", value=0)
if dest not in nodes_dict:
nodes_dict[dest] = TopologyNode(name=dest, type="country", value=0)
nodes_dict[orig].value += amt
nodes_dict[dest].value += amt
# 添加边
edges.append(TopologyEdge(source=orig, target=dest, value=amt))
return FlowTopologyResponse(
nodes=list(nodes_dict.values()),
edges=edges
)
except Exception as e:
# 如果 ClickHouse 没有运行,返回空模拟数据
print(f"ClickHouse query failed: {e}")
return FlowTopologyResponse(nodes=[], edges=[])
class MarketShareResponse(BaseModel):
period: str
share: float
growth_rate_yoy: float # 同比
growth_rate_mom: float # 环比
@router.get("/market-share")
async def get_market_share(
hs_code: str,
target_country: str,
api_key: str = Depends(get_api_key)
):
"""
提供“目标市场份额环比/同比”动态分析接口。
这里仅做接口示例,返回模拟的增长率。真实场景需在 ClickHouse 内做时间窗口聚合。
"""
return [
MarketShareResponse(period="2026-04", share=0.15, growth_rate_yoy=0.05, growth_rate_mom=0.02),
MarketShareResponse(period="2026-05", share=0.18, growth_rate_yoy=0.08, growth_rate_mom=0.03),
]