feat(core): score season packs per episode
A size band describes one episode (DESIGN.md §5.5), so both the target penalty and the floor now compare a release's size divided by the number of episodes it covers. The caller supplies the count — arr-core has no IO. Movies and unknown counts divide by one, so movie scoring is unchanged and pinned by test. Closes #180 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::time::UNIX_EPOCH;
|
||||
|
||||
use arr_core::policy::{evaluate, Candidate};
|
||||
use arr_core::score::score;
|
||||
use arr_core::score::{claimed_episode_count, score};
|
||||
use arr_core::{Language, Policy, Rule, TitleOverrides, Verdict};
|
||||
use arr_db::policy::language;
|
||||
use arr_db::{blacklist, Blacklist};
|
||||
@@ -488,6 +489,7 @@ async fn movie_releases(
|
||||
&overrides,
|
||||
&original_language,
|
||||
&blacklist,
|
||||
None,
|
||||
)?);
|
||||
}
|
||||
}
|
||||
@@ -511,7 +513,7 @@ async fn episode_releases(
|
||||
) -> Result<Vec<ClassifiedRelease>, ApiError> {
|
||||
let database = state.database().ok_or(ApiError::Unavailable)?;
|
||||
let episode = sqlx::query!(
|
||||
r#"SELECT s.title AS "series_title!: String", s.tvdb_id AS series_tvdb_id, s.original_language,
|
||||
r#"SELECT s.id AS "series_id!: i64", s.title AS "series_title!: String", s.tvdb_id AS series_tvdb_id, s.original_language,
|
||||
se.number AS "season_number!: i64", e.number AS "episode_number!: i64"
|
||||
FROM episodes e
|
||||
JOIN seasons se ON se.id = e.season_id
|
||||
@@ -522,6 +524,7 @@ async fn episode_releases(
|
||||
.fetch_optional(database.pool())
|
||||
.await?
|
||||
.ok_or(ApiError::EpisodeNotFound)?;
|
||||
let season_lengths = crate::series::season_lengths(state, episode.series_id).await?;
|
||||
let loaded = database
|
||||
.episode_policy(episode_id)
|
||||
.await
|
||||
@@ -564,6 +567,7 @@ async fn episode_releases(
|
||||
&loaded.overrides,
|
||||
&original_language,
|
||||
&blacklist,
|
||||
Some(&season_lengths),
|
||||
)?);
|
||||
}
|
||||
}
|
||||
@@ -704,14 +708,22 @@ fn classify(
|
||||
overrides: &TitleOverrides,
|
||||
original_language: &Language,
|
||||
blacklist: &Blacklist,
|
||||
season_lengths: Option<&BTreeMap<u32, u32>>,
|
||||
) -> Result<ClassifiedRelease, ApiError> {
|
||||
let parsed = arr_parse::parse(&release.name);
|
||||
// A size band describes one episode (`DESIGN.md` §5.5): a pack's size is
|
||||
// divided by how many episodes it covers. `None` is the movie search —
|
||||
// one release is one film.
|
||||
let episodes = season_lengths.map_or(1, |lengths| {
|
||||
claimed_episode_count(parsed.episode.as_ref(), lengths)
|
||||
});
|
||||
let evaluation = evaluate(
|
||||
policy,
|
||||
overrides,
|
||||
original_language,
|
||||
Candidate::PreGrab(&parsed),
|
||||
release.size,
|
||||
episodes,
|
||||
);
|
||||
let (verdict, rule) = if blacklist.blocks_candidate(&release.name, &release.download_url) {
|
||||
("rejected", Some(blacklist::RULE.to_owned()))
|
||||
@@ -723,6 +735,7 @@ fn classify(
|
||||
Candidate::PreGrab(&parsed),
|
||||
release.size.unwrap_or_default(),
|
||||
release.seeders.unwrap_or_default(),
|
||||
episodes,
|
||||
);
|
||||
// A release with no size has nothing to say about its size band, so that
|
||||
// term is dropped rather than scored as if it were at the floor. Every
|
||||
@@ -1451,6 +1464,7 @@ mod tests {
|
||||
&TitleOverrides::default(),
|
||||
&Language::Other("en".into()),
|
||||
&Blacklist::default(),
|
||||
None,
|
||||
)
|
||||
.expect("classified release")
|
||||
};
|
||||
@@ -1544,7 +1558,7 @@ mod tests {
|
||||
imdb_id: None,
|
||||
};
|
||||
let parsed = arr_parse::parse(&release.name);
|
||||
let core_score = score(&policy, Candidate::PreGrab(&parsed), 0, 8);
|
||||
let core_score = score(&policy, Candidate::PreGrab(&parsed), 0, 8, 1);
|
||||
|
||||
let classified = classify(
|
||||
release,
|
||||
@@ -1552,6 +1566,7 @@ mod tests {
|
||||
&TitleOverrides::default(),
|
||||
&Language::Other("en".into()),
|
||||
&Blacklist::default(),
|
||||
None,
|
||||
)
|
||||
.expect("classified release");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user