feat(offset): add suggested offset values to review panel

When offset scanner detects a mismatch, it now stores:
- suggested_season_offset: recommended season offset value
- suggested_episode_offset: recommended episode offset value

These values are returned in the API response for bangumi that need review,
allowing the frontend to display them to help users configure the correct offset.

Database migration v7 adds the new columns.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
EstrellaXD
2026-01-26 13:45:40 +01:00
parent 95165da3b6
commit 01a1a79a33
4 changed files with 64 additions and 12 deletions

View File

@@ -90,9 +90,15 @@ class OffsetScanner:
if suggestion and suggestion.confidence in ("high", "medium"):
with Database() as db:
db.bangumi.set_needs_review(bangumi.id, suggestion.reason)
db.bangumi.set_needs_review(
bangumi.id,
suggestion.reason,
suggested_season_offset=suggestion.season_offset,
suggested_episode_offset=suggestion.episode_offset,
)
logger.info(
f"[OffsetScanner] Flagged {bangumi.official_title} for review: {suggestion.reason}"
f"[OffsetScanner] Flagged {bangumi.official_title} for review: {suggestion.reason} "
f"(suggested: season={suggestion.season_offset}, episode={suggestion.episode_offset})"
)
return True

View File

@@ -373,26 +373,46 @@ class BangumiDatabase:
result = self.session.execute(statement)
return list(result.scalars().all())
def set_needs_review(self, _id: int, reason: str) -> bool:
"""Mark a bangumi as needing review."""
def set_needs_review(
self,
_id: int,
reason: str,
suggested_season_offset: int | None = None,
suggested_episode_offset: int | None = None,
) -> bool:
"""Mark a bangumi as needing review with suggested offsets.
Args:
_id: The bangumi ID
reason: Human-readable reason for the review
suggested_season_offset: Suggested season offset value
suggested_episode_offset: Suggested episode offset value
"""
bangumi = self.session.get(Bangumi, _id)
if not bangumi:
return False
bangumi.needs_review = True
bangumi.needs_review_reason = reason
bangumi.suggested_season_offset = suggested_season_offset
bangumi.suggested_episode_offset = suggested_episode_offset
self.session.add(bangumi)
self.session.commit()
_invalidate_bangumi_cache()
logger.debug(f"[Database] Marked bangumi id {_id} as needs_review: {reason}")
logger.debug(
f"[Database] Marked bangumi id {_id} as needs_review: {reason} "
f"(suggested: season={suggested_season_offset}, episode={suggested_episode_offset})"
)
return True
def clear_needs_review(self, _id: int) -> bool:
"""Clear the needs_review flag for a bangumi."""
"""Clear the needs_review flag and suggested offsets for a bangumi."""
bangumi = self.session.get(Bangumi, _id)
if not bangumi:
return False
bangumi.needs_review = False
bangumi.needs_review_reason = None
bangumi.suggested_season_offset = None
bangumi.suggested_episode_offset = None
self.session.add(bangumi)
self.session.commit()
_invalidate_bangumi_cache()

View File

@@ -88,6 +88,14 @@ MIGRATIONS = [
"CREATE INDEX IF NOT EXISTS ix_torrent_qb_hash ON torrent(qb_hash)",
],
),
(
7,
"add suggested offset columns for offset review",
[
"ALTER TABLE bangumi ADD COLUMN suggested_season_offset INTEGER DEFAULT NULL",
"ALTER TABLE bangumi ADD COLUMN suggested_episode_offset INTEGER DEFAULT NULL",
],
),
]

View File

@@ -11,7 +11,9 @@ class Bangumi(SQLModel, table=True):
default="official_title", alias="official_title", title="番剧中文名"
)
year: Optional[str] = Field(alias="year", title="番剧年份")
title_raw: str = Field(default="title_raw", alias="title_raw", title="番剧原名", index=True)
title_raw: str = Field(
default="title_raw", alias="title_raw", title="番剧原名", index=True
)
season: int = Field(default=1, alias="season", title="番剧季度")
season_raw: Optional[str] = Field(alias="season_raw", title="番剧季度原名")
group_name: Optional[str] = Field(alias="group_name", title="字幕组")
@@ -28,10 +30,22 @@ class Bangumi(SQLModel, table=True):
rule_name: Optional[str] = Field(alias="rule_name", title="番剧规则名")
save_path: Optional[str] = Field(alias="save_path", title="番剧保存路径")
deleted: bool = Field(False, alias="deleted", title="是否已删除", index=True)
archived: bool = Field(default=False, alias="archived", title="是否已归档", index=True)
air_weekday: Optional[int] = Field(default=None, alias="air_weekday", title="放送星期")
archived: bool = Field(
default=False, alias="archived", title="是否已归档", index=True
)
air_weekday: Optional[int] = Field(
default=None, alias="air_weekday", title="放送星期"
)
needs_review: bool = Field(default=False, alias="needs_review", title="需要检查")
needs_review_reason: Optional[str] = Field(default=None, alias="needs_review_reason", title="检查原因")
needs_review_reason: Optional[str] = Field(
default=None, alias="needs_review_reason", title="检查原因"
)
suggested_season_offset: Optional[int] = Field(
default=None, alias="suggested_season_offset", title="建议季度偏移"
)
suggested_episode_offset: Optional[int] = Field(
default=None, alias="suggested_episode_offset", title="建议集数偏移"
)
class BangumiUpdate(SQLModel):
@@ -57,9 +71,13 @@ class BangumiUpdate(SQLModel):
save_path: Optional[str] = Field(alias="save_path", title="番剧保存路径")
deleted: bool = Field(False, alias="deleted", title="是否已删除")
archived: bool = Field(default=False, alias="archived", title="是否已归档")
air_weekday: Optional[int] = Field(default=None, alias="air_weekday", title="放送星期")
air_weekday: Optional[int] = Field(
default=None, alias="air_weekday", title="放送星期"
)
needs_review: bool = Field(default=False, alias="needs_review", title="需要检查")
needs_review_reason: Optional[str] = Field(default=None, alias="needs_review_reason", title="检查原因")
needs_review_reason: Optional[str] = Field(
default=None, alias="needs_review_reason", title="检查原因"
)
class Notification(BaseModel):