feat: catalog Record model and validation

This commit is contained in:
Ben Stull
2026-06-04 01:33:50 -07:00
parent 1379751dda
commit 8b3d17a6aa
2 changed files with 142 additions and 0 deletions
+71
View File
@@ -1 +1,72 @@
"""Catalog data model, validation, and JSONL IO."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Optional
MODES = frozenset({"audio", "video", "av"})
LICENSES = frozenset({"public_domain", "cc0", "cc_by", "cc_by_nc"})
REVIEW_STATUSES = frozenset({"proposed", "approved"})
COORD_FIELDS = ("left", "right", "dark", "light")
COORD_MIN = 0
COORD_MAX = 4
ATTRIBUTION_LICENSES = frozenset({"cc_by", "cc_by_nc"})
class CatalogError(ValueError):
"""Raised when a catalog record is structurally invalid."""
@dataclass
class Record:
id: str
title: str
source_url: str
source_archive: str
license: str
mode: str
left: int
right: int
dark: int
light: int
duration_s: int
file_path: str
review_status: str = "proposed"
attribution: str = ""
resolution: str = ""
dominant_color: str = ""
rationale: str = ""
reviewed_at: Optional[str] = None
notes: str = ""
def validate(record: Record) -> None:
"""Raise CatalogError if the record is structurally invalid."""
if not record.id:
raise CatalogError("record id must be non-empty")
if record.mode not in MODES:
raise CatalogError(
f"invalid mode {record.mode!r}; expected one of {sorted(MODES)}"
)
if record.license not in LICENSES:
raise CatalogError(
f"invalid license {record.license!r}; expected one of {sorted(LICENSES)}"
)
if record.review_status not in REVIEW_STATUSES:
raise CatalogError(
f"invalid review_status {record.review_status!r}; "
f"expected one of {sorted(REVIEW_STATUSES)}"
)
for axis in COORD_FIELDS:
value = getattr(record, axis)
if isinstance(value, bool) or not isinstance(value, int):
raise CatalogError(f"coordinate {axis} must be an int, got {value!r}")
if not (COORD_MIN <= value <= COORD_MAX):
raise CatalogError(
f"coordinate {axis}={value} out of range {COORD_MIN}..{COORD_MAX}"
)
if record.duration_s < 0:
raise CatalogError("duration_s must be non-negative")
if record.license in ATTRIBUTION_LICENSES and not record.attribution:
raise CatalogError(f"license {record.license} requires attribution")