refactor: DLO-16/17/18/20 — CLI simplification, config Pydantic, planner split, type system unification
DLO-16: Reduce cli.py from 1073 to 83 lines by registering Click commands from commands/*.py modules DLO-17: Migrate Config to Pydantic BaseModel for validation DLO-18: Split planner.py (826 lines) into orchestration, path rendering, and duplicate handling modules DLO-20: Unify type system — convert 14 dataclasses to Pydantic BaseModel, keep TypedDicts as JSON schema hints Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.5
parent
8a60aaf9a9
commit
fe03a31dd4
@@ -16,134 +16,164 @@ from vlm.reports import (
|
||||
)
|
||||
|
||||
|
||||
def _movie(title="Movie", year=2020, **kw):
|
||||
return MovieIdentity(
|
||||
title=title, year=year, confidence=kw.pop("confidence", 0.9),
|
||||
needs_review=kw.pop("needs_review", False),
|
||||
original_filename=kw.pop("original_filename", f"{title}.{year}.mkv"),
|
||||
**kw,
|
||||
)
|
||||
|
||||
|
||||
def _series(title="Show", season=1, episodes=None, **kw):
|
||||
if episodes is None:
|
||||
episodes = [1]
|
||||
return SeriesIdentity(
|
||||
title=title, season=season, episodes=episodes,
|
||||
confidence=kw.pop("confidence", 0.9),
|
||||
needs_review=kw.pop("needs_review", False),
|
||||
original_filename=kw.pop(
|
||||
"original_filename",
|
||||
f"{title.replace(' ', '.')}.S{season:02d}E01.mkv"
|
||||
if season is not None
|
||||
else f"{title.replace(' ', '.')}.E01.mkv",
|
||||
),
|
||||
**kw,
|
||||
)
|
||||
|
||||
|
||||
def _video(filename="file.mkv", size=1000, category="movie", **kw):
|
||||
return VideoFile(
|
||||
path=kw.pop("path", Path(f"/tmp/{filename}")),
|
||||
filename=filename, size_bytes=size,
|
||||
modified_timestamp=kw.pop("modified_timestamp", datetime(2023, 1, 1)),
|
||||
category=category, **kw,
|
||||
)
|
||||
|
||||
|
||||
class TestReportsIntegration:
|
||||
"""Test report generation integrated with analysis engine."""
|
||||
|
||||
|
||||
def test_completeness_workflow(self):
|
||||
"""Test complete workflow from series analysis to completeness report."""
|
||||
# Create test episodes with gaps
|
||||
episodes = [
|
||||
SeriesIdentity("Breaking Bad", 1, [1], 0.9, False, "Breaking.Bad.S01E01.mkv"),
|
||||
SeriesIdentity("Breaking Bad", 1, [2], 0.9, False, "Breaking.Bad.S01E02.mkv"),
|
||||
SeriesIdentity("Breaking Bad", 1, [4], 0.9, False, "Breaking.Bad.S01E04.mkv"),
|
||||
SeriesIdentity("The Wire", 1, [1], 0.9, False, "The.Wire.S01E01.mkv"),
|
||||
SeriesIdentity("The Wire", 1, [3], 0.9, False, "The.Wire.S01E03.mkv"),
|
||||
_series("Breaking Bad", episodes=[1],
|
||||
original_filename="Breaking.Bad.S01E01.mkv"),
|
||||
_series("Breaking Bad", episodes=[2],
|
||||
original_filename="Breaking.Bad.S01E02.mkv"),
|
||||
_series("Breaking Bad", episodes=[4],
|
||||
original_filename="Breaking.Bad.S01E04.mkv"),
|
||||
_series("The Wire", episodes=[1],
|
||||
original_filename="The.Wire.S01E01.mkv"),
|
||||
_series("The Wire", episodes=[3],
|
||||
original_filename="The.Wire.S01E03.mkv"),
|
||||
]
|
||||
|
||||
|
||||
# Analyze completeness
|
||||
analysis = analyze_series_completeness(episodes)
|
||||
|
||||
|
||||
# Generate text report
|
||||
library_root = Path("/mnt/nas/videos")
|
||||
text_report = generate_completeness_report(analysis, "text", library_root)
|
||||
|
||||
|
||||
# Verify report contains expected information
|
||||
assert "Breaking Bad" in text_report
|
||||
assert "The Wire" in text_report
|
||||
assert "Episodes missing:" in text_report
|
||||
|
||||
|
||||
# Generate JSON report
|
||||
json_report = generate_completeness_report(analysis, "json", library_root)
|
||||
data = json.loads(json_report)
|
||||
|
||||
|
||||
# Verify JSON structure
|
||||
assert data["metadata"]["series_count"] == 2
|
||||
assert len(data["series"]) == 2
|
||||
|
||||
|
||||
def test_duplicate_workflow(self):
|
||||
"""Test complete workflow from duplicate detection to duplicate report."""
|
||||
# Create test identities and files
|
||||
now = datetime.now(timezone.utc)
|
||||
identities = [
|
||||
MovieIdentity("The Matrix", 1999, 0.9, False, "The.Matrix.1999.1080p.mkv"),
|
||||
MovieIdentity("The Matrix", 1999, 0.9, False, "The.Matrix.1999.720p.mkv"),
|
||||
MovieIdentity("Inception", 2010, 0.9, False, "Inception.2010.mkv"),
|
||||
_movie("The Matrix", 1999,
|
||||
original_filename="The.Matrix.1999.1080p.mkv"),
|
||||
_movie("The Matrix", 1999,
|
||||
original_filename="The.Matrix.1999.720p.mkv"),
|
||||
_movie("Inception", 2010),
|
||||
]
|
||||
|
||||
|
||||
files = [
|
||||
VideoFile(
|
||||
Path("/movies/The.Matrix.1999.1080p.mkv"),
|
||||
"The.Matrix.1999.1080p.mkv",
|
||||
2000000000,
|
||||
datetime.now(timezone.utc),
|
||||
"movie",
|
||||
resolution="1920x1080",
|
||||
codec="h264"
|
||||
),
|
||||
VideoFile(
|
||||
Path("/movies/The.Matrix.1999.720p.mkv"),
|
||||
"The.Matrix.1999.720p.mkv",
|
||||
1000000000,
|
||||
datetime.now(timezone.utc),
|
||||
"movie",
|
||||
resolution="1280x720",
|
||||
codec="h264"
|
||||
),
|
||||
VideoFile(
|
||||
Path("/movies/Inception.2010.mkv"),
|
||||
"Inception.2010.mkv",
|
||||
1500000000,
|
||||
datetime.now(timezone.utc),
|
||||
"movie"
|
||||
),
|
||||
_video("The.Matrix.1999.1080p.mkv", 2_000_000_000,
|
||||
modified_timestamp=now, resolution="1920x1080", codec="h264"),
|
||||
_video("The.Matrix.1999.720p.mkv", 1_000_000_000,
|
||||
modified_timestamp=now, resolution="1280x720", codec="h264"),
|
||||
_video("Inception.2010.mkv", 1_500_000_000,
|
||||
modified_timestamp=now),
|
||||
]
|
||||
|
||||
|
||||
# Detect duplicates
|
||||
duplicates = detect_duplicates(list(zip(identities, files)))
|
||||
|
||||
|
||||
# Generate text report
|
||||
library_root = Path("/mnt/nas/videos")
|
||||
text_report = generate_duplicate_report(duplicates, "text", library_root)
|
||||
|
||||
|
||||
# Verify report contains expected information
|
||||
assert "The Matrix (1999)" in text_report
|
||||
assert "1920x1080" in text_report
|
||||
assert "1280x720" in text_report
|
||||
|
||||
|
||||
# Generate JSON report
|
||||
json_report = generate_duplicate_report(duplicates, "json", library_root)
|
||||
data = json.loads(json_report)
|
||||
|
||||
|
||||
# Verify JSON structure
|
||||
assert data["metadata"]["duplicate_groups"] == 1
|
||||
assert len(data["duplicates"]) == 1
|
||||
assert data["duplicates"][0]["file_count"] == 2
|
||||
|
||||
|
||||
def test_summary_workflow(self):
|
||||
"""Test summary report generation with mixed file types."""
|
||||
# Create test files
|
||||
now = datetime.now(timezone.utc)
|
||||
files = [
|
||||
VideoFile(Path("/movies/Movie1.mkv"), "Movie1.mkv", 2000000000, datetime.now(timezone.utc), "movie"),
|
||||
VideoFile(Path("/movies/Movie2.mkv"), "Movie2.mkv", 1500000000, datetime.now(timezone.utc), "movie"),
|
||||
VideoFile(Path("/series/Show.S01E01.mkv"), "Show.S01E01.mkv", 1000000000, datetime.now(timezone.utc), "series"),
|
||||
VideoFile(Path("/series/Show.S01E02.mkv"), "Show.S01E02.mkv", 1000000000, datetime.now(timezone.utc), "series"),
|
||||
VideoFile(Path("/anime/Anime1.mkv"), "Anime1.mkv", 800000000, datetime.now(timezone.utc), "anime"),
|
||||
_video("Movie1.mkv", 2_000_000_000, "movie",
|
||||
modified_timestamp=now),
|
||||
_video("Movie2.mkv", 1_500_000_000, "movie",
|
||||
modified_timestamp=now),
|
||||
_video("Show.S01E01.mkv", 1_000_000_000, "series",
|
||||
modified_timestamp=now),
|
||||
_video("Show.S01E02.mkv", 1_000_000_000, "series",
|
||||
modified_timestamp=now),
|
||||
_video("Anime1.mkv", 800_000_000, "anime",
|
||||
modified_timestamp=now),
|
||||
]
|
||||
|
||||
|
||||
# Generate summary report
|
||||
library_root = Path("/mnt/nas/videos")
|
||||
report = generate_summary_report(files, library_root)
|
||||
|
||||
|
||||
# Verify report contains expected information
|
||||
assert "Total Files: 5" in report
|
||||
assert "Movie:" in report
|
||||
assert "Series:" in report
|
||||
assert "Anime:" in report
|
||||
assert "Category Breakdown:" in report
|
||||
|
||||
|
||||
def test_all_reports_include_metadata(self):
|
||||
"""Test that all reports include generation timestamp and library root."""
|
||||
library_root = Path("/mnt/nas/videos")
|
||||
|
||||
|
||||
# Test completeness report
|
||||
completeness_report = generate_completeness_report([], "text", library_root)
|
||||
assert "Generated:" in completeness_report
|
||||
assert str(library_root) in completeness_report
|
||||
|
||||
|
||||
# Test duplicate report
|
||||
duplicate_report = generate_duplicate_report([], "text", library_root)
|
||||
assert "Generated:" in duplicate_report
|
||||
assert str(library_root) in duplicate_report
|
||||
|
||||
|
||||
# Test summary report
|
||||
summary_report = generate_summary_report([], library_root)
|
||||
assert "Generated:" in summary_report
|
||||
|
||||
Reference in New Issue
Block a user