//go:build llm_script package main import ( "os" "path/filepath" "strings" "testing" ) 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 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 TestDecorateReportV1BuildsHotDaySummary(t *testing.T) { report := sampleReportForV1() report.ModelEvents = []ModelEvent{ { EventType: "new_model", ModelName: "DeepSeek-V4-Flash", ProviderName: "DeepSeek", OperatorName: "OpenRouter", TrustLabel: "聚合来源", Baseline: "首次出现", Summary: "新模型进入情报池,值得重新评估低成本编码默认选择。", SourceKindLabel: "模型快照", PrimarySource: "OpenRouter / region_pricing", UpdatedAt: "2026-05-13 09:30", EvidenceDetail: "models.created_at = 今日,且已存在最新价格快照", Priority: 95, }, { EventType: "price_cut", ModelName: "qwen-vl-max", ProviderName: "Alibaba", OperatorName: "DashScope", TrustLabel: "官方来源", Baseline: "较昨日 -18%", Summary: "价格下降已足以影响视觉模型默认选择。", SourceKindLabel: "价格快照", PrimarySource: "pricing_history", 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, "2 个新模型") { t.Fatalf("hero summary missing new model signal: %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 !strings.Contains(report.HeadlineItems[0].Title, "DeepSeek-V4-Flash") { t.Fatalf("expected first headline to come from model events, 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: "new_model", ModelName: "DeepSeek-V4-Flash", ProviderName: "DeepSeek", OperatorName: "OpenRouter", TrustLabel: "聚合来源", Baseline: "首次出现", Summary: "新模型进入情报池,值得重新评估低成本编码默认选择。", SourceKindLabel: "模型快照", PrimarySource: "OpenRouter / region_pricing", UpdatedAt: "2026-05-13 09:30", EvidenceDetail: "models.created_at = 今日,且已存在最新价格快照", Priority: 95, }, } decorateReportV1(report) 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{ "## 今日结论", "## 今日行动建议", "## 今日变化", "## 场景推荐", "## 完整数据附录", "主来源: OpenRouter / region_pricing", "更新时间: 2026-05-13 09:30", "判定依据: models.created_at = 今日,且已存在最新价格快照", "## 💳 腾讯云套餐订阅价", "通用 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: "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, }, } 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 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 !strings.Contains(items[0].Title, "GLM-5") || items[0].Label != "官方发布" { t.Fatalf("expected official release event to rank first, got %+v", items[0]) } if items[1].Baseline != "较昨日 -25%" { t.Fatalf("expected price_cut baseline to be preserved, got %+v", items[1]) } if items[0].SourceKindLabel != "官方发布" || items[0].PrimarySource != "https://open.bigmodel.cn/dev/howuse/model" { t.Fatalf("expected official release evidence fields to be preserved, got %+v", items[0]) } } 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 TestHeadlineItemFromModelEventIncludesEvidenceFields(t *testing.T) { item := headlineItemFromModelEvent(ModelEvent{ EventType: "new_model", ModelName: "DeepSeek-V4-Flash", TrustLabel: "聚合来源", Baseline: "首次出现", Summary: "新模型进入情报池。", SourceKindLabel: "模型快照", PrimarySource: "OpenRouter / region_pricing", UpdatedAt: "2026-05-13 09:30", EvidenceDetail: "models.created_at = 今日,且已存在最新价格快照", }) if item.SourceKindLabel != "模型快照" { t.Fatalf("expected source kind label to be propagated, got %+v", item) } if item.PrimarySource != "OpenRouter / region_pricing" { t.Fatalf("expected primary source to be propagated, got %+v", item) } if item.UpdatedAt != "2026-05-13 09:30" { t.Fatalf("expected updated at to be propagated, got %+v", item) } if item.EvidenceDetail == "" { t.Fatalf("expected evidence detail to be populated, got %+v", item) } } 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) } }