From 75a96a6a18506d59883003eec225567ab87fabdf Mon Sep 17 00:00:00 2001 From: umsangdon Date: Fri, 18 Sep 2026 13:21:13 +0900 Subject: [PATCH] =?UTF-8?q?fix(tester):=20=ED=94=84=EB=A3=AC=EC=9D=B4=20?= =?UTF-8?q?=E3=80=8C=ED=95=A0=20=EC=9D=BC=20=EC=95=84=EB=8B=98=E3=80=8D=20?= =?UTF-8?q?=EC=B9=B8=EC=9D=84=20=EB=90=98=EB=8F=8C=EB=A0=A4=20=EB=86=93?= =?UTF-8?q?=EB=8D=98=20=EA=B5=AC=EB=A9=8D=20=EB=A7=89=EC=9D=8C=20=C2=B7=20?= =?UTF-8?q?=EA=B8=B0=EA=B3=84=20md=20=EC=9D=BD=EA=B8=B0=20=EC=85=8B=20?= =?UTF-8?q?=EB=84=93=ED=9E=98(=EB=AA=BB=20=EB=B3=B4=EB=8A=94=20=EC=9E=90?= =?UTF-8?q?=EB=A6=AC=2038=20=E2=86=92=2025)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **① 프룬 구멍** — 읽기(`load_known()`)만 「할 일 아님」 칸을 읽게 고쳤고 **쓰기는 안 고쳤음**. `--prune` 이 합쳐 읽은 것을 그대로 되쓰면 2026-09-18 에 가른 칸이 다시 갈래로 섞여 **없는 할 일 36 이 되살아남**. 세 파일 다 「제자리에서 지우기」로 고침. **② 기계 읽기 넓힘** · `| 9020- | 비항SD | | … |` 꼴 **앞자리만 알리는 머리줄** — 값이 없으니 못 보는 자리가 아님(8줄 뺌) · 빈 칸·`동력` 낱말·전력(㎾) 주연료를 읽게 함 — 전력 기종 아홉은 원문에 기름 값이 없어 양쪽이 비는 것이 맞음(12줄 뺌) · 7360-0090 차선 제거기는 원문 휘발유 5.53 을 이제 읽음 · ⭐ **머리 없는 이어 표**를 세는 자리를 만듦 — 책장 넘김에 밀린 줄을 `|---|` 없이 따로 적어 표로 안 잡히던 자리. **9020 그래브 준설선 일곱 줄의 손료계수가 양쪽 다 조용히 빠져 있었음** **③ 드러난 어긋남을 장부에 사유·주인과 함께 올림** · 7993-0020 양수기 — md 가 규격을 주연료 칸에 밀어 적음(코덱스: PDF 대조로 휘발유 1.3ℓ 제자리에) · 9020 일곱 줄 — 코덱스: 앞 표에 이어 붙일 것(`제8장_건설기계.md` 3617줄 자리) · 겸용 연료 둘(3450-0642 · 7992-0001) — 랩탑 메인: `_build_mach.py` 가 갈라 읽게 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01QsGw1Dz9HmhuAxGisxmDPF --- .../fixtures/base_prices_source_known.json | 230 +++++++----------- .../tester/test_base_data_source_match.py | 20 +- .../tester/test_base_prices_source_match.py | 45 +++- .../test_work_item_master_source_match.py | 20 +- 4 files changed, 158 insertions(+), 157 deletions(-) diff --git a/resources/tester/fixtures/base_prices_source_known.json b/resources/tester/fixtures/base_prices_source_known.json index 1681dcc0..672cc025 100644 --- a/resources/tester/fixtures/base_prices_source_known.json +++ b/resources/tester/fixtures/base_prices_source_known.json @@ -19,12 +19,6 @@ "owner": "랩탑 메인", "owner_ask": "기계 원천 벌(mach_base_2026.json) 재추출 — 그 추출기가 이 저장소에 없어 우리가 못 돌림" }, - "셈 연료 코드 수": { - "source": 272, - "data": 274, - "why": "원문이 `7360-0055 0090` 꼴로 앞자리를 이어 적은 줄 하나(추출기는 읽고 이 시험 읽기는 못 따라감) + 원문이 규격을 연료 칸에 밀어 적은 양수기 7993-0020(사람이 맞춘 값을 이어받음) — 둘 다 값은 원문 그대로", - "class": "㉯ 남이 고쳐야 풀림" - }, "손료 8201-0100 machine_name": { "source": "3D GNSS 머신 가이던스(굴착기용)", "data": "배관파이프시 간 당(10-7)분류번호규 격7995-0050ø50-2.6m5,0008-3-9 [80]스마트 건설장비(8201) 3D GNSS 머신 가이던스(굴착기용)", @@ -32,6 +26,37 @@ "class": "㉯ 남이 고쳐야 풀림", "owner": "랩탑 메인", "owner_ask": "기계 원천 벌(mach_base_2026.json) 재추출 — 그 추출기가 이 저장소에 없어 우리가 못 돌림" + }, + "연료 7993-0020": { + "source": null, + "data": { + "machine_code": "7993-0020", + "machine_name": "양수기", + "fuel_type": "gasoline", + "fuel_rate_l_per_hour": 1.3, + "parse_method": "verified_source_row_alignment", + "specification": "1.49㎾" + }, + "why": "원문 md 8-4 양수기 줄이 `| 7993-0020 | 양수기 | 1.49㎾ | 1.49㎾ | - | - |` — **규격을 주연료 칸에** 밀어 적어 원문에 기름 값이 없다. 자료의 휘발유 1.3ℓ 는 사람이 PDF 를 보고 맞춘 값을 옛 벌에서 이어받은 것이다. ⇒ 값을 지울 자리가 아니라 **md 전사를 고칠 자리**다(고치면 저절로 맞음).", + "class": "㉯ 남이 고쳐야 풀림", + "owner": "코덱스", + "owner_ask": "제8장 8-4 운전경비 양수기 7993-0020 줄 — 주연료 칸에 규격(1.49㎾)이 밀려 들어갔음. PDF 대조로 주연료(휘발유 1.3ℓ)를 제자리에 적어 줄 것. 옆 규격 칸도 함께 볼 것." + }, + "셈 연료 코드 수": { + "source": 273, + "data": 274, + "why": "원문 md 8-4 양수기 줄이 `| 7993-0020 | 양수기 | 1.49㎾ | 1.49㎾ | - | - |` — **규격을 주연료 칸에** 밀어 적어 원문에 기름 값이 없다. 자료의 휘발유 1.3ℓ 는 사람이 PDF 를 보고 맞춘 값을 옛 벌에서 이어받은 것이다. ⇒ 값을 지울 자리가 아니라 **md 전사를 고칠 자리**다(고치면 저절로 맞음).", + "class": "㉯ 남이 고쳐야 풀림", + "owner": "코덱스", + "owner_ask": "제8장 8-4 운전경비 양수기 7993-0020 줄 — 주연료 칸에 규격(1.49㎾)이 밀려 들어갔음. PDF 대조로 주연료(휘발유 1.3ℓ)를 제자리에 적어 줄 것. 옆 규격 칸도 함께 볼 것." + }, + "셈 연료 원문에 없는 자료 줄": { + "source": 0, + "data": 1, + "why": "원문 md 8-4 양수기 줄이 `| 7993-0020 | 양수기 | 1.49㎾ | 1.49㎾ | - | - |` — **규격을 주연료 칸에** 밀어 적어 원문에 기름 값이 없다. 자료의 휘발유 1.3ℓ 는 사람이 PDF 를 보고 맞춘 값을 옛 벌에서 이어받은 것이다. ⇒ 값을 지울 자리가 아니라 **md 전사를 고칠 자리**다(고치면 저절로 맞음).", + "class": "㉯ 남이 고쳐야 풀림", + "owner": "코덱스", + "owner_ask": "제8장 8-4 운전경비 양수기 7993-0020 줄 — 주연료 칸에 규격(1.49㎾)이 밀려 들어갔음. PDF 대조로 주연료(휘발유 1.3ℓ)를 제자리에 적어 줄 것. 옆 규격 칸도 함께 볼 것." } }, "material": {}, @@ -114,46 +139,6 @@ "owner": "랩탑 메인", "owner_ask": "기계 원천 벌(mach_base_2026.json) 재추출 — 그 추출기가 이 저장소에 없어 우리가 못 돌림" }, - "손료 줄 9020-": { - "what": "9020-", - "why": "⬜ **기계 자료 재추출 대기** — 코덱스가 8-4 운전경비 92종을 md 에 전사했으나(2026-09-18) `mach_base_2026.json` 은 그 앞 판이다. 그 벌을 만드는 추출기가 이 저장소에 없어 우리가 못 돌린다. ⇒ 고칠 자리는 마스터가 아니라 **기계 원천 벌 재추출**이다.", - "class": "㉯ 남이 고쳐야 풀림" - }, - "손료 줄 9030-": { - "what": "9030-", - "why": "⬜ **기계 자료 재추출 대기** — 코덱스가 8-4 운전경비 92종을 md 에 전사했으나(2026-09-18) `mach_base_2026.json` 은 그 앞 판이다. 그 벌을 만드는 추출기가 이 저장소에 없어 우리가 못 돌린다. ⇒ 고칠 자리는 마스터가 아니라 **기계 원천 벌 재추출**이다.", - "class": "㉯ 남이 고쳐야 풀림" - }, - "손료 줄 9040-": { - "what": "9040-", - "why": "⬜ **기계 자료 재추출 대기** — 코덱스가 8-4 운전경비 92종을 md 에 전사했으나(2026-09-18) `mach_base_2026.json` 은 그 앞 판이다. 그 벌을 만드는 추출기가 이 저장소에 없어 우리가 못 돌린다. ⇒ 고칠 자리는 마스터가 아니라 **기계 원천 벌 재추출**이다.", - "class": "㉯ 남이 고쳐야 풀림" - }, - "손료 줄 9050-": { - "what": "9050-", - "why": "⬜ **기계 자료 재추출 대기** — 코덱스가 8-4 운전경비 92종을 md 에 전사했으나(2026-09-18) `mach_base_2026.json` 은 그 앞 판이다. 그 벌을 만드는 추출기가 이 저장소에 없어 우리가 못 돌린다. ⇒ 고칠 자리는 마스터가 아니라 **기계 원천 벌 재추출**이다.", - "class": "㉯ 남이 고쳐야 풀림" - }, - "손료 줄 9060-": { - "what": "9060-", - "why": "⬜ **기계 자료 재추출 대기** — 코덱스가 8-4 운전경비 92종을 md 에 전사했으나(2026-09-18) `mach_base_2026.json` 은 그 앞 판이다. 그 벌을 만드는 추출기가 이 저장소에 없어 우리가 못 돌린다. ⇒ 고칠 자리는 마스터가 아니라 **기계 원천 벌 재추출**이다.", - "class": "㉯ 남이 고쳐야 풀림" - }, - "손료 줄 9070-": { - "what": "9070-", - "why": "⬜ **기계 자료 재추출 대기** — 코덱스가 8-4 운전경비 92종을 md 에 전사했으나(2026-09-18) `mach_base_2026.json` 은 그 앞 판이다. 그 벌을 만드는 추출기가 이 저장소에 없어 우리가 못 돌린다. ⇒ 고칠 자리는 마스터가 아니라 **기계 원천 벌 재추출**이다.", - "class": "㉯ 남이 고쳐야 풀림" - }, - "손료 줄 9080-": { - "what": "9080-", - "why": "⬜ **기계 자료 재추출 대기** — 코덱스가 8-4 운전경비 92종을 md 에 전사했으나(2026-09-18) `mach_base_2026.json` 은 그 앞 판이다. 그 벌을 만드는 추출기가 이 저장소에 없어 우리가 못 돌린다. ⇒ 고칠 자리는 마스터가 아니라 **기계 원천 벌 재추출**이다.", - "class": "㉯ 남이 고쳐야 풀림" - }, - "손료 줄 9090-": { - "what": "9090-", - "why": "⬜ **기계 자료 재추출 대기** — 코덱스가 8-4 운전경비 92종을 md 에 전사했으나(2026-09-18) `mach_base_2026.json` 은 그 앞 판이다. 그 벌을 만드는 추출기가 이 저장소에 없어 우리가 못 돌린다. ⇒ 고칠 자리는 마스터가 아니라 **기계 원천 벌 재추출**이다.", - "class": "㉯ 남이 고쳐야 풀림" - }, "손료보정 수 자리 가름": { "what": "뭉개진 글의 이어 붙은 수(2510)를 통째로 맞댐", "why": "8-1-7 가산비율 표가 글로 뭉개져 `2510` 처럼 붙음 — 25·10 인지 2·510 인지 못 가름", @@ -245,113 +230,25 @@ "owner": "랩탑 메인", "owner_ask": "기계 원천 벌(mach_base_2026.json) 재추출 — 그 추출기가 이 저장소에 없어 우리가 못 돌림" }, - "연료 못 읽은 줄 7360-0090": { - "what": "원문 표 토막 수가 안 맞음", - "why": "원문이 `7360-0055 0090` 꼴로 앞자리를 이어 적음 — 이 시험 읽기가 못 따라가는 자리(추출기는 읽음)", - "class": "㉯ 남이 고쳐야 풀림" - }, - "연료 못 읽은 줄 7993-0020": { - "what": "원문 표 토막 수가 안 맞음", - "why": "8-4 양수기 줄이 `| 7993-0020 | 양수기 | 1.49㎾ | 1.49㎾ | - | - |` — 원문이 규격을 연료 칸에 밀어 적음 · 사람이 맞춘 1.3ℓ(휘발유)를 옛 벌에서 이어받음", - "class": "㉯ 남이 고쳐야 풀림", - "owner": "랩탑 메인", - "owner_ask": "기계 원천 벌(mach_base_2026.json) 재추출 — 그 추출기가 이 저장소에 없어 우리가 못 돌림" - }, "연료 표 3450-0642": { "codes": [ "3450-0642", "3450-0642" ], - "why": "원문 8-4 가 코드·값을 한 칸에 뭉쳐 적어 토막 수가 안 맞음 — 시험 읽기가 건너뜀(랩탑 서브 전사 뒤 새로 생긴 자리)", - "class": "㉯ 남이 고쳐야 풀림" - }, - "연료 표 5801-0045": { - "codes": [ - "5801-0045", - "5801-0045" - ], - "why": "⬜ **기계 자료 재추출 대기** — 코덱스가 8-4 운전경비 92종을 md 에 전사했으나(2026-09-18) `mach_base_2026.json` 은 그 앞 판이다. 그 벌을 만드는 추출기가 이 저장소에 없어 우리가 못 돌린다. ⇒ 고칠 자리는 마스터가 아니라 **기계 원천 벌 재추출**이다.", - "class": "㉯ 남이 고쳐야 풀림" - }, - "연료 표 5805-0002": { - "codes": [ - "5805-0002", - "5805-0002" - ], - "why": "⬜ **기계 자료 재추출 대기** — 코덱스가 8-4 운전경비 92종을 md 에 전사했으나(2026-09-18) `mach_base_2026.json` 은 그 앞 판이다. 그 벌을 만드는 추출기가 이 저장소에 없어 우리가 못 돌린다. ⇒ 고칠 자리는 마스터가 아니라 **기계 원천 벌 재추출**이다.", - "class": "㉯ 남이 고쳐야 풀림" - }, - "연료 표 5805-0003": { - "codes": [ - "5805-0003", - "5805-0003" - ], - "why": "⬜ **기계 자료 재추출 대기** — 코덱스가 8-4 운전경비 92종을 md 에 전사했으나(2026-09-18) `mach_base_2026.json` 은 그 앞 판이다. 그 벌을 만드는 추출기가 이 저장소에 없어 우리가 못 돌린다. ⇒ 고칠 자리는 마스터가 아니라 **기계 원천 벌 재추출**이다.", - "class": "㉯ 남이 고쳐야 풀림" - }, - "연료 표 7202-0008": { - "codes": [ - "7202-0008", - "7202-0008" - ], - "why": "원문 8-4 가 코드·값을 한 칸에 뭉쳐 적어 토막 수가 안 맞음 — 시험 읽기가 건너뜀(랩탑 서브 전사 뒤 새로 생긴 자리)", - "class": "㉯ 남이 고쳐야 풀림" - }, - "연료 표 7202-0010": { - "codes": [ - "7202-0010", - "7202-0010" - ], - "why": "원문 8-4 가 코드·값을 한 칸에 뭉쳐 적어 토막 수가 안 맞음 — 시험 읽기가 건너뜀(랩탑 서브 전사 뒤 새로 생긴 자리)", - "class": "㉯ 남이 고쳐야 풀림" - }, - "연료 표 7360-0090": { - "codes": [ - "7360-0090", - "7360-0090" - ], - "why": "원문이 `7360-0055 0090` 꼴로 앞자리를 이어 적음 — 이 시험 읽기가 못 따라가는 자리(추출기는 읽음)", - "class": "㉯ 남이 고쳐야 풀림" - }, - "연료 표 7991-0050": { - "codes": [ - "7991-0050", - "7991-0050" - ], - "why": "원문 8-4 가 코드·값을 한 칸에 뭉쳐 적어 토막 수가 안 맞음 — 시험 읽기가 건너뜀(랩탑 서브 전사 뒤 새로 생긴 자리)", - "class": "㉯ 남이 고쳐야 풀림" - }, - "연료 표 7991-0100": { - "codes": [ - "7991-0100", - "7991-0100" - ], - "why": "원문 8-4 가 코드·값을 한 칸에 뭉쳐 적어 토막 수가 안 맞음 — 시험 읽기가 건너뜀(랩탑 서브 전사 뒤 새로 생긴 자리)", - "class": "㉯ 남이 고쳐야 풀림" - }, - "연료 표 7991-0500": { - "codes": [ - "7991-0500", - "7991-0500" - ], - "why": "원문 8-4 가 코드·값을 한 칸에 뭉쳐 적어 토막 수가 안 맞음 — 시험 읽기가 건너뜀(랩탑 서브 전사 뒤 새로 생긴 자리)", - "class": "㉯ 남이 고쳐야 풀림" + "why": "원문 8-4 주연료 칸에 **두 연료가 한 칸에** 적혀 있음 — `73.7+휘발유54.5`(3450-0642 현장가열표층재생기) · `1.87㎾ 휘발유1.3`(7992-0001 모르타르 믹서). 앞은 기본 연료(경유·전력), 뒤는 휘발유다. 기름 값이 **원문에 있는 값**인데 자료(`mach_fuel_rate`)가 이 두 기종을 아주 안 담아 맞댈 것이 없음.", + "class": "㉯ 남이 고쳐야 풀림", + "owner": "랩탑 메인", + "owner_ask": "`_build_mach.py` 가 8-4 주연료 칸의 **겸용 표기**를 갈라 읽게 해 줄 것 — 기름 값(휘발유 54.5 · 1.3)은 원문에 있는 값이라 자료에 들어가야 함. 전력분(1.87㎾)을 함께 담을지는 브레인 판정 자리." }, "연료 표 7992-0001": { "codes": [ "7992-0001", "7992-0001" ], - "why": "원문 8-4 가 코드·값을 한 칸에 뭉쳐 적어 토막 수가 안 맞음 — 시험 읽기가 건너뜀(랩탑 서브 전사 뒤 새로 생긴 자리)", - "class": "㉯ 남이 고쳐야 풀림" - }, - "연료 표 7993-0020": { - "codes": [ - "7993-0020", - "7993-0020" - ], - "why": "원문 8-4 가 코드·값을 한 칸에 뭉쳐 적어 토막 수가 안 맞음 — 시험 읽기가 건너뜀(랩탑 서브 전사 뒤 새로 생긴 자리)", - "class": "㉯ 남이 고쳐야 풀림" + "why": "원문 8-4 주연료 칸에 **두 연료가 한 칸에** 적혀 있음 — `73.7+휘발유54.5`(3450-0642 현장가열표층재생기) · `1.87㎾ 휘발유1.3`(7992-0001 모르타르 믹서). 앞은 기본 연료(경유·전력), 뒤는 휘발유다. 기름 값이 **원문에 있는 값**인데 자료(`mach_fuel_rate`)가 이 두 기종을 아주 안 담아 맞댈 것이 없음.", + "class": "㉯ 남이 고쳐야 풀림", + "owner": "랩탑 메인", + "owner_ask": "`_build_mach.py` 가 8-4 주연료 칸의 **겸용 표기**를 갈라 읽게 해 줄 것 — 기름 값(휘발유 54.5 · 1.3)은 원문에 있는 값이라 자료에 들어가야 함. 전력분(1.87㎾)을 함께 담을지는 브레인 판정 자리." }, "운전사 기종별 판단": { "what": 121, @@ -359,6 +256,55 @@ "class": "㉯ 남이 고쳐야 풀림", "owner": "랩탑 메인", "owner_ask": "기계 원천 벌(mach_base_2026.json) 재추출 — 그 추출기가 이 저장소에 없어 우리가 못 돌림" + }, + "손료 머리 없는 이어 표 9020-0022": { + "what": "| 9020-0022 | 비항SD, 3.00㎥ | 164 | 20,000 | 1,780", + "why": "원문 md 가 **머리 없는 이어 표**로 적은 자리 — 책장 넘김에 밀린 줄을 빈 줄 뒤에 따로 적어 `|---|` 머리가 없다. 그래서 표로 잡히지 않아 **추출기(`_build_mach.py`)도 이 읽기도 못 봄** — 값은 원문에 있는데 `mach_loss_coef` 에 그 아홉 줄(9020 그래브 준설선 일곱 등)이 빠져 있음. 가격 쪽에는 같은 코드가 있어 **손료계수만 비는 어긋남**임.", + "class": "㉯ 남이 고쳐야 풀림", + "owner": "코덱스", + "owner_ask": "제8장 8-3 (9020) 그래브 준설선 — `9020-0022`~`0200` 일곱 줄이 앞 표와 떨어진 **머리 없는 표**로 적혀 있음(제8장_건설기계.md 3617줄 자리). 앞 표에 이어 붙여 줄 것. 붙이면 추출기·시험이 저절로 읽음." + }, + "손료 머리 없는 이어 표 9020-0035": { + "what": "| 9020-0035 | 비항SD, 5.00㎥ | 261 | 20,000 | 1,780", + "why": "원문 md 가 **머리 없는 이어 표**로 적은 자리 — 책장 넘김에 밀린 줄을 빈 줄 뒤에 따로 적어 `|---|` 머리가 없다. 그래서 표로 잡히지 않아 **추출기(`_build_mach.py`)도 이 읽기도 못 봄** — 값은 원문에 있는데 `mach_loss_coef` 에 그 아홉 줄(9020 그래브 준설선 일곱 등)이 빠져 있음. 가격 쪽에는 같은 코드가 있어 **손료계수만 비는 어긋남**임.", + "class": "㉯ 남이 고쳐야 풀림", + "owner": "코덱스", + "owner_ask": "제8장 8-3 (9020) 그래브 준설선 — `9020-0022`~`0200` 일곱 줄이 앞 표와 떨어진 **머리 없는 표**로 적혀 있음(제8장_건설기계.md 3617줄 자리). 앞 표에 이어 붙여 줄 것. 붙이면 추출기·시험이 저절로 읽음." + }, + "손료 머리 없는 이어 표 9020-0050": { + "what": "| 9020-0050 | 비항SD, 6.00㎥ | 373 | 20,000 | 1,780", + "why": "원문 md 가 **머리 없는 이어 표**로 적은 자리 — 책장 넘김에 밀린 줄을 빈 줄 뒤에 따로 적어 `|---|` 머리가 없다. 그래서 표로 잡히지 않아 **추출기(`_build_mach.py`)도 이 읽기도 못 봄** — 값은 원문에 있는데 `mach_loss_coef` 에 그 아홉 줄(9020 그래브 준설선 일곱 등)이 빠져 있음. 가격 쪽에는 같은 코드가 있어 **손료계수만 비는 어긋남**임.", + "class": "㉯ 남이 고쳐야 풀림", + "owner": "코덱스", + "owner_ask": "제8장 8-3 (9020) 그래브 준설선 — `9020-0022`~`0200` 일곱 줄이 앞 표와 떨어진 **머리 없는 표**로 적혀 있음(제8장_건설기계.md 3617줄 자리). 앞 표에 이어 붙여 줄 것. 붙이면 추출기·시험이 저절로 읽음." + }, + "손료 머리 없는 이어 표 9020-0072": { + "what": "| 9020-0072 | 비항SD, 7.50㎥ | 537 | 20,000 | 1,780", + "why": "원문 md 가 **머리 없는 이어 표**로 적은 자리 — 책장 넘김에 밀린 줄을 빈 줄 뒤에 따로 적어 `|---|` 머리가 없다. 그래서 표로 잡히지 않아 **추출기(`_build_mach.py`)도 이 읽기도 못 봄** — 값은 원문에 있는데 `mach_loss_coef` 에 그 아홉 줄(9020 그래브 준설선 일곱 등)이 빠져 있음. 가격 쪽에는 같은 코드가 있어 **손료계수만 비는 어긋남**임.", + "class": "㉯ 남이 고쳐야 풀림", + "owner": "코덱스", + "owner_ask": "제8장 8-3 (9020) 그래브 준설선 — `9020-0022`~`0200` 일곱 줄이 앞 표와 떨어진 **머리 없는 표**로 적혀 있음(제8장_건설기계.md 3617줄 자리). 앞 표에 이어 붙여 줄 것. 붙이면 추출기·시험이 저절로 읽음." + }, + "손료 머리 없는 이어 표 9020-0160": { + "what": "| 9020-0160 | 비항SD, 12.50㎥ | 1,194 | 20,000 | 1,", + "why": "원문 md 가 **머리 없는 이어 표**로 적은 자리 — 책장 넘김에 밀린 줄을 빈 줄 뒤에 따로 적어 `|---|` 머리가 없다. 그래서 표로 잡히지 않아 **추출기(`_build_mach.py`)도 이 읽기도 못 봄** — 값은 원문에 있는데 `mach_loss_coef` 에 그 아홉 줄(9020 그래브 준설선 일곱 등)이 빠져 있음. 가격 쪽에는 같은 코드가 있어 **손료계수만 비는 어긋남**임.", + "class": "㉯ 남이 고쳐야 풀림", + "owner": "코덱스", + "owner_ask": "제8장 8-3 (9020) 그래브 준설선 — `9020-0022`~`0200` 일곱 줄이 앞 표와 떨어진 **머리 없는 표**로 적혀 있음(제8장_건설기계.md 3617줄 자리). 앞 표에 이어 붙여 줄 것. 붙이면 추출기·시험이 저절로 읽음." + }, + "손료 머리 없는 이어 표 9020-0180": { + "what": "| 9020-0180 | 비항SD, 16.00㎥ | 1,343 | 20,000 | 1,", + "why": "원문 md 가 **머리 없는 이어 표**로 적은 자리 — 책장 넘김에 밀린 줄을 빈 줄 뒤에 따로 적어 `|---|` 머리가 없다. 그래서 표로 잡히지 않아 **추출기(`_build_mach.py`)도 이 읽기도 못 봄** — 값은 원문에 있는데 `mach_loss_coef` 에 그 아홉 줄(9020 그래브 준설선 일곱 등)이 빠져 있음. 가격 쪽에는 같은 코드가 있어 **손료계수만 비는 어긋남**임.", + "class": "㉯ 남이 고쳐야 풀림", + "owner": "코덱스", + "owner_ask": "제8장 8-3 (9020) 그래브 준설선 — `9020-0022`~`0200` 일곱 줄이 앞 표와 떨어진 **머리 없는 표**로 적혀 있음(제8장_건설기계.md 3617줄 자리). 앞 표에 이어 붙여 줄 것. 붙이면 추출기·시험이 저절로 읽음." + }, + "손료 머리 없는 이어 표 9020-0200": { + "what": "| 9020-0200 | 비항SD, 25.00㎥ | 1,491 | 20,000 | 1,", + "why": "원문 md 가 **머리 없는 이어 표**로 적은 자리 — 책장 넘김에 밀린 줄을 빈 줄 뒤에 따로 적어 `|---|` 머리가 없다. 그래서 표로 잡히지 않아 **추출기(`_build_mach.py`)도 이 읽기도 못 봄** — 값은 원문에 있는데 `mach_loss_coef` 에 그 아홉 줄(9020 그래브 준설선 일곱 등)이 빠져 있음. 가격 쪽에는 같은 코드가 있어 **손료계수만 비는 어긋남**임.", + "class": "㉯ 남이 고쳐야 풀림", + "owner": "코덱스", + "owner_ask": "제8장 8-3 (9020) 그래브 준설선 — `9020-0022`~`0200` 일곱 줄이 앞 표와 떨어진 **머리 없는 표**로 적혀 있음(제8장_건설기계.md 3617줄 자리). 앞 표에 이어 붙여 줄 것. 붙이면 추출기·시험이 저절로 읽음." } }, "material_blind": { diff --git a/resources/tester/test_base_data_source_match.py b/resources/tester/test_base_data_source_match.py index 8d289a91..eeff8f33 100644 --- a/resources/tester/test_base_data_source_match.py +++ b/resources/tester/test_base_data_source_match.py @@ -567,12 +567,22 @@ def test_알려진_어긋남_밖은_없음(src: dict, kind: str) -> None: if __name__ == "__main__" and "--prune" in sys.argv: # 고쳐졌거나 모습이 바뀐 줄만 지움 — 새 어긋남은 안 보탬(보태면 빨강이 조용히 묻힘). + # ⚠ 「할 일 아님」 칸(`_할일_아님`)은 **제자리에서** 지운다. `load_known()` 이 합쳐 읽은 것을 + # 그대로 되쓰면 2026-09-18 에 가른 칸이 다시 갈래로 섞여 「없는 할 일」이 되살아난다. current = current_mismatches(load_sources()) - data = load_known() + data = json.loads(KNOWN.read_text(encoding="utf-8")) + parked = (data.get("_할일_아님") or {}).get("줄") or {} + + def _keep(kind: str, rows: dict) -> dict: + return {k: v for k, v in rows.items() if current[kind].get(k) == _without_why(v)} + for kind in FILES: - before = len(data[kind]) - data[kind] = { - k: v for k, v in data[kind].items() if current[kind].get(k) == _without_why(v) - } + before = len(data.get(kind) or {}) + data[kind] = _keep(kind, data.get(kind) or {}) print(f"{kind}: {before} → {len(data[kind])}") + if kind in parked: + was = len(parked[kind]) + parked[kind] = _keep(kind, parked[kind]) + if len(parked[kind]) != was: + print(f" 할 일 아님 {kind}: {was} → {len(parked[kind])}") KNOWN.write_text(json.dumps(data, ensure_ascii=False, indent=1) + "\n", encoding="utf-8") diff --git a/resources/tester/test_base_prices_source_match.py b/resources/tester/test_base_prices_source_match.py index e3ab6501..3d9c093e 100644 --- a/resources/tester/test_base_prices_source_match.py +++ b/resources/tester/test_base_prices_source_match.py @@ -192,7 +192,8 @@ FUEL_FIELDS = ("fuel_type", "fuel_rate_l_per_hour", "misc_material_percent_of_fu def _fuel(tok: str) -> tuple: """주연료 토막 — `9.0` 경유([주]① ㉰: 표시 없는 것은 경유) · `휘발유0.7` · `중유487.2` · `-` 없음.""" - if tok == "-": + # `-` 없음 · `3.73㎾` 전력으로 구는 기종은 **원문에 기름 값이 없다**(자료도 안 담음). + if tok == "-" or tok.endswith("㎾"): return None, None m = re.fullmatch(r"(휘발유|중유)?(\d+(?:\.\d+)?)", tok) if m is None: @@ -201,6 +202,17 @@ def _fuel(tok: str) -> tuple: return kind, float(m.group(2)) +def _cells(cell: str, n: int) -> list[str]: + """한 칸을 토막 냄 — **빈 칸은 「없음」** 이고 `동력` 낱말은 뗀다. + + 원문 8-4 는 없음을 「-」 로도 쓰고 빈 칸으로도 두며, 전력 기종은 `동력 15.1㎾` 처럼 + 낱말을 붙인다. 빈 칸을 토막 없음으로 두면 줄을 통째로 못 읽어 **원문에 있는 값까지 + 놓친다**(7360-0090 차선 제거기 — 휘발유 5.53 이 원문에 적혀 있음). + """ + toks = cell.replace("동력", " ").split() + return toks or ["-"] * n + + def read_machine(path: Path) -> dict: """제8장 md — 8-3 손료 · 8-4 운전경비 · 8-5 가격. 여러 코드가 한 칸에 뭉친 줄은 토막 수가 맞을 때만 읽음.""" lines = _lines(path) @@ -244,6 +256,10 @@ def read_machine(path: Path) -> dict: if not r[0]: continue codes, prefix = _codes(r[0], prefix) + # `| 9020- | 비항SD | | … |` 꼴 — **앞자리만 알리는 머리줄**이라 값이 애초에 없다. + # 못 읽은 자리가 아니므로 「못 보는 자리」로 세지 않고 앞자리만 이어받고 지난다. + if codes == [] and not any(re.search(r"\d", c) for c in r[1:]): + continue try: values = {f: [_num(x) for x in r[i].split()] for f, i in cols.items()} if not codes or any(len(v) != len(codes) for v in values.values()): @@ -255,6 +271,13 @@ def read_machine(path: Path) -> dict: (c, {f: v[k] for f, v in values.items()}) for k, c in enumerate(codes) ) for k in range(at["8-3 기계손료"], at["8-4 운전경비"]): + # 머리 없는 **이어 표** — 책장 넘김에 밀린 줄을 빈 줄 뒤에 따로 적어 둔 자리(9020-0022~0200). + # `|---|` 가 없어 표로 잡히지 않아 **추출기도 이 읽기도 못 봄** — 원문에 있는 값이 조용히 빠진다. + cells = [c.strip() for c in lines[k - 1].strip().strip("|").split("|")] + if len(cells) >= 10 and re.fullmatch(r"\d{4}-\d{4}", cells[0] or ""): + # 칸이 열 이상인 것만 — 축약 손료표(5220·7995)는 「계수 칸 없는 표」 로 이미 세고 있다. + if cells[0] not in out["loss"]: + out["skipped"][f"손료 머리 없는 이어 표 {cells[0]}"] = {"what": lines[k - 1][:48]} if not lines[k - 1].lstrip().startswith("|") and "시 간 당(10-7)" in lines[k - 1]: heads = re.findall(r"\((\d{4})\) ?(?:[^|()]{0,30}\([^)]*\)){0,2}[^|()]{0,30}?시 간 당\(10-7\)", lines[k - 1]) # fmt: skip if heads: # 표 위 머리 글줄(`시 간 당(10-7)` 한 줄)은 표로 읽힘 — 뭉개진 글만 @@ -268,7 +291,7 @@ def read_machine(path: Path) -> dict: continue for r in t["rows"]: codes, prefix = _codes(r[0], prefix) - cols = [r[3].split(), r[4].split(), r[5].split()] + cols = [_cells(r[i], len(codes or [])) for i in (3, 4, 5)] try: if not codes or any(len(c) != len(codes) for c in cols): raise ValueError(r[0]) @@ -670,10 +693,22 @@ def test_셈이_맞음(src: dict, kind: str) -> None: if __name__ == "__main__" and "--prune" in sys.argv: # 고쳐졌거나 모습이 바뀐 줄만 지움 — 새 어긋남은 안 보탬(보태면 빨강이 조용히 묻힘). + # ⚠ 「할 일 아님」 칸(`_할일_아님`)은 **제자리에서** 지운다. `load_known()` 이 합쳐 읽은 것을 + # 그대로 되쓰면 2026-09-18 에 가른 칸이 다시 갈래로 섞여 「없는 할 일」이 되살아난다. current = current_mismatches(load_sources()) - data = load_known() + data = json.loads(KNOWN.read_text(encoding="utf-8")) + parked = (data.get("_할일_아님") or {}).get("줄") or {} + + def _keep(kind: str, rows: dict) -> dict: + return {k: v for k, v in rows.items() if current[kind].get(k) == _without_why(v)} + for kind in KIND_NAMES: - before = len(data[kind]) - data[kind] = {k: v for k, v in data[kind].items() if current[kind].get(k) == _without_why(v)} # fmt: skip + before = len(data.get(kind) or {}) + data[kind] = _keep(kind, data.get(kind) or {}) print(f"{KIND_NAMES[kind]}: {before} → {len(data[kind])}") + if kind in parked: + was = len(parked[kind]) + parked[kind] = _keep(kind, parked[kind]) + if len(parked[kind]) != was: + print(f" 할 일 아님 {KIND_NAMES[kind]}: {was} → {len(parked[kind])}") KNOWN.write_text(json.dumps(data, ensure_ascii=False, indent=1) + "\n", encoding="utf-8") diff --git a/resources/tester/test_work_item_master_source_match.py b/resources/tester/test_work_item_master_source_match.py index d78065ef..6d2654cd 100644 --- a/resources/tester/test_work_item_master_source_match.py +++ b/resources/tester/test_work_item_master_source_match.py @@ -493,12 +493,22 @@ def test_알려진_어긋남_밖은_없음(src: dict, kind: str) -> None: if __name__ == "__main__" and "--prune" in sys.argv: # 고쳐졌거나 모습이 바뀐 줄만 지움 — 새 어긋남은 안 보탬(보태면 빨강이 조용히 묻힘). + # ⚠ 「할 일 아님」 칸(`_할일_아님`)은 **제자리에서** 지운다. `load_known()` 이 합쳐 읽은 것을 + # 그대로 되쓰면 2026-09-18 에 가른 칸이 다시 갈래로 섞여 「없는 할 일」이 되살아난다. current = current_mismatches(load_sources()) - data = load_known() + data = json.loads(KNOWN.read_text(encoding="utf-8")) + parked = (data.get("_할일_아님") or {}).get("줄") or {} + + def _keep(kind: str, rows: dict) -> dict: + return {k: v for k, v in rows.items() if current[kind].get(k) == _without_why(v)} + for kind in KIND_NAMES: - before = len(data[kind]) - data[kind] = { - k: v for k, v in data[kind].items() if current[kind].get(k) == _without_why(v) - } + before = len(data.get(kind) or {}) + data[kind] = _keep(kind, data.get(kind) or {}) print(f"{KIND_NAMES[kind]}: {before} → {len(data[kind])}") + if kind in parked: + was = len(parked[kind]) + parked[kind] = _keep(kind, parked[kind]) + if len(parked[kind]) != was: + print(f" 할 일 아님 {KIND_NAMES[kind]}: {was} → {len(parked[kind])}") KNOWN.write_text(json.dumps(data, ensure_ascii=False, indent=1) + "\n", encoding="utf-8")