fix(meta): store unrated titles as NULL, not zero

TMDB reports vote_average: 0 where no rating exists (#156). Normalise
it to None at the arr-meta edge, like non_empty does for "", and let
the Option flow through the daemon refresh and API add paths so the
nullable columns from #145 do their job.
This commit is contained in:
Miguel Palhas
2026-08-23 22:21:57 +01:00
parent b59e30dbdb
commit 08e3c7bce7
6 changed files with 97 additions and 10 deletions
+15 -6
View File
@@ -82,8 +82,10 @@ pub struct Movie {
/// Path fragment, not a URL. §9.6 stores this on the row alongside
/// [`Movie::poster_path`] and `vote_average`.
pub backdrop_path: Option<String>,
/// TMDB's rating, out of 10. Stored with the artwork (§9.6).
pub vote_average: f64,
/// TMDB's rating, out of 10, when TMDB has votes for it. Stored with the
/// artwork (§9.6). TMDB sends `0` where "no rating yet" is meant, so a
/// zero rating is normalised away here rather than stored as one.
pub vote_average: Option<f64>,
}
impl Movie {
@@ -136,8 +138,9 @@ pub struct Series {
/// §9.6 stores this on the row alongside `poster_path` and
/// [`Series::vote_average`].
pub backdrop_path: Option<String>,
/// TMDB's rating, out of 10. Stored with the artwork (§9.6).
pub vote_average: f64,
/// TMDB's rating, out of 10, when TMDB has votes for it. Stored with the
/// artwork (§9.6); zero is normalised away like [`Movie::vote_average`].
pub vote_average: Option<f64>,
pub seasons: Vec<SeasonSummary>,
}
@@ -372,7 +375,7 @@ impl From<RawSeries> for Series {
overview: non_empty(raw.overview),
poster_path: non_empty(raw.poster_path),
backdrop_path: non_empty(raw.backdrop_path),
vote_average: raw.vote_average,
vote_average: rating(raw.vote_average),
seasons: raw
.seasons
.into_iter()
@@ -562,7 +565,7 @@ impl From<RawMovie> for Movie {
overview: non_empty(raw.overview),
poster_path: non_empty(raw.poster_path),
backdrop_path: non_empty(raw.backdrop_path),
vote_average: raw.vote_average,
vote_average: rating(raw.vote_average),
}
}
}
@@ -596,6 +599,12 @@ fn non_empty(value: Option<String>) -> Option<String> {
value.filter(|text| !text.is_empty())
}
/// TMDB uses `0` where "no votes yet" is meant, so an unrated title never
/// carries a rating rather than carrying zero.
fn rating(value: f64) -> Option<f64> {
(value != 0.0).then_some(value)
}
#[derive(Debug, Deserialize)]
pub(crate) struct RawMovieDetail {
id: u32,
+3
View File
@@ -217,6 +217,9 @@ async fn unreleased_movie_has_no_dates_and_no_imdb_id() {
assert_eq!(movie.imdb_id, None);
assert_eq!(movie.runtime, None);
assert!(movie.origin_countries.is_empty());
// TMDB reports vote_average: 0 for an unrated title — absence, not zero
// (#156), so it is normalised away like any other zero-valued null.
assert_eq!(movie.vote_average, None);
assert!(!movie.is_digitally_released(date(2026, 8, 22)));
}