1851 lines
62 KiB
Go
1851 lines
62 KiB
Go
//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)
|
||
}
|
||
}
|