fix(B09 통합 점검): 단가연결 낱말 별칭 · 줄 0 개 일위대가 = 빈 줄 · 단위 다름 알림 · 잊은 짝 짝 표 제안 제외 (통합 점검 흠 ① ③ ④ + 잊기)

- 기준 목록 별칭에 「터파기」→「파기」 낱말(측구파기 → 측구터파기) · 목록 모양 SHAPE 올림
- 줄 0 개 일위대가 = 빈 줄 하나 · 빈 줄 있는 대가를 이은 내역 줄 = 단가 모름(금액 0 아님) · py · ts 짝 + 거울 시험
- 단가연결 때 줄 단위 ≠ 일위대가 단위면 「단위 다름」 알림(연결은 됨)
- 잊은 짝은 짝 표 제안에서도 안 나옴(다시 이으면 풀림)
- 낡은 시험 여섯: 707-4 잇기 메뉴 묶음(UI_Link) 빠진 소스 목록 보충

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-31 18:43:32 +09:00
co-authored by Claude Sonnet 5.5
parent 8626b1a922
commit 51f6eeffb1
16 changed files with 94 additions and 18 deletions
+1 -1
View File
@@ -148,7 +148,7 @@ def suggest(rows: list[dict[str, Any]], memory: dict[str, dict[str, Any]]) -> li
hit = _resolve(got, free) if got else None
if hit:
keys, tag = [hit], RECALL
elif row["kind"] == "item":
elif row["kind"] == "item" and not (memory.get(key_of(row)) or {}).get(FORGOT):
keys, tag = [EARTH_KEY[n] for n in table(row["name"], row["spec"])], TABLE
keys = [k for k in keys if k in free]
else:
+7 -1
View File
@@ -171,7 +171,13 @@ def ref_price(
if row is None:
return None
no = f"대가{row['no']}" if tab == "dg" else f"산근 {row['no']}호표"
return {"no": no, "tab": tab, "key": key, **{p: row.get(p) for p in (*PARTS, "sum")}}
blank = tab == "dg" and row.get("blank") # 빈 줄 있는 대가(줄 0 개 포함) = 단가 모름
return {
"no": no,
"tab": tab,
"key": key,
**{p: None if blank else row.get(p) for p in (*PARTS, "sum")},
}
return hopyo.get(key) if key else None
+2 -1
View File
@@ -132,7 +132,8 @@ export function refPrice(ref: Row | null, books: Row, hopyo: Row, rowKey = ""):
if (!row) return null;
const no = tab === "dg" ? `대가${row.no}` : `산근 ${row.no}호표`;
const out: Row = { no, tab, key };
for (const p of [...PARTS, "sum"]) out[p] = row[p] ?? null;
const blank = tab === "dg" && row.blank; // 빈 줄 있는 대가(줄 0 개 포함) = 단가 모름
for (const p of [...PARTS, "sum"]) out[p] = blank ? null : (row[p] ?? null);
return out;
}
return key ? (hopyo[key] ?? null) : null;
+5 -1
View File
@@ -17,7 +17,8 @@
% 줄 = ROUNDDOWN(밑수 × % / 100, 1) · 관급 · 별도계상 자재 = 대가 안 0원 ·
블록 비목 합 = ROUNDDOWN(Σ, 0) · 합계 = 세 비목 합.
빈칸 = 단가 · 수량을 낸 원천이 없으면 None · 「연결 없음」 목록(gaps).
빈 줄(166-6) = 블록 합 = 아는 줄 합 + `blank` n · `blank_names` — 내역 단가 · 부른 대가로는 안 넘김.
빈 줄(166-6) = 블록 합 = 아는 줄 합 + `blank` n · `blank_names` — 내역 단가 · 부른 대가로는 안 넘김 ·
줄 0 개 블록 = 빈 줄 하나(`NO_LINES`).
고친 칸 덮개(702-2) = 사용자 값 `daega_cover` {블록 cover: {줄 cid: {qty · 노 · 재 · 경 단가}}} —
있는 칸만 셈 값 대신 · 줄 `base`(품셈 값 = 셈 수량 · 단가) · `covered`(고친 칸) ·
짝 잃은 덮개(그 블록 · 줄 없음) = `daega_orphans` [{cover, cid}] — 지우지 않고 알림만.
@@ -36,6 +37,7 @@ from B09_Estimation.B09_Estimation_Calc_Base import SUPPLY, gap, num, rounddown,
PARTS = ("labor", "material", "expense")
COVER_KEYS = ("qty", *PARTS)
TAB = "dg"
NO_LINES = "구성 줄 없음" # 줄 0 개 일위대가의 빈 줄 이름
COPY = "@copy"
ADD = "@add"
WHY = {
@@ -158,6 +160,8 @@ def _block(
known = [ln for ln in lines if ln["amount"] is not None]
parts = {part: rounddown(sum(ln["amounts"][part] for ln in known), 0) for part in PARTS}
blank_names = [ln.get("name") or "" for ln in lines if ln["amount"] is None]
if not lines: # 줄 0 개 일위대가 = 금액 0 이 아니라 모름(빈 줄)
blank_names = [NO_LINES]
return {
**{k: block.get(k) for k in ("id", "kind", "name", "spec", "unit", "cite", "cover")},
**({"spec_raw": block["spec_raw"]} if block.get("spec_raw") else {}), # 157-4 풍선
+6 -2
View File
@@ -14,7 +14,8 @@
* 줄 금액 ROUNDDOWN(단가 × 수량, 1) · 블록 비목 합 ROUNDDOWN(Σ, 0) · 합계 = 세 비목 합
* 관급 · 별도계상 자재 = 대가 안 0원(자재대 · 관급 단락에서 셈)
* 빈칸 = 원천이 없으면 null · 「연결 없음」 목록(gaps)
* 빈 줄(166-6) = 블록 합 = 아는 줄 합 + blank n · blank_names — 내역 단가 · 부른 대가로는 안 넘김
* 빈 줄(166-6) = 블록 합 = 아는 줄 합 + blank n · blank_names — 내역 단가 · 부른 대가로는 안 넘김 ·
* 줄 0 개 블록 = 빈 줄 하나(NO_LINES)
* 고친 칸 덮개(702-2) = 사용자 값 daega_cover {블록 cover: {줄 cid: {qty · labor · material · expense}}} —
* 있는 칸만 셈 값 대신 · 줄 base(품셈 값) · covered(고친 칸) · 짝 잃은 덮개 = daega_orphans(알림만)
* 복사본(703-2) = 덮개 K:… 의 @copy {from · name · spec} — 원본 cover 블록을 본뜬 새 호표
@@ -36,6 +37,7 @@ import {
const PARTS = ["labor", "material", "expense"] as const;
const COVER_KEYS = ["qty", ...PARTS] as const;
const TAB = "dg";
const NO_LINES = "구성 줄 없음"; // 줄 0 개 일위대가의 빈 줄 이름(파이썬 짝)
export const COPY = "@copy";
/** 붙인 줄 덮개 열쇠(705-2) */
export const ADD = "@add";
@@ -153,7 +155,9 @@ function block(b: Row, books: Row, sums: Record<string, Row>, gaps: Gap[], own:
known.reduce((acc, ln) => acc + ln.amounts[part], 0),
0,
);
const blankNames = lines.filter((ln) => ln.amount === null).map((ln) => ln.name || "");
const blankNames = lines.length
? lines.filter((ln) => ln.amount === null).map((ln) => ln.name || "")
: [NO_LINES]; // 줄 0 개 일위대가 = 금액 0 이 아니라 모름(빈 줄)
return {
id: b.id,
key: b.id,
@@ -25,6 +25,7 @@ import {
} from "@ui/ui_template_context_menu";
import type { Row } from "./B09_Estimation_Calc_Base";
import type { TabContext, TabTable } from "./B09_Estimation_UI_Tabs";
import { showToast } from "@ui/ui_template_elements";
import { linkItems, type ApplyRef } from "./B09_Estimation_UI_Tabs_Bill_Link";
import { linkMenu } from "./B09_Estimation_UI_Link";
@@ -208,6 +209,9 @@ export function editCell(
put(ctx, w.bill);
}
/** 단위 비교 꼴 — 띄어쓰기 · 대소문자 · 호환 글자(㎥ = m3) 무시 */
const unitKey = (u: string): string => u.normalize("NFKC").replace(/\s+/g, "").toLowerCase();
/** 연결 적용 — 새 줄이 「새 줄」 · 빈 이름이면 고른 호표 명칭 · 규격 · 단위를 채움 */
function applyRef(ctx: TabContext, rows: DataRow[], i: number): ApplyRef {
return (ref, from) => {
@@ -222,6 +226,10 @@ function applyRef(ctx: TabContext, rows: DataRow[], i: number): ApplyRef {
e.unit = e.unit || from.unit || "";
}
put(ctx, w.bill);
const mine = String(e.unit ?? row.unit ?? "");
const theirs = String(from?.unit ?? "");
if (ref && unitKey(mine) && unitKey(theirs) && unitKey(mine) !== unitKey(theirs))
showToast(`단위 다름 ${mine} ↔ ${theirs} — 연결은 됨 · 수량 단위 확인`, "warning");
};
}