/// How an item arrived in the user's feed. #[derive(Debug, Clone, serde::Serialize)] #[serde(rename_all = "snake_case")] pub enum ItemLabel { Match, Exploring, Trending, Resurfaced, /// Semantic intersection of the user's two strongest interest categories. /// /// Injected at most once per feed call. The category names (`cat_a`, `cat_b`) /// are the two categories with the highest scores in the user's preference /// vector. Serialises as `{"bridge": {"cat_a": "...", "cat_b": "..."}}`. Bridge { cat_a: String, cat_b: String, }, } /// A single item returned by the feed, with label and score attached. #[derive(Debug, Clone, serde::Serialize)] pub struct ForageItem { pub id: u64, pub title: String, pub source: String, pub category: String, pub reading_time_min: u32, pub description: String, pub label: ItemLabel, pub score: f32, pub url: String, /// Specific subtopics (e.g. `["modal jazz", "music theory"]`). /// Empty for seed items or items not yet enriched by the discovery agent. pub tags: Vec, /// Named entities (e.g. `["John Coltrane", "Blue Note"]`). /// Empty for seed items or items not yet enriched. pub entities: Vec, /// Content type classification (e.g. `"analysis"`, `"tutorial"`). /// Empty string when not enriched. pub content_type: String, /// Claude's 2-sentence summary of the article. /// Empty string when not enriched. pub summary: String, }