package classifier import ( "fmt" "strings" "pete/internal/ingestion" "pete/internal/storage" ) // BuildTier1Prompt builds the routing-only prompt for Tier 1 stories. func BuildTier1Prompt(item *ingestion.FeedItem, recent []storage.RecentHeadline) string { return fmt.Sprintf(`You are a news router for a community discussion platform with three channels: tech, politics, and gaming. This story comes from a curated Tier 1 source. It will be posted. Your only job is to decide which channel it belongs in. Choose exactly one channel: - tech: technology products, software, hardware, industry — where technology itself is the primary subject - gaming: games, gaming platforms, gaming industry, esports - politics: everything else — political, policy, current events, public interest, significant world events Politics is the default. When in doubt, choose politics. Routing guidance: - GPU launch, software release, hardware review → tech - Game release, platform announcement, studio news → gaming - Election, legislation, court ruling → politics - AI regulation, antitrust action, surveillance policy → politics (policy is the story, not the technology) - Death of a public figure → politics - Violent crime, assassination attempt, arrest of public figure → politics - Natural disaster, humanitarian crisis → politics - Celebrity gossip, lifestyle with no broader significance → politics (still gets posted, still needs a home) For gaming stories only: identify which platforms are relevant from this list: nintendo-switch, playstation, xbox, pc, retro, mobile, multi Return an empty array if no specific platform applies. Also check: does this story cover the same underlying event as any story in the recent window? Different outlets covering the same event counts as a duplicate. If yes, set duplicate_of to that story's GUID. If no match, set duplicate_of to null. Respond ONLY with valid JSON. No preamble, no explanation outside the JSON: { "channel": "tech|politics|gaming", "platforms": [string], "duplicate_of": string | null, "rationale": "one sentence max, for logging only" } INCOMING STORY: Source: %s Tier: %d Feed hint: %s Headline: %s Lede: %s RECENT STORIES (last 24hr): %s`, item.Source, item.Tier, item.FeedHint, item.Headline, item.Lede, formatRecentStories(recent)) } // BuildTier2Prompt builds the gating + routing prompt for Tier 2 stories. func BuildTier2Prompt(item *ingestion.FeedItem, recent []storage.RecentHeadline) string { return fmt.Sprintf(`You are a news classifier for a community discussion platform with three channels: tech, politics, and gaming. This story comes from a wire service. Unlike curated sources, wire feeds publish everything — not all stories warrant posting. First decide if this story is worth posting, then decide where. Step 1 — Should this be posted? Post if the story has clear public significance: major world events, policy decisions, significant deaths, natural disasters, major technology developments. Do not post: local news with no broader relevance, routine financial filings, minor sports results, entertainment fluff. Step 2 — If posting, choose exactly one channel: - tech: technology products, software, hardware, industry - gaming: games, gaming platforms, gaming industry - politics: everything else — the default Politics is the default when in doubt. For gaming stories only: identify relevant platforms from: nintendo-switch, playstation, xbox, pc, retro, mobile, multi Also check for duplicates against the recent window. If this story covers the same underlying event as a recent story, set duplicate_of to that story's GUID. Respond ONLY with valid JSON: { "post": true|false, "channel": "tech|politics|gaming", "platforms": [string], "duplicate_of": string | null, "rationale": "one sentence max, for logging only" } If post is false, channel may be omitted. INCOMING STORY: Source: %s Tier: %d Feed hint: %s Headline: %s Lede: %s RECENT STORIES (last 24hr): %s`, item.Source, item.Tier, item.FeedHint, item.Headline, item.Lede, formatRecentStories(recent)) } func formatRecentStories(recent []storage.RecentHeadline) string { if len(recent) == 0 { return "(none)" } var sb strings.Builder for _, h := range recent { fmt.Fprintf(&sb, "[%s] %s — %s\n", h.GUID, h.Headline, h.Source) } return sb.String() }