feat(report): expose headline evidence details

This commit is contained in:
phamnazage-jpg
2026-05-13 21:16:08 +08:00
parent 79d991a7e9
commit b4e28d5be4
2 changed files with 326 additions and 130 deletions

View File

@@ -205,29 +205,37 @@ type ActionItem struct {
}
type HeadlineItem struct {
Label string
Title string
Summary string
Baseline string
TrustLabel string
Tone string
Label string
Title string
Summary string
Baseline string
TrustLabel string
SourceKindLabel string
PrimarySource string
UpdatedAt string
EvidenceDetail string
Tone string
}
type ModelEvent struct {
EventType string
ModelName string
ProviderName string
OperatorName string
TrustLabel string
Baseline string
Summary string
Currency string
OldInputPrice float64
NewInputPrice float64
OldOutputPrice float64
NewOutputPrice float64
PriceChangePct float64
Priority int
EventType string
ModelName string
ProviderName string
OperatorName string
TrustLabel string
SourceKindLabel string
PrimarySource string
UpdatedAt string
EvidenceDetail string
Baseline string
Summary string
Currency string
OldInputPrice float64
NewInputPrice float64
OldOutputPrice float64
NewOutputPrice float64
PriceChangePct float64
Priority int
}
type Recommendation struct {
@@ -805,7 +813,8 @@ func loadNewModelEvents(db *sql.DB, date string) ([]ModelEvent, error) {
COALESCE(lp.output_price_per_mtok, 0) AS output_price,
COALESCE(lp.is_free, false) AS is_free,
COALESCE(m.context_length, 0) AS context_length,
COALESCE(mp.country, 'unknown') AS provider_country
COALESCE(mp.country, 'unknown') AS provider_country,
m.created_at
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
@@ -832,6 +841,7 @@ func loadNewModelEvents(db *sql.DB, date string) ([]ModelEvent, error) {
isFree bool
contextLength int
providerCountry string
createdAt time.Time
)
if err := rows.Scan(
&modelName,
@@ -844,6 +854,7 @@ func loadNewModelEvents(db *sql.DB, date string) ([]ModelEvent, error) {
&isFree,
&contextLength,
&providerCountry,
&createdAt,
); err != nil {
return nil, err
}
@@ -869,17 +880,21 @@ func loadNewModelEvents(db *sql.DB, date string) ([]ModelEvent, error) {
}
events = append(events, ModelEvent{
EventType: "new_model",
ModelName: modelName,
ProviderName: providerName,
OperatorName: operatorName,
TrustLabel: buildTrustLabel(model),
Baseline: "首次出现",
Summary: summary,
Currency: currency,
NewInputPrice: inputPrice,
EventType: "new_model",
ModelName: modelName,
ProviderName: providerName,
OperatorName: operatorName,
TrustLabel: buildTrustLabel(model),
SourceKindLabel: "模型快照",
PrimarySource: buildPrimarySource("region_pricing", operatorName),
UpdatedAt: createdAt.Format("2006-01-02 15:04"),
EvidenceDetail: "models.created_at = 今日,且已存在最新价格快照",
Baseline: "首次出现",
Summary: summary,
Currency: currency,
NewInputPrice: inputPrice,
NewOutputPrice: outputPrice,
Priority: 85 + minInt(contextLength/(1024*128), 10),
Priority: 85 + minInt(contextLength/(1024*128), 10),
})
}
return events, rows.Err()
@@ -909,7 +924,8 @@ func loadPriceChangeEvents(db *sql.DB, date string) ([]ModelEvent, error) {
COALESCE(ph.new_input_price, 0),
COALESCE(ph.old_output_price, 0),
COALESCE(ph.new_output_price, 0),
COALESCE(mp.country, 'unknown') AS provider_country
COALESCE(mp.country, 'unknown') AS provider_country,
ph.changed_at
FROM pricing_history ph
JOIN models m ON ph.model_id = m.id
LEFT JOIN model_provider mp ON m.provider_id = mp.id
@@ -936,6 +952,7 @@ func loadPriceChangeEvents(db *sql.DB, date string) ([]ModelEvent, error) {
oldOutputPrice float64
newOutputPrice float64
providerCountry string
changedAt time.Time
)
if err := rows.Scan(
&modelName,
@@ -948,6 +965,7 @@ func loadPriceChangeEvents(db *sql.DB, date string) ([]ModelEvent, error) {
&oldOutputPrice,
&newOutputPrice,
&providerCountry,
&changedAt,
); err != nil {
return nil, err
}
@@ -967,32 +985,32 @@ func loadPriceChangeEvents(db *sql.DB, date string) ([]ModelEvent, error) {
}
eventType := "price_increase"
label := "价格上调"
summary := "价格上调已足以影响默认成本,需要确认备用模型。"
if changePct < 0 {
eventType = "price_cut"
label = "价格下调"
summary = "价格下降已足以影响默认选型,值得重新评估同类模型。"
}
events = append(events, ModelEvent{
EventType: eventType,
ModelName: modelName,
ProviderName: providerName,
OperatorName: operatorName,
TrustLabel: buildTrustLabel(model),
Baseline: fmt.Sprintf("较昨日 %+.0f%%", changePct),
Summary: summary,
Currency: currency,
OldInputPrice: oldInputPrice,
NewInputPrice: newInputPrice,
OldOutputPrice: oldOutputPrice,
NewOutputPrice: newOutputPrice,
PriceChangePct: changePct,
Priority: 70 + minInt(int(abs(changePct)), 25),
EventType: eventType,
ModelName: modelName,
ProviderName: providerName,
OperatorName: operatorName,
TrustLabel: buildTrustLabel(model),
SourceKindLabel: "价格快照",
PrimarySource: "pricing_history",
UpdatedAt: changedAt.Format("2006-01-02 15:04"),
EvidenceDetail: buildPriceEvidenceDetail(changePct, oldInputPrice, newInputPrice, currency),
Baseline: fmt.Sprintf("较昨日 %+.0f%%", changePct),
Summary: summary,
Currency: currency,
OldInputPrice: oldInputPrice,
NewInputPrice: newInputPrice,
OldOutputPrice: oldOutputPrice,
NewOutputPrice: newOutputPrice,
PriceChangePct: changePct,
Priority: 70 + minInt(int(abs(changePct)), 25),
})
_ = label
}
return events, rows.Err()
}
@@ -1089,15 +1107,19 @@ func enrichModelEvents(r *ReportV3) []ModelEvent {
}
existing[key] = struct{}{}
events = append(events, ModelEvent{
EventType: "free_highlight",
ModelName: model.Name,
ProviderName: model.ProviderName,
OperatorName: model.OperatorName,
TrustLabel: buildTrustLabel(model),
Baseline: "今日快照",
Summary: buildModelEvidence(model),
Currency: model.Currency,
Priority: priority,
EventType: "free_highlight",
ModelName: model.Name,
ProviderName: model.ProviderName,
OperatorName: model.OperatorName,
TrustLabel: buildTrustLabel(model),
SourceKindLabel: "免费策略快照",
PrimarySource: buildPrimarySource("free_snapshot", model.OperatorName),
UpdatedAt: formatEventUpdatedAt(r.GeneratedAt, r.Date),
EvidenceDetail: buildFreeEvidenceDetail(model),
Baseline: "今日快照",
Summary: buildModelEvidence(model),
Currency: model.Currency,
Priority: priority,
})
}
@@ -1311,11 +1333,15 @@ func buildHeadlineItemsFromEvents(events []ModelEvent) []HeadlineItem {
func headlineItemFromModelEvent(event ModelEvent) HeadlineItem {
item := HeadlineItem{
Title: event.ModelName,
Summary: event.Summary,
Baseline: event.Baseline,
TrustLabel: event.TrustLabel,
Tone: "neutral",
Title: event.ModelName,
Summary: event.Summary,
Baseline: event.Baseline,
TrustLabel: event.TrustLabel,
SourceKindLabel: event.SourceKindLabel,
PrimarySource: event.PrimarySource,
UpdatedAt: event.UpdatedAt,
EvidenceDetail: event.EvidenceDetail,
Tone: "neutral",
}
switch event.EventType {
@@ -1343,6 +1369,58 @@ func headlineItemFromModelEvent(event ModelEvent) HeadlineItem {
return item
}
func buildPrimarySource(sourceKind, operatorName string) string {
switch sourceKind {
case "region_pricing":
if operatorName == "" {
return "region_pricing"
}
return operatorName + " / region_pricing"
case "free_snapshot":
if operatorName == "" {
return "free snapshot"
}
return operatorName + " / free snapshot"
default:
return sourceKind
}
}
func buildPriceEvidenceDetail(changePct, oldPrice, newPrice float64, currency string) string {
direction := "上涨"
if changePct < 0 {
direction = "下降"
}
return fmt.Sprintf(
"pricing_history 记录到输入价格由 %s 调整为 %s较昨日%s %.0f%%",
formatPrice(oldPrice, currency),
formatPrice(newPrice, currency),
direction,
abs(changePct),
)
}
func buildFreeEvidenceDetail(model ModelInfo) string {
switch classifyFreeSource(model) {
case "官方免费":
return fmt.Sprintf("%s 当前快照显示为官方免费入口", model.OperatorName)
case "聚合免费":
return fmt.Sprintf("%s 当前快照显示为聚合免费入口", model.OperatorName)
default:
return fmt.Sprintf("%s 当前快照显示免费,但来源仍待确认", model.OperatorName)
}
}
func formatEventUpdatedAt(value, fallbackDate string) string {
if strings.TrimSpace(value) != "" {
return value
}
if fallbackDate != "" {
return fallbackDate + " 00:00"
}
return "-"
}
func buildActionItems(r *ReportV3) []ActionItem {
var actions []ActionItem
@@ -1670,7 +1748,19 @@ func generateMarkdownV3(r *ReportV3, path string) error {
for _, item := range r.HeadlineItems {
fmt.Fprintf(f, "### %s · %s\n\n", item.Label, item.Title)
fmt.Fprintf(f, "- 影响: %s\n", item.Summary)
if item.SourceKindLabel != "" {
fmt.Fprintf(f, "- 事件来源: %s\n", item.SourceKindLabel)
}
if item.PrimarySource != "" {
fmt.Fprintf(f, "- 主来源: %s\n", item.PrimarySource)
}
if item.UpdatedAt != "" {
fmt.Fprintf(f, "- 更新时间: %s\n", item.UpdatedAt)
}
fmt.Fprintf(f, "- 基线: %s\n", item.Baseline)
if item.EvidenceDetail != "" {
fmt.Fprintf(f, "- 判定依据: %s\n", item.EvidenceDetail)
}
fmt.Fprintf(f, "- 可信度: %s\n\n", item.TrustLabel)
}
@@ -1984,11 +2074,26 @@ body {
font-weight: 700;
}
.trust-line,
.baseline-line {
.baseline-line,
.source-line {
margin-top: 8px;
font-size: 0.9rem;
color: var(--ink-soft);
}
.evidence-block {
margin-top: 10px;
padding-top: 10px;
border-top: 1px dashed var(--line);
display: grid;
gap: 6px;
}
.evidence-item {
font-size: 0.92rem;
color: var(--ink-soft);
}
.evidence-item strong {
color: var(--ink);
}
.scene-header {
display: flex;
justify-content: space-between;
@@ -2167,8 +2272,14 @@ th {
<div class="card-kicker">{{.Label}}</div>
<div class="card-title">{{.Title}}</div>
<div class="card-summary">{{.Summary}}</div>
{{if .SourceKindLabel}}<div class="source-line">事件来源:{{.SourceKindLabel}}</div>{{end}}
<div class="baseline-line">基线:{{.Baseline}}</div>
<div class="trust-line">可信度:{{.TrustLabel}}</div>
<div class="evidence-block">
{{if .PrimarySource}}<div class="evidence-item"><strong>主来源</strong>{{.PrimarySource}}</div>{{end}}
{{if .UpdatedAt}}<div class="evidence-item"><strong>更新时间</strong>{{.UpdatedAt}}</div>{{end}}
{{if .EvidenceDetail}}<div class="evidence-item"><strong>判定依据</strong>{{.EvidenceDetail}}</div>{{end}}
</div>
</article>
{{end}}
</div>