Support validated legacy portfolio composition
This commit is contained in:
@@ -28,15 +28,18 @@ def combine_specs(paths: list[Path]) -> dict[str, Any]:
|
|||||||
sources = []
|
sources = []
|
||||||
for path in paths:
|
for path in paths:
|
||||||
payload = json.loads(path.read_text(encoding="utf-8-sig"))
|
payload = json.loads(path.read_text(encoding="utf-8-sig"))
|
||||||
if payload.get("status") != "complete":
|
status = payload.get("status")
|
||||||
|
legacy_unstated = status is None and payload.get("schema_version") == 1 and bool(payload.get("samples"))
|
||||||
|
if status != "complete" and not legacy_unstated:
|
||||||
raise ValueError(f"Portfolio spec is not complete: {path}")
|
raise ValueError(f"Portfolio spec is not complete: {path}")
|
||||||
current_side = float(payload["side_m"])
|
current_side = float(payload["side_m"]) if payload.get("side_m") is not None else None
|
||||||
current_resolution = float(payload["resolution_m"])
|
current_resolution = float(payload["resolution_m"]) if payload.get("resolution_m") is not None else None
|
||||||
if side_m is not None and current_side != side_m:
|
if side_m is not None and current_side is not None and current_side != side_m:
|
||||||
raise ValueError(f"Portfolio side_m differs: {path}")
|
raise ValueError(f"Portfolio side_m differs: {path}")
|
||||||
if resolution_m is not None and current_resolution != resolution_m:
|
if resolution_m is not None and current_resolution is not None and current_resolution != resolution_m:
|
||||||
raise ValueError(f"Portfolio resolution_m differs: {path}")
|
raise ValueError(f"Portfolio resolution_m differs: {path}")
|
||||||
side_m, resolution_m = current_side, current_resolution
|
side_m = current_side if current_side is not None else side_m
|
||||||
|
resolution_m = current_resolution if current_resolution is not None else resolution_m
|
||||||
for sample in payload.get("samples") or []:
|
for sample in payload.get("samples") or []:
|
||||||
slug = str(sample.get("sample_slug") or sample.get("slug") or "")
|
slug = str(sample.get("sample_slug") or sample.get("slug") or "")
|
||||||
if not slug:
|
if not slug:
|
||||||
@@ -48,7 +51,14 @@ def combine_specs(paths: list[Path]) -> dict[str, Any]:
|
|||||||
normalized["sample_slug"] = slug
|
normalized["sample_slug"] = slug
|
||||||
normalized.pop("slug", None)
|
normalized.pop("slug", None)
|
||||||
samples.append(normalized)
|
samples.append(normalized)
|
||||||
sources.append({"path": str(path), "sha256": sha256(path), "sample_count": len(payload.get("samples") or [])})
|
sources.append({
|
||||||
|
"path": str(path),
|
||||||
|
"sha256": sha256(path),
|
||||||
|
"sample_count": len(payload.get("samples") or []),
|
||||||
|
"source_status": status or "legacy_unstated",
|
||||||
|
})
|
||||||
|
if side_m is None or resolution_m is None:
|
||||||
|
raise ValueError("Combined portfolio dimensions are unknown")
|
||||||
return {
|
return {
|
||||||
"schema_version": 2,
|
"schema_version": 2,
|
||||||
"status": "complete",
|
"status": "complete",
|
||||||
|
|||||||
@@ -36,3 +36,18 @@ def test_combine_specs_rejects_mismatched_resolution(tmp_path: Path) -> None:
|
|||||||
def test_combine_specs_rejects_incomplete_source(tmp_path: Path) -> None:
|
def test_combine_specs_rejects_incomplete_source(tmp_path: Path) -> None:
|
||||||
with pytest.raises(ValueError, match="not complete"):
|
with pytest.raises(ValueError, match="not complete"):
|
||||||
combine_specs([write_spec(tmp_path / "one.json", "one", status="in_progress")])
|
combine_specs([write_spec(tmp_path / "one.json", "one", status="in_progress")])
|
||||||
|
|
||||||
|
|
||||||
|
def test_combine_specs_accepts_populated_legacy_v1_when_dimensions_come_from_complete_spec(tmp_path: Path) -> None:
|
||||||
|
legacy = tmp_path / "legacy.json"
|
||||||
|
legacy.write_text(json.dumps({"schema_version": 1, "samples": [{"sample_slug": "legacy"}]}), encoding="utf-8")
|
||||||
|
payload = combine_specs([legacy, write_spec(tmp_path / "current.json", "current")])
|
||||||
|
assert payload["sample_count"] == 2
|
||||||
|
assert payload["source_specs"][0]["source_status"] == "legacy_unstated"
|
||||||
|
|
||||||
|
|
||||||
|
def test_combine_specs_rejects_empty_legacy_v1(tmp_path: Path) -> None:
|
||||||
|
legacy = tmp_path / "legacy.json"
|
||||||
|
legacy.write_text(json.dumps({"schema_version": 1, "samples": []}), encoding="utf-8")
|
||||||
|
with pytest.raises(ValueError, match="not complete"):
|
||||||
|
combine_specs([legacy, write_spec(tmp_path / "current.json", "current")])
|
||||||
|
|||||||
Reference in New Issue
Block a user