feat: review transition core (proposed -> approved)
Per sub-project-2 plan Task 11 / spec §8.1. proposed_records filter and approve() return an approved copy via dataclasses.replace (no in-place mutation), with optional coordinate/rationale override and an injected reviewed_at timestamp. Pure, no I/O, no clock. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
from hef.catalog import Record, validate
|
||||
from hef.selection import Coordinate
|
||||
from tools.review import approve, proposed_records
|
||||
|
||||
|
||||
def make_record(**o):
|
||||
base = dict(
|
||||
id="a",
|
||||
title="t",
|
||||
source_url="u",
|
||||
source_archive="nasa",
|
||||
license="public_domain",
|
||||
mode="video",
|
||||
left=0,
|
||||
right=0,
|
||||
dark=0,
|
||||
light=0,
|
||||
duration_s=1,
|
||||
file_path="p",
|
||||
)
|
||||
base.update(o)
|
||||
return Record(**base)
|
||||
|
||||
|
||||
def test_proposed_records_filters():
|
||||
a = make_record(id="a", review_status="proposed")
|
||||
b = make_record(id="b", review_status="approved")
|
||||
assert [r.id for r in proposed_records([a, b])] == ["a"]
|
||||
|
||||
|
||||
def test_approve_sets_status_and_timestamp():
|
||||
r = make_record(review_status="proposed")
|
||||
out = approve(r, reviewed_at="2026-06-04T13:00:00+00:00")
|
||||
assert out.review_status == "approved"
|
||||
assert out.reviewed_at == "2026-06-04T13:00:00+00:00"
|
||||
|
||||
|
||||
def test_approve_can_override_coordinates():
|
||||
r = make_record(left=0, right=0, dark=0, light=0, review_status="proposed")
|
||||
out = approve(r, reviewed_at="t", coordinate=Coordinate(4, 1, 2, 3))
|
||||
assert (out.left, out.right, out.dark, out.light) == (4, 1, 2, 3)
|
||||
|
||||
|
||||
def test_approve_does_not_mutate_input():
|
||||
r = make_record(review_status="proposed")
|
||||
approve(r, reviewed_at="t")
|
||||
assert r.review_status == "proposed" and r.reviewed_at is None
|
||||
|
||||
|
||||
def test_approve_can_set_rationale():
|
||||
r = make_record(review_status="proposed", rationale="auto")
|
||||
out = approve(r, reviewed_at="t", rationale="human note")
|
||||
assert out.rationale == "human note"
|
||||
|
||||
|
||||
def test_approved_record_revalidates():
|
||||
r = make_record(review_status="proposed")
|
||||
out = approve(r, reviewed_at="t", coordinate=Coordinate(2, 2, 1, 1))
|
||||
validate(out) # must not raise
|
||||
@@ -0,0 +1,34 @@
|
||||
"""Review state-transition core (pure, tested): proposed -> approved.
|
||||
|
||||
No I/O, no clock — the timestamp is injected so the logic stays deterministic.
|
||||
The CLI persists the result via hef.catalog.save_catalog after re-validating.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import replace
|
||||
|
||||
|
||||
def proposed_records(records):
|
||||
"""Records still awaiting review."""
|
||||
return [r for r in records if r.review_status == "proposed"]
|
||||
|
||||
|
||||
def approve(record, *, reviewed_at, coordinate=None, rationale=None):
|
||||
"""Return an approved copy of `record` (the input is not mutated).
|
||||
|
||||
review_status -> 'approved' and reviewed_at is stamped. Optionally override
|
||||
the four coordinates with a human correction and/or replace the rationale.
|
||||
The caller re-validates before saving.
|
||||
"""
|
||||
changes = {"review_status": "approved", "reviewed_at": reviewed_at}
|
||||
if coordinate is not None:
|
||||
changes.update(
|
||||
left=coordinate.left,
|
||||
right=coordinate.right,
|
||||
dark=coordinate.dark,
|
||||
light=coordinate.light,
|
||||
)
|
||||
if rationale is not None:
|
||||
changes["rationale"] = rationale
|
||||
return replace(record, **changes)
|
||||
Reference in New Issue
Block a user