"""자료 출처표(`resources/data_master_sources/sources_2026-09-15.json`) 시험. 이 표가 있는 까닭 — 마스터 파일 안에는 **제 판만** 있고 「최신이 무엇인지·어디서 받는지」가 없어서 건설 노임이 반 년 뒤처진 것을 아무도 몰랐다(2026-09-15). - `our_edition` 이 **실제 파일과 어긋나면 빨강** — 손으로 적힌 판이 굳는 것을 막는다 - 뒤처짐 판정이 뒤집히면 빨강 - 받는 자리를 모르는 줄에 **사유가 없으면 빨강**(주소를 지어내지 않기로 한 자리) """ from __future__ import annotations import json from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] SOURCES_PATH = ROOT / "resources" / "data_master_sources" / "sources_2026-09-15.json" @pytest.fixture(scope="module") def sources() -> dict: return json.loads(SOURCES_PATH.read_text(encoding="utf-8")) def _edition_of(path: Path) -> tuple[str, str]: doc = json.loads(path.read_text(encoding="utf-8")) if doc.get("effective_date"): return doc["effective_date"], "effective_date" derived = doc.get("derived_from") if isinstance(derived, dict) and derived.get("effective_date"): return derived["effective_date"], "derived_from.effective_date" return "", "" def test_출처표가_읽힌다(sources): assert sources["dataset_id"] == "data_master_sources" assert sources["sources"], "출처가 하나도 없다" def test_가리키는_파일이_다_있다(sources): missing = [ path for row in sources["sources"] for path in row["files"] if not (ROOT / path).is_file() ] assert not missing, f"없는 파일을 가리킨다: {missing}" def test_우리_판이_실제_파일과_같다(sources): """⭐ 손으로 적힌 판이 굳으면 또 뒤처진다 — 파일에서 읽은 값과 대 본다.""" wrong = [] for row in sources["sources"]: edition, where = _edition_of(ROOT / row["files"][0]) if row["our_edition"] != edition: wrong.append(f"{row['source_id']}: 표 {row['our_edition']} ≠ 파일 {edition}") elif row["our_edition_from"] != where: wrong.append(f"{row['source_id']}: 읽은 자리 {row['our_edition_from']} ≠ {where}") assert not wrong, wrong def test_뒤처짐_판정이_맞다(sources): for row in sources["sources"]: latest, ours = row["latest_published"], row["our_edition"] if not latest or not ours: assert row["status"] == "unknown", row["source_id"] elif latest > ours: assert row["status"] == "behind", row["source_id"] else: assert row["status"] == "current", row["source_id"] def test_건설_노임이_뒤처진_것으로_선다(sources): """2026-09-15 에 드러난 자리 — 우리 2026-01-01, 최신 2026-09-01.""" row = next(r for r in sources["sources"] if r["source_id"] == "labor_const") assert row["status"] == "behind" assert row["our_edition"] == "2026-01-01" assert row["latest_published"] == "2026-09-01" # 주기를 7월 1일로 알던 것이 틀렸다 — 1.1 / 9.1 이다. assert "9월 1일" in row["cycle"] assert row["where_to_get"], "받는 자리를 받았는데 비어 있다" def test_운전경비는_판_모름이_아니다(sources): """파일 안 `derived_from.effective_date` 에 판이 박혀 있다 — 「모름」으로 적으면 안 된다.""" row = next(r for r in sources["sources"] if r["source_id"] == "machine_operating") assert row["our_edition"] == "2026-01-01" assert row["our_edition_from"] == "derived_from.effective_date" assert row["status"] != "unknown" def test_모르는_칸에는_사유가_있다(sources): """주소·최신 공표일을 지어내지 않기로 한 자리 — 빈 칸이면 사유가 있어야 한다.""" bad = [] for row in sources["sources"]: if not row["where_to_get"] and not row.get("where_to_get_missing_reason"): bad.append(f"{row['source_id']} — 받는 자리 사유 없음") if not row["latest_published"] and not row.get("latest_missing_reason"): bad.append(f"{row['source_id']} — 최신 공표일 사유 없음") assert not bad, bad def test_줄마다_갈래와_주기가_있다(sources): known = set(sources["kinds"]) for row in sources["sources"]: assert row["kind"] in known, row["source_id"] assert row["kind_ko"] == sources["kinds"][row["kind"]] assert row["cycle"].strip(), row["source_id"] assert row["publisher"].strip(), row["source_id"] assert row["checked_on"].strip(), row["source_id"] def test_기초단가_다섯_갈래를_다_덮는다(sources): covered = {row["kind"] for row in sources["sources"]} assert covered == {"labor", "machine", "material", "oil", "rate"} def test_세어_둔_수가_실제와_같다(sources): counts = sources["counts"] assert counts["sources"] == len(sources["sources"]) tallied: dict[str, int] = {} for row in sources["sources"]: tallied[row["status"]] = tallied.get(row["status"], 0) + 1 assert counts["by_status"] == tallied def test_상태_낱말이_다_풀려_있다(sources): known = set(sources["status"]) assert {row["status"] for row in sources["sources"]} <= known