From 2c6e49f92cbcc03af66cd1db29d7d4d7a19251c1 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 15 Nov 2026 19:25:43 +0900 Subject: [PATCH] =?UTF-8?q?fix(842-3):=20=EB=B2=94=EC=9C=84=20=EC=9E=A1?= =?UTF-8?q?=EC=9E=AC=EB=A3=8C=EC=9C=A8=20=EA=B8=B0=EA=B3=84=20=EB=91=98=20?= =?UTF-8?q?=C2=B7=20=EC=88=98=EC=8B=9D=20=EB=B9=8C=EB=8D=94=EA=B0=80=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=EB=B3=B8=20=EB=AC=B8=EC=84=9C=EB=A5=BC=20?= =?UTF-8?q?=EC=A1=B0=EC=9A=A9=ED=9E=88=20=EB=B9=BC=EB=8D=98=20=ED=9D=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 원인: 옮김에서 펌프 준설선 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 --- common_util/common_util_machine_crew.ts | 8 +++---- .../scripts/build_기계_인력줄_842_3.py | 4 ++-- .../scripts/build_수식문서_802_2.py | 24 +++++++++++++++++++ .../scripts/master_machine_crew.py | 18 ++++++++++---- resources/master_data/기계/EQ000552.json | 8 +++++++ resources/master_data/기계/EQ000553.json | 8 +++++++ resources/tester/test_802_2_convert.py | 1 + .../tester/test_822_2_machine_hourly_all.py | 2 +- resources/tester/test_842_3_machine_crew.py | 1 + 9 files changed, 63 insertions(+), 11 deletions(-) diff --git a/common_util/common_util_machine_crew.ts b/common_util/common_util_machine_crew.ts index d37986917..75a36fcdd 100644 --- a/common_util/common_util_machine_crew.ts +++ b/common_util/common_util_machine_crew.ts @@ -62,10 +62,10 @@ export function fromOld(vals: Vals, base?: string[]): Vals { const job = vals.조종원직종; const rate = vals.잡재료율; if (isNum(n)) out[CREW] = n > 0 ? [{ 키: job ? `LB:${String(job)}` : "", 인: n }] : []; - if (isNum(rate)) { - const b = base ?? BASE.filter((k) => ![undefined, null, "", 0].includes(vals[k] as never)); - out[MISC] = rate > 0 && b.length ? b.map((줄) => ({ 줄, 율: rate })) : []; - } + const b = base ?? BASE.filter((k) => ![undefined, null, "", 0].includes(vals[k] as never)); + if (isNum(rate)) out[MISC] = rate > 0 && b.length ? b.map((줄) => ({ 줄, 율: rate })) : []; + else if (typeof rate === "string" && rate.includes("∼") && b.length) + out[MISC] = b.map((줄) => ({ 줄, 율: rate as unknown as number })); // 원문 범위 글 그대로 return out; } diff --git a/resources/master_data/scripts/build_기계_인력줄_842_3.py b/resources/master_data/scripts/build_기계_인력줄_842_3.py index b0b787241..231ba4323 100644 --- a/resources/master_data/scripts/build_기계_인력줄_842_3.py +++ b/resources/master_data/scripts/build_기계_인력줄_842_3.py @@ -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) diff --git a/resources/master_data/scripts/build_수식문서_802_2.py b/resources/master_data/scripts/build_수식문서_802_2.py index 41fbdeb63..42bf0dc92 100644 --- a/resources/master_data/scripts/build_수식문서_802_2.py +++ b/resources/master_data/scripts/build_수식문서_802_2.py @@ -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"): diff --git a/resources/master_data/scripts/master_machine_crew.py b/resources/master_data/scripts/master_machine_crew.py index cba0b87b3..de6c642dd 100644 --- a/resources/master_data/scripts/master_machine_crew.py +++ b/resources/master_data/scripts/master_machine_crew.py @@ -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: diff --git a/resources/master_data/기계/EQ000552.json b/resources/master_data/기계/EQ000552.json index 85c971941..68deae662 100644 --- a/resources/master_data/기계/EQ000552.json +++ b/resources/master_data/기계/EQ000552.json @@ -20,6 +20,7 @@ "손료계수": "10^-7/시간", "주연료": "ℓ/hr", "주연료종류": "", + "잡품": "%", "인력": "인/일", "준설선선장": "인", "준설선기관사": "인", @@ -38,6 +39,12 @@ "손료계수": 749, "주연료": 2291.9, "주연료종류": "경유", + "잡품": [ + { + "줄": "주연료", + "율": "13∼18" + } + ], "인력": [ { "키": "LB:1054", @@ -61,6 +68,7 @@ "손료계수": "건설품셈 공통 8-3-10", "주연료": "건설품셈 공통 8-4-9", "주연료종류": "건설품셈 공통 8-4 주①㉰", + "잡품": "건설품셈 공통 8-4-9", "인력": "건설품셈 공통 8-4-9 · 건설품셈 공통 8-1-3 5 · 건설노임 1054", "준설선선장": "건설품셈 공통 8-4-9", "준설선기관사": "건설품셈 공통 8-4-9", diff --git a/resources/master_data/기계/EQ000553.json b/resources/master_data/기계/EQ000553.json index ac195bda7..272bb1616 100644 --- a/resources/master_data/기계/EQ000553.json +++ b/resources/master_data/기계/EQ000553.json @@ -20,6 +20,7 @@ "손료계수": "10^-7/시간", "주연료": "ℓ/hr", "주연료종류": "", + "잡품": "%", "인력": "인/일", "준설선선장": "인", "준설선기관사": "인", @@ -38,6 +39,12 @@ "손료계수": 749, "주연료": 3819.9, "주연료종류": "경유", + "잡품": [ + { + "줄": "주연료", + "율": "13∼18" + } + ], "인력": [ { "키": "LB:1054", @@ -61,6 +68,7 @@ "손료계수": "건설품셈 공통 8-3-10", "주연료": "건설품셈 공통 8-4-9", "주연료종류": "건설품셈 공통 8-4 주①㉰", + "잡품": "건설품셈 공통 8-4-9", "인력": "건설품셈 공통 8-4-9 · 건설품셈 공통 8-1-3 5 · 건설노임 1054", "준설선선장": "건설품셈 공통 8-4-9", "준설선기관사": "건설품셈 공통 8-4-9", diff --git a/resources/tester/test_802_2_convert.py b/resources/tester/test_802_2_convert.py index 843ed3db7..8fca6a881 100644 --- a/resources/tester/test_802_2_convert.py +++ b/resources/tester/test_802_2_convert.py @@ -31,6 +31,7 @@ def result(): def test_빌더_check_저장본_목록_같음(result): + assert build.lost(result) == [] # 저장본 문서를 조용히 빼지 않음(842-3 뒤 · 빌더가 멈추고 알림) assert build.check(result) == [] diff --git a/resources/tester/test_822_2_machine_hourly_all.py b/resources/tester/test_822_2_machine_hourly_all.py index fbae94fdc..2c0a2421b 100644 --- a/resources/tester/test_822_2_machine_hourly_all.py +++ b/resources/tester/test_822_2_machine_hourly_all.py @@ -72,7 +72,7 @@ def test_every_machine_has_formula_or_reason(): assert mh.hourly("EQ000343")["reason"] == "가격 없음" # TBM 3.0m assert "일 단위" in mh.hourly("EQ000621")["reason"] # 산림품셈 예취기(1일 1대당) assert mh.hourly("EQ000202")["reason"] == "주연료 「73.7+」 — 수 아님" # 표층재생기 · 안 풂 - assert "잡재료율" in mh.hourly("EQ000552")["reason"] # 펌프 준설선 잡재료 13∼18(842-3 = 값원문 글만) + assert "범위" in mh.hourly("EQ000552")["reason"] # 펌프 준설선 잡재료 13∼18(842-3 = 잡품 율 글) assert ( "중유" in mh.hourly("EQ000187")["reason"] ) # 아스팔트 믹싱플랜트 40t — 유가 표에 중유 없음 diff --git a/resources/tester/test_842_3_machine_crew.py b/resources/tester/test_842_3_machine_crew.py index 8f7a5c665..7984c37ea 100644 --- a/resources/tester/test_842_3_machine_crew.py +++ b/resources/tester/test_842_3_machine_crew.py @@ -145,6 +145,7 @@ VALUES = [ {"가격": 1, "인력": [{"키": "LB:1048", "인": 1}, {"키": "LB:1002", "인": 2}], "잡품": []}, {"가격": 1, "인력": [{"키": "", "인": 1}], "잡품": [{"키": "MA:1", "수량": 2, "율": 5}]}, {"가격": 1}, + {"가격": 1, "주연료": 2, "잡재료율": "13∼18", "조종원": None}, ] TARGETS = [ ("GC000268", {"인력": [{"키": "LB:1048", "인": 1}], "잡품": [{"줄": "주연료", "율": 10}]}, None),