Skip to content

Commit b52dfa2

Browse files
Merge pull request #118 from cbusillo/fix-size-followup-wrapper-and-approval
Fix size followup wrapper and approval gate
2 parents 7582c46 + 72a4d6a commit b52dfa2

4 files changed

Lines changed: 112 additions & 0 deletions

File tree

mediaforce/web/app.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,13 +720,15 @@ def _proposal_alignment_issue(
720720
current_policy: dict[str, Any],
721721
preview_policy: dict[str, Any],
722722
allow_measured_size_quality_tradeoff: bool = False,
723+
allow_measured_size_quality_increase: bool = False,
723724
) -> str | None:
724725
return proposal_alignment_issue(
725726
operator_request=operator_request,
726727
request_disposition=request_disposition,
727728
current_policy=current_policy,
728729
preview_policy=preview_policy,
729730
allow_measured_size_quality_tradeoff=allow_measured_size_quality_tradeoff,
731+
allow_measured_size_quality_increase=allow_measured_size_quality_increase,
730732
)
731733

732734
def _folder_ai_tune_deps() -> FolderAiTuneDeps:

mediaforce/web/runtime/folder_actions.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,17 @@ def _high_impact_policy_change(current_policy: ActionPayload, draft_policy: Acti
9494
return False
9595

9696

97+
def _draft_raises_quality_target(current_policy: ActionPayload, draft_policy: ActionPayload) -> bool:
98+
current_video = object_dict(current_policy.get("video"))
99+
draft_video = object_dict(draft_policy.get("video"))
100+
for key in ("target_vmaf", "target_xpsnr"):
101+
current_value = _normalized_number(current_video.get(key))
102+
draft_value = _normalized_number(draft_video.get(key))
103+
if current_value is not None and draft_value is not None and draft_value > current_value + 0.01:
104+
return True
105+
return False
106+
107+
97108
class LoadFolderStagedItemsFn(Protocol):
98109
def __call__(
99110
self,
@@ -810,6 +821,12 @@ def save_profile_action(
810821
operator_request=operator_request or None,
811822
calibration_payload=calibration_payload,
812823
)
824+
if (
825+
allow_measured_size_quality_increase
826+
and str(size_target_analysis.get("status") or "").strip() == "under_target"
827+
and _draft_raises_quality_target(baseline_policy, object_dict(calibration_payload.get("policy")))
828+
):
829+
size_issue = None
813830
if size_issue is not None and not confirm_size_tradeoff:
814831
raise HTTPException(status_code=409, detail=size_issue)
815832
if str(calibration_payload.get("mode") or "sample") == "sample":

tests/test_encode_queue_recovery.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4912,6 +4912,53 @@ def test_save_profile_action_allows_explicit_size_tradeoff_approval(self) -> Non
49124912
self.assertEqual(saved_payloads[0]["accepted_at"], "2026-05-24T04:40:00+00:00")
49134913
self.assertTrue(merged_advice[0]["operator_approved_size_tradeoff"])
49144914

4915+
def test_save_profile_action_allows_under_target_quality_increase_without_tradeoff_confirmation(self) -> None:
4916+
saved_payloads: list[folder_actions_runtime.ActionPayload] = []
4917+
merged_advice: list[folder_actions_runtime.ActionPayload] = []
4918+
calibration_payload: folder_actions_runtime.ActionPayload = {
4919+
"mode": "sample",
4920+
"job_id": "sample-1",
4921+
"action": "ai_tune",
4922+
"review_media_ready": True,
4923+
"policy": {"video": {"target_vmaf": 95.0, "max_encoded_percent": 80}},
4924+
"sample_item": {
4925+
"library_item_id": 1,
4926+
"resolved_policy": {"video": {"target_vmaf": 92.0, "max_encoded_percent": 80}},
4927+
},
4928+
"sample_result": {"predicted_total_size_bytes": 250 * 1024 * 1024},
4929+
}
4930+
4931+
result = folder_actions_runtime.save_profile_action(
4932+
self.config,
4933+
"tv/show",
4934+
now_iso=lambda: "2026-05-24T04:40:00+00:00",
4935+
load_sample_item=lambda *_args, **_kwargs: None,
4936+
load_calibration_state=lambda *_args, **_kwargs: dict(calibration_payload),
4937+
calibration_draft_hash=web_app._calibration_draft_hash,
4938+
save_calibration_state=lambda _config, _prefix, payload: saved_payloads.append(dict(payload)),
4939+
load_advice_state=lambda *_args, **_kwargs: {
4940+
"request_disposition": "honored",
4941+
"operator_request": {
4942+
"operator_confirmed": True,
4943+
"request_type": "size_budget",
4944+
"budget_bytes": 300 * 1024 * 1024,
4945+
"budget_label": "300 MB per episode",
4946+
"applied_policy": None,
4947+
},
4948+
},
4949+
record_visual_approval_artifact=lambda *_args, **_kwargs: {"artifact_id": "approval-1"},
4950+
merge_advice_state=lambda _config, _prefix, payload: merged_advice.append(dict(payload)),
4951+
upsert_override=lambda *_args, **_kwargs: None,
4952+
auto_queue_folder_encode=lambda *_args, **_kwargs: {"ok": True, "message": "Queued folder encode."},
4953+
confirm_high_impact=True,
4954+
reviewed_draft_hash=web_app._calibration_draft_hash(calibration_payload),
4955+
)
4956+
4957+
self.assertTrue(result["ok"])
4958+
self.assertTrue(result["queued"])
4959+
self.assertEqual(saved_payloads[0]["accepted_at"], "2026-05-24T04:40:00+00:00")
4960+
self.assertFalse(merged_advice[0]["operator_approved_size_tradeoff"])
4961+
49154962
def test_run_sampled_calibration_keeps_review_directory_for_approval(self) -> None:
49164963
source_path = self._create_source_file("episode-review.mkv")
49174964
preview_dir = self.config.paths.review_dir / "run-123" / "item-00"

tests/test_tuning_runtime.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,6 +1354,52 @@ def test_completed_route_returns_payload(self) -> None:
13541354
self.assertEqual(response.status_code, 200)
13551355
self.assertEqual(json.loads(response.body), expected_payload)
13561356

1357+
def test_app_proposal_alignment_wrapper_accepts_quality_increase_flag(self) -> None:
1358+
from mediaforce.web import app as web_app
1359+
1360+
captured: list[dict[str, object]] = []
1361+
1362+
def fake_proposal_alignment_issue(**kwargs: object) -> None:
1363+
captured.append(dict(kwargs))
1364+
return None
1365+
1366+
def fake_preview_action(_config: MediaforceConfig, deps: FolderAiTuneDeps, *_args: object) -> dict[str, object]:
1367+
deps.proposal_alignment_issue(
1368+
operator_request={"request_type": "size_budget"},
1369+
request_disposition="honored",
1370+
current_policy={"video": {"target_vmaf": 92.0}},
1371+
preview_policy={"video": {"target_vmaf": 95.0}},
1372+
allow_measured_size_quality_increase=True,
1373+
)
1374+
return {"ok": True}
1375+
1376+
with patch("mediaforce.web.app.load_config", return_value=self.config), patch(
1377+
"mediaforce.web.app.purge_transient_artifacts"
1378+
), patch("mediaforce.web.app._start_calibration_queue_worker"), patch(
1379+
"mediaforce.web.app._start_encode_queue_worker"
1380+
), patch("mediaforce.web.app._refresh_host_status_cache", return_value=[]), patch(
1381+
"mediaforce.web.app.proposal_alignment_issue",
1382+
side_effect=fake_proposal_alignment_issue,
1383+
), patch(
1384+
"mediaforce.web.app.folder_ai_tune_preview_action",
1385+
side_effect=fake_preview_action,
1386+
):
1387+
app = web_app.create_app(self.root / "config.toml")
1388+
preview_route = next(
1389+
route
1390+
for route in app.routes
1391+
if isinstance(route, APIRoute) and route.path == "/api/folders/{prefix:path}/ai-tune/preview"
1392+
)
1393+
1394+
class _FakeRequest:
1395+
async def json(self) -> dict[str, str]:
1396+
return {"note": "raise quality", "host_key": "host-1"}
1397+
1398+
response = asyncio.run(preview_route.endpoint("tv/show", _FakeRequest()))
1399+
1400+
self.assertEqual(response.status_code, 200)
1401+
self.assertTrue(captured[0]["allow_measured_size_quality_increase"])
1402+
13571403
def test_completed_cleanup_route_passes_selected_prefixes(self) -> None:
13581404
from mediaforce.web import app as web_app
13591405

0 commit comments

Comments
 (0)