- 지움(git rm) 10 파일 3,374 줄: M02_Hopyo_Finger · Ours · Option · Fit · Combine · Verdict · Line · SkLink · SkPatch · Basis
- Match: 흐름 몫(candidates · name_cands · fix_typo · clause · b_order · _logic_specs · _line_mod · units_of · _state · view · guess · picks · chain) 지움 · 이름 도구 몫 · line_key 남김
- Words: conds · cond_score · close · cosine · jacc 지움 / Shares: sections 지움(trade · eq_key 남김)
- 옮김: Finger.kind_of → Fixed.kind_of · SkLink(sk_line · kept · left · LEFT · WHY) → Sangeun · SkPatch 기계 몫(MACH_IN · MACHINE · SOLO · _mach_logics) → Units.mach_logics
- Sangeun: 옛 /sangeun 몸(build · _sure · Maker.basis · judge · plan · make) 지움 · Kids: first · proposal · order 지움 · Build: build() · unlinked() 지움(모두 고정 Builder · build_all · _order · _origins 남김)
- Router: 옛 길 지움(GET hopyo/{키}/{번} · guess · hopyo-picks · hopyo-build · hopyo-unlinked · hopyo/{키}/sangeun) · GET hopyo/{키} = Judge.view(1차 줄 판정 줄상태 · 만든 · 블록 풀이 셈)
- Marks: 칠 = Judge.judged 만 · Forget: 맞춤 캐시 지움 줄 걷음 · Ledger: Ours.version 판 칸 걷음 · Item: Kids.first 걷음
- Row(덧): 단산 줄 = 장부 짝이 있어도 늘 「2차 필요」(장부 = 1차 후보 힌트만)
시험 정리(까닭 = 지운 길 · 모듈만 시험함):
- 지움 11 파일: test_m02_hopyo_finger · fit · golden · option · ours · basis · seam · flow · draft_calc · test_635 · test_628(지문 · 순위 · 옵션 거꾸로 · 맡은 절 · 옛 확인 흐름)
- 함수 지움: /sangeun · /hopyo-build · /hopyo-unlinked · hopyo-picks · 옛 줄 맞춤 보기를 부르던 것(572 · 718_1 · 729 · 739_1 · 777_1 · 778_5 · 780_1 · labor_national · 705_3 · m02_hopyo_match · 746_1)
- test_621 S2(지문 해시) 지움 · S1 남김 · test_m02_hopyo_build = 장부 길 시험만(PUT 으로 심음)
- 새 길로: 779_2(줄상태 = 1차 판정 · 칠 셈 전 None) · grid · stmate(hopyo-judge 줄 판) · 778_1 · 778_2(Units.mach_logics) · 642_6(Marks 원문) · 642_3 장부 짝 단산 줄 2차 지킴
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
298 lines
14 KiB
Python
298 lines
14 KiB
Python
"""M02 일위대가 불러오기 → 단가산출(PLAN 718-1 · 729-1 · 778-5 · 785-5 · 785-7 · 요구 621 · 623 · 642) — SK 쓰기 · 이음.
|
|
|
|
SK 쓰기 = `Maker.write`(옮기기 `M02_Hopyo_Make` 가 부름 · 확인한 블록만) — 이름 블록 이름(없으면 줄 품명) ·
|
|
단위 = 줄 단위(없으면 블록 · 「식」) · 구분 「실무자료」 · 상세구분 = 파일 · 본뜬 = {층 「실무자료」,
|
|
키 「HP-###### 단산N」, 파일, 원본, 확인, 갈래, 근거, 못옮김 []} · 같은 본뜬(키 · 원본)이 이 층에 있으면 그 키에 덮음 ·
|
|
만드는 층 = 부르는 층.
|
|
같은 내용(733-1 · `M02_Library_Same` 지문) SK 가 이 층에 있으면 새로 안 만들고 그것을 씀 — 이름 = 먼저 만든 것 ·
|
|
뒤 출처 = 그 SK `본뜬.또 [그 블록 본뜬 + 이름]`(`made` · `origin_of` 가 읽음) · 다른 출처가 쓰는 SK 는 내용을
|
|
안 바꿈(바뀌면 떼고 다시 찾음) · 위 층 같은 것 = `Maker.above`(알림만).
|
|
SK 이음(729-2 · 일위대가 모두 고정 길 · `M02_Hopyo_Build.Builder` · `M02_Hopyo_Make.Lines` 가 부름 — 옛 `SkLink`) =
|
|
확인 안 한 단가산출 줄 → 이 층 · 같은 원본 그 블록 SK 참조(`sk_line` · 없으면 「확인 전」 고름 대기 · 단위 다름 ·
|
|
못옮김 남음 = 못 이음) · 확인한 로직 줄이 차이 SK 의 바탕 = 그 SK 참조(`kept`) · 남은 줄 = `left`.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from decimal import Decimal
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from common_util.common_util_time import utc_now_text
|
|
from M01_MasterData import M01_MasterData_Store as m01
|
|
from M02_MasterTemplete import M02_Hopyo_Elem as elem
|
|
from M02_MasterTemplete import M02_Hopyo_Fixed as hfix
|
|
from M02_MasterTemplete import M02_Hopyo_Ledger as ledger
|
|
from M02_MasterTemplete import M02_Hopyo_Store as hopyo_store
|
|
from M02_MasterTemplete import M02_Hopyo_Units as units
|
|
from M02_MasterTemplete import M02_Labor_Doc as labor
|
|
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.M02_Hopyo_Kids import _same_mark
|
|
|
|
ORIGIN = "실무자료"
|
|
KIND = "sangeun"
|
|
WAIT_WHY = "확인 전 — [단가산출]에서 블록 [확인] 뒤 [옮기기]"
|
|
SK = "SK" # 옛 차이 SK 갈래(바탕 기초 + 차이 · 옛 파일)
|
|
LEFT = "안 이은 줄이 남음 — 줄마다 이을 것을 고름"
|
|
WHY = {
|
|
"단가산출": "맞는 기초 없음 — 고름 대기",
|
|
"자재": "자재 후보 여럿 · 없음",
|
|
"별도계상": "자재 후보 여럿 · 없음",
|
|
"노임": "같은 이름 노임 없음",
|
|
"기계": "같은 기계(EQ) 없음 · 여럿",
|
|
"호표": "하위 호표 단위 다름 · 같은 하위 둘째",
|
|
"%": "밑수가 다른 줄 금액 합 아님",
|
|
}
|
|
mm_unit = m01.cm.mf.mp.mm.plain_unit
|
|
|
|
|
|
def mark(key: str, no: int) -> str:
|
|
return f"{key} 단산{no}"
|
|
|
|
|
|
def made(
|
|
folder: str | Path, key: str | None = None, source: refs.Here | None = None
|
|
) -> dict[str, str]:
|
|
"""이 층 실무자료 단가산출 `{본뜬 키: SK 키}`(`key` = 그 HP 것만 · `source` = 파일 층으로 거름)."""
|
|
out = {}
|
|
for path in sorted((Path(folder) / KIND).glob("SK-*.json")):
|
|
try:
|
|
got = json.loads(path.read_bytes()).get("본뜬")
|
|
except (OSError, ValueError, AttributeError):
|
|
continue
|
|
if not isinstance(got, dict) or got.get("층") != ORIGIN:
|
|
continue
|
|
for one in [got, *(got.get("또") or [])]: # 또 = 같은 내용으로 이 SK 를 쓴 뒤 출처(733-1)
|
|
mark_ = str((one or {}).get("키") or "") if isinstance(one, dict) else ""
|
|
if not mark_ or (key is not None and not mark_.startswith(f"{key} ")):
|
|
continue
|
|
if source is None or not one.get("원본") or tuple(one["원본"]) == tuple(source):
|
|
out[mark_] = path.stem
|
|
return out
|
|
|
|
|
|
def origin_of(doc: dict, mark_: str, source: refs.Here | None = None) -> dict[str, Any]:
|
|
"""그 블록 출처의 본뜬(갈래 · 못옮김 · 놓음 …) — 또 가운데 `mark_` · 없으면 제 본뜬."""
|
|
own = doc.get("본뜬") or {}
|
|
want = {"키": mark_, "원본": list(source) if source else None}
|
|
also = [x for x in own.get("또") or [] if isinstance(x, dict)]
|
|
return next((x for x in also if _same_mark(x, want)), own)
|
|
|
|
|
|
def calls(hp: dict[str, Any]) -> tuple[dict[int, dict], dict[int, dict]]:
|
|
"""(블록 {번: 블록}, 부르는 첫 줄 {번: 줄 + `_호표`})."""
|
|
blocks = {int(b["번"]): b for b in hp.get("단산") or [] if b.get("번") is not None}
|
|
first: dict[int, dict] = {}
|
|
for h in hp.get("호표") or []:
|
|
for line in h.get("줄") or []:
|
|
ref = line.get("참조") or {}
|
|
no = int(ref.get("번") or 0) if ref.get("종류") == "단산" else 0
|
|
if no in blocks and no not in first:
|
|
first[no] = {**line, "_호표": h.get("번")}
|
|
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 _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 ("갈래", "엑셀품", "못옮김", "근거") if k in plan}
|
|
keep |= {"놓음": plan["놓음"]} if plan.get("놓음") else {}
|
|
return {
|
|
"종류": "단가산출",
|
|
"이름": name,
|
|
"단위": unit,
|
|
"구분": ORIGIN,
|
|
"상세구분": str(hp.get("파일") or ""),
|
|
"본뜬": {"층": ORIGIN, "키": mark(hp["키"], no), "파일": hp.get("파일"), **sure, **keep},
|
|
"바탕": plan["바탕"],
|
|
"입력값": plan["입력값"],
|
|
**({"환산": plan["환산"]} if plan.get("환산") else {}),
|
|
"열린입력": [],
|
|
"고름": {},
|
|
**{k: plan[k] for k in ("자재표시", "덮개", "더한줄", "뺀줄")},
|
|
}
|
|
|
|
|
|
class Maker:
|
|
"""한 번 만들기 — 파일 · 이 층 SK 를 한 번 읽음(`source` = 파일 층 · 쓰기 = `store.root()`)."""
|
|
|
|
def __init__(self, source: Path, key: str, who: Any = None) -> None:
|
|
self.source, self.key, self.who = Path(source), key, who
|
|
self.hp = hopyo_store.read(source, key)
|
|
self.blocks, self.first = calls(self.hp)
|
|
self.here = refs.where(source)
|
|
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._units: units.Units | None = None
|
|
|
|
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 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 → 여기 — 늦게 읽음
|
|
|
|
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 write(self, no: int, plan: dict, have: str | None) -> str:
|
|
"""그 블록 SK 를 씀 → 키 — 다른 출처(또)가 쓰는 SK 의 내용은 안 바꿈(바뀌면 떼고 다시 찾음)."""
|
|
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:
|
|
raw, old, own, rest = self._read(have, mine)
|
|
owner = _same_mark(own, mine)
|
|
if self.same.finger(KIND, old) == self.same.finger(KIND, doc): # 내용 같음 = 출처만
|
|
self._put(
|
|
have, raw, old, *((mine, rest) if owner else (own, [*rest, self._entry(doc)]))
|
|
)
|
|
return have
|
|
if owner and not rest: # 혼자 쓰던 SK — 제자리 덮음
|
|
store.write(KIND, have, store.version_of(raw), {**doc, "키": have})
|
|
self.same.add(root, KIND, have, doc)
|
|
return have
|
|
head = {k: v for k, v in rest[0].items() if k != "이름"} if owner else own
|
|
self._put(have, raw, old, head, rest[1:] if owner else rest) # 떼어 냄
|
|
found = self.same.find_same(root, KIND, doc)
|
|
if found:
|
|
raw, old, own, rest = self._read(found[0], mine)
|
|
self._put(found[0], raw, old, own, [*rest, self._entry(doc)])
|
|
self.reused.append({"번": no, "키": found[0]})
|
|
return found[0]
|
|
key = refs.create(KIND, doc)["이름"]
|
|
self.same.add(root, KIND, key, doc)
|
|
return key
|
|
|
|
@staticmethod
|
|
def _entry(doc: dict) -> dict[str, Any]:
|
|
return {**doc["본뜬"], "이름": doc["이름"]}
|
|
|
|
@staticmethod
|
|
def _read(key: str, mine: dict) -> tuple[bytes, dict, dict, list]:
|
|
"""(바이트, 문서, 제 본뜬(또 뺌), 또 가운데 `mine` 뺀 것)."""
|
|
raw = (store.root() / KIND / f"{key}.json").read_bytes()
|
|
doc = json.loads(raw)
|
|
got = doc.get("본뜬") or {}
|
|
own = {k: v for k, v in got.items() if k != "또"}
|
|
rest = [x for x in got.get("또") or [] if isinstance(x, dict) and not _same_mark(x, mine)]
|
|
return raw, doc, own, rest
|
|
|
|
@staticmethod
|
|
def _put(key: str, raw: bytes, doc: dict, own: dict, also: list) -> None:
|
|
body = {**doc, "본뜬": {**own, "또": also} if also else own}
|
|
if body != doc:
|
|
store.write(KIND, key, store.version_of(raw), body)
|
|
|
|
|
|
# ── SK 이음(일위대가 모두 고정 길 · 옛 `M02_Hopyo_SkLink`) ───────────────────────────
|
|
def _num(value: Decimal) -> int | float:
|
|
value = value.normalize()
|
|
return int(value) if value == value.to_integral_value() else float(value)
|
|
|
|
|
|
def sks(b: Any) -> dict[str, str]:
|
|
"""이 층 그 HP 의 SK `{본뜬 키: SK 키}`(한 번 읽어 둠 · `b` = `Builder`)."""
|
|
if b._sk is None:
|
|
b._sk = made(b.folder, b.key, b.source)
|
|
return b._sk
|
|
|
|
|
|
def sk_doc(b: Any, key: str) -> dict[str, Any]:
|
|
try:
|
|
got = json.loads((b.folder / KIND / f"{key}.json").read_bytes())
|
|
except (OSError, ValueError):
|
|
return {}
|
|
return got if isinstance(got, dict) else {}
|
|
|
|
|
|
def _ref(key: str, alias: Any, memo: str, qty: Decimal) -> dict[str, Any]:
|
|
head = {"층": refs.here()[0], "주인": refs.here()[1], "키": key}
|
|
return {"참조": head, "별칭": alias, "메모": memo, "수량": {"손": _num(qty)}}
|
|
|
|
|
|
def _lost(no: int, key: str, lost: list) -> dict[str, Any]:
|
|
why = f"단가산출 못옮김 {len(lost)} — [단가산출 만들기]에서 이음"
|
|
return {"까닭": why, "단산": no, "SK": key, "못옮김": lost}
|
|
|
|
|
|
def sk_line(b: Any, line: dict[str, Any], create: bool) -> tuple[dict | None, dict | None]:
|
|
"""단가산출 줄 → (그 블록 SK 참조 줄, 못 이은 까닭 {까닭, 후보?}) — SK 없음 + `create`(불러오기 길) =
|
|
부르는 블록이면 「확인 전」(SK 는 옮기기로만 만듦)."""
|
|
ref = line.get("참조") or {}
|
|
if ref.get("종류") != "단산":
|
|
return None, None
|
|
no = int(ref.get("번") or 0)
|
|
key = sks(b).get(mark(b.key, no))
|
|
if key is None:
|
|
if create and no in calls(b.doc)[1]:
|
|
return None, {"까닭": WAIT_WHY, "후보": []}
|
|
return None, None
|
|
doc = sk_doc(b, key)
|
|
lost = origin_of(doc, mark(b.key, no), b.source).get("못옮김") # 출처마다(733-1)
|
|
if lost: # 연결 안 되면 못 만듦(내역 20-4)
|
|
return None, _lost(no, key, lost)
|
|
unit = doc.get("단위") or line.get("단위")
|
|
if mm_unit(unit) != mm_unit(line.get("단위")):
|
|
return None, {"까닭": f"단가산출 단위 「{unit}」 다름"}
|
|
qty = Decimal(str(line.get("수량") or 0))
|
|
alias = str(line.get("이름") or "")[: labor.ALIAS_MAX].strip() or None
|
|
return _ref(key, alias, f"실무 {qty}{line.get('단위') or ''} · 단산{no}", qty), None
|
|
|
|
|
|
def kept(
|
|
b: Any, line: dict, one: dict, qty: Decimal, alias: Any, memo: str
|
|
) -> tuple[dict | None, dict | None]:
|
|
"""확인한 로직 줄이 그 블록 차이 SK 의 바탕 = (그 SK 참조, 못옮김 남으면 까닭) · 아니면 (None, None)."""
|
|
ref = line.get("참조") or {}
|
|
if ref.get("종류") != "단산" or one.get("몫") is not None:
|
|
return None, None
|
|
no = int(ref.get("번") or 0)
|
|
key = sks(b).get(mark(b.key, no))
|
|
doc = sk_doc(b, key) if key else {}
|
|
base = (doc.get("바탕") or {}).get("로직")
|
|
mine = origin_of(doc, mark(b.key, no), b.source) if doc else {}
|
|
if mine.get("갈래") != SK or base != one["로직"]:
|
|
return None, None
|
|
lost = mine.get("못옮김") # sk_line 과 같은 규칙 — 차이 잃지 않음(내역 20-4)
|
|
return _ref(key, alias, f"{memo} · 차이 SK", qty), _lost(no, key, lost) if lost else None
|
|
|
|
|
|
def left(line: dict[str, Any], why: dict | None) -> dict[str, Any]:
|
|
"""남은 줄 한 줄 — 엑셀 줄 · 갈래 · 까닭 · 후보(기계 = M01 EQ 셋 · 746-1)."""
|
|
kind = hfix.kind_of(line)
|
|
keep = ("차례", "이름", "규격", "단위", "수량")
|
|
out = {**{k: line.get(k) for k in keep}, "갈래": kind}
|
|
out["까닭"] = (why or {}).get("까닭") or WHY.get(kind, "이을 것 없음")
|
|
more = {k: v for k, v in (why or {}).items() if k not in ("까닭", "후보")}
|
|
cands = (why or {}).get("후보") or []
|
|
if not cands and kind == "기계":
|
|
cands = elem.machines(f"{line.get('이름') or ''} {line.get('규격') or ''}")
|
|
return {**out, "후보": cands, **more}
|