diff --git a/crates/arr-daemon/src/tv_grab.rs b/crates/arr-daemon/src/tv_grab.rs index 8afb566..134f3b4 100644 --- a/crates/arr-daemon/src/tv_grab.rs +++ b/crates/arr-daemon/src/tv_grab.rs @@ -756,7 +756,7 @@ mod tests { } /// A season pack and its three episodes, all eligible under the seeded - /// TV main policy (§5.5 bands: 2160p floor 8 GiB). + /// TV main policy (§5.5 bands: 2160p floor 3 GiB, per #95). const TV_RSS: &str = r#" Fallout.S01.2160p.WEB-DL.DDP5.1.Atmos diff --git a/crates/arr-db/migrations/0011_tv_episode_size_bands.sql b/crates/arr-db/migrations/0011_tv_episode_size_bands.sql new file mode 100644 index 0000000..cd6bdeb --- /dev/null +++ b/crates/arr-db/migrations/0011_tv_episode_size_bands.sql @@ -0,0 +1,10 @@ +-- The bands seeded in 0005 copied the movie policy's numbers verbatim: 2160p +-- floor 8 GiB, 1080p floor 3 GiB. Those fit a whole film; a single episode +-- (~40-60 min) at 1080p WEB-DL is typically 1-2 GiB, so §5.5's hard floor +-- rejected it outright (#95). Season packs still clear these floors easily +-- and just take the over-target penalty, which is the intended behaviour. +UPDATE policies SET size_bands = json('{ + "2160p": {"floor_gib": 3, "target_gib": 6, "penalty_points_per_gib_over": 60}, + "1080p": {"floor_gib": 1, "target_gib": 2, "penalty_points_per_gib_over": 60} +}') +WHERE name IN ('TV — main', 'TV — kids'); diff --git a/crates/arr-db/src/policy.rs b/crates/arr-db/src/policy.rs index 0449a40..b2655da 100644 --- a/crates/arr-db/src/policy.rs +++ b/crates/arr-db/src/policy.rs @@ -477,4 +477,37 @@ mod tests { let (_dir, db) = seeded().await; assert!(db.movie_policy(404).await.unwrap().is_none()); } + + /// #95: the TV bands used to copy the movie policy's floors verbatim, so a + /// typical 1080p single-episode WEB-DL (1-2 GiB) sat below the 3 GiB movie + /// floor and was hard-filtered outright. + #[tokio::test] + async fn a_single_episode_web_dl_clears_the_tv_floor() { + let (_dir, db) = seeded().await; + sqlx::query( + "INSERT INTO series (tmdb_id, title, root_id) + SELECT 1, 'Fallout', id FROM roots WHERE kind = 'tv' AND audience = 'main'", + ) + .execute(db.pool()) + .await + .unwrap(); + sqlx::query("INSERT INTO seasons (series_id, number) VALUES (1, 1)") + .execute(db.pool()) + .await + .unwrap(); + sqlx::query("INSERT INTO episodes (season_id, number, title) VALUES (1, 1, 'The End')") + .execute(db.pool()) + .await + .unwrap(); + + let loaded = db.episode_policy(1).await.unwrap().expect("episode 1"); + let band = loaded.policy.size_bands[&Resolution::R1080p]; + assert_eq!(band.floor_bytes, 1 << 30); + + let one_and_a_half_gib = 1536 << 20; + assert_eq!( + arr_core::score::is_below_floor(&loaded.policy, Resolution::R1080p, one_and_a_half_gib), + Some(false) + ); + } }