feat(778-5): 블록 = 줄 판정 모음 · 확인한 블록만 단위 SK(바탕 없음 · 근거 꼬리표) · 확인 목록 받는 길(D-3 · 요구 623 · 624)
- 새 M02_Hopyo_SkUnit: 줄 판정(Units 잇기 + 매개값 · 사용자 고름 {차례: 노임|기계|자재|기준|뺌} · 꼬리표 「내가 고름」) · 단가 = M01 지금 값으로 다시 셈(우리금액) · 엑셀 금액 차 = 참고 · 블록 판정 「옮길 수 있음」 | 「고를 줄 n」 · 단위 SK 더한줄 = 인력 · 기계경비 로직 줄 · 재료(손료율 든 수량 · 별도계상) · 율 줄(778-4 꼴 · 줄 목록 = 그 행 add:#k) · 하위 참조 · 값 고정 줄 · 저장 꼴로 다듬어 같은 내용 SK 지문 같음
- Sangeun: 기초 · SK · 고름 대기 갈림(SkPatch.plan) 걷고 단위 SK 하나 · 확인 [{번, 고름?}] 에 든 블록만 만듦 · 하위 단산 먼저 · 고를 줄 남으면 못만듦 · 일위대가 만들기가 부르는 Maker.plan(번) = 「확인 전」 고름 대기(자동 없음) · 별칭 제안 걷음(바탕 기초 없음)
- Router: sangeun 몸 바탕 → 확인(줄 수 그대로)
- 시험 새 뜻: test_729(기초 · SK · 고름 대기 · 계수 · 못옮김 잇기 · 729-5 차이 SK 바탕 길 = 바탕 없는 단위 SK 로 사라짐 → 확인 블록 단위 SK · 줄 고름 · 뺌으로 갈음) · test_718_1 · test_572 · test_779_2 · test_m02_hopyo_flow(확인 목록을 보내야 SK 생김 · 같은 파일 안 같은 내용 단위 SK 없을 수 있음 · 바탕 없는 SK = SC 에 참조 그대로)
- 새 test_778_5: 작업반장 = 건설업 · 규격 차례 무관 · 글자 규격 = 후보 · 손료율 · 설치비 · 제경비 율 줄 · 여덟 파일 줄 연결 표 · 확인 안 한 블록 안 만듦 · 고름 · 400 · 404 · 다시 올림 같은 SK 재사용 · 일위대가 확인 전 못 이음
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,24 +1,16 @@
|
||||
"""M02 일위대가 불러오기 → 단가산출(PLAN 718-1 · 729-1 · 요구서 내역19 1장 7 · 3장 6) —
|
||||
산근(단산) 블록마다 매칭 → 비교 → 기초 · SK · 고름 대기.
|
||||
"""M02 일위대가 불러오기 → 단가산출(PLAN 718-1 · 729-1 · 778-5 D-3 · 요구 621 · 623) — 산근(단산) 블록마다
|
||||
줄 판정 모음(`M02_Hopyo_SkUnit`) → 사용자가 확인한 블록만 단위 SK.
|
||||
|
||||
블록마다 그 블록을 부르는 첫 엑셀 줄(「단산N참조」)의 맞춤셈(`M02_Hopyo_Fit` 캐시 · 없으면 그 블록만
|
||||
셈) → `M02_Hopyo_SkPatch.plan`:
|
||||
기초 = SK 바탕 = 1위 기초 · 입력값 · 덮개 없음 · SK = 바탕 기초 + 덮개 · 더한줄 · 뺀줄(엑셀 품 그대로) ·
|
||||
고름 대기 = SK 안 만듦(바탕 없는 SK 로 몰래 채우지 않음) · 후보 다섯(성격 다른 단위 = `계수` 칸 뜻) →
|
||||
사용자가 고른 기초 `바탕 {번: {로직?, 입력값?, 환산?, 잇기?}}` 로 다시 부르면 그 기초에 견줘 SK(이미 있으면
|
||||
그 키에 덮음) · 잇기 `[{id, 노임|기계|자재: 키} | {id, 뺌}]` = 못옮김 → SK 더한줄 · 남은 못옮김 = 응답
|
||||
`만듦[].못옮김` · 본뜬 그대로(그 SK 를 쓰는 일위대가 만들기 = 남은 줄 409 · `M02_Hopyo_SkLink`) ·
|
||||
그대로 SK 의 못옮김 = 응답 `남은못옮김 [{번, 이름, 키, 못옮김}]` · 잇기만 보냈는데 1위 기초가 고름 대기면
|
||||
그 SK 바탕(로직 · 입력값 · 환산)으로 다시 셈(729-5).
|
||||
모두 줄로만(바탕 기초 안 씀 · 맞춤셈 1위 기초 = 근거 꼬리표만) · 자동 없음 — `확인 [{번, 고름?: {차례: 고름}}]`
|
||||
(블록마다 [확인] · [옮기기])에 든 블록만 만듦 · 확인 안 한 블록 = 판정만(안 만듦) · 줄 다 이어지지 않은 확인 블록 =
|
||||
`못만듦` · 하위 단산 줄 = 그 블록 SK 참조(확인 목록에 있으면 먼저 만듦 · 없으면 못만듦).
|
||||
SK = 이름 블록 이름(없으면 줄 품명) · 단위 = 줄 단위(없으면 블록 · 「식」) · 구분 「실무자료」 · 상세구분 = 파일 ·
|
||||
본뜬 = {층 「실무자료」, 키 「HP-###### 단산N」, 파일, 원본, 확인, 갈래, 엑셀품, 못옮김} · 같은 본뜬(키 · 원본)이
|
||||
이 층에 있으면 그대로(겹침 없음) · 만드는 층 = 부르는 층.
|
||||
바탕 없음 · 본뜬 = {층 「실무자료」, 키 「HP-###### 단산N」, 파일, 원본, 확인, 갈래 「단위」, 근거 {로직, 이름}, 못옮김 []} ·
|
||||
같은 본뜬(키 · 원본)이 이 층에 있으면 그 키에 덮음 · 만드는 층 = 부르는 층.
|
||||
같은 내용(733-1 · `M02_Library_Same` 지문) SK 가 이 층에 있으면 새로 안 만들고 그것을 씀 — 이름 = 먼저 만든 것 ·
|
||||
뒤 출처 = 그 SK `본뜬.또 [그 블록 본뜬 + 이름]`(`made` · `origin_of` 가 읽음 · 갈래 · 못옮김은 출처마다) ·
|
||||
다른 출처가 쓰는 SK 는 내용을 안 바꿈(고른 기초 · 잇기로 바뀌면 떼고 다시 찾음) · 응답 `같은것씀 [{번, 키}]` ·
|
||||
위 층(회사 · 마스터) 같은 것 = 응답 `위층같음 [{번, 층, 주인, 키}]`(알림만 · 연결 안 함).
|
||||
별칭 제안 = 기초 · SK 만든 블록의 엑셀 줄 → 이 층 별칭 장부 짝 = 바탕 기초까지만(차이 SK 는 이 층 문서 ·
|
||||
요구서 3장 6) · 장부에 이미 짝 · 갈래가 있는 줄은 그대로 · 승인 길 = M01 별칭 장부(716).
|
||||
뒤 출처 = 그 SK `본뜬.또 [그 블록 본뜬 + 이름]`(`made` · `origin_of` 가 읽음) · 다른 출처가 쓰는 SK 는 내용을
|
||||
안 바꿈(바뀌면 떼고 다시 찾음) · 응답 `같은것씀 [{번, 키}]` · 위 층 같은 것 = `위층같음 [{번, 층, 주인, 키}]`(알림만).
|
||||
일위대가 만들기(`M02_Hopyo_SkLink`)가 부르는 `Maker.plan(번)`(확인 없음) = 고름 대기 「확인 전」 — 못 이음.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -32,14 +24,16 @@ from M01_MasterData import M01_MasterData_Store as m01
|
||||
from M02_MasterTemplete import M02_Hopyo_Fit as fit
|
||||
from M02_MasterTemplete import M02_Hopyo_Ledger as ledger
|
||||
from M02_MasterTemplete import M02_Hopyo_SkPatch as skpatch
|
||||
from M02_MasterTemplete import M02_Hopyo_SkUnit as skunit
|
||||
from M02_MasterTemplete import M02_Hopyo_Units as units
|
||||
from M02_MasterTemplete import M02_Hopyo_Store as hopyo_store
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
from M02_MasterTemplete import M02_Library_Same as same
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from M02_MasterTemplete import M02_Sangeun_Doc as sd
|
||||
|
||||
ORIGIN = "실무자료"
|
||||
KIND = "sangeun"
|
||||
WAIT_WHY = "확인 전 — [단가산출]에서 블록 [확인] 뒤 [옮기기]"
|
||||
|
||||
|
||||
def mark(key: str, no: int) -> str:
|
||||
@@ -99,50 +93,26 @@ def _bad(text: str, status: int = 400) -> store.StoreError:
|
||||
return store.StoreError(status, text)
|
||||
|
||||
|
||||
def _picks(raw: Any) -> dict[int, dict[str, Any]]:
|
||||
"""사용자 고름 `{번: {로직?, 입력값?, 환산?, 잇기?}}` 재검사 — 돈 로직만 · 입력 이름 = 그 로직 입력 ·
|
||||
환산 = SK 꼴(`M02_Sangeun_Doc`) · 잇기 = `[{id, …}]`(id 짝 · 키는 `M02_Hopyo_SkPatch` 가 봄)."""
|
||||
def _sure(raw: Any) -> dict[int, dict[int, Any]]:
|
||||
"""확인 목록 `[{번, 고름?: {차례: 고름}}]` → `{번: {차례: 고름}}`(고름 꼴은 `M02_Hopyo_SkUnit` 이 봄)."""
|
||||
if not raw:
|
||||
return {}
|
||||
if not isinstance(raw, dict) or len(raw) > 500:
|
||||
raise _bad("바탕은 {블록 번: {로직, 입력값}}")
|
||||
logics = ledger._known()[0]
|
||||
out = {}
|
||||
for no, one in raw.items():
|
||||
code = str((one or {}).get("로직") or "") if isinstance(one, dict) else "?"
|
||||
if code and code not in logics:
|
||||
raise _bad(f"단산{no} · 돈 로직이 아님 「{code}」")
|
||||
if not code and not (one.get("환산") or one.get("잇기")):
|
||||
raise _bad(f"단산{no} · 로직 · 환산 · 잇기 중 하나")
|
||||
pick: dict[str, Any] = {}
|
||||
given = one.get("입력값") if code else None
|
||||
if code:
|
||||
pick["로직"] = code
|
||||
if given is not None:
|
||||
bad = set(given) - ledger.input_names(code) if isinstance(given, dict) else {1}
|
||||
if bad:
|
||||
raise _bad(f"단산{no} · 모르는 입력 {sorted(map(str, bad))}")
|
||||
pick["입력값"] = given
|
||||
if one.get("환산"):
|
||||
try:
|
||||
pick["환산"] = sd._swap_how(one["환산"])
|
||||
except sd.DocError as e:
|
||||
raise _bad(f"단산{no} · {e}") from e
|
||||
links = one.get("잇기")
|
||||
if links:
|
||||
if not isinstance(links, list) or not all(
|
||||
isinstance(x, dict) and x.get("id") for x in links
|
||||
):
|
||||
raise _bad(f"단산{no} · 잇기는 [{{id, 노임|기계|자재: 키}}]")
|
||||
pick["잇기"] = links
|
||||
out[int(no)] = pick
|
||||
if not isinstance(raw, list) or len(raw) > 500:
|
||||
raise _bad("확인은 [{번, 고름?: {차례: 고름}}]")
|
||||
out: dict[int, dict[int, Any]] = {}
|
||||
for one in raw:
|
||||
try:
|
||||
picks = one.get("고름") or {}
|
||||
out[int(one["번"])] = {int(k): v for k, v in picks.items()}
|
||||
except (TypeError, ValueError, KeyError, AttributeError) as e:
|
||||
raise _bad("확인은 [{번, 고름?: {차례: 고름}}]") from e
|
||||
return out
|
||||
|
||||
|
||||
def _doc(line: dict, block: dict, no: int, hp: dict, plan: dict, sure: dict) -> dict[str, Any]:
|
||||
name = str(block.get("이름") or line.get("이름") or "").strip() or f"단산{no}"
|
||||
unit = str(line.get("단위") or block.get("단위") or "").strip() or "식"
|
||||
keep = {k: plan[k] for k in ("갈래", "엑셀품", "못옮김")}
|
||||
keep = {k: plan[k] for k in ("갈래", "엑셀품", "못옮김", "근거") if k in plan}
|
||||
keep |= {"놓음": plan["놓음"]} if plan.get("놓음") else {}
|
||||
return {
|
||||
"종류": "단가산출",
|
||||
@@ -160,32 +130,6 @@ def _doc(line: dict, block: dict, no: int, hp: dict, plan: dict, sure: dict) ->
|
||||
}
|
||||
|
||||
|
||||
def _old_base(folder: Path, key: str, mark_: str, source: refs.Here) -> dict[str, Any]:
|
||||
"""이미 있는 SK 의 바탕 고름 `{로직, 입력값, 환산?}` · `본뜬` — 읽기 실패 = 빈 것."""
|
||||
try:
|
||||
doc = json.loads((Path(folder) / KIND / f"{key}.json").read_bytes())
|
||||
except (OSError, ValueError):
|
||||
return {}
|
||||
base = {"로직": (doc.get("바탕") or {}).get("로직"), "입력값": doc.get("입력값") or {}}
|
||||
return {
|
||||
**base,
|
||||
**({"환산": doc["환산"]} if doc.get("환산") else {}),
|
||||
"본뜬": origin_of(doc, mark_, source),
|
||||
}
|
||||
|
||||
|
||||
def _alias(line: dict, plan: dict) -> dict[str, Any]:
|
||||
origin = {"품명": line.get("이름"), "규격": line.get("규격"), "단위": line.get("단위")}
|
||||
clauses = (line.get("근거") or {}).get("출처") or []
|
||||
pair = {"로직": plan["바탕"]["로직"], "입력값": plan["입력값"], "배": 1, "몫": None}
|
||||
return {
|
||||
"원래": {k: str(v or "") for k, v in origin.items()},
|
||||
"갈래": "짝",
|
||||
"짝": [pair],
|
||||
"엑셀조항": clauses if isinstance(clauses, list) else [],
|
||||
}
|
||||
|
||||
|
||||
class Maker:
|
||||
"""한 번 만들기 — 파일 · 맞춤셈 · 이 층 SK 를 한 번 읽음(`source` = 파일 층 · 쓰기 = `store.root()`)."""
|
||||
|
||||
@@ -199,16 +143,77 @@ class Maker:
|
||||
self.whole = m01.loaded()[1]
|
||||
self.sure = {"원본": list(self.here), "확인": {"누가": who, "때": utc_now_text()}}
|
||||
self.same, self.reused, self.above = same.Same(), [], []
|
||||
self.keys = made(source, key, self.here) # {본뜬 키: SK} — 만든 것 더해 감
|
||||
self.judged: dict[int, dict] = {}
|
||||
self._units: units.Units | None = None
|
||||
|
||||
def plan(self, no: int, pick: dict | None = None) -> dict[str, Any]:
|
||||
def units(self) -> units.Units:
|
||||
if self._units is None:
|
||||
books = ledger.books(self.source)
|
||||
self._units = units.Units(books, ledger.doc_id(self.source, self.key))
|
||||
return self._units
|
||||
|
||||
def basis(self, no: int) -> dict[str, Any] | None:
|
||||
"""근거 꼬리표 = 맞춤셈 1위 기초(참고)."""
|
||||
entry = self.cache.get(fit.unit_key(self.first[no])) or {}
|
||||
top = (entry.get("차례") or [None])[0]
|
||||
if not top:
|
||||
return None
|
||||
name = (self.whole.logic(top) or {}).get("이름")
|
||||
return {"로직": top, "이름": name, "판정": (entry.get("판정") or {}).get("상태")}
|
||||
|
||||
def ref_of(self, sub: dict[str, Any]) -> dict[str, Any] | None:
|
||||
"""하위 줄 → 참조 칸 — 단산 = 이 HP 그 블록 SK · 일위 = 확인된 그 호표 SC · 없으면 None."""
|
||||
from M02_MasterTemplete import M02_Hopyo_Build as hopyo_build # Build → SkLink → 여기
|
||||
|
||||
if sub.get("종류") == "단산":
|
||||
got = self.keys.get(mark(self.key, int(sub["번"])))
|
||||
else:
|
||||
done = hopyo_build.confirmed(store.root(), self.here)
|
||||
got = done.get(hopyo_build.mark(self.key, int(sub["번"])))
|
||||
layer, owner = refs.here()
|
||||
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]
|
||||
entry = self.cache.get(fit.unit_key(line))
|
||||
if entry is None and not (pick or {}).get("로직"):
|
||||
entry = fit.compute(block, line)
|
||||
unit = str(line.get("단위") or block.get("단위") or "").strip()
|
||||
try:
|
||||
return skpatch.plan(entry, block, line, self.whole, pick)
|
||||
except skpatch.LinkError as e:
|
||||
got = skunit.judge(block, self.units(), self.ref_of, picks, unit)
|
||||
except skunit.PickError as e:
|
||||
raise _bad(f"단산{no} · {e}") from e
|
||||
self.judged[no] = got
|
||||
return got
|
||||
|
||||
def plan(self, no: int, picks: dict | None = None) -> dict[str, Any]:
|
||||
"""확인한 블록 → 단위 SK 꼴 · 확인 없음(`picks` None) · 고를 줄 남음 = 고름 대기."""
|
||||
if picks is None:
|
||||
return {"갈래": skpatch.WAIT, "까닭": WAIT_WHY, "후보": []}
|
||||
got = self.judge(no, picks)
|
||||
if got["고를줄"]:
|
||||
return {"갈래": skpatch.WAIT, "까닭": got["판정"], "후보": []}
|
||||
return skunit.plan(got, self.basis(no))
|
||||
|
||||
def make(self, no: int, sure: dict[int, dict], out: dict, path: tuple = ()) -> None:
|
||||
"""확인한 블록 하나 — 확인 목록의 하위 단산 먼저 · 못 만들면 `못만듦`."""
|
||||
mark_ = mark(self.key, no)
|
||||
if no in path or any(x["번"] == no for x in out["만듦"] + out["못만듦"]):
|
||||
return
|
||||
for one in self.blocks[no].get("항목") or []:
|
||||
sub = one.get("하위") or {}
|
||||
m = int(sub.get("번") or 0)
|
||||
if sub.get("종류") == "단산" and m in sure and mark(self.key, m) not in self.keys:
|
||||
self.make(m, sure, out, (*path, no))
|
||||
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("이름")
|
||||
if plan["갈래"] == skpatch.WAIT or lost:
|
||||
why = plan.get("까닭") or f"하위 「{lost[0]['이름']}」 — 그 블록 먼저 확인"
|
||||
out["못만듦"].append({"번": no, "이름": name, "까닭": why})
|
||||
return
|
||||
key = self.write(no, plan, self.keys.get(mark_))
|
||||
self.keys[mark_] = key
|
||||
out["만듦"].append({"번": no, "이름": name, "키": key, "갈래": skunit.UNIT, "못옮김": []})
|
||||
|
||||
def write(self, no: int, plan: dict, have: str | None) -> str:
|
||||
"""그 블록 SK 를 씀 → 키 — 다른 출처(또)가 쓰는 SK 의 내용은 안 바꿈(바뀌면 떼고 다시 찾음)."""
|
||||
@@ -261,53 +266,29 @@ class Maker:
|
||||
|
||||
|
||||
def build(folder: Path, key: str, who: Any = None, picks: Any = None) -> dict[str, Any]:
|
||||
"""[단가산출 만들기] — 산근 블록마다 기초 · SK · 고름 대기 → `{블록, 만듦 [{번, 이름, 키, 갈래, 못옮김,
|
||||
환산?}], 그대로, 남은못옮김 [{번, 이름, 키, 못옮김}], 기초, SK, 고름대기 [{번, 이름, 단위, 까닭, 후보}],
|
||||
별칭, 같은것씀 [{번, 키}], 위층같음 [{번, 층, 주인, 키}]}` · `picks` = 고름."""
|
||||
chosen = _picks(picks)
|
||||
"""[단가산출] — 블록마다 줄 판정 · 확인한 블록만 단위 SK → `{블록, 판정 [{번, 이름, 단위, 판정, 고를줄,
|
||||
우리금액, 엑셀금액, 근거, 만든, 줄}], 만듦 [{번, 이름, 키, 갈래, 못옮김}], 못만듦 [{번, 이름, 까닭}], 그대로,
|
||||
같은것씀, 위층같음}` · `picks` = 확인 목록."""
|
||||
sure = _sure(picks)
|
||||
maker = Maker(folder, key, who)
|
||||
bad = sorted(set(chosen) - set(maker.first))
|
||||
bad = sorted(set(sure) - set(maker.first))
|
||||
if bad:
|
||||
raise _bad(f"없는 블록 · 부르는 줄 없음 「단산{bad[0]}」", 404)
|
||||
if not maker.done and set(maker.first) - set(chosen):
|
||||
if not maker.done:
|
||||
fit.ensure(folder, key)
|
||||
raise _bad("맞춤 셈 중 — 끝나면 다시 누름", 409)
|
||||
have = made(folder, key, maker.here)
|
||||
out: dict[str, Any] = {"블록": len(maker.first), "만듦": [], "그대로": 0, "고름대기": []}
|
||||
out["남은못옮김"] = []
|
||||
out.update({skpatch.BASE: 0, skpatch.SK: 0})
|
||||
books, alias = ledger.books(folder), {}
|
||||
out: dict[str, Any] = {"블록": len(maker.first), "만듦": [], "못만듦": []}
|
||||
for no in sorted(sure):
|
||||
maker.make(no, sure, out)
|
||||
rows = []
|
||||
for no in sorted(maker.first):
|
||||
old = have.get(mark(key, no))
|
||||
line = maker.first[no]
|
||||
name = maker.blocks[no].get("이름") or line.get("이름")
|
||||
if old and no not in chosen:
|
||||
out["그대로"] += 1
|
||||
lost = _old_base(folder, old, mark(key, no), maker.here)["본뜬"].get("못옮김")
|
||||
if lost:
|
||||
out["남은못옮김"].append({"번": no, "이름": name, "키": old, "못옮김": lost})
|
||||
continue
|
||||
plan = maker.plan(no, chosen.get(no))
|
||||
if plan["갈래"] == skpatch.WAIT and old and not chosen[no].get("로직"):
|
||||
base = _old_base(
|
||||
folder, old, mark(key, no), maker.here
|
||||
) # 고른 기초로 만든 SK 의 잇기 = 그 바탕 그대로
|
||||
if base.get("로직"):
|
||||
pick = {k: v for k, v in base.items() if k != "본뜬"}
|
||||
plan = maker.plan(no, {**pick, **chosen[no]})
|
||||
if plan["갈래"] == skpatch.WAIT:
|
||||
head = {"번": no, "이름": maker.blocks[no].get("이름") or line.get("이름")}
|
||||
out["고름대기"].append({**head, "단위": line.get("단위"), **plan})
|
||||
continue
|
||||
got = maker.write(no, plan, old)
|
||||
out[plan["갈래"]] += 1
|
||||
made_ = {"번": no, "이름": name, "키": got, "갈래": plan["갈래"], "못옮김": plan["못옮김"]}
|
||||
out["만듦"].append({**made_, **({"환산": plan["환산"]} if plan.get("환산") else {})})
|
||||
found = ledger.recall(line, folder, books)
|
||||
if no in chosen or found is None or found.get("규격다름"):
|
||||
alias[ledger.key(line)] = _alias(line, plan)
|
||||
out["별칭"] = ledger.write(
|
||||
folder, alias, who, maker.hp.get("파일") or None, ledger.doc_id(folder, key)
|
||||
)
|
||||
line, block = maker.first[no], maker.blocks[no]
|
||||
got = maker.judged.get(no) or maker.judge(no)
|
||||
head = {"번": no, "이름": block.get("이름") or line.get("이름"), "단위": line.get("단위")}
|
||||
keep = ("판정", "고를줄", "우리금액", "엑셀금액", "줄")
|
||||
head |= {k: got[k] for k in keep} | {"근거": maker.basis(no)}
|
||||
rows.append({**head, "만든": maker.keys.get(mark(key, no))})
|
||||
out["판정"] = rows
|
||||
out["그대로"] = sum(1 for r in rows if r["만든"] and r["번"] not in sure)
|
||||
out["같은것씀"], out["위층같음"] = maker.reused, maker.above
|
||||
return out
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
"""M02 실무 단가산출 블록 = 줄 판정 모음 · 단위 SK(PLAN 778-5 · D-3 · 요구 623 · 624).
|
||||
|
||||
모두 줄로만 — 바탕 기초 안 씀(기초 = 근거 꼬리표만) · 자동 없음(사용자가 확인한 블록만 만듦).
|
||||
줄 판정 = `M02_Hopyo_Units` 잇기(상태 · 방법 · 단위) + 매개값(품 · 시간 · 수량 · 율) · 사용자 고름
|
||||
`{차례: 단위 | {뺌: 까닭}}`(그 줄만 · 꼬리표 「내가 고름」) · 단가 = M01 지금 값으로 다시 셈(`우리금액`) ·
|
||||
엑셀 금액과의 `차` = 참고(엑셀 = 과거 값 · 판정에 안 씀) · 셈 밖 줄 = 상태 「밖」(SK 에 안 들임).
|
||||
블록 판정 = 줄 모음 — 모두 이어짐 = 「옮길 수 있음」 · 아니면 「고를 줄 n」.
|
||||
단위 SK 더한줄(바탕 없음 · 778-4 꼴) = 인력 `{요소 LB, 수량 품}` · 기계경비 로직 `{로직 GC, 입력값 {기계},
|
||||
수량 시간 × 할증}` · 재료 `{요소 MT, 수량(손료율 든), 별도계상?}`(못 찾은 별도계상 = 0 원 고정 줄) ·
|
||||
율 `{고정 {비율 {기준, 비목}}, 수량 = 율 %}`(줄 목록 = 그 행 줄 `add:#k`) · 하위 `{참조 SK · SC}` · 값 고정 줄.
|
||||
근거 꼬리표 = 맞춤셈 1위 기초(본뜬 칸 · 참고). 서버 단독 · 쓰기 = `M02_Hopyo_Sangeun`.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from M02_MasterTemplete import M02_Hopyo_Fixed as hfix
|
||||
from M02_MasterTemplete import M02_Hopyo_SkPatch as skpatch
|
||||
from M02_MasterTemplete import M02_Hopyo_UnitLine as unit_line
|
||||
from M02_MasterTemplete import M02_Hopyo_Units as units
|
||||
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
||||
from M02_MasterTemplete import M02_Sangeun_Doc as sd
|
||||
from M02_MasterTemplete.M02_Hopyo_Words import mtypes
|
||||
|
||||
GOOD = "옮길 수 있음"
|
||||
OUT, DROP = "밖", "뺌"
|
||||
UNIT = "단위" # 본뜬 갈래
|
||||
MINE = "내가 고름"
|
||||
WORD = {"L": "품", "X": "시간", "M": "수량", "R": "율", "S": "수량"}
|
||||
PICKS = {"L": ("노임", "LB"), "X": ("기계", "EQ"), "M": ("자재", "MT")}
|
||||
|
||||
|
||||
class PickError(ValueError):
|
||||
"""고름 틀림 — 부르는 쪽이 400."""
|
||||
|
||||
|
||||
def _picked(one: dict[str, Any], pick: Any, us: units.Units) -> dict[str, Any]:
|
||||
"""사용자 고름 → 이어짐 단위 — 노임 LB · 기계 EQ(→ 기계경비 로직) · 자재 MT · 율 기준 · 뺌."""
|
||||
if not isinstance(pick, dict):
|
||||
raise PickError("고름은 {노임|기계|자재: 키} · {기준, 비목?} · {뺌}")
|
||||
if pick.get("뺌"):
|
||||
return {"상태": DROP, "단위": None, "글": str(pick["뺌"]), "꼬리표": [MINE]}
|
||||
kind = one["묶음"]
|
||||
if kind == "R" and pick.get("기준") is not None:
|
||||
unit = {"기준": pick["기준"], "비목": pick.get("비목") or units.rate_part(one.get("이름"))}
|
||||
hfix.fixed.clean({"이름": "율", "비율": unit}, "고름", us.whole)
|
||||
return {"상태": units.LINKED, "단위": unit, "꼬리표": [MINE]}
|
||||
field, head = PICKS.get(kind, ("", "?"))
|
||||
code = str(pick.get(field) or "")
|
||||
if not field or not code.startswith(head):
|
||||
raise PickError(f"{units.HOW.get(kind, kind)} 줄 · {field or '뺌'} 키({head}…)로 고름")
|
||||
try:
|
||||
row = us.whole.get(code)
|
||||
except Exception as e: # noqa: BLE001 — 없는 키
|
||||
raise PickError(f"없는 키 「{code}」") from e
|
||||
unit: dict[str, Any] = {field: code}
|
||||
if kind == "X":
|
||||
kinds = mtypes(f"{row.get('상세구분')} {row.get('이름')}")
|
||||
unit["로직"] = skpatch._mach_logics(us.whole, kinds, row)[0]
|
||||
unit |= {"할증": one["할증"]} if one.get("할증") else {}
|
||||
if kind == "M" and (one.get("어떻게") == "별도" or pick.get("별도계상")):
|
||||
unit["별도계상"] = True
|
||||
return {"상태": units.LINKED, "단위": unit, "꼬리표": [MINE]}
|
||||
|
||||
|
||||
def lines(block: dict[str, Any], us: units.Units, picks: dict | None = None) -> list[dict]:
|
||||
"""블록 단위 줄 → 줄 판정 `[{차례, 행, 묶음, 이름, 규격, 매개값, 엑셀금액, 재현, 상태, 방법, 단위, 후보,
|
||||
꼬리표, 글}]`(재현 = 엑셀 단가 × 매개값 ≈ 엑셀 금액 · D-6 띠) · `picks` = `{차례: 고름}`."""
|
||||
out = []
|
||||
for at, one in enumerate(block.get("항목") or []):
|
||||
q = one.get("품")
|
||||
row = {"차례": at, "행": one.get("행"), "묶음": one["묶음"], "이름": one.get("이름")}
|
||||
row |= {"규격": one.get("규격") or "", "엑셀금액": one.get("금액"), "재현": one.get("재현")}
|
||||
row["매개값"] = {WORD[one["묶음"]]: q} if one["묶음"] in WORD and q is not None else {}
|
||||
if one.get("밖"):
|
||||
out.append({**row, "상태": OUT, "방법": "", "단위": None, "후보": [], "꼬리표": []})
|
||||
continue
|
||||
got = us.line(one)
|
||||
pick = (picks or {}).get(at)
|
||||
if pick is not None:
|
||||
got = {**got, "후보": [], "글": "", **_picked(one, pick, us)}
|
||||
out.append({**row, **got})
|
||||
return out
|
||||
|
||||
|
||||
def verdict(rows: list[dict]) -> tuple[str, int]:
|
||||
"""블록 판정 — (「옮길 수 있음」 | 「고를 줄 n」, n)."""
|
||||
n = sum(1 for r in rows if r["상태"] not in (units.LINKED, OUT, DROP))
|
||||
return (GOOD if not n else f"고를 줄 {n}"), n
|
||||
|
||||
|
||||
def adds(block: dict, rows: list[dict], us: units.Units, ref_of: Any) -> tuple[list, dict]:
|
||||
"""이어진 줄 → 단위 SK 더한줄 · `{차례: add 번}` — `ref_of(하위)` = 참조 칸 | None(빠짐)."""
|
||||
out: list[dict] = []
|
||||
at_of: dict[int, int] = {}
|
||||
by_row = {r.get("행"): r["차례"] for r in rows}
|
||||
items = block.get("항목") or []
|
||||
for r in rows:
|
||||
if r["상태"] != units.LINKED:
|
||||
continue
|
||||
one, unit = items[r["차례"]], r["단위"] or {}
|
||||
name = " ".join(str(one.get("이름") or "").split()) or "이름 없음"
|
||||
q = float(one.get("품") or 0)
|
||||
kind = one["묶음"]
|
||||
if kind == "L":
|
||||
add = {"종류": "인력", "요소": unit["노임"], "이름": name, "단위": "인", "수량": q}
|
||||
elif kind == "X":
|
||||
eq = us.whole.get(unit["기계"]) or {}
|
||||
given = {skpatch.MACH_IN: eq.get("원문번호")}
|
||||
q *= float(unit.get("할증") or 1)
|
||||
add = {
|
||||
"종류": "로직",
|
||||
"로직": unit["로직"],
|
||||
"입력값": given,
|
||||
"이름": name,
|
||||
"단위": "시간",
|
||||
}
|
||||
add["수량"] = q
|
||||
elif kind == "M" and unit.get("자재"):
|
||||
add = {"종류": "재료", "요소": unit["자재"], "이름": name, "수량": q}
|
||||
add |= {"별도계상": True} if unit.get("별도계상") else {}
|
||||
elif kind == "R":
|
||||
basis = unit["기준"]
|
||||
if isinstance(basis, dict): # 줄 목록 = 그 행 줄
|
||||
basis = [f"add:#{at_of[k]}" for h in basis.get("행") or []
|
||||
if (k := by_row.get(h)) in at_of] # fmt: skip
|
||||
fx = {
|
||||
"이름": name,
|
||||
"단가": {},
|
||||
"이음": {},
|
||||
"비율": {"기준": basis, "비목": unit["비목"]},
|
||||
}
|
||||
add = {"고정": fx, "이름": name, "단위": "%", "수량": round(q * 100, 9)}
|
||||
elif kind == "S":
|
||||
ref = ref_of(unit["하위"])
|
||||
if ref is None:
|
||||
continue
|
||||
add = {"참조": ref, "이름": name, "수량": q or 1}
|
||||
else: # 값 · 못 찾은 별도계상 = 고정 금액 줄
|
||||
money = unit.get("값", 0)
|
||||
fx = {
|
||||
"이름": name,
|
||||
"단가": {k: float(v) for k, v in hfix._parts({"계": money}).items()},
|
||||
}
|
||||
add = {"고정": {**fx, "이음": {}}, "이름": name, "수량": q if kind == "M" else 1}
|
||||
at_of[r["차례"]] = len(out)
|
||||
out.append(add)
|
||||
return out, at_of
|
||||
|
||||
|
||||
def priced(rows: list[dict], made: list[dict], at_of: dict, unit: str, whole: Any) -> Any:
|
||||
"""M01 지금 값으로 다시 셈 → (저장 꼴 더한줄, 블록 1 단위 계 | None) · 줄 `우리금액` · `차`(참고) ·
|
||||
셈 못 함 = 금액 칸 비움(판정은 그대로)."""
|
||||
doc = {"키": "SK-000000", "이름": "판정", "단위": unit or "식", "바탕": None, "더한줄": made}
|
||||
try:
|
||||
doc = sd.clean(doc, None, whole) # 저장 꼴(같은 내용 SK 지문이 저장본과 같게)
|
||||
got = calc.run(doc, whole)
|
||||
except Exception: # noqa: BLE001
|
||||
return made, None
|
||||
money = {x.get("cid"): x.get("금액") for x in got.get("줄") or []}
|
||||
for r in rows:
|
||||
k = at_of.get(r["차례"])
|
||||
mine = money.get(f"add:#{k}") if k is not None else None
|
||||
r["우리금액"] = float(mine) if mine is not None else None
|
||||
theirs = r.get("엑셀금액")
|
||||
r["차"] = round(r["우리금액"] - float(theirs), 4) if mine is not None and theirs else None
|
||||
total = (got.get("1단위") or {}).get("계")
|
||||
return doc["더한줄"], float(total) if total is not None else None
|
||||
|
||||
|
||||
def judge(
|
||||
block: dict, us: units.Units, ref_of: Any, picks: dict | None = None, unit: str = ""
|
||||
) -> dict[str, Any]:
|
||||
"""블록 하나 → `{판정, 고를줄, 우리금액, 엑셀금액, 줄 [...], 더한줄, 담음}`(더한줄 = 단위 SK 몸 ·
|
||||
담음 = 더한줄에 든 줄 차례 · 이어졌는데 빠진 줄 = 하위 SK · SC 아직 없음)."""
|
||||
rows = lines(block, us, picks)
|
||||
word, n = verdict(rows)
|
||||
made, at_of = adds(block, rows, us, ref_of)
|
||||
made, total = priced(rows, made, at_of, unit, us.whole)
|
||||
head = {"판정": word, "고를줄": n, "우리금액": total}
|
||||
head["엑셀금액"] = unit_line.money(block.get("금액"))
|
||||
return {**head, "줄": rows, "더한줄": made, "담음": sorted(at_of)}
|
||||
|
||||
|
||||
def plan(got: dict[str, Any], basis: dict | None) -> dict[str, Any]:
|
||||
"""판정 → `Sangeun.Maker.write` 꼴 — 바탕 없음 · 덮개 · 뺀줄 없음 · 근거 꼬리표."""
|
||||
keep = {"갈래": UNIT, "바탕": None, "입력값": {}, "덮개": {}, "자재표시": {}, "뺀줄": []}
|
||||
out = {**keep, "더한줄": got["더한줄"], "못옮김": [], "놓음": []}
|
||||
return out | ({"근거": basis} if basis else {})
|
||||
@@ -489,9 +489,9 @@ def hopyo_delete(
|
||||
|
||||
|
||||
class SangeunBody(_Strict):
|
||||
"""`바탕` = 고름 대기 블록에 사용자가 고른 기초 `{블록 번: {로직, 입력값?}}`(729-1)."""
|
||||
"""`확인` = 사용자가 확인한 블록만 단위 SK `[{번, 고름?: {차례: 고름}}]`(778-5 · 자동 없음)."""
|
||||
|
||||
바탕: dict[str, dict[str, Any]] = Field(default_factory=dict)
|
||||
확인: list[dict[str, Any]] = Field(default_factory=list, max_length=500)
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/hopyo/{key}/sangeun")
|
||||
@@ -502,10 +502,10 @@ def hopyo_make_sangeun(
|
||||
owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""불러오기 결과 「단가산출」(718-1 · 729-1) — 산근 블록마다 매칭 → 비교 → 기초 · SK · 고름 대기
|
||||
(파일 층 = 이 층) → `{블록, 만듦, 그대로, 기초, SK, 고름대기, 별칭}` · 맞춤 셈 전 409."""
|
||||
"""불러오기 결과 「단가산출」(718-1 · 778-5) — 블록마다 줄 판정 · 확인한 블록만 단위 SK
|
||||
(파일 층 = 이 층) → `{블록, 판정, 만듦, 못만듦, 그대로, 같은것씀, 위층같음}` · 맞춤 셈 전 409."""
|
||||
folder = _root(session, layer, owner, write=True)
|
||||
picks = body.바탕 if body else {}
|
||||
picks = body.확인 if body else []
|
||||
return _call(folder, hopyo_sangeun.build, folder, key, _who(session), picks)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user