insights-correlations (#22)
Some checks failed
CI / Check / Test (push) Has been cancelled

Reviewed-on: #22
This commit was merged in pull request #22.
This commit is contained in:
2026-10-12 18:56:55 +00:00
parent 4d221e60de
commit e303ae7f08
139 changed files with 2371 additions and 696 deletions

View File

@@ -60,7 +60,6 @@ pub struct FeedEntryDto {
#[derive(Debug, Clone, Deserialize, utoipa::IntoParams)]
#[into_params(parameter_in = Query)]
pub struct ExportQueryParams {
/// Output format: `csv` (default) or `json`
#[serde(default = "default_export_format")]
pub format: String,
}

View File

@@ -17,13 +17,9 @@ pub struct SessionStateResponse {
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct ApiFieldMapping {
/// Column name in the source file
pub source_column: String,
/// Domain field: title | release_year | director | rating | watched_at | comment | external_metadata_id
pub domain_field: String,
/// For rating fields: multiply raw value by this factor (e.g. 0.5 for 10-point → 5-point scale)
pub rating_scale: Option<f64>,
/// For watched_at fields: strftime format hint (e.g. "%d/%m/%Y")
pub date_format: Option<String>,
}
@@ -34,15 +30,12 @@ pub struct ApplyMappingRequest {
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct ConfirmRequest {
/// Indices (0-based) of rows from the mapping preview to import
pub confirmed_indices: Vec<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct SaveProfileRequest {
/// Session UUID whose current field_mappings to save
pub session_id: String,
/// Human-readable profile name (e.g. "Letterboxd")
pub name: String,
}

View File

@@ -0,0 +1,57 @@
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
/// One conservative, baseline-relative statement about the user's viewing
/// behavior. Tagged by `kind` so the SPA can switch on it; every variant
/// carries `sample_count` so the UI can explain a thin sample.
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum InsightDto {
/// `genre`'s average rating sits `delta` stars from the user's own
/// average (first-time watches only).
RatingLift {
genre: String,
genre_avg: f64,
user_avg: f64,
delta: f64,
sample_count: u32,
},
/// `genre`'s share of watches in `month` (1–12) is `multiplier` times the
/// user's own share of watches in that month.
Seasonality {
genre: String,
month: u32,
multiplier: f64,
sample_count: u32,
},
/// `medium`'s weekend share of watches is `multiplier` times the user's
/// overall weekend share.
MediumTiming {
medium: String,
multiplier: f64,
weekend_share: f64,
baseline_share: f64,
sample_count: u32,
},
/// Watching concentrates in a 3-hour window starting `peak_hour_start`
/// (0–23) on `top_days` (Mon=1 … Sun=7).
Rhythm {
peak_hour_start: u32,
peak_hour_end: u32,
top_days: Vec<u32>,
sample_count: u32,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct InsightsResponse {
pub insights: Vec<InsightDto>,
}
#[derive(Debug, Clone, Deserialize, utoipa::IntoParams)]
#[into_params(parameter_in = Query)]
pub struct InsightsQueryParams {
/// IANA time zone (e.g. `Europe/Warsaw`) months, weekdays and hours are
/// bucketed in. Defaults to UTC.
pub tz: Option<String>,
}

View File

@@ -3,6 +3,7 @@ pub mod common;
pub mod diary;
pub mod goals;
pub mod import;
pub mod insights;
pub mod movies;
pub mod rendering;
pub mod search;
@@ -17,6 +18,7 @@ pub use common::*;
pub use diary::*;
pub use goals::*;
pub use import::*;
pub use insights::*;
pub use movies::*;
pub use rendering::*;
pub use social::*;

View File

@@ -10,11 +10,8 @@ use crate::common::SocialFeedResponse;
pub struct MoviesQueryParams {
pub limit: Option<u32>,
pub offset: Option<u32>,
/// Optional title filter (case-insensitive substring match)
pub search: Option<String>,
/// Filter by genre name (case-insensitive)
pub genre: Option<String>,
/// Filter by ISO language code (e.g. "en")
pub language: Option<String>,
}

View File

@@ -6,21 +6,13 @@ pub use crate::common::{PaginatedMovieHits, PaginatedPersonHits};
#[derive(Debug, Deserialize, IntoParams)]
pub struct SearchQueryParams {
/// Free-text query matched across title, cast, crew, genres and keywords.
pub q: Option<String>,
/// Filter by genre name (exact match, case-sensitive).
pub genre: Option<String>,
/// Filter by release year.
pub year: Option<u16>,
/// Filter by person ID (UUID).
pub person_id: Option<Uuid>,
/// Filter crew results by department (e.g. "Directing", "Writing").
pub department: Option<String>,
/// Filter by original language code (e.g. "en", "fr").
pub language: Option<String>,
/// Max results to return (default 20).
pub limit: Option<u32>,
/// Offset for pagination (default 0).
pub offset: Option<u32>,
}

View File

@@ -16,7 +16,6 @@ pub struct RemoteActorDto {
pub handle: String,
pub display_name: Option<String>,
pub url: String,
/// `Some` for local actors, so the SPA can link internally to `/users/{id}`.
pub user_id: Option<Uuid>,
pub avatar_url: Option<String>,
}
@@ -79,12 +78,11 @@ pub struct PendingCountResponse {
pub count: usize,
}
//todo: move out to separate file
#[cfg(test)]
mod tests {
use super::*;
/// Task 7's SPA zod schema parses these exact lowercase literals —
/// a casing or naming drift here breaks the SPA at runtime.
#[test]
fn follow_state_dto_serializes_to_lowercase_strings() {
assert_eq!(

View File

@@ -23,7 +23,6 @@ pub struct UsersResponse {
#[derive(Debug, Clone, Deserialize, utoipa::IntoParams)]
#[into_params(parameter_in = Query)]
pub struct UserProfileQueryParams {
/// One of: `recent` (default), `ratings`, `history`, `trends`
pub view: Option<String>,
pub limit: Option<u32>,
pub offset: Option<u32>,