From e714a803015cf0454615082ad1b5a810996b8dd5 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 8 Sep 2026 06:11:22 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat(B09):=20=EC=9E=90=EC=9E=AC=EA=B0=80=20?= =?UTF-8?q?=EC=97=86=EC=9D=84=20=EB=95=8C=20=EB=B9=88=20=ED=91=9C=20?= =?UTF-8?q?=EC=85=8B=20=EB=8C=80=EC=8B=A0=20=ED=95=9C=20=EC=A4=84=20?= =?UTF-8?q?=EC=95=88=EB=82=B4=20+=20=EA=B2=80=EC=A6=9D=20=ED=95=A8?= =?UTF-8?q?=EC=A0=95=20=EA=B8=B0=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 화면 전수(다섯 탭)에서 드러난 것 - 자재가 아예 없는 프로젝트에서 **빈 표 셋이 「(0) — 0」 으로 늘어서** 있었음. 「이 프로젝트에는 자재 줄이 없습니다」 한 줄로 바꿈 - 계획서 「검증 함정 모음」에 셋 적음 · 세션은 DB 라 재시작으로 안 지워지되 **튕기면 브라우저 쿠키를 의심할 것** · 화면이 막혀도 **엔진은 파이썬에서 직접 부를 수 있음** — 단 `await init_db_pool()` 을 먼저 부를 것(안 부르면 **빈 응답이 「자료 없음」으로 잘못 읽힘**), 한 이벤트 루프 안에서 끝낼 것 · 되받기 전후로 **인계 응답을 파일로 떠 둘 것** — 「합계가 221원 줄었다」를 짚으려면 어느 줄이 얼마나 바뀌었는지 대조할 자료가 있어야 함 화면 전수 결과 (프로젝트 b269ea34, 포트 5174) ⑤ 원가계산서 26줄 · [재계산] 동작 ④ 예산내역서 16줄 · 금액줄 1 (측구터파기 92.45 × 5,288.6 = 488,926) 비고에 「단산 1 참조」 · 안내 5 · 미확보 16 (두 갈래로 갈려 뜸) 일위대가 203줄 · 안내 3 ③ 단가산출서 1장 · 본표와 참조 코드 자재대 — 이 프로젝트는 자재 0건이라 한 줄 안내(다른 프로젝트에선 미정 4건 확인) ⚠ 바꾼 화면 상태는 원래 탭으로 되돌려 놓음 검증: pytest 211 통과, tsc 0건 Co-Authored-By: Claude Opus 5 (1M context) --- B09_Estimation/B09_Estimation_UI_Page.ts | 10 ++++++++++ ui_template/ui_template_locale_b2.ts | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/B09_Estimation/B09_Estimation_UI_Page.ts b/B09_Estimation/B09_Estimation_UI_Page.ts index 7afc0166..ff6538f6 100644 --- a/B09_Estimation/B09_Estimation_UI_Page.ts +++ b/B09_Estimation/B09_Estimation_UI_Page.ts @@ -1019,6 +1019,16 @@ export async function renderB09Estimation(root: HTMLElement): Promise { return; } + // 자재가 아예 없으면 **빈 표 셋을 늘어놓지 않는다** — 「없다」 한 줄이면 된다 + // (2026-09-08 화면 전수에서 세 무리가 모두 「(0) — 0」 으로 뜨고 있었다). + if (sheet.contractor.length === 0 && sheet.owner.length === 0 && sheet.unknown.length === 0) { + const none = document.createElement("div"); + none.className = "b09-empty"; + none.textContent = L("B09_Estimation_Mat_None"); + body.append(none); + return; + } + for (const [labelKey, rows, total] of [ ["B09_Estimation_Mat_Contractor", sheet.contractor, sheet.contractor_total_krw], ["B09_Estimation_Mat_Owner", sheet.owner, sheet.owner_total_krw], diff --git a/ui_template/ui_template_locale_b2.ts b/ui_template/ui_template_locale_b2.ts index b25bbbb4..738e388e 100644 --- a/ui_template/ui_template_locale_b2.ts +++ b/ui_template/ui_template_locale_b2.ts @@ -696,6 +696,10 @@ export const ui_locales_b2 = { "관급·사급이 안 갈린 것 — 어느 합계에도 안 넣습니다", "Supply type undecided — excluded from both totals", ], + B09_Estimation_Mat_None: [ + "이 프로젝트에는 자재 줄이 없습니다 — 구조물이 없거나 자재가 안 나온 상태입니다.", + "This project has no material rows yet.", + ], B09_Estimation_Mat_Empty: [ "설계내역서를 먼저 불러오면 자재대가 섭니다.", "Load the bill first and the material sheet appears.", From cdbc93271703ba156ef6fccb339d2c46e80a0f5d Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 8 Sep 2026 06:17:29 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat(B09):=20=EC=83=88=20=EC=9D=B8=EA=B3=84?= =?UTF-8?q?=20=EC=B9=B8=20=EB=91=98=20=EC=88=98=EC=9A=A9=20+=20=EC=98=88?= =?UTF-8?q?=EC=8B=9C=20=EC=84=9C=EC=8B=9D=20=EC=9A=B0=ED=9A=8C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0(=EC=9B=90=EC=B2=9C=EC=9D=B4=20=EC=9D=B4=EA=B9=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 계약 시험이 **제 일을 했음** — 「인계가 보내는데 안 읽는 칸: spec_class, spec_class_basis」로 깨짐. 둘 다 읽고, 갈래 판정 근거(`spec_class_basis`)는 **B08 문구 그대로** 줄 비고에 이음 - 예시 서식 거르기를 뺌 — B08 이 **마스터 원천에서** 거름(8건). 두 곳에서 같은 것을 거르면 나중에 한쪽만 고쳐짐 - 짝 시험은 남기되 모양을 바꿈 — 인공 표 대신 **실물 마스터에서 그 8공종이 자원으로 서지 않는지**. 원천이 바뀌면 이 시험이 알려 줌 검증: pytest 211 통과. 못 맞춘 자원 497(원천 필터 뒤 값과 일치) Co-Authored-By: Claude Opus 5 (1M context) --- B09_Estimation/B09_Estimation_BillOfQuantities.py | 6 ++++++ .../B09_Estimation_BillOfQuantities_Rows.py | 3 +++ B09_Estimation/B09_Estimation_ResourceAxis.py | 11 +++-------- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/B09_Estimation/B09_Estimation_BillOfQuantities.py b/B09_Estimation/B09_Estimation_BillOfQuantities.py index 4a5f7329..b75c5b13 100644 --- a/B09_Estimation/B09_Estimation_BillOfQuantities.py +++ b/B09_Estimation/B09_Estimation_BillOfQuantities.py @@ -96,6 +96,10 @@ class HandoffWorkItem: #: 갈래 축·원본값 — 「stone_cm」·「60~80」. **키 문자열은 우리가 만든다**(두 창 합의). variant_axis: str = "" variant_value: str = "" + #: 규격 갈래를 B08 이 판정해 보낸 것(「철근구조물」)과 그 근거 문구. + #: **근거는 산출근거 칸에 그대로 적는다** — 우리가 다시 지어내지 않는다. + spec_class: str = "" + spec_class_basis: str = "" @property def display_name(self) -> str: @@ -243,6 +247,8 @@ def parse_handoff(payload: dict[str, Any]) -> tuple[list[HandoffWorkItem], list[ blocked_kind=row.get("blocked_kind") or "", variant_axis=row.get("variant_axis") or "", variant_value=str(row.get("variant_value") or ""), + spec_class=row.get("spec_class") or "", + spec_class_basis=row.get("spec_class_basis") or "", ) for row in payload["work_items"] ] diff --git a/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py b/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py index 46a19f4c..787dd508 100644 --- a/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py +++ b/B09_Estimation/B09_Estimation_BillOfQuantities_Rows.py @@ -140,6 +140,9 @@ def _leaf_row( quantity=item.quantity, in_bill=item.in_bill, ) + if item.spec_class_basis: + # 갈래 판정 근거는 **B08 문구를 그대로** 쓴다(두 벌로 짜지 않는다). + row.note = " / ".join(part for part in (row.note, item.spec_class_basis) if part) if item.application_ratio_pct is not None: # ⚠ 곱하지 않는다 — B08 이 이미 곱한 값이다. 산출근거로만 적는다. row.note = f"반영률 {item.application_ratio_pct}% 적용 후 수량" diff --git a/B09_Estimation/B09_Estimation_ResourceAxis.py b/B09_Estimation/B09_Estimation_ResourceAxis.py index 958ce3c2..bc790a7f 100644 --- a/B09_Estimation/B09_Estimation_ResourceAxis.py +++ b/B09_Estimation/B09_Estimation_ResourceAxis.py @@ -357,14 +357,9 @@ def match_table( result: AxisResult, ) -> None: """표 하나에 자원 축을 붙인다. 값이 안 서면 `unmatched` 로 보낸다.""" - # ⚠ **예시 서식 표는 자원 표가 아니다.** 품셈이 「ha당 …단가산출서(예시)」처럼 - # **빈 서식**을 실어 두는데(4-2 · 3-2 등), 그것을 읽으려 들면 구간 라벨·항목명이 - # 자원 이름으로 오해돼 못 맞춘 목록이 못 쓰게 된다(2026-09-08 실측 39줄). - # 값이 있는데 안 서는 자리가 아니라 **애초에 값이 없는 표**다. - headers = " ".join(str(c) for c in (table.get("condition_note") or [])) - if "단가산출서" in headers or "예시" in headers: - result.skipped_forms["example_form"] = result.skipped_forms.get("example_form", 0) + 1 - return + # 예시 서식(「ha당 …단가산출서(예시)」)은 **B08 이 마스터 원천에서 거른다**(8건). + # 두 곳에서 같은 것을 거르면 **나중에 한쪽만 고쳐진다** — 원천이 이긴다. + # ⚠ 짝 시험은 남겨 둔다(예시 서식이 자원으로 안 서는지) — 원천이 바뀌면 그것이 알려 준다. from B09_Estimation.B09_Estimation_CrewOutput import match_crew_table From 2898e81a89f9177dad7d1508ae5623dc6fa9eec9 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 8 Sep 2026 06:19:45 +0900 Subject: [PATCH 3/7] =?UTF-8?q?docs(B09):=209-7=20=EC=97=90=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EC=A0=84=EC=88=98=20=EA=B2=B0=EA=B3=BC=C2=B7?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=A0=88=EC=B0=A8=20=EB=B3=B4?= =?UTF-8?q?=ED=83=9C=20=EB=A7=88=EA=B0=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 화면 전수 표 다섯 탭 결과와 「화면에서만 잡힌 것」(자재 0건인데 빈 표 셋) - 공용 브라우저 로그인 절차 — 사용자가 유지하는 자격 파일 + 페이지 오리진에서 로그인 요청, 확인은 세션 200. ⚠ 파일이 내 폴더에 없으면 **그 자리에서 읽어 쓰고 복사·기록하지 말 것**(자격을 옮기면 지우는 사람도 늘어남) - 「튕기면 서버가 아니라 브라우저 쿠키를 의심할 것」 마감 상태: 표 다섯 화면으로 섬 · 가드 7 + 열 방향 검사 · pytest 211 · tsc 0건 · 700줄 초과 0 Co-Authored-By: Claude Opus 5 (1M context) From 759f8c59a4e73263f8ebad6ef160caa6864f7b6e Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 8 Sep 2026 06:22:59 +0900 Subject: [PATCH 4/7] =?UTF-8?q?fix(B09):=20=EC=95=88=EB=82=B4=20=EC=A4=84?= =?UTF-8?q?=20=EA=B2=B9=EC=B9=A8=20=EC=A0=95=EB=A6=AC=20=E2=80=94=20?= =?UTF-8?q?=EA=B0=99=EC=9D=80=20=EC=88=98=EA=B0=80=20=EB=91=90=20=EB=B2=88?= =?UTF-8?q?=20=EB=9C=A8=EB=8D=98=20=EA=B2=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 메인 창 지적(「안내를 더하는 것이 곧 다른 안내를 묻는 것」)을 내 화면에도 대어 봄 - 잘리는 줄은 없었음(본문 높이 = 스크롤 높이, 6줄 다 보임) - 다만 **같은 수가 두 번** 떴음 — 「금액을 못 세운 줄 (16)」 뒤에 갈래별 머리글이 또 붙어 「… (16)」이 이어짐. 머리글을 **한 번만** 달게 함 (6줄 → 5줄) - 갈래가 하나뿐일 때만 「금액을 못 세운 줄」을 앞에 붙여 뜻이 온전하게 함 - 문구도 줄임 — 「0 으로 채우지 않고 그대로 보입니다」 → 「0 으로 안 채웁니다」 등 실측(5174): 안내 5줄, 잘림 없음. 바꾼 화면 상태는 원래 탭으로 되돌림 검증: pytest 211 통과, tsc 0건 Co-Authored-By: Claude Opus 5 (1M context) --- B09_Estimation/B09_Estimation_UI_Page.ts | 13 +++++++------ ui_template/ui_template_locale_b2.ts | 6 +++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/B09_Estimation/B09_Estimation_UI_Page.ts b/B09_Estimation/B09_Estimation_UI_Page.ts index ff6538f6..df04452b 100644 --- a/B09_Estimation/B09_Estimation_UI_Page.ts +++ b/B09_Estimation/B09_Estimation_UI_Page.ts @@ -908,11 +908,9 @@ export async function renderB09Estimation(root: HTMLElement): Promise { } if (bill.summary.missing.length > 0) { - const note = document.createElement("div"); - note.className = "b09-hint"; - note.textContent = `${L("B09_Estimation_Boq_Missing")} (${bill.summary.missing.length})`; - body.append(note); - + // ⚠ **머리글을 한 번만 단다.** 종전엔 「못 세운 줄 (16)」 뒤에 갈래별 머리글이 + // 또 붙어 **같은 수가 두 번** 떴다. 안내를 더하는 것이 곧 다른 안내를 묻는 것이라 + // (2026-09-08 메인 창 지적), 한 화면에 뜨는 줄 수를 늘리지 않는다. // ⚠ **할 일이 다르므로 갈라 보인다** — 「사용자가 입력하면 풀리는 것」과 // 「우리가 만들어야 하는 것」. 한 목록에 섞으면 사용자가 무엇을 해야 할지 못 읽는다. const needsInput = bill.summary.missing.filter( @@ -926,7 +924,10 @@ export async function renderB09Estimation(root: HTMLElement): Promise { if (group.length === 0) continue; const head = document.createElement("div"); head.className = "b09-hint"; - head.textContent = `${L(labelKey)} (${group.length})`; + // 갈래가 하나뿐이면 「금액을 못 세운 줄」이라는 말을 앞에 붙여 뜻이 온전하게 한다. + const prefix = + needsInput.length > 0 && rest.length > 0 ? "" : `${L("B09_Estimation_Boq_Missing")} — `; + head.textContent = `${prefix}${L(labelKey)} (${group.length})`; body.append(head); const list = document.createElement("ul"); for (const item of group) { diff --git a/ui_template/ui_template_locale_b2.ts b/ui_template/ui_template_locale_b2.ts index f2ec9e52..f54e751d 100644 --- a/ui_template/ui_template_locale_b2.ts +++ b/ui_template/ui_template_locale_b2.ts @@ -713,15 +713,15 @@ export const ui_locales_b2 = { "Check rows — quantity only, never priced", ], B09_Estimation_Boq_NeedsInput: [ - "입력하면 풀리는 것 — 설계 화면에서 값을 고르면 금액이 섭니다", + "입력하면 풀리는 것 (설계 화면에서 값을 고르면 금액이 섭니다)", "Waiting on input — pick the value on the design screen and the amount appears", ], B09_Estimation_Boq_NeedsWork: [ - "우리가 만들어야 하는 것 — 원단위·전개식이 아직 없습니다", + "우리가 만들어야 하는 것 (원단위·전개식 없음)", "Needs build — unit data or formula is missing", ], B09_Estimation_Boq_Missing: [ - "금액을 못 세운 줄 — 0 으로 채우지 않고 그대로 보입니다", + "금액을 못 세운 줄 — 0 으로 안 채웁니다", "Rows without an amount — shown as-is, not zero-filled", ], B09_Estimation_Boq_Materials: ["자재 (별도 벌)", "Materials (separate set)"], From 556e71cffe15763fb463c267f029aa131c956e4f Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 8 Sep 2026 06:31:06 +0900 Subject: [PATCH 5/7] =?UTF-8?q?docs(B09):=20=EC=8A=A4=EB=83=85=EC=88=8F=20?= =?UTF-8?q?=EB=8F=84=EA=B5=AC=20+=20=E3=80=8C=EA=B0=92=EC=9D=B4=20?= =?UTF-8?q?=EC=B1=84=EC=9B=8C=EC=A7=84=20=EC=B2=AB=20=EC=9D=B8=EA=B3=84?= =?UTF-8?q?=E3=80=8D=EC=97=90=EC=84=9C=20=EB=B3=BC=20=EA=B2=83=20=EC=97=AC?= =?UTF-8?q?=EC=84=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 대기 중 둘 처리 - `tmp/snapshot_b09.py before|after|diff` — 인계 응답과 **코드 시각**을 함께 뜸. `/api/health` 의 `code_mtime`·`stale` 을 같이 적어, 값이 움직였을 때 「자료가 바뀐 것」과 「코드 버전이 바뀐 것」을 가름 (성토 −16.81 % 가 그 자리였고 **절반이 코드 버전**이었음, 장부 규칙 7·8) ⚠ diff 는 **어느 줄이 얼마나** 바뀌었는지로 냄 — 합계 한 줄로 말하지 않음 - 9-7 에 「값이 채워진 첫 인계에서 볼 것」 여섯을 표로 적어 둠 — 암 갈래별 단가 · 타설 줄(65,826.4원/㎥) · 반영률 곱한 수량 · 공급 구분 · 돌 규격/쌓기 방식 · 개소 구조물 단위(메인 `de31cd9c`) 내 검증 프로젝트는 `2f940d8a` (배정). 첫 스냅숏 떠 둠 — 공종 10 · 자재 0 검증: pytest 211 통과. 서버 `stale` 이던 것 재시작해 코드 시각 맞춤 Co-Authored-By: Claude Opus 5 (1M context) From 1b5db4eb68d2df98d87a44e9d158543fcfb4d979 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 8 Sep 2026 06:32:01 +0900 Subject: [PATCH 6/7] =?UTF-8?q?docs(B09):=20=E3=80=8C=EC=97=AC=EA=B8=B0?= =?UTF-8?q?=EC=84=9C=20=EB=A9=88=EC=B6=A4=E3=80=8D=20=ED=91=9C=EC=8B=9C=20?= =?UTF-8?q?+=20=EC=9E=A5=EB=B6=80=20=EC=83=81=ED=83=9C=20=EB=8C=80?= =?UTF-8?q?=EA=B8=B0=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 조율 창 제어 종료에 따른 마무리 - 9-7 에 「여기서 멈춤」 — B09 완료 상태(표 다섯·화면 전수·가드 8·시험 211· tsc 0·700줄 0)와 **이어받을 사람이 볼 미처리 둘** · 값이 채워진 첫 인계 받기(볼 것 여섯은 바로 아래 표) · 타설 줄이 실린 자료로 65,826.4원/㎥ 이 금액에 걸리는지 확인 - 확정 답이 오면 손댈 자리 셋을 짚어 둠 — 제잡비 밑수 · 수량 자릿수 · 석재 할증 - `OWNERS.md` desktop-sub 줄을 「대기」로 브라우저 화면은 원래 탭(공사원가계산서)으로 되돌려 놓음. 워킹트리 깨끗 · 폴더 `D:\aislo-wt\sub` 확인 Co-Authored-By: Claude Opus 5 (1M context) From 60fbc51285a330094638acaa22dec28c31faa662 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 8 Sep 2026 06:32:12 +0900 Subject: [PATCH 7/7] =?UTF-8?q?docs(OWNERS):=20desktop-sub=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EB=8C=80=EA=B8=B0=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 조율 창 제어 종료에 따라 자기 줄만 갱신. 담당 폴더·브랜치·포트는 그대로. Co-Authored-By: Claude Opus 5 (1M context)