111 lines
4.3 KiB
Go
111 lines
4.3 KiB
Go
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()
|
|
}
|