feedsignal/crates/core/src/models.rs

59 lines
2.1 KiB
Rust
Raw Normal View History

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Feed {
pub id: Uuid,
pub url: String,
pub title: String,
pub last_fetched_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Article {
pub id: Uuid,
pub feed_id: Uuid,
pub url: String,
pub title: String,
pub summary: String,
pub published_at: Option<DateTime<Utc>>,
/// Short canonical tags assigned at ingest time (e.g. by the LLM stage),
/// used both for display and as keys into `TopicAffinities`.
pub topics: Vec<String>,
/// Cosine similarity between the article embedding and the user's
/// preference-profile embedding. Cheap first-pass filter score.
pub embedding_score: Option<f32>,
/// 0.0-1.0 relevance judgment from the LLM stage, only computed for
/// articles that clear the embedding-score shortlist threshold.
pub llm_score: Option<f32>,
/// Final blended score actually used for ranking/surfacing, see
/// `scoring::score_article`.
pub final_score: Option<f32>,
pub estimated_read_seconds: Option<u32>,
}
/// A single recorded interaction between the user and an article. Every
/// event is appended to an immutable log (see `feedsignal-db`); affinities
/// are derived from replaying/aggregating these, never mutated in place,
/// so the scoring model can be recomputed or tuned retroactively.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReadingEvent {
pub id: Uuid,
pub article_id: Uuid,
pub occurred_at: DateTime<Utc>,
pub outcome: ReadingOutcome,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ReadingOutcome {
/// Shown in the feed list but no further interaction (yet).
Impression,
/// User opened the article. `dwell_seconds` is filled in later via an
/// update event (e.g. on tab close / navigation away) once known.
Opened { dwell_seconds: Option<u32> },
Starred,
/// Explicitly marked not relevant, independent of whether it was opened.
Dismissed,
}