fix(842-3): 범위 잡재료율 기계 둘 · 수식 빌더가 저장본 문서를 조용히 빼던 흠

- 원인: 옮김에서 펌프 준설선 EQ000552 · 553 잡재료율 「13∼18」 이 사라짐 → 옛 엔진 멈춤 · 새 계산기 셈(빈 잡품)
  → GC000268 옵션 「다름」 → 저장 밖 → 그 식을 부르는 문서 320 개가 저장본에서 지워짐(1,520 → 1,200)
- 범위 글 = 잡품 율 글 그대로(from_old · TS fromOld · check 허용) · 두 대 다시 옮김 → 까닭 「범위」 옛 칸과 같음
- 빌더 = 저장본 문서가 저장 밖으로 빠지면 멈춤 · 안 씀 · 키와 까닭 알림(lost) · 시험 802_2 에 lost 0
- 시험 822_2 범위 까닭 기대 되돌림 · 842_3 거울에 범위 값 더함
- 확인: 빌더 --check 저장 1,520 · 기본 다름 0 · 옵션 다름 3(채택) · 저장본 · 목록 같음 · 655 대 시간당(옛 자료 대 새 자료) 다름 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-15 19:25:43 +09:00
co-authored by Claude Opus 5.5
parent b30927aace
commit 2c6e49f92c
9 changed files with 63 additions and 11 deletions
@@ -4,7 +4,7 @@
규칙 = `master_machine_crew.from_old_row`(값 · 단위 · 출처 — 사본 대조와 같은 규칙):
인력 = 조종원 > 0 이면 [{키 'LB:<조종원직종>'(직종 없음 = 빈 키 = 식 입력 직종 고르기), 인 = 조종원}] · 0 = []
잡품 = 잡재료율 > 0 이면 밑 재료 줄(주연료 · 전력 가운데 값 > 0)마다 {줄, 율 = 잡재료율} · 0 = []
수 아닌 잡재료율(범위 글 「13∼18」) = 잡품 없음(원문 글은 `값원문` 그대로)
잡재료율 범위 글(「13∼18」) = 잡품 율 글 그대로(식이 못 셈 · 까닭 「범위」 — 옛 칸과 같음)
칸 차례 = 옛 조종원 자리에 인력 · 옛 잡재료율 자리에 잡품 · 선원류 칸(선원 · 준설선선장 · 준설선기관사 · 고급선원) ·
맨 위 칸(비활성 표지 등) = 그대로 · 다시 돌려도 같음(새 꼴 줄 = 그대로).
실행 = `./venv/Scripts/python.exe resources/master_data/scripts/build_기계_인력줄_842_3.py [--dry]`
@@ -62,7 +62,7 @@ def main(dry: bool = False) -> Counter:
seen[f"인력 {sum(r['인'] for r in vb[mcrew.CREW])}인"] += 1
seen["직종 빈 줄(직종 고르기)"] += any(not r["키"] for r in vb[mcrew.CREW])
seen["잡품 줄 있음"] += bool(vb.get(mcrew.MISC))
seen["잡품 범위 글(잡품 없음)"] += isinstance(va.get("잡재료율"), str)
seen["잡품 범위 글(율 글 그대로)"] += isinstance(va.get("잡재료율"), str)
seen["선원류 칸 그대로"] += any(k in vb for k in ("선원", "준설선선장", "준설선기관사", "고급선원"))
seen["비활성 표지 그대로"] += bool(b.get("비활성"))
seen["옛 칸 남음"] += any(k in vb for k in mcrew.OLD)
@@ -18,6 +18,7 @@
`resources/master_data/수식문서/수식_<옛 로직 파일 꼬리>.json`(로직 파일마다
· 옛 로직 JSON 은 안 씀).
- 목록 = `docs/raw/요구_1104/_목록_802-2_변환대조.xlsx`(로직 · 옵션 · 옛 · 새 · 차이 · 까닭 + 요약).
- 이미 저장된 문서가 이번에 저장 밖(다름 · 부르는 문서 빠짐)이면 멈춤 — 안 씀 · 그 문서와 까닭을 알림(`lost`).
"""
from __future__ import annotations
@@ -206,6 +207,24 @@ def _keep(docs: dict, runs: list[dict]) -> set[str]:
keep -= drop
def saved_keys() -> set[str]:
"""지금 저장본 수식 문서 키."""
return {
str(d.get("키"))
for p in ml.FOLDER.glob("수식_*.json")
for d in ml.load_file(p).get("줄") or []
}
def lost(result: dict) -> list[str]:
"""저장본에 있는데 이번 빌드가 저장 밖으로 뺀 문서 — 「키 까닭」(빌더가 조용히 지우지 않게)."""
why = {}
for one in result["대조"]:
if one["로직"] not in result["저장"]:
why.setdefault(one["로직"], one.get("까닭") or one.get("빠짐") or "")
return [f"{k} {why.get(k) or '저장 밖'}" for k in sorted(saved_keys() - result["저장"])]
def files_of(result: dict) -> dict[str, str]:
"""저장 파일 이름 → 글."""
out = {}
@@ -310,6 +329,11 @@ def main() -> int:
print(line)
print("같음" if not bad else f"다름 {len(bad)}")
return 1 if bad else 0
if gone := lost(result):
for line in gone:
print(f"저장본에서 빠질 문서 {line}")
print(f"멈춤 — 저장본 문서 {len(gone)} 개가 빠짐(안 씀) · 까닭을 고친 뒤 다시")
return 1
ml.FOLDER.mkdir(parents=True, exist_ok=True)
want = files_of(result)
for path in ml.FOLDER.glob("수식_*.json"):
@@ -53,20 +53,28 @@ def view(vals):
def from_old(vals: dict, base: list[str] | None = None) -> dict:
"""옛 꼴 값 → 새 꼴(옛 칸 셋 → 인력 · 잡품) — 옛 칸 없음 = 그대로.
인력 = 조종원 > 0 이면 한 줄(직종 없음 = 빈 키) · 0 = 빈 목록 · 없음 = 칸 없음.
잡품 = 잡재료율 > 0 이면 밑 재료 줄(`base` · 없으면 주연료 · 전력 가운데 값 있는 줄)마다 그 율 · 0 = 빈 목록."""
잡품 = 잡재료율 > 0 이면 밑 재료 줄(`base` · 없으면 주연료 · 전력 가운데 값 있는 줄)마다 그 율 · 0 = 빈 목록 ·
원문 범위 글(「13∼18」) = 율 글 그대로(식이 못 셈 · 옛 칸과 같은 까닭)."""
if not isinstance(vals, dict) or is_new(vals):
return vals
out = {k: v for k, v in vals.items() if k not in OLD}
n, job, rate = vals.get("조종원"), vals.get("조종원직종"), vals.get("잡재료율")
if _isnum(n):
out[CREW] = [{"키": f"LB:{job}" if job else "", "인": n}] if n > 0 else []
if base is None:
base = [b for b in BASE if vals.get(b) not in (None, "", 0)] # 「73.7+」 글도 줄
if _isnum(rate):
if base is None:
base = [b for b in BASE if vals.get(b) not in (None, "", 0)] # 「73.7+」 글도 줄
out[MISC] = [{"줄": b, "율": rate} for b in base] if rate > 0 and base else []
elif _range(rate) and base: # 원문 범위 「13∼18」 = 율 글 그대로(식이 못 셈 · 까닭 「범위」)
out[MISC] = [{"줄": b, "율": rate} for b in base]
return out
def _range(v) -> bool:
"""원문 범위 글(「13∼18」) — M01 자료에만 · 셈은 못 함."""
return isinstance(v, str) and "∼" in v
def _isnum(v) -> bool:
return isinstance(v, Number) and not isinstance(v, bool)
@@ -123,7 +131,9 @@ def check(vals) -> list[str]:
out.append(f"{col} — 목록(30 줄까지)")
continue
for n, r in enumerate(rows, 1):
bad = not isinstance(r, dict) or not _isnum(r.get(need)) or r[need] < 0
bad = not isinstance(r, dict) or not (
_isnum(r.get(need)) and r[need] >= 0 or col == MISC and _range(r.get(need))
)
if not bad and r.get("수량") is not None:
bad = not _isnum(r["수량"]) or r["수량"] < 0
if not bad and col == CREW: