Some checks failed
CI / check (push) Failing after 26s
Rust workspace with core (topic-affinity learning engine + relevance scoring), db (sqlite/sqlx schema + repo), feeds (RSS/Atom fetch), llm (rig + local Ollama embeddings/completion), and web (axum + Dioxus fullstack UI, no separate JS stack). Two-stage relevance filtering (embedding shortlist -> LLM judgment) and an engagement/surprise-based topic affinity engine with daily decay. All crates compile and core's affinity engine has passing unit tests; server and wasm client targets of feedsignal-web both check clean.
58 lines
2.1 KiB
Rust
58 lines
2.1 KiB
Rust
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,
|
|
}
|