mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-10 03:09:50 +08:00
fix(overview): 成本分析“提供商用量”展示提供商名称而非 ID
成本分析页「提供商用量」表格的“提供商”列渲染接口返回的 label,而后端在 UsageAnalyticsView::Breakdown + group_by=provider 时把 label 直接写成了 分组键 provider_id,页面因此显示 provider-1 这类内部编号。 - label 改为按「providers.name → 使用记录里的 provider_name 快照 → provider_id 兜底」解析;分组键 id 仍是 provider_id,“查看”链接、按 提供商匹配的已登记支出列、时间序列图表均不受影响。 - 空白与 unknown/unknow/pending 不当作名称;provider_id 为空时保持空标签, 前端沿用“未归属提供商”文案。 - 内存仓储(无数据库部署与测试)同步同一口径,避免两种实现表现不一致。 - 新增用例:内存侧 overview_provider_breakdown_labels_rows_with_provider_name, SQL 侧 provider_breakdown_labels_resolve_catalog_name_before_recorded_name_and_id。
This commit is contained in:
@@ -40,6 +40,20 @@ count(*) FILTER (WHERE actor_user_id IS NOT NULL)::bigint AS trusted_attribution
|
||||
count(*) FILTER (WHERE status = 'failed' AND failure_origin IS NOT NULL AND failure_origin <> 'unknown')::bigint AS classified_failure_count
|
||||
"#;
|
||||
|
||||
// 提供商分组的展示标签:分组键仍然是 provider_id,但页面上要展示“提供商名称”。
|
||||
// 解析顺序与用量审计聚合保持一致:提供商目录中的当前名称 → 使用记录里的名称快照 → 原始 provider_id。
|
||||
// 'unknown' / 'unknow' / 'pending' 是历史占位值,不能当成名称展示;
|
||||
// provider_id 为空说明这条记录本身无法归属,保持空标签让前端显示“未归属提供商”。
|
||||
pub(super) const ANALYTICS_PROVIDER_LABEL_SQL: &str = r#"COALESCE(
|
||||
NULLIF(BTRIM(provider_catalog.name), ''),
|
||||
CASE
|
||||
WHEN page.group_id IS NULL THEN NULL
|
||||
WHEN lower(BTRIM(COALESCE(page.provider_name, ''))) IN ('', 'unknown', 'unknow', 'pending') THEN NULL
|
||||
ELSE BTRIM(page.provider_name)
|
||||
END,
|
||||
page.group_id::text
|
||||
)"#;
|
||||
|
||||
pub(super) fn dashboard_total_metrics_sql() -> &'static str {
|
||||
r#"count(*)::bigint AS request_count,
|
||||
COALESCE(sum(total_tokens),0)::bigint AS total_tokens,
|
||||
@@ -359,6 +373,10 @@ impl SqlxUsageReadRepository {
|
||||
| UsageAnalyticsView::DashboardCharts
|
||||
| UsageAnalyticsView::Breakdown => {
|
||||
let timeseries = query.view != UsageAnalyticsView::Breakdown;
|
||||
// 提供商分组的行需要额外带出名称快照,并在最外层关联提供商目录解析展示名。
|
||||
// 只有明细分组(Breakdown)才需要,时间序列仍然直接用 provider_id 作为标签。
|
||||
let provider_labels =
|
||||
!timeseries && query.group_by == UsageAnalyticsGroupBy::Provider;
|
||||
let group = if timeseries {
|
||||
let granularity = match query.granularity {
|
||||
UsageAnalyticsGranularity::Hour => "hour",
|
||||
@@ -393,6 +411,11 @@ impl SqlxUsageReadRepository {
|
||||
.push(", grouped AS (SELECT ")
|
||||
.push(group)
|
||||
.push(" AS group_id, ")
|
||||
.push(if provider_labels {
|
||||
"max(provider_name) AS provider_name, "
|
||||
} else {
|
||||
""
|
||||
})
|
||||
.push(&metrics_sql)
|
||||
.push(if timeseries {
|
||||
" FROM dated GROUP BY "
|
||||
@@ -414,9 +437,22 @@ impl SqlxUsageReadRepository {
|
||||
.push(", group_id ASC NULLS LAST");
|
||||
}
|
||||
builder.push(" LIMIT ").push_bind(if timeseries { 10_001 } else { i64::from(query.limit) }).push(" OFFSET ").push_bind(if timeseries { 0 } else { query.offset as i64 })
|
||||
.push(") SELECT (SELECT count(*) FROM grouped) AS total, COALESCE(jsonb_agg(jsonb_build_object('id', group_id::text, 'label', group_id::text, 'bucket_start', ")
|
||||
.push(") SELECT (SELECT count(*) FROM grouped) AS total, COALESCE(jsonb_agg(jsonb_build_object('id', page.group_id::text, 'label', ")
|
||||
.push(if provider_labels { ANALYTICS_PROVIDER_LABEL_SQL } else { "page.group_id::text" })
|
||||
.push(", 'bucket_start', ")
|
||||
.push(if timeseries { "to_char(group_id AT TIME ZONE 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"')" } else { "NULL" })
|
||||
.push(", 'metrics', to_jsonb(page) - 'group_id')), '[]'::jsonb) AS items FROM page");
|
||||
.push(", 'metrics', to_jsonb(page) - 'group_id'")
|
||||
.push(if provider_labels {
|
||||
" - 'provider_name'"
|
||||
} else {
|
||||
""
|
||||
})
|
||||
.push(")), '[]'::jsonb) AS items FROM page")
|
||||
.push(if provider_labels {
|
||||
" LEFT JOIN public.providers AS provider_catalog ON provider_catalog.id = page.group_id"
|
||||
} else {
|
||||
""
|
||||
});
|
||||
let row = builder
|
||||
.build()
|
||||
.fetch_one(&mut *tx)
|
||||
|
||||
@@ -5414,3 +5414,21 @@ fn attach_usage_settlement_pricing_snapshot_metadata_adds_missing_values_without
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_breakdown_labels_resolve_catalog_name_before_recorded_name_and_id() {
|
||||
let sql = super::analytics::ANALYTICS_PROVIDER_LABEL_SQL;
|
||||
let catalog = sql
|
||||
.find("provider_catalog.name")
|
||||
.expect("provider catalog name");
|
||||
let recorded = sql
|
||||
.find("page.provider_name")
|
||||
.expect("recorded provider name snapshot");
|
||||
let raw_id = sql
|
||||
.find("page.group_id::text")
|
||||
.expect("raw provider id fallback");
|
||||
// 成本分析“提供商”列的解析顺序:目录名称 → 使用记录里的名称快照 → 原始 provider_id。
|
||||
assert!(catalog < recorded && recorded < raw_id);
|
||||
// 历史占位值不能被当成提供商名称展示。
|
||||
assert!(sql.contains("'unknown', 'unknow', 'pending'"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user