feat: always-on Insights — correlation analysis over viewing habits
Some checks failed
CI / Check / Test (push) Failing after 9m16s
Some checks failed
CI / Check / Test (push) Failing after 9m16s
Conservative, live insights (ADR-0010) derived from each user's diary: rating lift by genre (first-time watches only), genre seasonality vs a uniform 1/12 month baseline, medium weekend-lift, and watch-time rhythm. - domain: WatchRow model, InsightsQuery port, pure insights_analyzer with all honesty thresholds (sample minimums, strength cutoffs, suppressions for import noise), unit-tested - adapters: thin Sqlite/Postgres fetch twins, reusing WrapUp's genre fetch - application: get_insights use case, InsightsGroup in Deps - composition/server: wiring through Repositories/DatabaseOutput - presentation: GET /api/v1/insights handler, typed serde-tagged DTOs, openapi doc - spa: /insights route (shadcn cards), insights feature hook, profile entry link, en locale strings - server test: end-to-end seed sqlite → HTTP → rating lift + rhythm Also records ADR-0010, defines "Insight" in the glossary, and includes pre-existing worktree comment-stripping cleanups.
This commit is contained in:
@@ -8,9 +8,6 @@ use domain::{
|
||||
ports::{EventHandler, MovieQuery, SearchCommand},
|
||||
};
|
||||
|
||||
/// Reacts to `MovieDiscovered` and inserts a bare search index entry immediately,
|
||||
/// so movies are findable before TMDb enrichment runs.
|
||||
/// Enrichment will later overwrite this with the full document (cast, genres, etc.).
|
||||
pub struct MovieDiscoveryIndexer {
|
||||
movie_repository: Arc<dyn MovieQuery>,
|
||||
search_command: Arc<dyn SearchCommand>,
|
||||
|
||||
@@ -8,22 +8,18 @@ use domain::{
|
||||
use crate::movies::{commands::EnrichMovieCommand, deps::EnrichMovieDeps};
|
||||
|
||||
pub async fn execute(deps: &EnrichMovieDeps, cmd: EnrichMovieCommand) -> Result<(), DomainError> {
|
||||
// 1. Persist the enriched profile (also handles movie_cast, movie_crew, genres, keywords)
|
||||
deps.movie_profile.upsert(&cmd.profile).await?;
|
||||
|
||||
// 2. Upsert persons extracted from cast + crew (no reads — only upsert)
|
||||
let persons = extract_persons(&cmd.profile.cast, &cmd.profile.crew);
|
||||
if !persons.is_empty() {
|
||||
deps.person_command.upsert_batch(&persons).await?;
|
||||
}
|
||||
|
||||
// 3. Fetch the movie for the search index document
|
||||
let Some(movie) = deps.movie_query.get_movie_by_id(&cmd.movie_id).await? else {
|
||||
tracing::warn!(movie_id = %cmd.movie_id.value(), "enrich_movie: movie not found after profile upsert");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// 4. Index the movie in search
|
||||
deps.search_command
|
||||
.index(IndexableDocument::Movie {
|
||||
id: cmd.movie_id.clone(),
|
||||
@@ -32,7 +28,6 @@ pub async fn execute(deps: &EnrichMovieDeps, cmd: EnrichMovieCommand) -> Result<
|
||||
})
|
||||
.await?;
|
||||
|
||||
// 5. Index each unique person in search (no reads — persons built from in-memory data)
|
||||
for person in &persons {
|
||||
deps.search_command
|
||||
.index(IndexableDocument::Person {
|
||||
@@ -50,9 +45,6 @@ pub async fn execute(deps: &EnrichMovieDeps, cmd: EnrichMovieCommand) -> Result<
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Build unique Person values from cast and crew.
|
||||
/// Uses deterministic UUIDv5 so the same tmdb_person_id always maps to the same PersonId.
|
||||
/// No DB reads — persons are built entirely from in-memory TMDb data.
|
||||
fn extract_persons(cast: &[CastMember], crew: &[CrewMember]) -> Vec<Person> {
|
||||
let mut seen: HashMap<u64, Person> = HashMap::new();
|
||||
|
||||
|
||||
@@ -9,9 +9,6 @@ use domain::{
|
||||
use crate::diary::commands::MovieInput;
|
||||
use crate::diary::movie_resolver::{MovieResolver, MovieResolverDeps};
|
||||
|
||||
/// Resolves a movie from input, persists it, and publishes `MovieDiscovered` if new.
|
||||
///
|
||||
/// Returns `(movie, is_new_movie)`.
|
||||
pub async fn resolve_and_persist_movie(
|
||||
input: &MovieInput,
|
||||
movie_command: &dyn MovieCommand,
|
||||
|
||||
Reference in New Issue
Block a user