This commit is contained in:
HuangHai
2026-01-21 08:41:47 +08:00
parent 306a3a4127
commit e51dc18d06
40 changed files with 1309 additions and 965 deletions

View File

@@ -6,7 +6,6 @@ from typing import List, Optional, Dict, Any
from fastapi import APIRouter, HTTPException
from fastapi.responses import StreamingResponse, FileResponse, JSONResponse
from DbKit.Db import Db
from Config.Config import DB_URL
from Util.LlmUtil import get_llm_response
from Tools.T6_Export import export_excel, DorisExcelExporter, extract_hourly_prices_from_schedule
@@ -24,31 +23,35 @@ from Model.YltAnalyticsModel import (
PriceSeries,
PriceComparisonResponse,
PriceComparisonSummary,
YltAnalyticsModel,
)
from DbKit.Db import Db
router = APIRouter()
db = Db(db_url=DB_URL)
# db = Db(db_url=DB_URL) # Removed direct db instance
async def init_db():
db = Db()
await db.init_db()
async def close_db():
db = Db()
await db.close()
@router.get("/api/operators/hourly-prices")
async def get_operators_hourly_prices():
operators = ["新电途", "特来电", "驿来特", "艾特吉易充"]
exporter = DorisExcelExporter(db_url=DB_URL)
await exporter.init()
model = YltAnalyticsModel()
try:
result = []
for op in operators:
rows = await exporter.fetch_current_station_rows(op)
rows = await model.fetch_current_station_rows(op)
if not rows:
result.append({"operator": op, "series": [None] * 24})
continue
@@ -71,11 +74,72 @@ async def get_operators_hourly_prices():
else:
avg_series.append(None)
result.append({"operator": op, "series": avg_series})
finally:
await exporter.close()
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
return {"operators": result}
@router.get("/api/operators/price-trends")
async def get_operators_price_trends(days: int = 7):
operators = ["新电途", "特来电", "驿来特", "艾特吉易充"]
model = YltAnalyticsModel()
rows = await model.get_operators_price_trends(days)
# 数据结构: { operator: { date_str: [sums_of_24h, counts_of_24h] } }
trend_data = {}
for op in operators:
trend_data[op] = {}
for row in rows:
op = row.get("operator")
if op not in trend_data:
continue
d_str = str(row.get("date_str"))
schedule_json = row.get("schedule_json")
if d_str not in trend_data[op]:
trend_data[op][d_str] = {"sums": [0.0] * 24, "counts": [0] * 24}
series = extract_hourly_prices_from_schedule(schedule_json)
for i in range(24):
v = series[i]
if v is not None:
trend_data[op][d_str]["sums"][i] += float(v)
trend_data[op][d_str]["counts"][i] += 1
# 转换为 ECharts 友好格式
# 1. 获取所有日期并排序
all_dates = sorted(list(set(str(row.get("date_str")) for row in rows)))
# 2. 为每个运营商计算每天的平均价格(24小时的平均值)
series_result = []
for op in operators:
op_trend = []
for d in all_dates:
if d in trend_data[op]:
day_stats = trend_data[op][d]
day_avg_sum = 0.0
day_hour_count = 0
for i in range(24):
if day_stats["counts"][i] > 0:
day_avg_sum += (day_stats["sums"][i] / day_stats["counts"][i])
day_hour_count += 1
if day_hour_count > 0:
op_trend.append(round(day_avg_sum / day_hour_count, 4))
else:
op_trend.append(None)
else:
op_trend.append(None)
series_result.append({"name": op, "data": op_trend})
return {
"dates": all_dates,
"series": series_result
}
@router.get("/api/export/prices-zip")
async def export_prices_zip():
operators = ["新电途", "特来电", "驿来特", "艾特吉易充"]
@@ -177,26 +241,8 @@ async def ai_pricing_strategy_summary():
@router.get("/api/ylt/stations", response_model=List[StationBase])
async def list_ylt_stations(q: Optional[str] = None):
base_sql = """
SELECT
p.station_hash,
p.operator,
p.station_name,
p.address,
p.coord_x,
p.coord_y,
s.current_price
FROM t_station_profile_scd p
LEFT JOIN t_station_status_scd s
ON p.station_hash = s.station_hash AND s.is_current = 1
WHERE p.operator = '驿来特'
"""
params: Dict[str, Any] = {}
if q:
base_sql += " AND (p.station_name LIKE :kw OR p.address LIKE :kw)"
params["kw"] = f"%{q}%"
base_sql += " ORDER BY p.station_name"
rows = await db.find(base_sql, params)
model = YltAnalyticsModel()
rows = await model.list_ylt_stations(q)
result: List[StationBase] = []
for r in rows:
result.append(
@@ -225,21 +271,8 @@ def haversine_km(lon1: float, lat1: float, lon2: float, lat2: float) -> float:
async def fetch_current_stations() -> List[dict]:
sql = """
SELECT
p.station_hash,
p.operator,
p.station_name,
p.address,
p.coord_x,
p.coord_y,
s.current_price
FROM t_station_profile_scd p
LEFT JOIN t_station_status_scd s
ON p.station_hash = s.station_hash AND s.is_current = 1
WHERE p.is_current = 1
"""
return await db.find(sql, {})
model = YltAnalyticsModel()
return await model.fetch_current_stations()
async def build_geo_competition(station_hash: str, radius_km: float = 3.0) -> GeoCompetitionResponse:
@@ -318,17 +351,8 @@ async def build_geo_competition(station_hash: str, radius_km: float = 3.0) -> Ge
async def fetch_station_schedule_json(station_hash: str) -> Optional[str]:
sql = """
SELECT schedule_json
FROM t_station_price_schedule_scd
WHERE station_hash = :h AND is_current = 1
ORDER BY valid_start_time DESC
LIMIT 1
"""
rows = await db.find(sql, {"h": station_hash})
if not rows:
return None
value = rows[0].get("schedule_json")
model = YltAnalyticsModel()
value = await model.fetch_station_schedule_json(station_hash)
if value is None:
return None
if isinstance(value, str):