feat(780-1): 일위대가 밖 단가산출도 판정 · 확인 · 칠 — 모든 블록 · 부름 칸 · 빈 블록 「줄 없음」(요구 627)

- Sangeun: 판정 · 확인 몸 = 모든 블록(북부청 임도 49 → 95) · 줄마다 `부름 [호표 번]`([] = 일위대가 밖) ·
  밖 블록 = 이름 · 단위 블록 것 · 근거 없음 · 없는 번만 404 · 하위 밖 블록(울진소광 492 → 83)은 확인 목록에 함께 = 하위 먼저
- SkUnit: 줄 0 블록 = 판정 「줄 없음」 고를줄 1 → 확인해도 못만듦(빈 SK 없음)
- Marks: 블록마다 `부름` · `줄` · `어긋남` = 모든 블록(밖 포함) · 맞춤셈 `판정` 밖 = None 그대로
- 시험 고침(낡은 기대 = 일위대가 안 블록만): 778-5 units 49 → 95(안 49 따로) · 779-2 어긋남 + 밖 4 · 44 · 48 · 77 · 78 ·
  779-5 줄 칠 모든 블록 · 778-5 excel misses 밖 블록 5 · 새 시험 test_780_1_sangeun_outside(밖 확인 · 빈 블록 · 울진소광 · 여덟 파일 459)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-04 18:08:50 +09:00
