feat(740-1): 일위대가 안 일위대가 = 사본 블록(원본 연결 끊음 · 하위 호표 끝수 그대로) · 초기화 이음 기계 다시 받음

- SC 안 SC = 안쪽 SC 몸 통째를 그 줄 참조 꼴 {층: 사본, 주인: 몸 지문, 키: 본 SC, 몸} 에 담음(Labor_Ref.copy_ref · read_ref 가 몸을 돌려줌 → 셈 · 알약 · B09 하위 대가 블록 · 지문 = 옛 참조 줄과 같은 길) · 엑셀 대조 거창 같음 14 그대로 · 같은 기초 겹침도 블록 안(400 없음)
- 저장 · 읽기 · 옮기기 · 찾아 넣기 모두 사본 블록 · 자기 자신을 담은 줄 = 안 펼침(저장 422 그대로)
- 단가산출에 SC 넣기 = 사본 블록 줄도 더한줄로 펼침(바깥 × 안 수량)
- 초기화 = 사본 몸 안까지 · 이음 기계 고정 줄(736-3) 단가 = M01 기계 한 시간 값 다시 받음
- 옮기기 = 쓰는 곳 검사 걷음(같은 자리 한 줄이라 안 줄 알약 차례 그대로) · 실 storage 보기만 = 1/3 SC-000002 사본 블록 · 전후 1,446,078 같음
- 낡은 시험 고침(까닭 = 하위 호표 참조가 사본 블록으로): m02_labor_ref(순환 대신 사본 · 복사 딸림 없음) · 733-2(딸림 재사용 = 구조물 조합 복사) · 735-1(쓰는 곳 = 구조물 조합 둘) · hopyo_build · 707-7(하위 호표 줄 = 사본 블록 · 본뜬)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-02 20:17:17 +09:00
co-authored by Claude Opus 5.5
parent 6c0211af1f
commit 6893599fa9
8 changed files with 206 additions and 164 deletions
+83 -95
View File
@@ -10,15 +10,17 @@
수량 = 넣은 수 × 줄 수량(저장 `수량.손` › 실무수량 › 1 · 742-1) ÷ 배(물량 알약 배)
SC 에 SK 로직 줄 하나 + 패치(= SK 몸) · 별칭 = SK 이름 · 같은 자리(안 줄 알약 키 그대로) ·
바탕 없는 SK · 단위 환산 SK = 못 넣음(값 단위가 달라짐)
SC 에 SC 찾아 넣기 길만 — 그 자리에 안 줄 · 자재를 펴 넣음 · 줄 수량 · 본뜬 실무수량 =
바깥 × 안 · 같은 기초 · 고정 · 자재가 겹치면 못 넣음(일위대가 줄 짝 = 로직 키) ·
저장 · 읽기 · 옮기기는 하위 호표 참조 그대로(엑셀 하위 호표 끝수 · B09 하위 대가)
SC 에 SC 사본 블록 — 안쪽 SC 몸 통째(M01 키 줄 + 덮개 · 실무수량)를 그 줄 참조 꼴
`{층: 사본, 주인: 몸 지문, 키: 본 SC, 몸}` 에 담음(`M02_Labor_Ref.copy_ref` · 원본
연결 없음 · 하위 호표 끝수 단계 · B09 하위 대가 블록 그대로 · 같은 기초 겹침도
블록 안)
바탕 SK 참조 · 패치 있는 SK 참조 줄 = 위 덮개를 아래 덮개에 겹쳐 한 벌(`compose`)
못 펼친 줄 = 저장 · 읽기 · 옮기기에선 옛 참조 그대로(값 그대로) · 넣기 길(`strict`)에선 400 까닭.
초기화(`reset`) = 덮개 · 뺀줄 · 더한줄 단가 걷음(안쪽 넣은 몸까지) · 항목 · 수량 · 입력값 · 고름 ·
자재표시 · 켜쌓기율 · 고정 줄 · 기본정보 남김 → 값 = 연결된 M01 키 값.
초기화(`reset`) = 덮개 · 뺀줄 · 더한줄 단가 걷음(안쪽 넣은 몸 · 사본 블록까지) · 이음 기계 고정 줄
단가 = M01 기계 한 시간 값 다시 받음(736-3) · 항목 · 수량 · 입력값 · 고름 · 자재표시 · 켜쌓기율 ·
기본정보 남김 → 값 = 연결된 M01 키 값.
옮기기(`migrate`) = 층 문서 펼쳐 다시 씀 — 전후 1 단위 금액 같을 때만(안 줄 수량 모두 1 로 대조) ·
쓰는 구조물 조합이 있는 SC 펼쳐 넣기(차례 바뀜)는 안 옮김 · 공유 storage 는 브레인이 돌림.
공유 storage 는 브레인이 돌림.
"""
from __future__ import annotations
@@ -193,6 +195,9 @@ def sc_adds(z: dict, q: Decimal, whole: Any, tag: dict) -> list[dict]:
"이름": one["고정"].get("이름") or "",
"수량": sd.plain(n),
}
elif refs.is_copy(one.get("참조")): # 하위 호표 사본 블록 = 그 줄들도 더한줄로
out += sc_adds(one["참조"]["몸"], n, whole, tag)
continue
else:
raise sd.DocError("비율 고정 줄 · 참조 줄은 단가산출에 못 담음")
out.append({**entry, "본뜬": tag})
@@ -346,19 +351,13 @@ def _twice(doc: dict) -> str | None:
def flat_sc(doc: dict, read: Read, whole: Any, trail: tuple = (), strict: bool = True) -> dict:
"""SC 문서 → M01 키만 — SK 참조 = 로직 줄 · SC 참조 = 안 줄 펴 넣음(`strict` 만 · 넣기 길) ·
`strict` False(저장 · 읽기 · 옮기기) = SC 참조 그대로(하위 호표 · 엑셀 끝수 단계) · 못 펼친
줄 그대로."""
hands = (doc.get("본뜬") or {}).get("실무수량")
old_q = dict((hands or {}).get("줄") or {})
new_q, mat_q = {}, dict((hands or {}).get("자재") or {})
lines, mats = [], list(doc.get("자재") or [])
"""SC 문서 → M01 키만 — SK 참조 = 로직 줄 + 패치(같은 자리) · SC 참조 = 사본 블록(같은 자리) ·
`strict` False = 못 펼친 줄 그대로(겹침 · 바탕 없음 · 환산 · 없는 참조)."""
lines = []
for at, one in enumerate(doc.get("담은로직") or []):
ref = one.get("참조") if isinstance(one, dict) else None
kind = refs.kind_of(ref.get("키")) if isinstance(ref, dict) else None
here_q = old_q.get(str(at))
if kind != "sangeun" and not (strict and kind == "precombo"): # 하위 호표 = 참조 그대로
new_q |= {str(len(lines)): here_q} if here_q is not None else {}
if kind not in ("sangeun", "precombo") or refs.is_copy(ref):
lines.append(one)
continue
try:
@@ -366,80 +365,39 @@ def flat_sc(doc: dict, read: Read, whole: Any, trail: tuple = (), strict: bool =
tag = mark(ref, src)
if kind == "sangeun":
y = flat_sk(sd.clean(src, None, whole), read, whole, path)
put, put_m = [sk_line(y, one, whole, tag)], []
put_q = {"0": here_q} if here_q is not None else {}
put = sk_line(y, one, whole, tag)
else:
z = flat_sc(src, read, whole, path)
put, put_q, put_m = _spread(z, one, here_q, tag)
body = flat_sc(src, read, whole, path)
put = {**one, "참조": refs.copy_ref(body, ref["키"]), "본뜬": tag}
rest = (doc.get("담은로직") or [])[at + 1 :]
trial = {"담은로직": [*lines, *put, *rest], "자재": [*mats, *put_m]}
trial = {"담은로직": [*lines, put, *rest], "자재": doc.get("자재") or []}
if twice := _twice(trial):
raise sd.DocError(f"같은 줄이 겹침 「{twice}」 — 펼쳐 넣지 못함")
except FAIL as e:
if strict:
raise sd.DocError(f"「{ref.get('키')}」 · {e}") from e
new_q |= {str(len(lines)): here_q} if here_q is not None else {}
lines.append(one)
continue
new_q |= {str(len(lines) + int(n)): v for n, v in put_q.items()}
mat_q |= {m["코드"]: m.pop("_q") for m in put_m if "_q" in m}
lines, mats = [*lines, *put], [*mats, *put_m]
out = {**doc, "담은로직": lines, "자재": mats}
if hands is not None:
out["본뜬"] = {**doc["본뜬"], "실무수량": {**hands, "줄": new_q, "자재": mat_q}}
return out
def _spread(z: dict, one: dict, here_q: Any, tag: dict) -> tuple[list, dict, list]:
"""안 SC 줄 · 자재 펴기 — 실무수량 = 바깥 × 안(둘 다 있을 때만) · 비율 고정 줄 있으면 못 폄
(밑 = 그 SC 합 → 바깥 합으로 바뀜)."""
if any(refs.is_rate(x) for x in z.get("담은로직") or []):
raise sd.DocError("비율 고정 줄이 있는 일위대가는 펴 넣지 못함(밑 합이 바뀜)")
hands = (z.get("본뜬") or {}).get("실무수량") or {}
inner, inner_m = hands.get("줄") or {}, hands.get("자재") or {}
outer = (one.get("수량") or {}).get("손") if isinstance(one.get("수량"), dict) else None
def times(
x: dict,
) -> Any: # 줄 수량(742-1) = 바깥 × 안 · 둘 중 하나라도 없으면 쓰는 쪽 몫(null)
inner = (x.get("수량") or {}).get("손") if isinstance(x.get("수량"), dict) else None
both = outer not in EMPTY and inner not in EMPTY
return {"손": sd.plain(_d(outer) * _d(inner))} if both else None
lines = [{**x, "수량": times(x), "본뜬": tag} for x in z.get("담은로직") or []]
if len(lines) == 1 and one.get("별칭"):
lines[0]["별칭"] = one["별칭"]
both = here_q is not None
qty = {
str(n): sd.plain(_d(here_q) * _d(inner[str(n)]))
for n in range(len(lines))
if both and inner.get(str(n)) is not None
}
mats = []
for m in z.get("자재") or []:
extra = (
{"_q": sd.plain(_d(here_q) * _d(inner_m[m["코드"]]))}
if both and m.get("코드") in inner_m
else {}
)
mats.append({**m, "본뜬": tag, **extra})
return lines, qty, mats
put = one
lines.append(put)
return {**doc, "담은로직": lines}
# ── 문 ────────────────────────────────────────────────────────────────
def flat_doc(kind: str, doc: Any, read: Read = refs.read_ref, strict: bool = False) -> Any:
"""층 문서 하나 펼침(저장 · 읽기 · 복사 · 옮기기) — 참조 없으면 받은 그대로."""
def flat_doc(
kind: str, doc: Any, read: Read = refs.read_ref, strict: bool = False, me: Any = None
) -> Any:
"""층 문서 하나 펼침(저장 · 읽기 · 복사 · 옮기기) — 참조 없으면 받은 그대로 · `me` = 이 문서
참조(자기 자신을 담은 줄 = 안 펼침 → 저장 검사가 422)."""
if not isinstance(doc, dict) or kind not in refs.PREFIX or not refs.refs_in(doc):
return doc
whole = _whole()
whole, trail = _whole(), (refs.ref_id(me),) if isinstance(me, dict) else ()
if kind == "sangeun":
return flat_sk(doc, read, whole, (), strict)
return flat_sc(doc, read, whole, (), strict)
return flat_sk(doc, read, whole, trail, strict)
return flat_sc(doc, read, whole, trail, strict)
def flat_lines(kind: str, ref: Any, qty: Any = 1, doc: Any = None) -> dict[str, Any]:
"""찾아 넣기(`-flat` 길) — `kind` 문서에 `ref` 를 넣을 줄 · 못 펼침 = StoreError 400 ·
SC = 지금 화면 문서 `doc` 과 겹치면 400."""
SC = 로직 줄(SK) · 사본 블록 줄(SC) 하나 · 지금 화면 문서 `doc` 과 겹치면 400."""
from M02_MasterTemplete import M02_MasterTemplete_Store as store
at = refs.here()
@@ -450,15 +408,27 @@ def flat_lines(kind: str, ref: Any, qty: Any = 1, doc: Any = None) -> dict[str,
return {"더한줄": into_sk(ref, qty if qty not in EMPTY else 1, refs.read_ref, whole)}
got = flat_sc({"담은로직": [{"참조": ref, "수량": None}]}, refs.read_ref, whole)
host = doc if isinstance(doc, dict) else {}
trial = {k: [*(host.get(k) or []), *got[k]] for k in ("담은로직", "자재")}
if twice := _twice(trial):
trial = {"담은로직": [*(host.get("담은로직") or []), *got["담은로직"]]}
if twice := _twice({**trial, "자재": host.get("자재") or []}):
raise sd.DocError(f"이미 담은 줄과 겹침 「{twice}」")
return {"담은로직": got["담은로직"], "자재": got["자재"]}
return {"담은로직": got["담은로직"], "자재": []}
except FAIL as e:
raise store.StoreError(getattr(e, "status", 400), str(e)) from e
# ── 초기화 ────────────────────────────────────────────────────────────
def _reprice(fx: dict) -> tuple[dict, int]:
"""이음 기계 고정 줄(736-3) 단가 = M01 기계 한 시간 값 다시 받음 — (줄, 바뀐 수)."""
from M02_MasterTemplete import M02_Machine_Hourly as machine
key = (fx.get("이음") or {}).get("기계")
got = machine.hourly(str(key)) if key else {}
if not got.get("ok"):
return fx, 0
price = {k: got[k] for k in sd.PARTS.values()}
return {**fx, "단가": price}, int(price != fx.get("단가"))
def _reset_adds(adds: list) -> tuple[list, int]:
out, n = [], 0
for one in adds or []:
@@ -474,31 +444,50 @@ def _reset_adds(adds: list) -> tuple[list, int]:
if one.get("더한줄"):
one["더한줄"], k = _reset_adds(one["더한줄"])
n += k
if isinstance(one.get("고정"), dict):
one["고정"], k = _reprice(one["고정"])
n += k
out.append(one)
return out, n
def reset(kind: str, doc: dict) -> tuple[dict, int]:
"""[초기화] — (걷은 문서, 걷힌 고침 수) · 먼저 펼침(옛 참조 줄도 M01 키로)."""
def _reset_sc(doc: dict) -> tuple[dict, int]:
"""일위대가 초기화 — 줄 패치 · 사본 블록 몸 · 이음 기계 고정 줄."""
from M02_MasterTemplete import M02_Labor_Patch as patch
lines, n = [], 0
for one in doc.get("담은로직") or []:
if not isinstance(one, dict):
lines.append(one)
continue
ref, body = one.get("참조"), one.get(patch.FIELD)
if refs.is_copy(ref):
inner, k = _reset_sc(ref["몸"])
n += k
one = {**one, "참조": refs.copy_ref(inner, ref["키"])}
elif isinstance(one.get("고정"), dict):
fx, k = _reprice(one["고정"])
n += k
one = {**one, "고정": fx}
elif body:
adds, k = _reset_adds(body.get("더한줄") or [])
n += k + len(body.get("덮개") or {}) + len(body.get("뺀줄") or [])
keep = {x: v for x, v in body.items() if x not in ("덮개", "뺀줄", "더한줄")}
rest = {**keep, **({"더한줄": adds} if adds else {})}
one = {x: v for x, v in one.items() if x != patch.FIELD}
one = {**one, **({patch.FIELD: rest} if rest else {})}
lines.append(one)
return {**doc, "담은로직": lines}, n
def reset(kind: str, doc: dict) -> tuple[dict, int]:
"""[초기화] — (걷은 문서, 걷힌 고침 수) · 먼저 펼침(옛 참조 줄도 M01 키로)."""
doc = flat_doc(kind, doc)
if kind == "sangeun":
adds, n = _reset_adds(doc.get("더한줄") or [])
n += len(doc.get("덮개") or {}) + len(doc.get("뺀줄") or [])
return {**doc, "덮개": {}, "뺀줄": [], "더한줄": adds}, n
lines, n = [], 0
for one in doc.get("담은로직") or []:
body = one.get(patch.FIELD) if isinstance(one, dict) else None
if not body:
lines.append(one)
continue
adds, k = _reset_adds(body.get("더한줄") or [])
n += k + len(body.get("덮개") or {}) + len(body.get("뺀줄") or [])
rest = {**({"더한줄": adds} if adds else {})}
line = {x: v for x, v in one.items() if x != patch.FIELD}
lines.append({**line, **({patch.FIELD: rest} if rest else {})})
return {**doc, "담은로직": lines}, n
return _reset_sc(doc)
def reset_doc(kind: str, key: str | None, doc: Any, look: bool, version: str | None) -> dict:
@@ -550,7 +539,9 @@ def migrate(apply: bool = False) -> list[dict[str, Any]]:
ref = {"층": layer, "주인": owner, "키": path.stem}
got = flat_doc(kind, doc)
left = [
r.get("키") for r in refs.refs_in(got) if refs.kind_of(r.get("키")) != "promote"
r.get("키")
for r in refs.refs_in(got)
if refs.kind_of(r.get("키")) != "promote" and not refs.is_copy(r)
]
row = {
"어디": where_,
@@ -565,10 +556,7 @@ def migrate(apply: bool = False) -> list[dict[str, Any]]:
row["전"], row["후"] = _value(ref, refs.read_ref, whole), _value(ref, mine, whole)
row["같음"] = row["전"] == row["후"]
used = kind == "precombo" and len(got["담은로직"]) != len(doc.get("담은로직") or [])
if used and refs.users(ref):
row["까닭"] = "쓰는 곳 있는 일위대가 — 안 줄 차례가 바뀌어 안 옮김"
elif not row["같음"]:
if not row["같음"]:
row["까닭"] = "전후 금액 다름 — 안 옮김"
elif apply and got != doc:
with store.at(path.parent.parent):