//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-max(CN / 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) } }