co-authored by Claude Opus 5.5
parent 34288c1980
commit 75db009175
8 changed files with 166 additions and 29 deletions
+7 -4
View File
@@ -1,7 +1,8 @@
"""M02 불러오기 엑셀 보기 하이라이트 재료(PLAN 779-2 · 요구서 621 3장 5) — 읽기만(파일 · storage 변화 0).
블록 `[{번, 판정, SK, 갈래, 못옮김}]` = 단가산출 만들기 결과(`Sangeun.made`) + 맞춤셈 캐시(`Fit.result`)를 읽기만.
블록 `[{번, 판정, SK, 갈래, 못옮김, 부름}]` = 단가산출 만들기 결과(`Sangeun.made`) + 맞춤셈 캐시(`Fit.result`)를 읽기만.
- `판정` = 그 블록을 부르는 첫 줄의 맞춤셈 판정 상태(맞음 · 다른 점 · 후보 …) · 안 불리거나 셈 전이면 None
- `부름` = 부르는 호표 번 [] = 일위대가 밖(780-1) · `어긋남` · `줄` = 모든 블록(밖 포함)
- `SK` = 만든 SK 키 · 없으면 None / `갈래` · `못옮김` = 그 SK 본뜬(출처별) · 없으면 None · []
줄 상태는 `Match.view` 호표마다 `줄상태 [[차례, 상태, 체크]]` · `만든`(SC 키)로 실려 감.
@@ -127,7 +128,8 @@ def blocks(folder: Path, key: str, to: Path | None = None) -> list[dict[str, Any
cache = (fit.result(folder, key) or {}).get("블록") or {}
have = sangeun.made(to, key, here)
all_, first = sangeun.calls(hp)
units = hunits.Units() if first else None
units = hunits.Units() if all_ else None
call = sangeun.callers(hp)
out = []
for no in sorted(all_):
line = first.get(no)
@@ -142,8 +144,9 @@ def blocks(folder: Path, key: str, to: Path | None = None) -> list[dict[str, Any
"SK": sk,
"갈래": origin.get("갈래"),
"못옮김": origin.get("못옮김") or [],
"어긋남": misses(all_[no]) if line else None,
"줄": lines(all_[no], units) if line and units else [],
"부름": call.get(no, []),
"어긋남": misses(all_[no]),
"줄": lines(all_[no], units) if units else [],
}
)
return out
+34 -10
View File
@@ -7,6 +7,8 @@
SK = 이름 블록 이름(없으면 줄 품명) · 단위 = 줄 단위(없으면 블록 · 「식」) · 구분 「실무자료」 · 상세구분 = 파일 ·
바탕 없음 · 본뜬 = {층 「실무자료」, 키 「HP-###### 단산N」, 파일, 원본, 확인, 갈래 「단위」, 근거 {로직, 이름}, 못옮김 []} ·
같은 본뜬(키 · 원본)이 이 층에 있으면 그 키에 덮음 · 만드는 층 = 부르는 층.
판정 · 확인 = 모든 블록(780-1 · 요구 627) — 일위대가가 부르는 블록 + 일위대가 밖(내역서 등이 부름) · 줄마다
`부름 [호표 번]`([] = 일위대가 밖) · 밖 블록 = 이름 · 단위 블록 것 · 근거 꼬리표 없음(맞춤셈 열쇠 = 부르는 줄).
같은 내용(733-1 · `M02_Library_Same` 지문) SK 가 이 층에 있으면 새로 안 만들고 그것을 씀 — 이름 = 먼저 만든 것 ·
뒤 출처 = 그 SK `본뜬.또 [그 블록 본뜬 + 이름]`(`made` · `origin_of` 가 읽음) · 다른 출처가 쓰는 SK 는 내용을
안 바꿈(바뀌면 떼고 다시 찾음) · 응답 `같은것씀 [{번, 키}]` · 위 층 같은 것 = `위층같음 [{번, 층, 주인, 키}]`(알림만).
@@ -89,6 +91,19 @@ def calls(hp: dict[str, Any]) -> tuple[dict[int, dict], dict[int, dict]]:
return blocks, first
def callers(hp: dict[str, Any]) -> dict[int, list]:
"""{단산 번: 부르는 호표 번 [차례대로 · 겹침 뺌]} — 없는 번 = 일위대가 밖."""
out: dict[int, list] = {}
for h in hp.get("호표") or []:
for line in h.get("줄") or []:
ref = line.get("참조") or {}
if ref.get("종류") == "단산" and ref.get("번"):
got = out.setdefault(int(ref["번"]), [])
if h.get("번") not in got:
got.append(h.get("번"))
return out
def _bad(text: str, status: int = 400) -> store.StoreError:
return store.StoreError(status, text)
@@ -155,6 +170,8 @@ class Maker:
def basis(self, no: int) -> dict[str, Any] | None:
"""근거 꼬리표 = 맞춤셈 1위 기초(참고)."""
if no not in self.first: # 일위대가 밖 = 맞춤셈 없음
return None
entry = self.cache.get(fit.unit_key(self.first[no])) or {}
top = (entry.get("차례") or [None])[0]
if not top:
@@ -175,7 +192,7 @@ class Maker:
return {"층": layer, "주인": owner, "키": got} if got else None
def judge(self, no: int, picks: dict | None = None) -> dict[str, Any]:
line, block = self.first[no], self.blocks[no]
line, block = self.first.get(no) or {}, self.blocks[no]
unit = str(line.get("단위") or block.get("단위") or "").strip()
try:
got = skunit.judge(block, self.units(), self.ref_of, picks, unit)
@@ -206,7 +223,7 @@ class Maker:
plan = self.plan(no, sure[no])
got = self.judged[no]
lost = [r for r in got["줄"] if r["상태"] == units.LINKED and r["차례"] not in got["담음"]]
name = self.blocks[no].get("이름") or self.first[no].get("이름")
name = self.blocks[no].get("이름") or (self.first.get(no) or {}).get("이름")
if plan["갈래"] == skpatch.WAIT or lost:
why = plan.get("까닭") or f"하위 「{lost[0]['이름']}」 — 그 블록 먼저 확인"
out["못만듦"].append({"번": no, "이름": name, "까닭": why})
@@ -217,7 +234,7 @@ class Maker:
def write(self, no: int, plan: dict, have: str | None) -> str:
"""그 블록 SK 를 씀 → 키 — 다른 출처(또)가 쓰는 SK 의 내용은 안 바꿈(바뀌면 떼고 다시 찾음)."""
doc = _doc(self.first[no], self.blocks[no], no, self.hp, plan, self.sure)
doc = _doc(self.first.get(no) or {}, self.blocks[no], no, self.hp, plan, self.sure)
root, mine = store.root(), doc["본뜬"]
self.above += [{"번": no, **r} for r in self.same.find_above(root, KIND, doc)]
if have:
@@ -267,24 +284,31 @@ class Maker:
def build(folder: Path, key: str, who: Any = None, picks: Any = None) -> dict[str, Any]:
"""[단가산출] — 블록마다 줄 판정 · 확인한 블록만 단위 SK → `{블록, 판정 [{번, 이름, 단위, 판정, 고를줄,
우리금액, 엑셀금액, 근거, 만든, 줄}], 만듦 [{번, 이름, 키, 갈래, 못옮김}], 못만듦 [{번, 이름, 까닭}], 그대로,
우리금액, 엑셀금액, 근거, 만든, 줄, 부름}], 만듦 [{번, 이름, 키, 갈래, 못옮김}], 못만듦 [{번, 이름, 까닭}], 그대로,
같은것씀, 위층같음}` · `picks` = 확인 목록."""
sure = _sure(picks)
maker = Maker(folder, key, who)
bad = sorted(set(sure) - set(maker.first))
bad = sorted(set(sure) - set(maker.blocks))
if bad:
raise _bad(f"없는 블록 · 부르는 줄 없음 「단산{bad[0]}」", 404)
raise _bad(f"없는 블록 「단산{bad[0]}」", 404)
if not maker.done:
fit.ensure(folder, key)
raise _bad("맞춤 셈 중 — 끝나면 다시 누름", 409)
out: dict[str, Any] = {"블록": len(maker.first), "만듦": [], "못만듦": []}
out: dict[str, Any] = {"블록": len(maker.blocks), "만듦": [], "못만듦": []}
for no in sorted(sure):
maker.make(no, sure, out)
rows = []
for no in sorted(maker.first):
line, block = maker.first[no], maker.blocks[no]
call = callers(maker.hp)
for no in sorted(maker.blocks):
line, block = maker.first.get(no), maker.blocks[no]
got = maker.judged.get(no) or maker.judge(no)
head = {"번": no, "이름": block.get("이름") or line.get("이름"), "단위": line.get("단위")}
name = block.get("이름") or (line or {}).get("이름")
head = {
"번": no,
"이름": name,
"단위": (line or block).get("단위"),
"부름": call.get(no, []),
}
keep = ("판정", "고를줄", "우리금액", "엑셀금액", "줄")
head |= {k: got[k] for k in keep} | {"근거": maker.basis(no)}
rows.append({**head, "만든": maker.keys.get(mark(key, no))})
+5 -2
View File
@@ -4,7 +4,7 @@
줄 판정 = `M02_Hopyo_Units` 잇기(상태 · 방법 · 단위) + 매개값(품 · 시간 · 수량 · 율) · 사용자 고름
`{차례: 단위 | {뺌: 까닭}}`(그 줄만 · 꼬리표 「내가 고름」) · 단가 = M01 지금 값으로 다시 셈(`우리금액`) ·
엑셀 금액과의 `차` = 참고(엑셀 = 과거 값 · 판정에 안 씀) · 셈 밖 줄 = 상태 「밖」(SK 에 안 들임).
블록 판정 = 줄 모음 — 모두 이어짐 = 「옮길 수 있음」 · 아니면 「고를 줄 n」.
블록 판정 = 줄 모음 — 모두 이어짐 = 「옮길 수 있음」 · 아니면 「고를 줄 n」 · 줄 0 = 「줄 없음」(고를줄 1 · 빈 SK 막음).
단위 SK 더한줄(바탕 없음 · 778-4 꼴) = 인력 `{요소 LB, 수량 품}` · 기계경비 로직 `{로직 GC, 입력값 {기계},
수량 시간 × 할증}` · 재료 `{요소 MT, 수량(손료율 든), 별도계상?}`(못 찾은 별도계상 = 0 원 고정 줄) ·
율 `{고정 {비율 {기준, 비목}}, 수량 = 율 %}`(줄 목록 = 그 행 줄 `add:#k`) · 하위 `{참조 SK · SC}` · 값 고정 줄.
@@ -24,6 +24,7 @@ from M02_MasterTemplete import M02_Sangeun_Doc as sd
from M02_MasterTemplete.M02_Hopyo_Words import mtypes
GOOD = "옮길 수 있음"
EMPTY = "줄 없음"
OUT, DROP = "밖", "뺌"
UNIT = "단위" # 본뜬 갈래
MINE = "내가 고름"
@@ -85,7 +86,9 @@ def lines(block: dict[str, Any], us: units.Units, picks: dict | None = None) ->
def verdict(rows: list[dict]) -> tuple[str, int]:
"""블록 판정 — (「옮길 수 있음」 | 「고를 줄 n」, n)."""
"""블록 판정 — (「옮길 수 있음」 | 「고를 줄 n」 | 「줄 없음」, n) · 줄 0 = 1(확인해도 못 만듦)."""
if not rows:
return EMPTY, 1
n = sum(1 for r in rows if r["상태"] not in (units.LINKED, OUT, DROP))
return (GOOD if not n else f"고를 줄 {n}"), n