diff --git a/i18n/zh/msg.txt b/i18n/zh/msg.txt index ebf01cf..489b27b 100644 --- a/i18n/zh/msg.txt +++ b/i18n/zh/msg.txt @@ -1 +1 @@ -{"模型性能监控":"Model Performance","供应商性价比对比":"Provider ROI","平均 TTFT":"Avg TTFT","成功率":"Success Rate","今日调用":"Today Calls","失败数":"Failures","模型性能":"Model Perf","供应商性价比":"Provider ROI","刷新":"Refresh"} \ No newline at end of file +{"模型性能监控":"Model Performance","供应商性价比对比":"Provider ROI","平均 TTFT":"Avg TTFT","成功率":"Success Rate","今日调用":"Today Calls","失败数":"Failures","模型性能":"Model Perf","供应商性价比":"Provider ROI","刷新":"Refresh","今日调用笔数":"Today's Calls","今日交易金额":"Today's Amount","今日活跃":"Active Today","在线用户":"Online Users","用户总数":"Total Users","用户":"Users","模型性能排行":"Model Performance","币种分布":"Revenue/Cost/Profit","币种成本收入":"Currency Cost/Revenue","币种费用":"Currency Cost","热门模型":"Popular Models","用户排行":"User Ranking","供应商排行":"Supplier Ranking","我的模型(按金额Top5)":"My Models (Top 5)","API Key排行":"API Key Ranking","财务概览":"Financial Overview","收入":"Revenue","成本":"Cost","毛利":"Gross Profit","累计":"Cumulative","当年":"This Year","当月":"This Month","当天":"Today","今天调用":"Today Calls"} \ No newline at end of file diff --git a/sage_datamart/dashboards.py b/sage_datamart/dashboards.py index 01b4c69..589d618 100644 --- a/sage_datamart/dashboards.py +++ b/sage_datamart/dashboards.py @@ -514,7 +514,48 @@ async def get_customer_currency_stats(sor, org_filter, period='today'): FROM dm_model_call_fact f WHERE f.userorgid = ${{org}}$ {date_sql} GROUP BY f.currency ORDER BY revenue_cny DESC""" - recs = await sor.sqlExe(sql, ns) - return [{'currency': r.currency, 'call_count': int(r.call_count or 0), - 'revenue': round(float(r.revenue or 0), 2), - 'revenue_cny': round(float(r.revenue_cny or 0), 2)} for r in recs] +async def get_financial_summary(sor, period='all'): + """平台级财务汇总:收入/成本/毛利(不分机构) + period: 'today' | 'month' | 'year' | 'all' + """ + today_str = _date_module_date.today().isoformat() + ns = {'today': today_str} + date_sql = "" + if period == 'month': + date_sql = "AND call_date BETWEEN ${month_start}$ AND ${today}$" + ns['month_start'] = today_str[:7] + '-01' + elif period == 'year': + date_sql = "AND call_date BETWEEN ${year_start}$ AND ${today}$" + ns['year_start'] = today_str[:4] + '-01-01' + elif period == 'today': + date_sql = "AND call_date = ${today}$" + + sql_rev = f"""SELECT + COALESCE(SUM(amount_cny), 0) as revenue_cny, + COUNT(*) as call_count + FROM dm_model_call_fact + WHERE 1=1 {date_sql}""" + recs = await sor.sqlExe(sql_rev, ns) + total_revenue_cny = round(float(recs[0].revenue_cny or 0), 2) if recs else 0 + total_calls = int(recs[0].call_count or 0) if recs else 0 + + total_cost_cny = 0.0 + try: + cost_sql = f"""SELECT COALESCE(SUM(c.total_amount_cny), 0) as total_cost_cny + FROM dm_provider_cost_daily c + WHERE 1=1 {date_sql.replace('call_date', 'c.stat_date')}""" + cost_recs = await sor.sqlExe(cost_sql, ns) + if cost_recs: + total_cost_cny = round(float(cost_recs[0].total_cost_cny or 0), 2) + except Exception: + pass + + gross_profit_cny = round(total_revenue_cny - total_cost_cny, 2) + + return { + 'total_revenue_cny': total_revenue_cny, + 'total_cost_cny': total_cost_cny, + 'gross_profit_cny': gross_profit_cny, + 'total_calls': total_calls, + 'period': period, + } diff --git a/sage_datamart/init.py b/sage_datamart/init.py index f84aa88..087c652 100644 --- a/sage_datamart/init.py +++ b/sage_datamart/init.py @@ -17,6 +17,8 @@ from .dashboards import ( get_distributor_top_customers, get_scoped_today_usage, get_scoped_today_amount, get_scoped_success_rate, get_scoped_fail_count, + get_distributor_financial, get_customer_currency_stats, + get_financial_summary, ) MODULE_NAME = "sage_datamart" @@ -529,6 +531,18 @@ async def j2_top_providers_data(request): return [] +async def api_financial_summary(request): + """平台级财务汇总 API""" + try: + env = request._run_ns + period = (request._run_ns.params_kw or {}).get('period', 'all') + async with get_sor_context(env, MODULE_NAME) as sor: + return await get_financial_summary(sor, period) + except Exception as e: + debug("sage_datamart API error: " + str(e)) + return {} + + # ── Cron ETL ── async def cron_etl_sync(request): @@ -599,6 +613,7 @@ def load_sage_datamart(): env.j2_top_models_data = j2_top_models_data env.j2_top_users_data = j2_top_users_data env.j2_top_providers_data = j2_top_providers_data + env.api_financial_summary = api_financial_summary env.cron_etl_sync = cron_etl_sync env.cron_etl_aggregate = cron_etl_aggregate env.cron_etl_provider_cost = cron_etl_provider_cost diff --git a/wwwroot/api/financial_chart.dspy b/wwwroot/api/financial_chart.dspy new file mode 100644 index 0000000..688ac4c --- /dev/null +++ b/wwwroot/api/financial_chart.dspy @@ -0,0 +1,9 @@ +import json +period = (params_kw or {}).get('period', 'all') +data = await request._run_ns.api_financial_summary(request) +chart_data = [ + {"metric": "收入", "value": data.get('total_revenue_cny', 0)}, + {"metric": "成本", "value": data.get('total_cost_cny', 0)}, + {"metric": "毛利", "value": data.get('gross_profit_cny', 0)}, +] +return json.dumps(chart_data, ensure_ascii=False) diff --git a/wwwroot/api/financial_summary.dspy b/wwwroot/api/financial_summary.dspy new file mode 100644 index 0000000..2150a1a --- /dev/null +++ b/wwwroot/api/financial_summary.dspy @@ -0,0 +1,5 @@ +import json +env = request._run_ns +period = (params_kw or {}).get('period', 'all') +data = await env.api_financial_summary(request) +return json.dumps(data, ensure_ascii=False) diff --git a/wwwroot/sect_currency.ui b/wwwroot/sect_currency.ui index 77ab177..0c2200e 100644 --- a/wwwroot/sect_currency.ui +++ b/wwwroot/sect_currency.ui @@ -1,10 +1,32 @@ { - "widgettype": "ChartPie", - "options": { - "height": "200px", - "data_url": "/sage_datamart/api/currency_stats.dspy", - "nameField": "currency", - "valueFields": ["call_count"], - "pie_options": {"type": "pie", "radius": "60%"} - } + "widgettype": "VBox", + "options": {"width": "100%", "gap": "8px"}, + "subwidgets": [ + { + "widgettype": "HBox", + "options": {"gap": "4px"}, + "subwidgets": [ + {"widgettype": "Button", "id": "btn_all", "options": {"label": "累计", "i18n": true}}, + {"widgettype": "Button", "id": "btn_year", "options": {"label": "当年", "i18n": true}}, + {"widgettype": "Button", "id": "btn_month", "options": {"label": "当月", "i18n": true}}, + {"widgettype": "Button", "id": "btn_today", "options": {"label": "当天", "i18n": true}} + ] + }, + { + "widgettype": "ChartBar", + "id": "financial_chart", + "options": { + "height": "180px", + "data_url": "{{entire_url('/sage_datamart/api/financial_chart.dspy')}}?period=all", + "nameField": "metric", + "valueFields": ["value"] + } + } + ], + "binds": [ + {"wid": "btn_all", "event": "click", "actiontype": "urldata", "target": "financial_chart", "options": {"url": "{{entire_url('/sage_datamart/api/financial_chart.dspy')}}?period=all"}}, + {"wid": "btn_year", "event": "click", "actiontype": "urldata", "target": "financial_chart", "options": {"url": "{{entire_url('/sage_datamart/api/financial_chart.dspy')}}?period=year"}}, + {"wid": "btn_month", "event": "click", "actiontype": "urldata", "target": "financial_chart", "options": {"url": "{{entire_url('/sage_datamart/api/financial_chart.dspy')}}?period=month"}}, + {"wid": "btn_today", "event": "click", "actiontype": "urldata", "target": "financial_chart", "options": {"url": "{{entire_url('/sage_datamart/api/financial_chart.dspy')}}?period=today"}} + ] }