fix(index-builder): add missing getCurrentSeasonTheme and buildHeadToHeadFromArc
Two functions referenced in generateLLMChronicle were undefined: - getCurrentSeasonTheme: returns the active season's theme string - buildHeadToHeadFromArc: computes W/L head-to-head records for a bot against all opponents from match data, enriching LLM narrative prompts Also improves the sports journalist system prompt with more detailed coverage style guidance for better narrative quality. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
17dbef0927
commit
fddcc0ba34
3 changed files with 94 additions and 14 deletions
|
|
@ -1 +1 @@
|
|||
38f14e1997145a3900b10124a18e31a812a65330
|
||||
17dbef092717f4f1f81dc870be3f8025466740a0
|
||||
|
|
|
|||
|
|
@ -1444,19 +1444,25 @@ func generateLLMChronicles(ctx context.Context, data *IndexData, llmClient *LLMC
|
|||
// generateLLMChronicle creates a chronicle using LLM narrative generation
|
||||
func generateLLMChronicle(ctx context.Context, arc StoryArc, data *IndexData, llmClient *LLMClient) (BlogPost, error) {
|
||||
seasonName := getCurrentSeasonName(data)
|
||||
seasonTheme := getCurrentSeasonTheme(data)
|
||||
|
||||
req := NarrativeRequest{
|
||||
ArcType: arc.Type,
|
||||
BotName: arc.BotName,
|
||||
SeasonName: seasonName,
|
||||
RatingStart: arc.RatingStart,
|
||||
RatingEnd: arc.RatingEnd,
|
||||
KeyMatches: arc.KeyMatches,
|
||||
Archetype: arc.Archetype,
|
||||
Origin: arc.Origin,
|
||||
ParentIDs: arc.ParentIDs,
|
||||
Generation: arc.Generation,
|
||||
BotBName: arc.BotBName,
|
||||
ArcType: arc.Type,
|
||||
BotName: arc.BotName,
|
||||
BotID: arc.BotID,
|
||||
SeasonName: seasonName,
|
||||
SeasonTheme: seasonTheme,
|
||||
RatingStart: arc.RatingStart,
|
||||
RatingEnd: arc.RatingEnd,
|
||||
KeyMatches: arc.KeyMatches,
|
||||
Archetype: arc.Archetype,
|
||||
Origin: arc.Origin,
|
||||
ParentIDs: arc.ParentIDs,
|
||||
Generation: arc.Generation,
|
||||
BotBName: arc.BotBName,
|
||||
BotRank: getBotRank(arc.BotID, data),
|
||||
CommunityHint: arc.CommunityHint,
|
||||
HeadToHead: buildHeadToHeadFromArc(arc, data),
|
||||
}
|
||||
|
||||
if arc.Type == ArcRivalry {
|
||||
|
|
@ -1832,6 +1838,17 @@ func getCurrentSeasonName(data *IndexData) string {
|
|||
return "Season 1"
|
||||
}
|
||||
|
||||
func getCurrentSeasonTheme(data *IndexData) string {
|
||||
for _, s := range data.Seasons {
|
||||
if s.StartsAt.Before(data.GeneratedAt) {
|
||||
if s.EndsAt.IsZero() || s.EndsAt.After(data.GeneratedAt) {
|
||||
return s.Theme
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getTopBots(data *IndexData, count int) []BotData {
|
||||
if len(data.Bots) < count {
|
||||
return data.Bots
|
||||
|
|
|
|||
|
|
@ -143,7 +143,8 @@ func buildNarrativePrompt(req NarrativeRequest) string {
|
|||
|
||||
// §15.5 instruction: sports-journalism narrative with structured contextual match data
|
||||
sb.WriteString("Write a 200-word sports-journalism narrative about this event in the AI Code Battle platform. ")
|
||||
sb.WriteString("Be dramatic but factual. Reference specific matches, ELO before/after deltas, rivalry context, and critical turning points. ")
|
||||
sb.WriteString("Be dramatic but factual. Reference specific matches by ID, ELO before/after deltas, rivalry context, head-to-head records, critical turning points, and season standings. ")
|
||||
sb.WriteString("Weave the data into a compelling story — quote scores, cite map names, describe the strategic moments that defined the outcome. ")
|
||||
sb.WriteString("Write in present tense with a punchy, journalistic tone. Do not use emojis.\n\n")
|
||||
|
||||
// Season and standings context
|
||||
|
|
@ -399,7 +400,15 @@ type llmChatResponse struct {
|
|||
|
||||
// systemPromptSportsJournalist is the system prompt framing the LLM as a
|
||||
// sports journalist covering AI Code Battle — per plan §15.1 and §15.5.
|
||||
const systemPromptSportsJournalist = `You are a sports journalist covering an emergent bot league called AI Code Battle, where autonomous programs compete in grid-based strategy matches. Write with the energy and narrative instinct of esports journalism — dramatic but factual, specific but accessible. Reference bots by name, cite ratings and score lines, and describe strategic turning points the way a commentator would. Use present tense. Do not use emojis. Keep paragraphs tight and punchy.`
|
||||
const systemPromptSportsJournalist = `You are a sports journalist covering an emergent bot league called AI Code Battle, where autonomous programs compete in grid-based strategy matches. Write with the energy and narrative instinct of esports journalism — dramatic but factual, specific but accessible.
|
||||
|
||||
Your coverage style:
|
||||
- Reference bots by name, cite ELO ratings with before/after deltas, and describe strategic turning points the way a play-by-play commentator would.
|
||||
- Weave in rivalry context, head-to-head records, season standings, and critical moments from match data.
|
||||
- Describe ELO shifts the way a power rankings columnist describes team movement — "surged 200 points" not "increased."
|
||||
- Use present tense. Keep paragraphs tight and punchy. Do not use emojis.
|
||||
- When lineage or evolution data is provided, frame it like a scouting report — origin story, parent strategies, behavioral archetype.
|
||||
- Always ground narrative in the specific match data, scores, and ratings provided — never fabricate match details.`
|
||||
|
||||
func (c *LLMClient) chatCompletion(ctx context.Context, prompt string) (string, error) {
|
||||
body, err := json.Marshal(llmChatRequest{
|
||||
|
|
@ -921,6 +930,60 @@ func getBotRank(botID string, data *IndexData) int {
|
|||
return 0
|
||||
}
|
||||
|
||||
func buildHeadToHeadFromArc(arc StoryArc, data *IndexData) []HeadToHeadRecord {
|
||||
if arc.BotID == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
type wl struct{ wins, losses int }
|
||||
recordMap := make(map[string]*wl)
|
||||
|
||||
for _, m := range data.Matches {
|
||||
var botIn, opponentIn bool
|
||||
var opponentID string
|
||||
for _, p := range m.Participants {
|
||||
if p.BotID == arc.BotID {
|
||||
botIn = true
|
||||
} else {
|
||||
opponentIn = true
|
||||
opponentID = p.BotID
|
||||
}
|
||||
}
|
||||
if !botIn || !opponentIn || opponentID == "" {
|
||||
continue
|
||||
}
|
||||
r, ok := recordMap[opponentID]
|
||||
if !ok {
|
||||
r = &wl{}
|
||||
recordMap[opponentID] = r
|
||||
}
|
||||
if m.WinnerID == arc.BotID {
|
||||
r.wins++
|
||||
} else if m.WinnerID == opponentID {
|
||||
r.losses++
|
||||
}
|
||||
}
|
||||
|
||||
var records []HeadToHeadRecord
|
||||
for oppID, r := range recordMap {
|
||||
name := oppID
|
||||
for _, b := range data.Bots {
|
||||
if b.ID == oppID {
|
||||
name = b.Name
|
||||
break
|
||||
}
|
||||
}
|
||||
records = append(records, HeadToHeadRecord{
|
||||
OpponentName: name,
|
||||
OpponentRank: getBotRank(oppID, data),
|
||||
Wins: r.wins,
|
||||
Losses: r.losses,
|
||||
TotalMatches: r.wins + r.losses,
|
||||
})
|
||||
}
|
||||
return records
|
||||
}
|
||||
|
||||
|
||||
// getBotRatingHistory returns rating history entries for a specific bot
|
||||
func getBotRatingHistory(botID string, data *IndexData) []RatingHistoryEntry {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue