feat(report): add official release event source

This commit is contained in:
phamnazage-jpg
2026-05-13 21:36:18 +08:00
parent b4e28d5be4
commit b9ca312366
2 changed files with 184 additions and 9 deletions

View File

@@ -769,6 +769,12 @@ func loadModelEvents(db *sql.DB, date string) ([]ModelEvent, error) {
}
events = append(events, newModelEvents...)
releaseEvents, err := loadOfficialReleaseEvents(db, date)
if err != nil {
return nil, err
}
events = append(events, releaseEvents...)
priceEvents, err := loadPriceChangeEvents(db, date)
if err != nil {
return nil, err
@@ -785,6 +791,98 @@ func loadModelEvents(db *sql.DB, date string) ([]ModelEvent, error) {
return dedupeModelEvents(events), nil
}
func loadOfficialReleaseEvents(db *sql.DB, date string) ([]ModelEvent, error) {
rows, err := db.Query(`
WITH latest_prices AS (
SELECT
rp.model_id,
COALESCE(o.name, 'Unknown') AS operator_name,
COALESCE(o.type, 'reseller') AS operator_type,
rp.currency,
ROW_NUMBER() OVER (
PARTITION BY rp.model_id
ORDER BY rp.effective_date DESC NULLS LAST, rp.id DESC
) AS rn
FROM region_pricing rp
LEFT JOIN operator o ON rp.operator_id = o.id
)
SELECT
COALESCE(NULLIF(m.name, ''), m.external_id) AS model_name,
COALESCE(mp.name, split_part(m.external_id, '/', 1)) AS provider_name,
COALESCE(lp.operator_name, 'Unknown') AS operator_name,
COALESCE(lp.operator_type, 'reseller') AS operator_type,
COALESCE(m.source_url, '') AS source_url,
COALESCE(mp.country, 'unknown') AS provider_country,
COALESCE(m.release_date, m.created_at::date) AS release_date,
COALESCE(lp.currency, 'USD') AS currency
FROM models m
LEFT JOIN model_provider mp ON m.provider_id = mp.id
LEFT JOIN latest_prices lp ON lp.model_id = m.id AND lp.rn = 1
WHERE m.deleted_at IS NULL
AND m.release_date = $1::date
AND COALESCE(m.source_url, '') <> ''
AND COALESCE(lp.operator_type, 'reseller') IN ('official', 'cloud')
ORDER BY m.release_date DESC, m.id DESC
LIMIT 8
`, date)
if err != nil {
return nil, err
}
defer rows.Close()
var events []ModelEvent
for rows.Next() {
var (
modelName string
providerName string
operatorName string
operatorType string
sourceURL string
providerCountry string
releaseDate time.Time
currency string
)
if err := rows.Scan(
&modelName,
&providerName,
&operatorName,
&operatorType,
&sourceURL,
&providerCountry,
&releaseDate,
&currency,
); err != nil {
return nil, err
}
model := ModelInfo{
Name: modelName,
ProviderName: providerName,
ProviderCountry: providerCountry,
Currency: currency,
OperatorName: operatorName,
OperatorType: operatorType,
}
events = append(events, ModelEvent{
EventType: "official_release",
ModelName: modelName,
ProviderName: providerName,
OperatorName: operatorName,
TrustLabel: buildTrustLabel(model),
SourceKindLabel: "官方发布",
PrimarySource: sourceURL,
UpdatedAt: releaseDate.Format("2006-01-02 15:04"),
EvidenceDetail: "models.release_date = 今日,且 source_url 指向官方发布页",
Baseline: "官方首次发布",
Summary: fmt.Sprintf("%s 官方发布新模型,值得优先复查默认选型。", providerName),
Currency: currency,
Priority: 120,
})
}
return events, rows.Err()
}
func loadNewModelEvents(db *sql.DB, date string) ([]ModelEvent, error) {
rows, err := db.Query(`
WITH latest_prices AS (
@@ -1079,11 +1177,11 @@ func decorateReportV1(r *ReportV3) {
}
}
r.PageMode = buildPageMode(r.DailySignals)
r.ModelEvents = enrichModelEvents(r)
r.PageMode = buildPageModeWithEvents(r.DailySignals, r.ModelEvents)
r.MarketLabels = buildMarketLabels(r)
r.HeroSummary, r.HeroEvidence = buildHeroSummary(r)
r.SceneSections = buildSceneSections(r)
r.ModelEvents = enrichModelEvents(r)
r.ActionItems = buildActionItems(r)
r.HeadlineItems = buildHeadlineItems(r)
r.AppendixLinks = []AppendixLink{
@@ -1193,6 +1291,13 @@ func isVerifiedAggregator(name string) bool {
}
func buildPageMode(signals DailySignals) string {
return buildPageModeWithEvents(signals, nil)
}
func buildPageModeWithEvents(signals DailySignals, events []ModelEvent) string {
if hasEventType(events, "official_release") {
return "hot"
}
if signals.NewModels == 0 && signals.PriceChanges == 0 {
return "calm"
}
@@ -1204,7 +1309,7 @@ func buildPageMode(signals DailySignals) string {
func buildMarketLabels(r *ReportV3) []string {
labels := []string{}
switch r.PageMode {
switch buildPageModeWithEvents(r.DailySignals, r.ModelEvents) {
case "hot":
labels = append(labels, "热点日")
case "calm":
@@ -1212,6 +1317,9 @@ func buildMarketLabels(r *ReportV3) []string {
default:
labels = append(labels, "常规日")
}
if hasEventType(r.ModelEvents, "official_release") {
labels = append(labels, "官方发布")
}
if r.DailySignals.NewModels > 0 {
labels = append(labels, "新模型日")
}
@@ -1229,7 +1337,20 @@ func buildMarketLabels(r *ReportV3) []string {
return labels
}
func hasEventType(events []ModelEvent, eventType string) bool {
for _, event := range events {
if event.EventType == eventType {
return true
}
}
return false
}
func buildHeroSummary(r *ReportV3) (string, string) {
if official := firstEventByType(r.ModelEvents, "official_release"); official != nil {
return fmt.Sprintf("今天最值得关注的是 %s 已出现官方发布信号,优先复查它对默认选型的影响。", official.ModelName),
fmt.Sprintf("主来源:%s", official.PrimarySource)
}
switch r.PageMode {
case "hot":
return fmt.Sprintf(
@@ -1248,6 +1369,15 @@ func buildHeroSummary(r *ReportV3) (string, string) {
}
}
func firstEventByType(events []ModelEvent, eventType string) *ModelEvent {
for i := range events {
if events[i].EventType == eventType {
return &events[i]
}
}
return nil
}
func buildHeadlineItems(r *ReportV3) []HeadlineItem {
if items := buildHeadlineItemsFromEvents(r.ModelEvents); len(items) > 0 {
return items
@@ -1345,6 +1475,10 @@ func headlineItemFromModelEvent(event ModelEvent) HeadlineItem {
}
switch event.EventType {
case "official_release":
item.Label = "官方发布"
item.Title = fmt.Sprintf("%s 官方发布", event.ModelName)
item.Tone = "info"
case "new_model":
item.Label = "新模型"
item.Title = fmt.Sprintf("%s 进入今日情报池", event.ModelName)

View File

@@ -405,6 +405,20 @@ func TestGenerateHTMLV3IncludesTencentSubscriptionSection(t *testing.T) {
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",
@@ -441,14 +455,14 @@ func TestBuildHeadlineItemsUsesModelEvents(t *testing.T) {
if len(items) < 2 {
t.Fatalf("expected at least 2 headline items, got %d", len(items))
}
if !strings.Contains(items[0].Title, "glm-5") {
t.Fatalf("expected price_cut event to rank first, got %+v", items[0])
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[0].Baseline != "较昨日 -25%" {
t.Fatalf("expected event baseline to be preserved, 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 != "pricing_history" {
t.Fatalf("expected event evidence fields to be preserved, got %+v", items[0])
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])
}
}
@@ -534,3 +548,30 @@ func TestHeadlineItemFromModelEventIncludesEvidenceFields(t *testing.T) {
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)
}
}