feat(report): expose headline evidence details

This commit is contained in:
phamnazage-jpg
2026-05-13 21:16:08 +08:00
parent 79d991a7e9
commit b4e28d5be4
2 changed files with 326 additions and 130 deletions

View File

@@ -205,29 +205,37 @@ type ActionItem struct {
} }
type HeadlineItem struct { type HeadlineItem struct {
Label string Label string
Title string Title string
Summary string Summary string
Baseline string Baseline string
TrustLabel string TrustLabel string
Tone string SourceKindLabel string
PrimarySource string
UpdatedAt string
EvidenceDetail string
Tone string
} }
type ModelEvent struct { type ModelEvent struct {
EventType string EventType string
ModelName string ModelName string
ProviderName string ProviderName string
OperatorName string OperatorName string
TrustLabel string TrustLabel string
Baseline string SourceKindLabel string
Summary string PrimarySource string
Currency string UpdatedAt string
OldInputPrice float64 EvidenceDetail string
NewInputPrice float64 Baseline string
OldOutputPrice float64 Summary string
NewOutputPrice float64 Currency string
PriceChangePct float64 OldInputPrice float64
Priority int NewInputPrice float64
OldOutputPrice float64
NewOutputPrice float64
PriceChangePct float64
Priority int
} }
type Recommendation struct { type Recommendation struct {
@@ -805,7 +813,8 @@ func loadNewModelEvents(db *sql.DB, date string) ([]ModelEvent, error) {
COALESCE(lp.output_price_per_mtok, 0) AS output_price, COALESCE(lp.output_price_per_mtok, 0) AS output_price,
COALESCE(lp.is_free, false) AS is_free, COALESCE(lp.is_free, false) AS is_free,
COALESCE(m.context_length, 0) AS context_length, COALESCE(m.context_length, 0) AS context_length,
COALESCE(mp.country, 'unknown') AS provider_country COALESCE(mp.country, 'unknown') AS provider_country,
m.created_at
FROM models m FROM models m
LEFT JOIN model_provider mp ON m.provider_id = mp.id LEFT JOIN model_provider mp ON m.provider_id = mp.id
LEFT JOIN latest_prices lp ON lp.model_id = m.id AND lp.rn = 1 LEFT JOIN latest_prices lp ON lp.model_id = m.id AND lp.rn = 1
@@ -832,6 +841,7 @@ func loadNewModelEvents(db *sql.DB, date string) ([]ModelEvent, error) {
isFree bool isFree bool
contextLength int contextLength int
providerCountry string providerCountry string
createdAt time.Time
) )
if err := rows.Scan( if err := rows.Scan(
&modelName, &modelName,
@@ -844,6 +854,7 @@ func loadNewModelEvents(db *sql.DB, date string) ([]ModelEvent, error) {
&isFree, &isFree,
&contextLength, &contextLength,
&providerCountry, &providerCountry,
&createdAt,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@@ -869,17 +880,21 @@ func loadNewModelEvents(db *sql.DB, date string) ([]ModelEvent, error) {
} }
events = append(events, ModelEvent{ events = append(events, ModelEvent{
EventType: "new_model", EventType: "new_model",
ModelName: modelName, ModelName: modelName,
ProviderName: providerName, ProviderName: providerName,
OperatorName: operatorName, OperatorName: operatorName,
TrustLabel: buildTrustLabel(model), TrustLabel: buildTrustLabel(model),
Baseline: "首次出现", SourceKindLabel: "模型快照",
Summary: summary, PrimarySource: buildPrimarySource("region_pricing", operatorName),
Currency: currency, UpdatedAt: createdAt.Format("2006-01-02 15:04"),
NewInputPrice: inputPrice, EvidenceDetail: "models.created_at = 今日,且已存在最新价格快照",
Baseline: "首次出现",
Summary: summary,
Currency: currency,
NewInputPrice: inputPrice,
NewOutputPrice: outputPrice, NewOutputPrice: outputPrice,
Priority: 85 + minInt(contextLength/(1024*128), 10), Priority: 85 + minInt(contextLength/(1024*128), 10),
}) })
} }
return events, rows.Err() return events, rows.Err()
@@ -909,7 +924,8 @@ func loadPriceChangeEvents(db *sql.DB, date string) ([]ModelEvent, error) {
COALESCE(ph.new_input_price, 0), COALESCE(ph.new_input_price, 0),
COALESCE(ph.old_output_price, 0), COALESCE(ph.old_output_price, 0),
COALESCE(ph.new_output_price, 0), COALESCE(ph.new_output_price, 0),
COALESCE(mp.country, 'unknown') AS provider_country COALESCE(mp.country, 'unknown') AS provider_country,
ph.changed_at
FROM pricing_history ph FROM pricing_history ph
JOIN models m ON ph.model_id = m.id JOIN models m ON ph.model_id = m.id
LEFT JOIN model_provider mp ON m.provider_id = mp.id LEFT JOIN model_provider mp ON m.provider_id = mp.id
@@ -936,6 +952,7 @@ func loadPriceChangeEvents(db *sql.DB, date string) ([]ModelEvent, error) {
oldOutputPrice float64 oldOutputPrice float64
newOutputPrice float64 newOutputPrice float64
providerCountry string providerCountry string
changedAt time.Time
) )
if err := rows.Scan( if err := rows.Scan(
&modelName, &modelName,
@@ -948,6 +965,7 @@ func loadPriceChangeEvents(db *sql.DB, date string) ([]ModelEvent, error) {
&oldOutputPrice, &oldOutputPrice,
&newOutputPrice, &newOutputPrice,
&providerCountry, &providerCountry,
&changedAt,
); err != nil { ); err != nil {
return nil, err return nil, err
} }
@@ -967,32 +985,32 @@ func loadPriceChangeEvents(db *sql.DB, date string) ([]ModelEvent, error) {
} }
eventType := "price_increase" eventType := "price_increase"
label := "价格上调"
summary := "价格上调已足以影响默认成本,需要确认备用模型。" summary := "价格上调已足以影响默认成本,需要确认备用模型。"
if changePct < 0 { if changePct < 0 {
eventType = "price_cut" eventType = "price_cut"
label = "价格下调"
summary = "价格下降已足以影响默认选型,值得重新评估同类模型。" summary = "价格下降已足以影响默认选型,值得重新评估同类模型。"
} }
events = append(events, ModelEvent{ events = append(events, ModelEvent{
EventType: eventType, EventType: eventType,
ModelName: modelName, ModelName: modelName,
ProviderName: providerName, ProviderName: providerName,
OperatorName: operatorName, OperatorName: operatorName,
TrustLabel: buildTrustLabel(model), TrustLabel: buildTrustLabel(model),
Baseline: fmt.Sprintf("较昨日 %+.0f%%", changePct), SourceKindLabel: "价格快照",
Summary: summary, PrimarySource: "pricing_history",
Currency: currency, UpdatedAt: changedAt.Format("2006-01-02 15:04"),
OldInputPrice: oldInputPrice, EvidenceDetail: buildPriceEvidenceDetail(changePct, oldInputPrice, newInputPrice, currency),
NewInputPrice: newInputPrice, Baseline: fmt.Sprintf("较昨日 %+.0f%%", changePct),
OldOutputPrice: oldOutputPrice, Summary: summary,
NewOutputPrice: newOutputPrice, Currency: currency,
PriceChangePct: changePct, OldInputPrice: oldInputPrice,
Priority: 70 + minInt(int(abs(changePct)), 25), NewInputPrice: newInputPrice,
OldOutputPrice: oldOutputPrice,
NewOutputPrice: newOutputPrice,
PriceChangePct: changePct,
Priority: 70 + minInt(int(abs(changePct)), 25),
}) })
_ = label
} }
return events, rows.Err() return events, rows.Err()
} }
@@ -1089,15 +1107,19 @@ func enrichModelEvents(r *ReportV3) []ModelEvent {
} }
existing[key] = struct{}{} existing[key] = struct{}{}
events = append(events, ModelEvent{ events = append(events, ModelEvent{
EventType: "free_highlight", EventType: "free_highlight",
ModelName: model.Name, ModelName: model.Name,
ProviderName: model.ProviderName, ProviderName: model.ProviderName,
OperatorName: model.OperatorName, OperatorName: model.OperatorName,
TrustLabel: buildTrustLabel(model), TrustLabel: buildTrustLabel(model),
Baseline: "今日快照", SourceKindLabel: "免费策略快照",
Summary: buildModelEvidence(model), PrimarySource: buildPrimarySource("free_snapshot", model.OperatorName),
Currency: model.Currency, UpdatedAt: formatEventUpdatedAt(r.GeneratedAt, r.Date),
Priority: priority, EvidenceDetail: buildFreeEvidenceDetail(model),
Baseline: "今日快照",
Summary: buildModelEvidence(model),
Currency: model.Currency,
Priority: priority,
}) })
} }
@@ -1311,11 +1333,15 @@ func buildHeadlineItemsFromEvents(events []ModelEvent) []HeadlineItem {
func headlineItemFromModelEvent(event ModelEvent) HeadlineItem { func headlineItemFromModelEvent(event ModelEvent) HeadlineItem {
item := HeadlineItem{ item := HeadlineItem{
Title: event.ModelName, Title: event.ModelName,
Summary: event.Summary, Summary: event.Summary,
Baseline: event.Baseline, Baseline: event.Baseline,
TrustLabel: event.TrustLabel, TrustLabel: event.TrustLabel,
Tone: "neutral", SourceKindLabel: event.SourceKindLabel,
PrimarySource: event.PrimarySource,
UpdatedAt: event.UpdatedAt,
EvidenceDetail: event.EvidenceDetail,
Tone: "neutral",
} }
switch event.EventType { switch event.EventType {
@@ -1343,6 +1369,58 @@ func headlineItemFromModelEvent(event ModelEvent) HeadlineItem {
return item return item
} }
func buildPrimarySource(sourceKind, operatorName string) string {
switch sourceKind {
case "region_pricing":
if operatorName == "" {
return "region_pricing"
}
return operatorName + " / region_pricing"
case "free_snapshot":
if operatorName == "" {
return "free snapshot"
}
return operatorName + " / free snapshot"
default:
return sourceKind
}
}
func buildPriceEvidenceDetail(changePct, oldPrice, newPrice float64, currency string) string {
direction := "上涨"
if changePct < 0 {
direction = "下降"
}
return fmt.Sprintf(
"pricing_history 记录到输入价格由 %s 调整为 %s较昨日%s %.0f%%",
formatPrice(oldPrice, currency),
formatPrice(newPrice, currency),
direction,
abs(changePct),
)
}
func buildFreeEvidenceDetail(model ModelInfo) string {
switch classifyFreeSource(model) {
case "官方免费":
return fmt.Sprintf("%s 当前快照显示为官方免费入口", model.OperatorName)
case "聚合免费":
return fmt.Sprintf("%s 当前快照显示为聚合免费入口", model.OperatorName)
default:
return fmt.Sprintf("%s 当前快照显示免费,但来源仍待确认", model.OperatorName)
}
}
func formatEventUpdatedAt(value, fallbackDate string) string {
if strings.TrimSpace(value) != "" {
return value
}
if fallbackDate != "" {
return fallbackDate + " 00:00"
}
return "-"
}
func buildActionItems(r *ReportV3) []ActionItem { func buildActionItems(r *ReportV3) []ActionItem {
var actions []ActionItem var actions []ActionItem
@@ -1670,7 +1748,19 @@ func generateMarkdownV3(r *ReportV3, path string) error {
for _, item := range r.HeadlineItems { for _, item := range r.HeadlineItems {
fmt.Fprintf(f, "### %s · %s\n\n", item.Label, item.Title) fmt.Fprintf(f, "### %s · %s\n\n", item.Label, item.Title)
fmt.Fprintf(f, "- 影响: %s\n", item.Summary) fmt.Fprintf(f, "- 影响: %s\n", item.Summary)
if item.SourceKindLabel != "" {
fmt.Fprintf(f, "- 事件来源: %s\n", item.SourceKindLabel)
}
if item.PrimarySource != "" {
fmt.Fprintf(f, "- 主来源: %s\n", item.PrimarySource)
}
if item.UpdatedAt != "" {
fmt.Fprintf(f, "- 更新时间: %s\n", item.UpdatedAt)
}
fmt.Fprintf(f, "- 基线: %s\n", item.Baseline) fmt.Fprintf(f, "- 基线: %s\n", item.Baseline)
if item.EvidenceDetail != "" {
fmt.Fprintf(f, "- 判定依据: %s\n", item.EvidenceDetail)
}
fmt.Fprintf(f, "- 可信度: %s\n\n", item.TrustLabel) fmt.Fprintf(f, "- 可信度: %s\n\n", item.TrustLabel)
} }
@@ -1984,11 +2074,26 @@ body {
font-weight: 700; font-weight: 700;
} }
.trust-line, .trust-line,
.baseline-line { .baseline-line,
.source-line {
margin-top: 8px; margin-top: 8px;
font-size: 0.9rem; font-size: 0.9rem;
color: var(--ink-soft); color: var(--ink-soft);
} }
.evidence-block {
margin-top: 10px;
padding-top: 10px;
border-top: 1px dashed var(--line);
display: grid;
gap: 6px;
}
.evidence-item {
font-size: 0.92rem;
color: var(--ink-soft);
}
.evidence-item strong {
color: var(--ink);
}
.scene-header { .scene-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@@ -2167,8 +2272,14 @@ th {
<div class="card-kicker">{{.Label}}</div> <div class="card-kicker">{{.Label}}</div>
<div class="card-title">{{.Title}}</div> <div class="card-title">{{.Title}}</div>
<div class="card-summary">{{.Summary}}</div> <div class="card-summary">{{.Summary}}</div>
{{if .SourceKindLabel}}<div class="source-line">事件来源:{{.SourceKindLabel}}</div>{{end}}
<div class="baseline-line">基线:{{.Baseline}}</div> <div class="baseline-line">基线:{{.Baseline}}</div>
<div class="trust-line">可信度:{{.TrustLabel}}</div> <div class="trust-line">可信度:{{.TrustLabel}}</div>
<div class="evidence-block">
{{if .PrimarySource}}<div class="evidence-item"><strong>主来源</strong>{{.PrimarySource}}</div>{{end}}
{{if .UpdatedAt}}<div class="evidence-item"><strong>更新时间</strong>{{.UpdatedAt}}</div>{{end}}
{{if .EvidenceDetail}}<div class="evidence-item"><strong>判定依据</strong>{{.EvidenceDetail}}</div>{{end}}
</div>
</article> </article>
{{end}} {{end}}
</div> </div>

View File

@@ -184,25 +184,33 @@ func TestDecorateReportV1BuildsHotDaySummary(t *testing.T) {
report := sampleReportForV1() report := sampleReportForV1()
report.ModelEvents = []ModelEvent{ report.ModelEvents = []ModelEvent{
{ {
EventType: "new_model", EventType: "new_model",
ModelName: "DeepSeek-V4-Flash", ModelName: "DeepSeek-V4-Flash",
ProviderName: "DeepSeek", ProviderName: "DeepSeek",
OperatorName: "OpenRouter", OperatorName: "OpenRouter",
TrustLabel: "聚合来源", TrustLabel: "聚合来源",
Baseline: "首次出现", Baseline: "首次出现",
Summary: "新模型进入情报池,值得重新评估低成本编码默认选择。", Summary: "新模型进入情报池,值得重新评估低成本编码默认选择。",
Priority: 95, SourceKindLabel: "模型快照",
PrimarySource: "OpenRouter / region_pricing",
UpdatedAt: "2026-05-13 09:30",
EvidenceDetail: "models.created_at = 今日,且已存在最新价格快照",
Priority: 95,
}, },
{ {
EventType: "price_cut", EventType: "price_cut",
ModelName: "qwen-vl-max", ModelName: "qwen-vl-max",
ProviderName: "Alibaba", ProviderName: "Alibaba",
OperatorName: "DashScope", OperatorName: "DashScope",
TrustLabel: "官方来源", TrustLabel: "官方来源",
Baseline: "较昨日 -18%", Baseline: "较昨日 -18%",
Summary: "价格下降已足以影响视觉模型默认选择。", Summary: "价格下降已足以影响视觉模型默认选择。",
PriceChangePct: -18, SourceKindLabel: "价格快照",
Priority: 90, PrimarySource: "pricing_history",
UpdatedAt: "2026-05-13 10:00",
EvidenceDetail: "pricing_history 记录到输入价格较昨日下降 18%",
PriceChangePct: -18,
Priority: 90,
}, },
} }
@@ -277,6 +285,22 @@ func TestGenerateMarkdownV3IncludesTencentSubscriptionSection(t *testing.T) {
ModelPreview: "hy3-preview", ModelPreview: "hy3-preview",
}, },
} }
report.ModelEvents = []ModelEvent{
{
EventType: "new_model",
ModelName: "DeepSeek-V4-Flash",
ProviderName: "DeepSeek",
OperatorName: "OpenRouter",
TrustLabel: "聚合来源",
Baseline: "首次出现",
Summary: "新模型进入情报池,值得重新评估低成本编码默认选择。",
SourceKindLabel: "模型快照",
PrimarySource: "OpenRouter / region_pricing",
UpdatedAt: "2026-05-13 09:30",
EvidenceDetail: "models.created_at = 今日,且已存在最新价格快照",
Priority: 95,
},
}
decorateReportV1(report) decorateReportV1(report)
if err := generateMarkdownV3(report, path); err != nil { if err := generateMarkdownV3(report, path); err != nil {
@@ -295,6 +319,9 @@ func TestGenerateMarkdownV3IncludesTencentSubscriptionSection(t *testing.T) {
"## 今日变化", "## 今日变化",
"## 场景推荐", "## 场景推荐",
"## 完整数据附录", "## 完整数据附录",
"主来源: OpenRouter / region_pricing",
"更新时间: 2026-05-13 09:30",
"判定依据: models.created_at = 今日,且已存在最新价格快照",
"## 💳 腾讯云套餐订阅价", "## 💳 腾讯云套餐订阅价",
"通用 Token Plan Lite", "通用 Token Plan Lite",
"Hy Token Plan Max", "Hy Token Plan Max",
@@ -313,14 +340,18 @@ func TestGenerateHTMLV3IncludesTencentSubscriptionSection(t *testing.T) {
report := sampleReportForV1() report := sampleReportForV1()
report.ModelEvents = []ModelEvent{ report.ModelEvents = []ModelEvent{
{ {
EventType: "new_model", EventType: "new_model",
ModelName: "DeepSeek-V4-Flash", ModelName: "DeepSeek-V4-Flash",
ProviderName: "DeepSeek", ProviderName: "DeepSeek",
OperatorName: "OpenRouter", OperatorName: "OpenRouter",
TrustLabel: "聚合来源", TrustLabel: "聚合来源",
Baseline: "首次出现", Baseline: "首次出现",
Summary: "新模型进入情报池,值得重新评估低成本编码默认选择。", Summary: "新模型进入情报池,值得重新评估低成本编码默认选择。",
Priority: 95, SourceKindLabel: "模型快照",
PrimarySource: "OpenRouter / region_pricing",
UpdatedAt: "2026-05-13 09:30",
EvidenceDetail: "models.created_at = 今日,且已存在最新价格快照",
Priority: 95,
}, },
} }
report.TencentSubscriptionPlans = []SubscriptionPlanInfo{ report.TencentSubscriptionPlans = []SubscriptionPlanInfo{
@@ -354,6 +385,10 @@ func TestGenerateHTMLV3IncludesTencentSubscriptionSection(t *testing.T) {
"今日头条", "今日头条",
"DeepSeek-V4-Flash", "DeepSeek-V4-Flash",
"首次出现", "首次出现",
"主来源",
"更新时间",
"判定依据",
"模型快照",
"场景推荐", "场景推荐",
"完整数据附录", "完整数据附录",
"官方免费", "官方免费",
@@ -371,25 +406,33 @@ func TestBuildHeadlineItemsUsesModelEvents(t *testing.T) {
report := sampleReportForV1() report := sampleReportForV1()
report.ModelEvents = []ModelEvent{ report.ModelEvents = []ModelEvent{
{ {
EventType: "price_cut", EventType: "price_cut",
ModelName: "glm-5", ModelName: "glm-5",
ProviderName: "Zhipu", ProviderName: "Zhipu",
OperatorName: "Zhipu", OperatorName: "Zhipu",
TrustLabel: "官方来源", TrustLabel: "官方来源",
Baseline: "较昨日 -25%", Baseline: "较昨日 -25%",
Summary: "价格下降已足以影响中文通用场景默认选型。", Summary: "价格下降已足以影响中文通用场景默认选型。",
PriceChangePct: -25, SourceKindLabel: "价格快照",
Priority: 100, PrimarySource: "pricing_history",
UpdatedAt: "2026-05-13 10:00",
EvidenceDetail: "pricing_history 记录到输入价格较昨日下降 25%",
PriceChangePct: -25,
Priority: 100,
}, },
{ {
EventType: "new_model", EventType: "new_model",
ModelName: "DeepSeek-V4-Flash", ModelName: "DeepSeek-V4-Flash",
ProviderName: "DeepSeek", ProviderName: "DeepSeek",
OperatorName: "OpenRouter", OperatorName: "OpenRouter",
TrustLabel: "聚合来源", TrustLabel: "聚合来源",
Baseline: "首次出现", Baseline: "首次出现",
Summary: "新模型进入情报池,值得重新评估低成本编码默认选择。", Summary: "新模型进入情报池,值得重新评估低成本编码默认选择。",
Priority: 90, SourceKindLabel: "模型快照",
PrimarySource: "OpenRouter / region_pricing",
UpdatedAt: "2026-05-13 09:30",
EvidenceDetail: "models.created_at = 今日,且已存在最新价格快照",
Priority: 90,
}, },
} }
@@ -404,39 +447,54 @@ func TestBuildHeadlineItemsUsesModelEvents(t *testing.T) {
if items[0].Baseline != "较昨日 -25%" { if items[0].Baseline != "较昨日 -25%" {
t.Fatalf("expected event baseline to be preserved, got %+v", items[0]) t.Fatalf("expected event baseline to be preserved, got %+v", items[0])
} }
if items[0].SourceKindLabel != "价格快照" || items[0].PrimarySource != "pricing_history" {
t.Fatalf("expected event evidence fields to be preserved, got %+v", items[0])
}
} }
func TestBuildHeadlineItemsDeduplicatesSameModel(t *testing.T) { func TestBuildHeadlineItemsDeduplicatesSameModel(t *testing.T) {
report := sampleReportForV1() report := sampleReportForV1()
report.ModelEvents = []ModelEvent{ report.ModelEvents = []ModelEvent{
{ {
EventType: "price_cut", EventType: "price_cut",
ModelName: "OpenAI: GPT-4o", ModelName: "OpenAI: GPT-4o",
ProviderName: "OpenAI", ProviderName: "OpenAI",
TrustLabel: "官方来源", TrustLabel: "官方来源",
Baseline: "较昨日 -20%", Baseline: "较昨日 -20%",
Summary: "价格下降影响默认成本。", Summary: "价格下降影响默认成本。",
PriceChangePct: -20, SourceKindLabel: "价格快照",
Priority: 95, PrimarySource: "pricing_history",
UpdatedAt: "2026-05-13 10:00",
EvidenceDetail: "pricing_history 记录到输入价格较昨日下降 20%",
PriceChangePct: -20,
Priority: 95,
}, },
{ {
EventType: "price_increase", EventType: "price_increase",
ModelName: "OpenAI: GPT-4o", ModelName: "OpenAI: GPT-4o",
ProviderName: "OpenAI", ProviderName: "OpenAI",
TrustLabel: "官方来源", TrustLabel: "官方来源",
Baseline: "较昨日 +5%", Baseline: "较昨日 +5%",
Summary: "同日另有上调记录。", Summary: "同日另有上调记录。",
PriceChangePct: 5, SourceKindLabel: "价格快照",
Priority: 80, PrimarySource: "pricing_history",
UpdatedAt: "2026-05-13 11:00",
EvidenceDetail: "pricing_history 记录到输入价格较昨日上涨 5%",
PriceChangePct: 5,
Priority: 80,
}, },
{ {
EventType: "new_model", EventType: "new_model",
ModelName: "Claude Opus 4.7", ModelName: "Claude Opus 4.7",
ProviderName: "Anthropic", ProviderName: "Anthropic",
TrustLabel: "聚合来源", TrustLabel: "聚合来源",
Baseline: "首次出现", Baseline: "首次出现",
Summary: "新模型上线。", Summary: "新模型上线。",
Priority: 70, SourceKindLabel: "模型快照",
PrimarySource: "OpenRouter / region_pricing",
UpdatedAt: "2026-05-13 09:00",
EvidenceDetail: "models.created_at = 今日,且已存在最新价格快照",
Priority: 70,
}, },
} }
@@ -449,3 +507,30 @@ func TestBuildHeadlineItemsDeduplicatesSameModel(t *testing.T) {
t.Fatalf("expected duplicate model event to be removed, got %+v", items) t.Fatalf("expected duplicate model event to be removed, got %+v", items)
} }
} }
func TestHeadlineItemFromModelEventIncludesEvidenceFields(t *testing.T) {
item := headlineItemFromModelEvent(ModelEvent{
EventType: "new_model",
ModelName: "DeepSeek-V4-Flash",
TrustLabel: "聚合来源",
Baseline: "首次出现",
Summary: "新模型进入情报池。",
SourceKindLabel: "模型快照",
PrimarySource: "OpenRouter / region_pricing",
UpdatedAt: "2026-05-13 09:30",
EvidenceDetail: "models.created_at = 今日,且已存在最新价格快照",
})
if item.SourceKindLabel != "模型快照" {
t.Fatalf("expected source kind label to be propagated, got %+v", item)
}
if item.PrimarySource != "OpenRouter / region_pricing" {
t.Fatalf("expected primary source to be propagated, got %+v", item)
}
if item.UpdatedAt != "2026-05-13 09:30" {
t.Fatalf("expected updated at to be propagated, got %+v", item)
}
if item.EvidenceDetail == "" {
t.Fatalf("expected evidence detail to be populated, got %+v", item)
}
}