Files
llm-intelligence/scripts/generate_daily_report_test.go
2026-05-29 18:48:48 +08:00

1851 lines
62 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//go:build llm_script
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func sampleReportForV1() *ReportV3 {
return &ReportV3{
Date: "2026-05-13",
GeneratedAt: "2026-05-13T09:30:00+08:00",
TotalModels: 504,
AllModels: []ModelInfo{
{
Name: "DeepSeek-V4-Flash",
ProviderName: "DeepSeek",
ProviderCountry: "CN",
ContextLength: 262144,
InputPrice: 0.30,
OutputPrice: 1.20,
Currency: "USD",
OperatorName: "OpenRouter",
OperatorType: "reseller",
Region: "global",
SceneTags: []SceneTag{SceneCode, SceneReasoning},
},
{
Name: "glm-5",
ProviderName: "Zhipu",
ProviderCountry: "CN",
ContextLength: 131072,
InputPrice: 0,
OutputPrice: 0,
Currency: "CNY",
IsFree: true,
OperatorName: "Zhipu",
OperatorType: "official",
Region: "cn",
SceneTags: []SceneTag{SceneWriting, SceneChat},
},
{
Name: "claude-3.7-sonnet",
ProviderName: "Anthropic",
ProviderCountry: "US",
ContextLength: 200000,
InputPrice: 3.0,
OutputPrice: 15.0,
Currency: "USD",
OperatorName: "Anthropic",
OperatorType: "official",
Region: "global",
SceneTags: []SceneTag{SceneWriting, SceneChat},
},
{
Name: "qwen-vl-max",
ProviderName: "Alibaba",
ProviderCountry: "CN",
ContextLength: 65536,
InputPrice: 0.8,
OutputPrice: 2.4,
Currency: "CNY",
OperatorName: "DashScope",
OperatorType: "cloud",
Region: "cn",
SceneTags: []SceneTag{SceneVision},
},
},
FreeModels: []ModelInfo{
{
Name: "glm-5",
ProviderName: "Zhipu",
ProviderCountry: "CN",
ContextLength: 131072,
Currency: "CNY",
IsFree: true,
OperatorName: "Zhipu",
OperatorType: "official",
Region: "cn",
SceneTags: []SceneTag{SceneWriting, SceneChat},
},
{
Name: "DeepSeek-V4-Flash",
ProviderName: "DeepSeek",
ProviderCountry: "CN",
ContextLength: 262144,
Currency: "USD",
IsFree: true,
OperatorName: "OpenRouter",
OperatorType: "reseller",
Region: "global",
SceneTags: []SceneTag{SceneCode, SceneReasoning},
},
{
Name: "mystery-free-model",
ProviderName: "Unknown",
ProviderCountry: "unknown",
ContextLength: 65536,
Currency: "USD",
IsFree: true,
OperatorName: "Unknown Gateway",
OperatorType: "self_hosted_gateway",
Region: "global",
SceneTags: []SceneTag{SceneChat},
},
},
FreeTop20: []ModelInfo{
{
Name: "glm-5",
ProviderName: "Zhipu",
ProviderCountry: "CN",
ContextLength: 131072,
Currency: "CNY",
IsFree: true,
OperatorName: "Zhipu",
OperatorType: "official",
Region: "cn",
},
},
IntlTop5: []ModelInfo{
{
Name: "DeepSeek-V4-Flash",
ProviderName: "DeepSeek",
ProviderCountry: "CN",
ContextLength: 262144,
InputPrice: 0.30,
OutputPrice: 1.20,
Currency: "USD",
OperatorName: "OpenRouter",
OperatorType: "reseller",
Region: "global",
SceneTags: []SceneTag{SceneCode, SceneReasoning},
},
},
DomesticTop10: []ModelInfo{
{
Name: "qwen-vl-max",
ProviderName: "Alibaba",
ProviderCountry: "CN",
ContextLength: 65536,
InputPrice: 0.8,
OutputPrice: 2.4,
Currency: "CNY",
OperatorName: "DashScope",
OperatorType: "cloud",
Region: "cn",
SceneTags: []SceneTag{SceneVision},
},
},
DailySignals: DailySignals{
NewModels: 2,
PriceChanges: 1,
OfficialFree: 1,
AggregatorFree: 1,
UnknownFree: 1,
},
}
}
func TestBuildModelSelectionsSeparatesTopListsFromAppendixLists(t *testing.T) {
intlModels := []ModelInfo{
{Name: "intl-1", InputPrice: 0.1, Currency: "USD"},
{Name: "intl-2", InputPrice: 0.2, Currency: "USD"},
{Name: "intl-3", InputPrice: 0.3, Currency: "USD"},
{Name: "intl-4", InputPrice: 0.4, Currency: "USD"},
{Name: "intl-5", InputPrice: 0.5, Currency: "USD"},
{Name: "intl-6", InputPrice: 0.6, Currency: "USD"},
}
var domesticModels []ModelInfo
for i := 0; i < 14; i++ {
domesticModels = append(domesticModels, ModelInfo{
Name: fmt.Sprintf("domestic-%02d", i+1),
InputPrice: float64(i + 1),
Currency: "CNY",
ContextLength: 65536,
})
}
selections := buildModelSelections(intlModels, domesticModels, nil)
if len(selections.IntlTop5) != 5 {
t.Fatalf("expected intl top5 length 5, got %d", len(selections.IntlTop5))
}
if len(selections.IntlAppendixList) != 6 {
t.Fatalf("expected intl appendix length 6, got %d", len(selections.IntlAppendixList))
}
if len(selections.DomesticTop10) != 10 {
t.Fatalf("expected domestic top10 length 10, got %d", len(selections.DomesticTop10))
}
if len(selections.DomesticAppendixList) != 14 {
t.Fatalf("expected domestic appendix length 14, got %d", len(selections.DomesticAppendixList))
}
if selections.DomesticTop10[0].Name != "domestic-01" || selections.DomesticTop10[9].Name != "domestic-10" {
t.Fatalf("domestic top10 ordering mismatch: first=%s tenth=%s", selections.DomesticTop10[0].Name, selections.DomesticTop10[9].Name)
}
if selections.DomesticAppendixList[13].Name != "domestic-14" {
t.Fatalf("domestic appendix should preserve full list, got tail=%s", selections.DomesticAppendixList[13].Name)
}
}
func TestBuildFreeSourceBreakdown(t *testing.T) {
report := sampleReportForV1()
breakdown := buildFreeSourceBreakdown(report.FreeModels)
if len(breakdown) != 3 {
t.Fatalf("expected 3 free source groups, got %d", len(breakdown))
}
if breakdown[0].Label != "官方免费" || breakdown[0].Count != 1 {
t.Fatalf("unexpected official free breakdown: %+v", breakdown[0])
}
if breakdown[1].Label != "聚合免费" || breakdown[1].Count != 1 {
t.Fatalf("unexpected aggregator free breakdown: %+v", breakdown[1])
}
if breakdown[2].Label != "待确认" || breakdown[2].Count != 1 {
t.Fatalf("unexpected unknown free breakdown: %+v", breakdown[2])
}
}
func TestResolveReportDateDefaultsToToday(t *testing.T) {
got, err := resolveReportDate(time.Date(2026, 5, 14, 9, 0, 0, 0, time.FixedZone("CST", 8*3600)), nil, "")
if err != nil {
t.Fatalf("resolveReportDate returned error: %v", err)
}
if got != "2026-05-14" {
t.Fatalf("date = %q, want %q", got, "2026-05-14")
}
}
func TestResolveReportDateUsesEnvDate(t *testing.T) {
got, err := resolveReportDate(time.Now(), nil, "2026-05-09")
if err != nil {
t.Fatalf("resolveReportDate returned error: %v", err)
}
if got != "2026-05-09" {
t.Fatalf("date = %q, want %q", got, "2026-05-09")
}
}
func TestResolveReportDateCLIOverridesEnv(t *testing.T) {
got, err := resolveReportDate(time.Now(), []string{"-date", "2024-06-05"}, "2026-05-09")
if err != nil {
t.Fatalf("resolveReportDate returned error: %v", err)
}
if got != "2024-06-05" {
t.Fatalf("date = %q, want %q", got, "2024-06-05")
}
}
func TestResolveReportDateSupportsEqualsSyntax(t *testing.T) {
got, err := resolveReportDate(time.Now(), []string{"--date=2024-10-25"}, "")
if err != nil {
t.Fatalf("resolveReportDate returned error: %v", err)
}
if got != "2024-10-25" {
t.Fatalf("date = %q, want %q", got, "2024-10-25")
}
}
func TestResolveReportDateRejectsInvalidDate(t *testing.T) {
if _, err := resolveReportDate(time.Now(), []string{"-date", "2026/05/09"}, ""); err == nil {
t.Fatalf("expected invalid date error")
}
}
func TestResolveReportRunContextDefaultsToManualCLI(t *testing.T) {
ctx := resolveReportRunContext("2026-05-14", time.Date(2026, 5, 14, 8, 0, 0, 0, time.FixedZone("CST", 8*3600)), "", "", "", "")
if ctx.RunKind != "manual" {
t.Fatalf("run kind = %q, want manual", ctx.RunKind)
}
if ctx.TriggerSource != "cli" {
t.Fatalf("trigger source = %q, want cli", ctx.TriggerSource)
}
if ctx.IsOfficialDaily {
t.Fatalf("manual run should not be official daily: %+v", ctx)
}
}
func TestResolveReportRunContextHonorsScheduledEnv(t *testing.T) {
ctx := resolveReportRunContext("2026-05-14", time.Date(2026, 5, 14, 8, 0, 0, 0, time.FixedZone("CST", 8*3600)), "scheduled", "cron", "true", "")
if ctx.RunKind != "scheduled" || ctx.TriggerSource != "cron" || !ctx.IsOfficialDaily {
t.Fatalf("unexpected scheduled context: %+v", ctx)
}
}
func TestResolveReportRunContextMarksHistoricalRebuildAsNonOfficial(t *testing.T) {
ctx := resolveReportRunContext("2025-08-07", time.Date(2026, 5, 14, 8, 0, 0, 0, time.FixedZone("CST", 8*3600)), "historical_rebuild", "rebuild_script", "false", "")
if ctx.RunKind != "historical_rebuild" {
t.Fatalf("run kind = %q, want historical_rebuild", ctx.RunKind)
}
if ctx.TriggerSource != "rebuild_script" {
t.Fatalf("trigger source = %q, want rebuild_script", ctx.TriggerSource)
}
if ctx.IsOfficialDaily {
t.Fatalf("historical rebuild should not be official daily: %+v", ctx)
}
}
func TestResolveReportOutputPathsKeepsManualRunOutOfOfficialDailyPaths(t *testing.T) {
official, err := resolveReportOutputPaths("2026-05-14", ReportRunContext{
RunKind: "scheduled",
TriggerSource: "cron",
IsOfficialDaily: true,
})
if err != nil {
t.Fatalf("resolveReportOutputPaths returned error: %v", err)
}
if official.MarkdownPath != "reports/daily/daily_report_2026-05-14.md" {
t.Fatalf("official markdown path = %q", official.MarkdownPath)
}
if official.HTMLPath != "reports/daily/html/daily_report_2026-05-14.html" {
t.Fatalf("official html path = %q", official.HTMLPath)
}
manual, err := resolveReportOutputPaths("2026-05-14", ReportRunContext{
RunKind: "manual",
TriggerSource: "pipeline",
IsOfficialDaily: false,
})
if err != nil {
t.Fatalf("resolveReportOutputPaths returned error: %v", err)
}
if manual.MarkdownPath == official.MarkdownPath {
t.Fatalf("manual run should not overwrite official markdown path: %q", manual.MarkdownPath)
}
if manual.HTMLPath == official.HTMLPath {
t.Fatalf("manual run should not overwrite official html path: %q", manual.HTMLPath)
}
if !strings.Contains(manual.MarkdownPath, "reports/ad_hoc/") {
t.Fatalf("manual markdown path should be isolated, got %q", manual.MarkdownPath)
}
if !strings.Contains(manual.HTMLPath, "reports/ad_hoc/") {
t.Fatalf("manual html path should be isolated, got %q", manual.HTMLPath)
}
}
func TestResolveSignatureAuditReportConfigDefaults(t *testing.T) {
t.Setenv("REPORT_SIGNATURE_AUDIT_WINDOW", "")
t.Setenv("REPORT_SIGNATURE_AUDIT_CHANGED_THRESHOLD", "")
cfg := resolveSignatureAuditReportConfig()
if cfg.Window != 5 {
t.Fatalf("window = %d, want 5", cfg.Window)
}
if cfg.ChangedRunsThreshold != 1 {
t.Fatalf("changed threshold = %d, want 1", cfg.ChangedRunsThreshold)
}
}
func TestResolveSignatureAuditReportConfigReadsEnvOverride(t *testing.T) {
t.Setenv("REPORT_SIGNATURE_AUDIT_WINDOW", "9")
t.Setenv("REPORT_SIGNATURE_AUDIT_CHANGED_THRESHOLD", "3")
cfg := resolveSignatureAuditReportConfig()
if cfg.Window != 9 {
t.Fatalf("window = %d, want 9", cfg.Window)
}
if cfg.ChangedRunsThreshold != 3 {
t.Fatalf("changed threshold = %d, want 3", cfg.ChangedRunsThreshold)
}
}
func TestComposeTrackedSummaryPrependsRuntimeAudit(t *testing.T) {
summary := composeTrackedSummary(
"models=42 free=3 intl=5 domestic=10",
ReportRunContext{
RunKind: "scheduled",
TriggerSource: "cron",
IsOfficialDaily: true,
RuntimeAudit: "runtime_audit stage_set=openrouter,multi_source,official_imports,daily_report selected_source_keys=openrouter,moonshot,deepseek,openai,zhipu,baidu,bytedance failed_source_keys=none",
},
)
if !strings.Contains(summary, "runtime_audit stage_set=openrouter,multi_source,official_imports,daily_report") {
t.Fatalf("expected runtime audit in tracked summary, got %q", summary)
}
if !strings.Contains(summary, "failed_source_keys=none") {
t.Fatalf("expected failed source keys in tracked summary, got %q", summary)
}
if !strings.Contains(summary, "models=42 free=3 intl=5 domestic=10") {
t.Fatalf("expected report summary to be preserved, got %q", summary)
}
}
func TestDecorateReportV1BuildsHotDaySummary(t *testing.T) {
report := sampleReportForV1()
report.ModelEvents = []ModelEvent{
{
EventType: "official_release",
ModelName: "GLM-5",
ProviderName: "Zhipu",
OperatorName: "Zhipu",
TrustLabel: "官方来源 / 一级证据",
Baseline: "官方首次发布",
Summary: "官方发布新模型,值得优先复查中文通用与推理场景默认选择。",
SourceKindLabel: "一级官方发布",
PrimarySource: "https://open.bigmodel.cn/dev/howuse/model",
UpdatedAt: "2026-05-13 08:30",
EvidenceDetail: "models.release_date = 今日,且 source_url 指向官方文档",
Priority: 120,
},
{
EventType: "official_release",
ModelName: "Doubao Seed 1.8",
ProviderName: "ByteDance",
OperatorName: "ByteDance Volcano",
TrustLabel: "官方来源 / 二级佐证",
Baseline: "官方首次发布",
Summary: "次级权威报道已形成稳定发布日期信号,适合进入观察池但不应与一级公告混同。",
SourceKindLabel: "二级权威佐证发布",
PrimarySource: "https://developer.volcengine.com/articles/7601918680544641034",
UpdatedAt: "2025-12-18 00:00",
EvidenceDetail: "models.release_date = 今日,发布日期采用次级权威报道佐证,模型来源页保留官方文档",
Priority: 110,
},
{
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,
},
{
EventType: "price_cut",
ModelName: "qwen-vl-max",
ProviderName: "Alibaba",
ProviderCountry: "CN",
OperatorName: "DashScope",
TrustLabel: "官方来源",
Baseline: "较昨日 -18%",
Summary: "价格下降已足以影响视觉模型默认选择。",
SourceKindLabel: "价格快照",
PrimarySource: "pricing_history",
SourceURL: "https://dashscope.aliyun.com/model/qwen-vl-max",
UpdatedAt: "2026-05-13 10:00",
EvidenceDetail: "pricing_history 记录到输入价格较昨日下降 18%",
PriceChangePct: -18,
Priority: 90,
},
}
decorateReportV1(report)
if report.PageMode != "hot" {
t.Fatalf("expected hot page mode, got %q", report.PageMode)
}
if !strings.Contains(report.HeroSummary, "qwen-vl-max") || !strings.Contains(report.HeroSummary, "CN / Alibaba / DashScope") || !strings.Contains(report.HeroSummary, "价格下降") {
t.Fatalf("hero summary should prioritize price change signal with org metadata, got %s", report.HeroSummary)
}
if len(report.ActionItems) != 3 {
t.Fatalf("expected 3 action items, got %d", len(report.ActionItems))
}
if len(report.HeadlineItems) == 0 {
t.Fatalf("expected headline items to be built")
}
if report.ActionItems[0].Evidence == "" {
t.Fatalf("expected action item evidence to be populated")
}
if report.HeadlineItems[0].ProviderCountry == "" {
t.Fatalf("expected headline item country metadata, got %+v", report.HeadlineItems[0])
}
if report.HeadlineItems[0].Label != "价格下调" || !strings.Contains(report.HeadlineItems[0].Title, "qwen-vl-max") {
t.Fatalf("expected first headline to prioritize price cut, got %+v", report.HeadlineItems[0])
}
}
func TestDecorateReportV1BuildsCalmDaySummary(t *testing.T) {
report := sampleReportForV1()
report.DailySignals = DailySignals{}
decorateReportV1(report)
if report.PageMode != "calm" {
t.Fatalf("expected calm page mode, got %q", report.PageMode)
}
if !strings.Contains(report.HeroSummary, "稳定") {
t.Fatalf("expected calm day summary to emphasize stability, got %s", report.HeroSummary)
}
}
func TestGenerateMarkdownV3IncludesTencentSubscriptionSection(t *testing.T) {
path := filepath.Join(t.TempDir(), "daily_report.md")
report := sampleReportForV1()
report.QualitySummary = DataQualitySummary{
Total: 502,
Fresh: 490,
CNY: 126,
USD: 376,
}
report.TencentSubscriptionPlans = []SubscriptionPlanInfo{
{
PlanName: "通用 Token Plan Lite",
PlanFamily: "token_plan",
Tier: "Lite",
Currency: "CNY",
ListPrice: 39,
QuotaValue: 35000000,
QuotaUnit: "tokens/month",
ContextWindow: 0,
ModelCount: 10,
ModelPreview: "tc-code-latest, glm-5, glm-5.1",
},
{
PlanName: "Hy Token Plan Max",
PlanFamily: "token_plan",
Tier: "Max",
Currency: "CNY",
ListPrice: 468,
QuotaValue: 650000000,
QuotaUnit: "tokens/month",
ContextWindow: 262144,
ModelCount: 1,
ModelPreview: "hy3-preview",
},
}
report.ModelEvents = []ModelEvent{
{
EventType: "official_release",
ModelName: "GLM-5",
ProviderName: "Zhipu",
ProviderCountry: "CN",
OperatorName: "Zhipu",
TrustLabel: "官方来源 / 一级证据",
Baseline: "官方首次发布",
Summary: "官方发布新模型,值得优先复查中文通用与推理场景默认选择。",
SourceKindLabel: "一级官方发布",
PrimarySource: "https://open.bigmodel.cn/dev/howuse/model",
SourceURL: "https://open.bigmodel.cn/dev/howuse/model",
UpdatedAt: "2026-05-13 08:30",
EvidenceDetail: "models.release_date = 今日,且 source_url 指向官方文档",
Priority: 120,
},
{
EventType: "official_release",
ModelName: "Doubao Seed 1.8",
ProviderName: "ByteDance",
OperatorName: "ByteDance Volcano",
TrustLabel: "官方来源 / 二级佐证",
Baseline: "官方首次发布",
Summary: "次级权威报道已形成稳定发布日期信号,适合进入观察池但不应与一级公告混同。",
SourceKindLabel: "二级权威佐证发布",
PrimarySource: "https://developer.volcengine.com/articles/7601918680544641034",
UpdatedAt: "2025-12-18 00:00",
EvidenceDetail: "models.release_date = 今日,发布日期采用次级权威报道佐证,模型来源页保留官方文档",
Priority: 110,
},
{
EventType: "new_model",
ModelName: "DeepSeek-V4-Flash",
ProviderName: "DeepSeek",
ProviderCountry: "CN",
OperatorName: "OpenRouter",
Audience: "适合想尽快验证新模型价值的选型读者",
TrustLabel: "聚合来源",
Baseline: "首次出现",
Summary: "新模型进入情报池,值得重新评估低成本编码默认选择。",
SourceKindLabel: "模型快照",
PrimarySource: "OpenRouter / region_pricing",
SourceURL: "https://openrouter.ai/models/deepseek/deepseek-v4-flash",
UpdatedAt: "2026-05-13 09:30",
EvidenceDetail: "models.created_at = 今日,且已存在最新价格快照",
Priority: 95,
},
{
EventType: "promo_campaign",
ModelName: "DeepSeek-V3.2-Exp",
ProviderName: "DeepSeek",
OperatorName: "DeepSeek",
Audience: "适合计划趁活动窗口压低推理成本的团队",
TrustLabel: "官方来源 / 一级证据",
Baseline: "活动窗口开启",
Summary: "官方活动窗口出现后,值得重新评估低成本推理和批量调用方案。",
SourceKindLabel: "官方活动页",
PrimarySource: "https://api-docs.deepseek.com/news/news250929",
UpdatedAt: "2025-09-29 00:00",
EvidenceDetail: "官方活动页记录 V3.2-Exp 在活动窗口内价格下调 50%+",
Priority: 115,
},
{
EventType: "price_cut",
ModelName: "qwen-vl-max",
ProviderName: "Alibaba",
ProviderCountry: "CN",
OperatorName: "DashScope",
Audience: "适合需要当天重排价格带的团队",
TrustLabel: "官方来源",
Baseline: "较昨日 -18%",
Summary: "视觉模型价格下降已足以影响默认选型。",
SourceKindLabel: "价格快照",
PrimarySource: "pricing_history",
SourceURL: "https://dashscope.aliyun.com/model/qwen-vl-max",
UpdatedAt: "2026-05-13 10:00",
EvidenceDetail: "pricing_history 记录到输入价格较昨日下降 18%",
PriceChangePct: -18,
Priority: 100,
},
}
decorateReportV1(report)
if err := generateMarkdownV3(report, path); err != nil {
t.Fatalf("generateMarkdownV3 returned error: %v", err)
}
body, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read markdown output: %v", err)
}
content := string(body)
if !strings.Contains(content, "### 国内模型 TOP 10") {
t.Fatalf("markdown missing domestic top10 heading\n%s", content)
}
content = string(body)
for _, want := range []string{
"## 今日结论",
"## 今日行动建议",
"## 今日价格新闻",
"### ↓ Opportunity · 降价机会",
"qwen-vl-max",
"## 场景推荐",
"## 完整数据附录",
"- 影响对象:",
"营销活动",
"## 💳 中转平台套餐订阅价",
"通用 Token Plan Lite",
"Hy Token Plan Max",
"¥39.00/月",
"3500万 Tokens/月",
"256K",
} {
if !strings.Contains(content, want) {
t.Fatalf("markdown missing %q\n%s", want, content)
}
}
}
func TestGenerateHTMLV3IncludesTencentSubscriptionSection(t *testing.T) {
path := filepath.Join(t.TempDir(), "daily_report.html")
report := sampleReportForV1()
report.ModelEvents = []ModelEvent{
{
EventType: "official_release",
ModelName: "GLM-5",
ProviderName: "Zhipu",
OperatorName: "Zhipu",
TrustLabel: "官方来源 / 一级证据",
Baseline: "官方首次发布",
Summary: "官方发布新模型,值得优先复查中文通用与推理场景默认选择。",
SourceKindLabel: "一级官方发布",
PrimarySource: "https://open.bigmodel.cn/dev/howuse/model",
UpdatedAt: "2026-05-13 08:30",
EvidenceDetail: "models.release_date = 今日,且 source_url 指向官方文档",
Priority: 120,
},
{
EventType: "official_release",
ModelName: "Doubao Seed 1.8",
ProviderName: "ByteDance",
OperatorName: "ByteDance Volcano",
TrustLabel: "官方来源 / 二级佐证",
Baseline: "官方首次发布",
Summary: "次级权威报道已形成稳定发布日期信号,适合进入观察池但不应与一级公告混同。",
SourceKindLabel: "二级权威佐证发布",
PrimarySource: "https://developer.volcengine.com/articles/7601918680544641034",
UpdatedAt: "2025-12-18 00:00",
EvidenceDetail: "models.release_date = 今日,发布日期采用次级权威报道佐证,模型来源页保留官方文档",
Priority: 110,
},
{
EventType: "new_model",
ModelName: "DeepSeek-V4-Flash",
ProviderName: "DeepSeek",
OperatorName: "OpenRouter",
Audience: "适合想尽快验证新模型价值的选型读者",
TrustLabel: "聚合来源",
Baseline: "首次出现",
Summary: "新模型进入情报池,值得重新评估低成本编码默认选择。",
SourceKindLabel: "模型快照",
PrimarySource: "OpenRouter / region_pricing",
UpdatedAt: "2026-05-13 09:30",
EvidenceDetail: "models.created_at = 今日,且已存在最新价格快照",
Priority: 95,
},
{
EventType: "promo_campaign",
ModelName: "DeepSeek-V3.2-Exp",
ProviderName: "DeepSeek",
OperatorName: "DeepSeek",
Audience: "适合计划趁活动窗口压低推理成本的团队",
TrustLabel: "官方来源 / 一级证据",
Baseline: "活动窗口开启",
Summary: "官方活动窗口出现后,值得重新评估低成本推理和批量调用方案。",
SourceKindLabel: "官方活动页",
PrimarySource: "https://api-docs.deepseek.com/news/news250929",
UpdatedAt: "2025-09-29 00:00",
EvidenceDetail: "官方活动页记录 V3.2-Exp 在活动窗口内价格下调 50%+",
Priority: 115,
},
{
EventType: "price_cut",
ModelName: "qwen-vl-max",
ProviderName: "Alibaba",
OperatorName: "DashScope",
Audience: "适合需要当天重排价格带的团队",
TrustLabel: "官方来源",
Baseline: "较昨日 -18%",
Summary: "视觉模型价格下降已足以影响默认选型。",
SourceKindLabel: "价格快照",
PrimarySource: "pricing_history",
UpdatedAt: "2026-05-13 10:00",
EvidenceDetail: "pricing_history 记录到输入价格较昨日下降 18%",
PriceChangePct: -18,
Priority: 100,
},
}
report.TencentSubscriptionPlans = []SubscriptionPlanInfo{
{
PlanName: "通用 Token Plan Lite",
PlanFamily: "token_plan",
Tier: "Lite",
Currency: "CNY",
ListPrice: 39,
QuotaValue: 35000000,
QuotaUnit: "tokens/month",
ModelCount: 10,
ModelPreview: "tc-code-latest, glm-5, glm-5.1",
},
}
decorateReportV1(report)
if err := generateHTMLV3(report, path); err != nil {
t.Fatalf("generateHTMLV3 returned error: %v", err)
}
body, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read html output: %v", err)
}
content := string(body)
for _, want := range []string{
"今日一句话结论",
"三条行动建议",
"今日价格新闻",
"降价机会",
"今日头条",
"DeepSeek-V4-Flash",
"一级官方发布",
"二级权威佐证",
"营销活动",
"影响对象",
"主来源",
"更新时间",
"判定依据",
"场景推荐",
"完整数据附录",
"官方免费",
"聚合免费",
"待确认",
"💳 中转平台套餐订阅价",
} {
if !strings.Contains(content, want) {
t.Fatalf("html missing %q\n%s", want, content)
}
}
}
func TestGenerateMarkdownV3IncludesLinkedHeroAndHeadline(t *testing.T) {
path := filepath.Join(t.TempDir(), "daily_report_markdown_links.md")
report := sampleReportForV1()
report.ModelEvents = []ModelEvent{
{
EventType: "price_cut",
ModelName: "qwen-vl-max",
ProviderName: "Alibaba",
ProviderCountry: "CN",
OperatorName: "DashScope",
TrustLabel: "官方来源",
Baseline: "较昨日 -18%",
Summary: "视觉模型价格下降已足以影响默认选型。",
SourceKindLabel: "价格快照",
PrimarySource: "pricing_history",
SourceURL: "https://dashscope.aliyun.com/model/qwen-vl-max",
UpdatedAt: "2026-05-13 10:00",
EvidenceDetail: "pricing_history 记录到输入价格较昨日下降 18%",
PriceChangePct: -18,
Priority: 100,
},
}
decorateReportV1(report)
if err := generateMarkdownV3(report, path); err != nil {
t.Fatalf("generateMarkdownV3 returned error: %v", err)
}
body, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read markdown output: %v", err)
}
content := string(body)
for _, want := range []string{
"> [今天最值得关注的是 qwen-vl-maxCN / Alibaba / DashScope价格下降 18%,优先复查它是否改变默认选型与预算策略。](https://dashscope.aliyun.com/model/qwen-vl-max)",
"## 今日头条",
"[qwen-vl-max 成本下调 18%](https://dashscope.aliyun.com/model/qwen-vl-max)",
} {
if !strings.Contains(content, want) {
t.Fatalf("markdown missing %q\n%s", want, content)
}
}
}
func TestGenerateHTMLV3IncludesLinksLowestPlanAndGPT56Leak(t *testing.T) {
path := filepath.Join(t.TempDir(), "daily_report_links_leak.html")
report := sampleReportForV1()
report.ModelEvents = []ModelEvent{
{
EventType: "promo_campaign",
ModelName: "GPT-5.6",
ProviderName: "OpenAI",
ProviderCountry: "US",
OperatorName: "OpenAI",
Audience: "适合关注高端模型路线图、预算和替换窗口的团队",
TrustLabel: "行业情报 / 待官方确认",
SourceKindLabel: "泄露情报",
PrimarySource: "https://openai.example.com/gpt-5-6-leak",
SourceURL: "https://openai.example.com/gpt-5-6-leak",
UpdatedAt: "2026-05-27 00:00",
EvidenceDetail: "多个公开情报源出现 GPT-5.6 命名与规格片段,尚待官方正式发布确认。",
Baseline: "提前泄露",
Summary: "GPT-5.6 提前泄露信号出现,需立即复查其是否改变默认高端模型路线与预算预期。",
Priority: 135,
},
}
report.TencentSubscriptionPlans = []SubscriptionPlanInfo{
{OperatorName: "MiniMax", PlanName: "Starter", PlanFamily: "token_plan", BillingCycle: "monthly", Currency: "USD", ListPrice: 10, PriceUnit: "USD/month", ModelCount: 1},
{OperatorName: "MiniMax", PlanName: "Plus", PlanFamily: "token_plan", BillingCycle: "monthly", Currency: "USD", ListPrice: 20, PriceUnit: "USD/month", ModelCount: 1},
}
decorateReportV1(report)
if err := generateHTMLV3(report, path); err != nil {
t.Fatalf("generateHTMLV3 returned error: %v", err)
}
body, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read html output: %v", err)
}
content := string(body)
for _, want := range []string{
"GPT-5.6",
"US / OpenAI",
"https://openai.example.com/gpt-5-6-leak",
"🏷 最低价",
} {
if !strings.Contains(content, want) {
t.Fatalf("html missing %q\n%s", want, content)
}
}
}
func TestGenerateHTMLV3IncludesResellerSubscriptionComparison(t *testing.T) {
path := filepath.Join(t.TempDir(), "daily_report.html")
report := sampleReportForV1()
report.TencentSubscriptionPlans = []SubscriptionPlanInfo{
{
ProviderName: "Tencent",
OperatorName: "Tencent Cloud",
PlanName: "通用 Token Plan 首月活动版",
PlanFamily: "token_plan",
Tier: "首月活动版",
BillingCycle: "monthly",
Currency: "CNY",
ListPrice: 19,
PriceUnit: "CNY/month",
QuotaValue: 35000000,
QuotaUnit: "tokens/month",
ContextWindow: 131072,
ModelCount: 2,
ModelPreview: "glm-5, hunyuan-t1",
Notes: "首购用户首月优惠,次月恢复标准价。",
EffectiveDate: "2026-05-14",
},
{
ProviderName: "Alibaba",
OperatorName: "Alibaba Cloud Bailian",
PlanName: "百炼 Coding Plan 首购版",
PlanFamily: "coding_plan",
Tier: "首购版",
BillingCycle: "monthly",
Currency: "CNY",
ListPrice: 29,
PriceUnit: "CNY/month",
QuotaValue: 50000000,
QuotaUnit: "tokens/month",
ContextWindow: 262144,
ModelCount: 3,
ModelPreview: "qwen-coder-plus, qwen3-coder, deepseek-r1",
Notes: "首月活动价,适合低成本试用编码模型。",
EffectiveDate: "2026-05-14",
},
}
decorateReportV1(report)
if err := generateHTMLV3(report, path); err != nil {
t.Fatalf("generateHTMLV3 returned error: %v", err)
}
body, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read html output: %v", err)
}
content := string(body)
for _, want := range []string{
"💳 中转平台套餐订阅价",
"Tencent Cloud",
"Alibaba Cloud Bailian",
"通用 Token Plan 首月活动版",
"百炼 Coding Plan 首购版",
"首购用户首月优惠,次月恢复标准价。",
"首月活动价,适合低成本试用编码模型。",
"Token Plan",
"Coding Plan",
} {
if !strings.Contains(content, want) {
t.Fatalf("html missing %q\n%s", want, content)
}
}
if !strings.Contains(content, "🏷 最低价") {
t.Fatalf("expected lowest plan marker in subscription table\n%s", content)
}
}
func TestGenerateMarkdownV3IncludesSignatureStabilitySection(t *testing.T) {
path := filepath.Join(t.TempDir(), "daily_report.md")
report := sampleReportForV1()
report.SignatureAuditSummaries = []SignatureAuditSourceSummary{
{
SourceKey: "cloudflare_pricing_signature",
SourceLabel: "Cloudflare Workers AI",
RunsInWindow: 5,
ChangedRuns: 2,
LatestStatus: "passed",
LatestStructureState: "stable",
LatestCheckedAt: "2026-05-15 20:01:46",
},
{
SourceKey: "vertex_pricing_signature",
SourceLabel: "Google Cloud Vertex AI",
RunsInWindow: 5,
ChangedRuns: 0,
LatestStatus: "passed",
LatestStructureState: "stable",
LatestCheckedAt: "2026-05-15 19:47:11",
},
}
report.SignatureAuditRows = []SignatureAuditReportRow{
{
SourceKey: "cloudflare_pricing_signature",
SourceLabel: "Cloudflare Workers AI",
RecentRank: 2,
CheckedAt: "2026-05-14 20:01:46",
StructureState: "changed",
StructureChanged: true,
Status: "drift_detected",
StructureSHA256: "def456",
},
}
decorateReportV1(report)
if err := generateMarkdownV3(report, path); err != nil {
t.Fatalf("generateMarkdownV3 returned error: %v", err)
}
body, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read markdown output: %v", err)
}
content := string(body)
for _, want := range []string{
"## 结构稳定性",
"Cloudflare Workers AI",
"Google Cloud Vertex AI",
"最近 5 次中出现 2 次结构变化",
"changed",
"def456",
} {
if !strings.Contains(content, want) {
t.Fatalf("markdown missing %q\n%s", want, content)
}
}
}
func TestGenerateMarkdownV3IncludesThemedPriceNewsSections(t *testing.T) {
path := filepath.Join(t.TempDir(), "daily_report_price_news.md")
report := sampleReportForV1()
report.ModelEvents = []ModelEvent{
{
EventType: "price_cut",
ModelName: "qwen-vl-max",
ProviderName: "Alibaba",
OperatorName: "DashScope",
Summary: "视觉模型价格下降已足以影响默认选型。",
Audience: "适合需要当天重排价格带的团队",
TrustLabel: "官方来源",
SourceKindLabel: "价格快照",
PrimarySource: "pricing_history",
UpdatedAt: "2026-05-13 10:00",
EvidenceDetail: "pricing_history 记录到输入价格较昨日下降 18%",
Baseline: "较昨日 -18%",
PriceChangePct: -18,
Priority: 100,
},
{
EventType: "price_increase",
ModelName: "claude-3.7-sonnet",
ProviderName: "Anthropic",
OperatorName: "Anthropic",
Summary: "核心写作模型价格上调,需要准备预算回退。",
Audience: "适合需要稳定预算的商用团队",
TrustLabel: "官方来源",
SourceKindLabel: "价格快照",
PrimarySource: "pricing_history",
UpdatedAt: "2026-05-13 10:10",
EvidenceDetail: "pricing_history 记录到输入价格较昨日上涨 12%",
Baseline: "较昨日 +12%",
PriceChangePct: 12,
Priority: 90,
},
{
EventType: "promo_campaign",
ModelName: "DeepSeek-V4-Flash",
ProviderName: "DeepSeek",
OperatorName: "DeepSeek",
Summary: "平台活动窗口出现后,值得重新评估低成本推理方案。",
Audience: "适合计划趁活动窗口压低推理成本的团队",
TrustLabel: "官方来源 / 一级证据",
SourceKindLabel: "官方活动页",
PrimarySource: "https://api-docs.deepseek.com/news/news250929",
UpdatedAt: "2026-05-13 09:00",
EvidenceDetail: "官方活动页记录活动窗口价格下调",
Baseline: "活动窗口开启",
Priority: 80,
},
}
decorateReportV1(report)
if err := generateMarkdownV3(report, path); err != nil {
t.Fatalf("generateMarkdownV3 returned error: %v", err)
}
body, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read markdown output: %v", err)
}
content := string(body)
for _, want := range []string{
"## 今日价格新闻",
"### ↓ Opportunity · 降价机会",
"### ↑ Warning · 涨价预警",
"### ✦ Campaign · 平台活动",
"qwen-vl-max",
"claude-3.7-sonnet",
"DeepSeek-V4-Flash",
} {
if !strings.Contains(content, want) {
t.Fatalf("markdown missing %q\n%s", want, content)
}
}
}
func TestGenerateHTMLV3IncludesThemedPriceNewsSections(t *testing.T) {
path := filepath.Join(t.TempDir(), "daily_report_price_news.html")
report := sampleReportForV1()
report.ModelEvents = []ModelEvent{
{
EventType: "price_cut",
ModelName: "qwen-vl-max",
ProviderName: "Alibaba",
OperatorName: "DashScope",
Summary: "视觉模型价格下降已足以影响默认选型。",
Audience: "适合需要当天重排价格带的团队",
TrustLabel: "官方来源",
SourceKindLabel: "价格快照",
PrimarySource: "pricing_history",
UpdatedAt: "2026-05-13 10:00",
EvidenceDetail: "pricing_history 记录到输入价格较昨日下降 18%",
Baseline: "较昨日 -18%",
PriceChangePct: -18,
Priority: 100,
},
{
EventType: "price_increase",
ModelName: "claude-3.7-sonnet",
ProviderName: "Anthropic",
OperatorName: "Anthropic",
Summary: "核心写作模型价格上调,需要准备预算回退。",
Audience: "适合需要稳定预算的商用团队",
TrustLabel: "官方来源",
SourceKindLabel: "价格快照",
PrimarySource: "pricing_history",
UpdatedAt: "2026-05-13 10:10",
EvidenceDetail: "pricing_history 记录到输入价格较昨日上涨 12%",
Baseline: "较昨日 +12%",
PriceChangePct: 12,
Priority: 90,
},
{
EventType: "promo_campaign",
ModelName: "DeepSeek-V4-Flash",
ProviderName: "DeepSeek",
OperatorName: "DeepSeek",
Summary: "平台活动窗口出现后,值得重新评估低成本推理方案。",
Audience: "适合计划趁活动窗口压低推理成本的团队",
TrustLabel: "官方来源 / 一级证据",
SourceKindLabel: "官方活动页",
PrimarySource: "https://api-docs.deepseek.com/news/news250929",
UpdatedAt: "2026-05-13 09:00",
EvidenceDetail: "官方活动页记录活动窗口价格下调",
Baseline: "活动窗口开启",
Priority: 80,
},
}
decorateReportV1(report)
if err := generateHTMLV3(report, path); err != nil {
t.Fatalf("generateHTMLV3 returned error: %v", err)
}
body, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read html output: %v", err)
}
content := string(body)
for _, want := range []string{
"今日价格新闻",
"降价机会",
"涨价预警",
"平台活动",
"Opportunity",
"Warning",
"Campaign",
">↓<",
">↑<",
">✦<",
"qwen-vl-max",
"claude-3.7-sonnet",
"DeepSeek-V4-Flash",
} {
if !strings.Contains(content, want) {
t.Fatalf("html missing %q\n%s", want, content)
}
}
}
func TestGenerateHTMLV3IncludesSignatureStabilitySection(t *testing.T) {
path := filepath.Join(t.TempDir(), "daily_report.html")
report := sampleReportForV1()
report.SignatureAuditSummaries = []SignatureAuditSourceSummary{
{
SourceKey: "perplexity_pricing_signature",
SourceLabel: "Perplexity API",
RunsInWindow: 5,
ChangedRuns: 1,
LatestStatus: "baseline_initialized",
LatestStructureState: "initial",
LatestCheckedAt: "2026-05-15 20:01:46",
},
}
report.SignatureAuditRows = []SignatureAuditReportRow{
{
SourceKey: "perplexity_pricing_signature",
SourceLabel: "Perplexity API",
RecentRank: 1,
CheckedAt: "2026-05-15 20:01:46",
StructureState: "initial",
StructureChanged: false,
Status: "baseline_initialized",
StructureSHA256: "abc123",
},
}
decorateReportV1(report)
if err := generateHTMLV3(report, path); err != nil {
t.Fatalf("generateHTMLV3 returned error: %v", err)
}
body, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read html output: %v", err)
}
content := string(body)
for _, want := range []string{
"结构稳定性",
"Perplexity API",
"最近 5 次中出现 1 次结构变化",
"baseline_initialized",
"abc123",
} {
if !strings.Contains(content, want) {
t.Fatalf("html missing %q\n%s", want, content)
}
}
}
func TestGenerateHTMLV3IncludesPriceNewsBadgeIcons(t *testing.T) {
path := filepath.Join(t.TempDir(), "daily_report_price_news_badges.html")
report := sampleReportForV1()
report.ModelEvents = []ModelEvent{
{
EventType: "price_cut",
ModelName: "qwen-vl-max",
ProviderName: "Alibaba",
OperatorName: "DashScope",
Summary: "视觉模型价格下降已足以影响默认选型。",
Audience: "适合需要当天重排价格带的团队",
TrustLabel: "官方来源",
SourceKindLabel: "价格快照",
PrimarySource: "pricing_history",
UpdatedAt: "2026-05-13 10:00",
EvidenceDetail: "pricing_history 记录到输入价格较昨日下降 18%",
Baseline: "较昨日 -18%",
PriceChangePct: -18,
Priority: 100,
},
{
EventType: "price_increase",
ModelName: "claude-3.7-sonnet",
ProviderName: "Anthropic",
OperatorName: "Anthropic",
Summary: "核心写作模型价格上调,需要准备预算回退。",
Audience: "适合需要稳定预算的商用团队",
TrustLabel: "官方来源",
SourceKindLabel: "价格快照",
PrimarySource: "pricing_history",
UpdatedAt: "2026-05-13 10:10",
EvidenceDetail: "pricing_history 记录到输入价格较昨日上涨 12%",
Baseline: "较昨日 +12%",
PriceChangePct: 12,
Priority: 90,
},
{
EventType: "promo_campaign",
ModelName: "DeepSeek-V4-Flash",
ProviderName: "DeepSeek",
OperatorName: "DeepSeek",
Summary: "平台活动窗口出现后,值得重新评估低成本推理方案。",
Audience: "适合计划趁活动窗口压低推理成本的团队",
TrustLabel: "官方来源 / 一级证据",
SourceKindLabel: "官方活动页",
PrimarySource: "https://api-docs.deepseek.com/news/news250929",
UpdatedAt: "2026-05-13 09:00",
EvidenceDetail: "官方活动页记录活动窗口价格下调",
Baseline: "活动窗口开启",
Priority: 80,
},
}
decorateReportV1(report)
if err := generateHTMLV3(report, path); err != nil {
t.Fatalf("generateHTMLV3 returned error: %v", err)
}
body, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read html output: %v", err)
}
content := string(body)
for _, want := range []string{
"Opportunity",
"Warning",
"Campaign",
">↓<",
">↑<",
">✦<",
} {
if !strings.Contains(content, want) {
t.Fatalf("html missing %q\n%s", want, content)
}
}
}
func TestGenerateHTMLV3PaginatesAppendices(t *testing.T) {
path := filepath.Join(t.TempDir(), "daily_report_appendix_pagination.html")
report := sampleReportForV1()
report.IntlTop5 = nil
report.IntlAppendixList = nil
report.DomesticTop10 = nil
report.DomesticAppendixList = nil
report.FreeTop20 = nil
report.Operators = nil
report.Resellers = nil
for i := 0; i < 65; i++ {
report.DomesticAppendixList = append(report.DomesticAppendixList, ModelInfo{
Name: fmt.Sprintf("domestic-model-%02d", i+1),
ProviderName: "ProviderCN",
InputPrice: 1,
OutputPrice: 2,
Currency: "CNY",
ContextLength: 131072,
})
}
for i := 0; i < 22; i++ {
report.FreeTop20 = append(report.FreeTop20, ModelInfo{
Name: fmt.Sprintf("free-model-%02d", i+1),
ProviderName: "ProviderFree",
OperatorType: "official",
ContextLength: 65536,
})
}
for i := 0; i < 23; i++ {
report.Operators = append(report.Operators, OperatorInfo{Name: fmt.Sprintf("Operator-%02d", i+1), ModelCount: i + 1, MinInputPrice: 0.1, AvgInputPrice: 0.2})
}
for i := 0; i < 22; i++ {
report.Resellers = append(report.Resellers, OperatorInfo{Name: fmt.Sprintf("Reseller-%02d", i+1), ModelCount: i + 1, MinInputPrice: 0.3, AvgInputPrice: 0.4})
}
decorateReportV1(report)
report.AppendixLinks = []AppendixLink{
{Title: "国际低价", Description: "查看国际低价模型附录(日报仅保留前几页)", Anchor: "#appendix-pricing-intl"},
{Title: "国内低价", Description: "查看国内低价模型附录(日报仅保留前几页)", Anchor: "#appendix-pricing-domestic"},
{Title: "免费样本", Description: "查看免费模型代表样本附录", Anchor: "#appendix-free"},
{Title: "平台覆盖", Description: "查看官方平台与聚合平台覆盖", Anchor: "#appendix-platforms"},
{Title: "全量导出 JSON", Description: "其余完整数据请下载独立导出文件或转到查询页查看", Anchor: "/reports/daily/appendix/2026-05-13/full_appendix.json"},
}
if err := generateHTMLV3(report, path); err != nil {
t.Fatalf("generateHTMLV3 returned error: %v", err)
}
body, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read html output: %v", err)
}
content := string(body)
for _, want := range []string{
"完整价格附录(国际低价)",
"完整价格附录(国内低价)",
"完整免费附录",
"平台覆盖附录",
"国际低价",
"国内低价",
"全量导出 JSON",
"/reports/daily/appendix/2026-05-13/full_appendix.json",
"data-appendix-page=\"1\"",
"data-appendix-total-pages=\"1\"",
"data-appendix-total-pages=\"2\"",
"data-appendix-total-pages=\"3\"",
"#appendix-pricing-intl",
"#appendix-pricing-domestic",
"#appendix-free",
"#appendix-platforms",
"上一页",
"下一页",
} {
if !strings.Contains(content, want) {
t.Fatalf("html missing %q\n%s", want, content)
}
}
}
func TestBuildHeadlineItemsUsesModelEvents(t *testing.T) {
report := sampleReportForV1()
report.ModelEvents = []ModelEvent{
{
EventType: "official_release",
ModelName: "GLM-5",
ProviderName: "Zhipu",
OperatorName: "Zhipu",
TrustLabel: "官方来源",
Baseline: "官方首次发布",
Summary: "官方发布新模型,值得优先复查中文通用与推理场景默认选择。",
SourceKindLabel: "一级官方发布",
PrimarySource: "https://open.bigmodel.cn/dev/howuse/model",
UpdatedAt: "2026-05-13 08:30",
EvidenceDetail: "models.release_date = 今日,且 source_url 指向官方文档",
Priority: 120,
},
{
EventType: "price_cut",
ModelName: "glm-5",
ProviderName: "Zhipu",
OperatorName: "Zhipu",
TrustLabel: "官方来源",
Baseline: "较昨日 -25%",
Summary: "价格下降已足以影响中文通用场景默认选型。",
SourceKindLabel: "价格快照",
PrimarySource: "pricing_history",
UpdatedAt: "2026-05-13 10:00",
EvidenceDetail: "pricing_history 记录到输入价格较昨日下降 25%",
PriceChangePct: -25,
Priority: 100,
},
{
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: 90,
},
}
items := buildHeadlineItems(report)
if len(items) < 2 {
t.Fatalf("expected at least 2 headline items, got %d", len(items))
}
if items[0].Label != "价格下调" || !strings.Contains(items[0].Title, "glm-5") {
t.Fatalf("expected price change event to rank first, got %+v", items[0])
}
if items[1].Label != "一级官方发布" {
t.Fatalf("expected official release to stay immediately after price change, got %+v", items[1])
}
if items[0].Baseline != "较昨日 -25%" {
t.Fatalf("expected price_cut baseline to be preserved, got %+v", items[0])
}
if items[1].SourceKindLabel != "一级官方发布" || items[1].PrimarySource != "https://open.bigmodel.cn/dev/howuse/model" {
t.Fatalf("expected official release evidence fields to be preserved, got %+v", items[1])
}
}
func TestBuildHeadlineItemsDeduplicatesSameModel(t *testing.T) {
report := sampleReportForV1()
report.ModelEvents = []ModelEvent{
{
EventType: "price_cut",
ModelName: "OpenAI: GPT-4o",
ProviderName: "OpenAI",
TrustLabel: "官方来源",
Baseline: "较昨日 -20%",
Summary: "价格下降影响默认成本。",
SourceKindLabel: "价格快照",
PrimarySource: "pricing_history",
UpdatedAt: "2026-05-13 10:00",
EvidenceDetail: "pricing_history 记录到输入价格较昨日下降 20%",
PriceChangePct: -20,
Priority: 95,
},
{
EventType: "price_increase",
ModelName: "OpenAI: GPT-4o",
ProviderName: "OpenAI",
TrustLabel: "官方来源",
Baseline: "较昨日 +5%",
Summary: "同日另有上调记录。",
SourceKindLabel: "价格快照",
PrimarySource: "pricing_history",
UpdatedAt: "2026-05-13 11:00",
EvidenceDetail: "pricing_history 记录到输入价格较昨日上涨 5%",
PriceChangePct: 5,
Priority: 80,
},
{
EventType: "new_model",
ModelName: "Claude Opus 4.7",
ProviderName: "Anthropic",
TrustLabel: "聚合来源",
Baseline: "首次出现",
Summary: "新模型上线。",
SourceKindLabel: "模型快照",
PrimarySource: "OpenRouter / region_pricing",
UpdatedAt: "2026-05-13 09:00",
EvidenceDetail: "models.created_at = 今日,且已存在最新价格快照",
Priority: 70,
},
}
items := buildHeadlineItems(report)
if len(items) != 2 {
t.Fatalf("expected 2 deduplicated headline items, got %d", len(items))
}
if strings.Contains(items[1].Title, "GPT-4o") {
t.Fatalf("expected duplicate model event to be removed, got %+v", items)
}
}
func TestBuildHeadlineItemsElevatesSignatureDrift(t *testing.T) {
report := sampleReportForV1()
report.SignatureAuditConfig = SignatureAuditReportConfig{Window: 5, ChangedRunsThreshold: 2}
report.ModelEvents = []ModelEvent{
{
EventType: "new_model",
ModelName: "DeepSeek-V4-Flash",
ProviderName: "DeepSeek",
TrustLabel: "聚合来源",
Baseline: "首次出现",
Summary: "新模型进入情报池。",
SourceKindLabel: "模型快照",
PrimarySource: "OpenRouter / region_pricing",
UpdatedAt: "2026-05-13 09:30",
EvidenceDetail: "models.created_at = 今日,且已存在最新价格快照",
Priority: 90,
},
}
report.SignatureAuditSummaries = []SignatureAuditSourceSummary{
{
SourceKey: "cloudflare_pricing_signature",
SourceLabel: "Cloudflare Workers AI",
RunsInWindow: 5,
ChangedRuns: 2,
LatestCheckedAt: "2026-05-15 20:01:46",
LatestStatus: "drift_detected",
LatestStructureState: "changed",
},
}
items := buildHeadlineItems(report)
if len(items) == 0 {
t.Fatalf("expected headline items")
}
if items[0].Label != "结构波动" {
t.Fatalf("expected signature drift headline first, got %+v", items[0])
}
if !strings.Contains(items[0].Title, "Cloudflare Workers AI") {
t.Fatalf("expected drift headline title to mention source, got %+v", items[0])
}
}
func TestBuildActionItemsElevatesSignatureDrift(t *testing.T) {
report := sampleReportForV1()
report.SignatureAuditConfig = SignatureAuditReportConfig{Window: 5, ChangedRunsThreshold: 1}
report.SignatureAuditSummaries = []SignatureAuditSourceSummary{
{
SourceKey: "perplexity_pricing_signature",
SourceLabel: "Perplexity API",
RunsInWindow: 5,
ChangedRuns: 1,
LatestCheckedAt: "2026-05-15 20:01:46",
LatestStatus: "drift_detected",
LatestStructureState: "changed",
},
}
decorateReportV1(report)
if len(report.ActionItems) == 0 {
t.Fatalf("expected action items")
}
if !strings.Contains(report.ActionItems[0].Title, "Perplexity API") {
t.Fatalf("expected first action item to elevate signature drift, got %+v", report.ActionItems[0])
}
if !strings.Contains(report.ActionItems[0].Evidence, "最近 5 次中出现 1 次结构变化") {
t.Fatalf("expected signature drift evidence, got %+v", report.ActionItems[0])
}
}
func TestBuildHeadlineItemsDoesNotElevateSignatureDriftBelowThreshold(t *testing.T) {
report := sampleReportForV1()
report.SignatureAuditConfig = SignatureAuditReportConfig{Window: 5, ChangedRunsThreshold: 3}
report.ModelEvents = []ModelEvent{
{
EventType: "new_model",
ModelName: "DeepSeek-V4-Flash",
ProviderName: "DeepSeek",
TrustLabel: "聚合来源",
Baseline: "首次出现",
Summary: "新模型进入情报池。",
SourceKindLabel: "模型快照",
PrimarySource: "OpenRouter / region_pricing",
UpdatedAt: "2026-05-13 09:30",
EvidenceDetail: "models.created_at = 今日,且已存在最新价格快照",
Priority: 90,
},
}
report.SignatureAuditSummaries = []SignatureAuditSourceSummary{
{
SourceKey: "cloudflare_pricing_signature",
SourceLabel: "Cloudflare Workers AI",
RunsInWindow: 5,
ChangedRuns: 2,
LatestCheckedAt: "2026-05-15 20:01:46",
LatestStatus: "drift_detected",
LatestStructureState: "changed",
},
}
items := buildHeadlineItems(report)
if len(items) == 0 {
t.Fatalf("expected headline items")
}
if items[0].Label == "结构波动" {
t.Fatalf("signature drift should stay below threshold, got %+v", items[0])
}
}
func TestDecorateReportV1ElevatesSignatureDriftIntoHeroSummary(t *testing.T) {
report := sampleReportForV1()
report.ModelEvents = nil
report.SignatureAuditConfig = SignatureAuditReportConfig{Window: 5, ChangedRunsThreshold: 2}
report.SignatureAuditSummaries = []SignatureAuditSourceSummary{
{
SourceKey: "vertex_pricing_signature",
SourceLabel: "Google Cloud Vertex AI",
RunsInWindow: 5,
ChangedRuns: 3,
LatestCheckedAt: "2026-05-15 20:01:46",
LatestStatus: "drift_detected",
LatestStructureState: "changed",
},
}
decorateReportV1(report)
if !strings.Contains(report.HeroSummary, "Google Cloud Vertex AI") {
t.Fatalf("expected hero summary to mention signature drift source, got %q", report.HeroSummary)
}
if !strings.Contains(report.HeroEvidence, "最近 5 次中出现 3 次结构变化") {
t.Fatalf("expected hero evidence to mention drift count, got %q", report.HeroEvidence)
}
}
func TestDecorateReportV1PrefersPriceChangeInHeroSummary(t *testing.T) {
report := sampleReportForV1()
report.ModelEvents = []ModelEvent{
{
EventType: "official_release",
ModelName: "GLM-5",
Summary: "官方发布新模型。",
PrimarySource: "official release",
EvidenceDetail: "models.release_date = 今日",
Priority: 120,
},
{
EventType: "price_cut",
ModelName: "DeepSeek-V4-Flash",
ProviderName: "DeepSeek",
OperatorName: "OpenRouter",
Summary: "价格下降已足以影响默认选型,值得重新评估同类模型。",
PrimarySource: "pricing_history",
EvidenceDetail: "pricing_history 记录到输入价格由 $0.60 调整为 $0.30,较昨日下降 50%",
PriceChangePct: -50,
OldInputPrice: 0.60,
NewInputPrice: 0.30,
OldOutputPrice: 2.40,
NewOutputPrice: 1.20,
Currency: "USD",
Priority: 95,
},
}
decorateReportV1(report)
if !strings.Contains(report.HeroSummary, "DeepSeek-V4-Flash") || !strings.Contains(report.HeroSummary, "价格") {
t.Fatalf("expected hero summary to prioritize price change, got %q", report.HeroSummary)
}
if !strings.Contains(report.HeroEvidence, "pricing_history") {
t.Fatalf("expected hero evidence to mention pricing history, got %q", report.HeroEvidence)
}
}
func TestBuildHeadlineItemsPlacesPriceChangeBeforeOfficialRelease(t *testing.T) {
report := sampleReportForV1()
report.ModelEvents = []ModelEvent{
{
EventType: "official_release",
ModelName: "GLM-5",
Summary: "官方发布新模型。",
TrustLabel: "官方来源 / 一级证据",
SourceKindLabel: "一级官方发布",
PrimarySource: "official release",
UpdatedAt: "2026-05-13 08:30",
EvidenceDetail: "models.release_date = 今日",
Priority: 120,
},
{
EventType: "price_cut",
ModelName: "DeepSeek-V4-Flash",
ProviderName: "DeepSeek",
OperatorName: "OpenRouter",
Summary: "价格下降已足以影响默认选型,值得重新评估同类模型。",
TrustLabel: "聚合来源",
SourceKindLabel: "价格快照",
PrimarySource: "pricing_history",
UpdatedAt: "2026-05-13 09:30",
EvidenceDetail: "pricing_history 记录到输入价格由 $0.60 调整为 $0.30,较昨日下降 50%",
PriceChangePct: -50,
OldInputPrice: 0.60,
NewInputPrice: 0.30,
OldOutputPrice: 2.40,
NewOutputPrice: 1.20,
Currency: "USD",
Priority: 95,
},
}
items := buildHeadlineItems(report)
if len(items) == 0 {
t.Fatalf("expected headline items")
}
if items[0].Label != "价格下调" {
t.Fatalf("expected price change headline first, got %+v", items[0])
}
}
func TestSignatureAuditSummaryToneRespectsConfiguredThreshold(t *testing.T) {
report := &ReportV3{
SignatureAuditConfig: SignatureAuditReportConfig{Window: 5, ChangedRunsThreshold: 3},
}
if tone := signatureAuditSummaryTone(report, SignatureAuditSourceSummary{
SourceLabel: "Cloudflare Workers AI",
ChangedRuns: 2,
RunsInWindow: 5,
}); tone != "official" {
t.Fatalf("tone below threshold = %q, want official", tone)
}
if tone := signatureAuditSummaryTone(report, SignatureAuditSourceSummary{
SourceLabel: "Cloudflare Workers AI",
ChangedRuns: 3,
RunsInWindow: 5,
}); tone != "warning" {
t.Fatalf("tone at threshold = %q, want warning", tone)
}
}
func TestHeadlineItemFromModelEventIncludesEvidenceFields(t *testing.T) {
item := headlineItemFromModelEvent(ModelEvent{
EventType: "new_model",
ModelName: "DeepSeek-V4-Flash",
TrustLabel: "聚合来源",
Baseline: "首次出现",
Summary: "新模型进入情报池。",
Audience: "适合想尽快验证新模型价值的读者",
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)
}
if item.Audience != "适合想尽快验证新模型价值的读者" {
t.Fatalf("expected audience to be propagated, got %+v", item)
}
}
func TestHeadlineItemFromOfficialReleaseEvent(t *testing.T) {
item := headlineItemFromModelEvent(ModelEvent{
EventType: "official_release",
ModelName: "Claude Sonnet 4.5",
TrustLabel: "官方来源",
Baseline: "官方首次发布",
Summary: "官方发布新模型。",
SourceKindLabel: "一级官方发布",
PrimarySource: "https://docs.anthropic.com/en/release-notes/api",
UpdatedAt: "2026-05-13 07:00",
EvidenceDetail: "models.release_date = 今日,且 source_url 指向官方发布页",
})
if item.Label != "一级官方发布" {
t.Fatalf("expected label to be 一级官方发布, got %+v", item)
}
if !strings.Contains(item.Title, "官方发布") {
t.Fatalf("expected title to mention 官方发布, got %+v", item)
}
if item.SourceKindLabel != "一级官方发布" {
t.Fatalf("expected source kind label to be 一级官方发布, got %+v", item)
}
if item.PrimarySource != "https://docs.anthropic.com/en/release-notes/api" {
t.Fatalf("expected primary source to be preserved, got %+v", item)
}
if item.Tone != "official-primary" {
t.Fatalf("expected tone to be official-primary, got %+v", item)
}
}
func TestHeadlineItemFromSecondaryReleaseEvent(t *testing.T) {
item := headlineItemFromModelEvent(ModelEvent{
EventType: "official_release",
ModelName: "Doubao Seed 1.8",
TrustLabel: "官方来源 / 二级佐证",
Baseline: "官方首次发布",
Summary: "模型进入正式发布日期观察池。",
SourceKindLabel: "二级权威佐证发布",
PrimarySource: "https://developer.volcengine.com/articles/7601918680544641034",
UpdatedAt: "2025-12-18 00:00",
EvidenceDetail: "models.release_date = 今日,发布日期采用次级权威报道佐证,模型来源页保留官方文档",
})
if item.Label != "二级权威佐证" {
t.Fatalf("expected label to be 二级权威佐证, got %+v", item)
}
if !strings.Contains(item.Title, "权威佐证发布时间线") {
t.Fatalf("expected title to mention 权威佐证发布时间线, got %+v", item)
}
if item.Tone != "secondary-evidence" {
t.Fatalf("expected tone to be secondary-evidence, got %+v", item)
}
}
func TestHeadlineItemFromPromoCampaignEvent(t *testing.T) {
item := headlineItemFromModelEvent(ModelEvent{
EventType: "promo_campaign",
ModelName: "DeepSeek-V3.2-Exp",
TrustLabel: "官方来源 / 一级证据",
Baseline: "活动窗口开启",
Summary: "官方活动窗口出现后,值得重新评估低成本推理方案。",
Audience: "适合计划趁活动窗口压低推理成本的团队",
SourceKindLabel: "官方活动页",
PrimarySource: "https://api-docs.deepseek.com/news/news250929",
UpdatedAt: "2025-09-29 00:00",
EvidenceDetail: "官方活动页记录 V3.2-Exp 在活动窗口内价格下调 50%+",
})
if item.Label != "营销活动" {
t.Fatalf("expected label to be 营销活动, got %+v", item)
}
if !strings.Contains(item.Title, "活动窗口") {
t.Fatalf("expected title to mention 活动窗口, got %+v", item)
}
if item.Tone != "promo" {
t.Fatalf("expected tone to be promo, got %+v", item)
}
if item.Audience != "适合计划趁活动窗口压低推理成本的团队" {
t.Fatalf("expected audience to be preserved, got %+v", item)
}
}