- Row.judge: 별도계상 줄은 M01 · 사전 · 장부 후보 안 찾고 고정 줄 하나(품목 「프로젝트에서」 표지) · 갈래 수 그대로 - Make: 별도계상 고정 줄 = 단가 0 · 메모 「별도계상 · 품목은 프로젝트에서」(합 밖) - Picks.ts: apartPick 표지 셈 - 시험 642_3 · 642_5 · 642_6 Picks 몫을 새 꼴로 고침(옛 단언: 레미콘 자재 목록 별도계상 칸) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
410 lines
21 KiB
Python
410 lines
21 KiB
Python
"""M02 엑셀 불러오기 만들기(PLAN 785-5 · 요구 642 · 설계 642-A 2-7 ⑥) — 확인한 것 → 우리 SC · SK · 장부.
|
||
|
||
옮기기 몸 = `{호표 {번: {차례: [고른 것]}}, 블록 {번: [고른 것]}}` · 고른 것 = 785-3 꼴
|
||
`{갈래 기초|단가산출|일위대가|인력|재료|유가|기계|율|고정, 키, 입력값, 몫, 배?, 비목?, 기준?, 별도계상?, 층?,
|
||
주인?, 항목?}` · 서버가 다시 셈(브라우저 금액 · 품 안 받음 · 배 = `Row.factor` · 사용자 배 우선 ·
|
||
입력값 = 엔진이 쓴 칸 모두 `Judge.inputs`) · 「입력 필요」 · 배 못 정함 · 없는 키 = 그 줄(블록)만 `막힘`.
|
||
SC(`M02_Hopyo_Build.Builder` 가 줄 차례 · 확인 안 한 줄 = 모두 고정 길 그대로) — 한 엑셀 줄 = 고른 것마다
|
||
우리 줄(같은 별칭 = 엑셀 품명 · 줄 `본뜬 {호표, 차례, 행}`) · 수량 {손 = 엑셀 수량 × 몫 × 배} ·
|
||
기초 = 로직 줄(같은 로직 둘째 = 한 줄 SC 사본) · 재료 · 유가 = 자재 목록(별도계상) · 인력 · 기계 · 유가 이음 ·
|
||
별도계상 줄(`고정` + 별도계상 · 786-1) = 단가 0 고정 줄(합 밖 · 메모 「별도계상 · 품목은 프로젝트에서」) ·
|
||
율 = 고정 줄 `비율 {기준, 비목}` · 고정 = 엑셀 단가 고정 줄 · 단가산출 · 일위대가 = 참조
|
||
(`단산N` = 같은 옮기기 블록 SK · `제N호표` = 같은 HP 호표 SC — 하위 먼저 · 확인 안 된 하위 = 모두 고정).
|
||
SK(블록) = 바탕 없음 + 더한줄 — 기초 `{종류 로직, 입력값, 수량 = 몫}` · 참조 · 단위 줄(`항목`)에 이은
|
||
인력 · 기계 · 재료 · 유가 · 율 = `SkUnit.adds` · 고정 = 엑셀 금액 · 쓰기 = `Sangeun.Maker.write`.
|
||
같은 내용 재사용(`Kids.save` · `Sangeun.Maker.write`) · 장부 = 고른 것 꼴 짝(`Ledger.check_pair`) ·
|
||
엑셀 대조 = `Compare.compare`(참고 · 비목마다 1 원).
|
||
`Lines` = 확인한 줄 몸(옛 대상 꼴 `put` 은 옛 길 몫 · 785-7 지움).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
from decimal import Decimal
|
||
from pathlib import Path
|
||
from typing import Any
|
||
|
||
from M02_MasterTemplete import M02_Hopyo_Build as hbuild
|
||
from M02_MasterTemplete import M02_Hopyo_Fixed as hfix
|
||
from M02_MasterTemplete import M02_Hopyo_Judge as judge
|
||
from M02_MasterTemplete import M02_Hopyo_Kids as hkids
|
||
from M02_MasterTemplete import M02_Hopyo_Ledger as ledger
|
||
from M02_MasterTemplete import M02_Hopyo_Material as hmat
|
||
from M02_MasterTemplete import M02_Hopyo_Sangeun as sangeun
|
||
from M02_MasterTemplete import M02_Hopyo_SkUnit as skunit
|
||
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 lsame
|
||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||
from M02_MasterTemplete import M02_Sangeun_Doc as sd
|
||
from M02_MasterTemplete.M02_Hopyo_Compare import compare
|
||
|
||
_SUB = {"단가산출": re.compile(r"^단산(\d+)$"), "일위대가": re.compile(r"^제(\d+)호표$")}
|
||
LINK = {"인력": "노임", "기계": "기계", "유가": "유가"}
|
||
OLD_PICK = {"인력": "노임", "기계": "기계", "재료": "자재", "유가": "자재"}
|
||
NEED = "입력 필요"
|
||
APART_MEMO = "별도계상 · 품목은 프로젝트에서"
|
||
COVER = "이미 옮긴 것이 있음 — 덮으려면 [덮어 옮기기](덮음)"
|
||
|
||
|
||
class Stuck(Exception):
|
||
"""그 줄(블록)만 못 옮김 — `{까닭, 칸?}`."""
|
||
|
||
def __init__(self, why: str, cells: list[str] | None = None) -> None:
|
||
super().__init__(why)
|
||
self.why, self.cells = why, cells or []
|
||
|
||
|
||
def _num(value: Decimal) -> int | float:
|
||
return hbuild._num(value)
|
||
|
||
|
||
# ── 확인한 줄 몸(SC) ─────────────────────────────────────────────────────
|
||
class Lines:
|
||
"""한 호표 줄 모음 — `Builder.build` 가 줄 차례대로 부름 · 확인 안 한 줄은 Builder 가 고정 길로."""
|
||
|
||
def __init__(self, b: Any, rows: list[dict], where: dict, stamp: str, linker: Any) -> None:
|
||
self.b, self.rows, self.where, self.stamp, self.linker = b, rows, where, stamp, linker
|
||
self.lines: list[dict] = []
|
||
self.mats: dict[str, dict] = {}
|
||
self.missing: list[dict] = []
|
||
self.used: set[str] = set()
|
||
self.mat_lines: dict[str, dict] = {}
|
||
self.nation: dict[tuple, dict] = {}
|
||
self.taken: set[str] = set()
|
||
self.kids: set[int] = set()
|
||
self.left: list[dict] = []
|
||
|
||
def put(self, line: dict, one: dict, alias: str | None, qty: Decimal | None = None,
|
||
mark: dict | None = None) -> None: # fmt: skip
|
||
"""대상 하나 → 줄 · 자재 — 옛 꼴 `{로직|자재|호표|전국|이음, 입력값, 배, 몫}` · `qty` = 새 길 수량
|
||
(엑셀 × 몫 × 배) · `mark` = 새 길 줄 `본뜬`."""
|
||
keep = len(self.lines)
|
||
if "이음" in one:
|
||
others = [x for x in self.rows if x is not line]
|
||
fx = hfix.line_of(line, others, self.where, self.taken, self.linker, one["이음"])
|
||
self.lines.append(fx)
|
||
else:
|
||
qty = (
|
||
qty
|
||
if qty is not None
|
||
else Decimal(str(line.get("수량") or 0)) * Decimal(str(one["배"]))
|
||
)
|
||
self._put(line, one, alias, qty, mark is not None)
|
||
for row in self.lines[keep:]:
|
||
row.update({"본뜬": mark} if mark else {})
|
||
|
||
def _put(self, line: dict, one: dict, alias: str | None, qty: Decimal, new: bool) -> None:
|
||
b = self.b
|
||
memo = hbuild._memo(line, one)
|
||
if "전국" in one:
|
||
at = (one["이름"], one["규격"], one["단위"])
|
||
have = self.nation.get(at)
|
||
hand = qty + Decimal(str(have["수량"]["손"])) if have else qty
|
||
head = {"이름": at[0], "규격": at[1], "단위": at[2], "수량": {"손": _num(hand)}}
|
||
self.nation[at] = {**head, **(hmat.marks(line, at[0]) if not have else {
|
||
k: have[k] for k in labor.MARKS if k in have
|
||
})} # fmt: skip
|
||
self.missing.append(hbuild._missing(line, hbuild.NATIONAL))
|
||
return
|
||
if "자재" in one:
|
||
code = one["자재"]
|
||
have = self.mats.get(code)
|
||
hand = qty + Decimal(str(have["수량"]["손"])) if have else qty
|
||
name = b.mat_names.get(code, "")
|
||
marks = (
|
||
have and {k: have[k] for k in labor.MARKS if k in have} or hmat.marks(line, name)
|
||
)
|
||
if one.get("별도계상") is not None: # 세 칸 값(빈 칸 = 기본 「별도계상」)
|
||
marks = {**marks, "별도계상": "별도계상" if one["별도계상"] else "직접계산"}
|
||
self.mats[code] = {"코드": code, "수량": {"손": _num(hand)}, **marks}
|
||
if not have:
|
||
self.mat_lines[code] = line
|
||
return
|
||
if "호표" in one or "참조" in one:
|
||
if "호표" in one:
|
||
self.kids.add(one["호표"])
|
||
key = b.done.get(hbuild.mark(b.key, one["호표"]), "SC-미리")
|
||
ref = {"층": refs.here()[0], "주인": refs.here()[1], "키": key}
|
||
else:
|
||
ref = one["참조"]
|
||
self.lines.append({"참조": ref, "별칭": alias, "메모": memo, "수량": {"손": _num(qty)}})
|
||
return
|
||
row = {"로직": one["로직"], "입력값": one["입력값"], "메모": memo}
|
||
if not new:
|
||
kept, why = sangeun.kept(b, line, one, qty, alias, memo)
|
||
if why:
|
||
self.left.append(sangeun.left(line, why))
|
||
if kept is not None:
|
||
self.lines.append(kept)
|
||
return
|
||
if one["로직"] not in self.used:
|
||
self.used.add(one["로직"])
|
||
self.lines.append({**row, "별칭": alias, "수량": {"손": _num(qty)}})
|
||
return
|
||
at = int(line.get("차례", 0))
|
||
hopyo, file = b.hopyo, b.doc.get("파일") or ""
|
||
wrap = { # 같은 로직 둘째 — 한 줄 사전조합으로 감쌈
|
||
"이름": f"{hopyo['이름']} — {line.get('이름')}",
|
||
"단위": line.get("단위") or None,
|
||
"구분": hbuild.ORIGIN,
|
||
"상세구분": file,
|
||
"담은로직": [{**row, "별칭": alias, "수량": {"손": 1}}],
|
||
"자재": [],
|
||
"본뜬": b._origin(f"{hbuild.mark(b.key, b.no, at)} {one['로직']}", self.stamp),
|
||
}
|
||
ref = {"키": "SC-미리"} if b.dry else hkids.save(wrap, b.same)[0] # 미리 보기 = 쓰기 없음
|
||
self.lines.append({"참조": ref, "별칭": alias, "메모": memo, "수량": {"손": _num(qty)}})
|
||
|
||
def apart(self, line: dict, alias: str | None, mark: dict) -> None:
|
||
"""별도계상 줄(786-1) = 합 밖 고정 줄 — 단가 0(셈에 안 듦) · 품명 · 규격 · 단위 · 수량 그대로 ·
|
||
품목은 프로젝트에 복사할 때 정함(메모 표지)."""
|
||
zero = {**line, "단가": {}, "금액": {}}
|
||
self.put(zero, {"이음": dict.fromkeys(hfix.fixed.LINKS)}, alias, None, mark)
|
||
self.lines[-1]["메모"] = f"{APART_MEMO} · {self.lines[-1].get('메모') or ''}".strip(" ·")
|
||
|
||
def picks(self, at: int, line: dict, picks: list[dict]) -> None:
|
||
"""고른 것(검사 · 다시 셈 끝난 것) → 그 엑셀 줄의 우리 줄 · 자재."""
|
||
alias = str(line.get("이름") or "")[: labor.ALIAS_MAX].strip() or None
|
||
mark = {"호표": self.b.no, "차례": at, "행": line.get("행")}
|
||
for p in picks:
|
||
kind, code = p["갈래"], p.get("키")
|
||
one = {"배": p["배"], "몫": p.get("몫")}
|
||
qty = p["수량"]
|
||
if kind == "기초":
|
||
self.put(line, {**one, "로직": code, "입력값": p["입력값"]}, alias, qty, mark)
|
||
elif kind in ("재료", "유가") and not p.get("이음"):
|
||
self.put(
|
||
line, {**one, "자재": code, "별도계상": p.get("별도계상")}, alias, qty, mark
|
||
)
|
||
elif kind in ("단가산출", "일위대가"):
|
||
self.put(line, {**one, "참조": p["참조"]}, alias, qty, mark)
|
||
elif kind == "율":
|
||
self.put(line, {"이음": {"비율": p["비목"]}}, alias, None, mark)
|
||
self.lines[-1]["고정"]["비율"] = {"기준": p["기준"], "비목": p["비목"]}
|
||
elif kind == "고정" and p.get("별도계상"):
|
||
self.apart(line, alias, mark)
|
||
elif kind == "고정":
|
||
self.put(line, {"이음": dict.fromkeys(hfix.fixed.LINKS)}, alias, None, mark)
|
||
else:
|
||
self.put(line, {"이음": {LINK[kind]: code}}, alias, None, mark)
|
||
|
||
|
||
def ledger_pick(p: dict[str, Any]) -> dict[str, Any]:
|
||
"""장부 짝(고른 것 꼴 · `Ledger.check_pair`) — 참조 = 만든 SK · SC 키."""
|
||
keep = ("갈래", "키", "입력값", "배", "몫", "비목", "기준", "별도계상")
|
||
out = {k: p[k] for k in keep if p.get(k) is not None}
|
||
return out | ({"키": p["참조"]["키"]} if "참조" in p else {})
|
||
|
||
|
||
# ── 고른 것 검사 · 다시 셈 ──────────────────────────────────────────────
|
||
class Move:
|
||
"""한 번 옮기기 — 파일 · M01 · 만든 SC · SK 를 한 번 읽음(쓰기 = `store.root()`)."""
|
||
|
||
def __init__(self, folder: Path, source: Path, key: str, who: Any) -> None:
|
||
self.folder, self.source, self.key, self.who = Path(folder), Path(source), key, who
|
||
self.hp = hopyo_store.read(source, key)
|
||
self.units = {int(h["번"]): h for h in self.hp.get("호표") or []}
|
||
self.m = judge.master()
|
||
self.known = ledger._known()
|
||
self.sk = sangeun.Maker(source, key, who)
|
||
self.same = lsame.Same()
|
||
self.out: dict[str, Any] = {"만듦": [], "단가산출": [], "막힘": [], "하위": []}
|
||
self.out["장부"] = {"씀": 0}
|
||
|
||
def _ref(self, p: dict) -> dict[str, Any]:
|
||
"""단가산출 · 일위대가 키 → 참조 칸(같은 HP 블록 · 호표 = 만든 것) · 없으면 막힘."""
|
||
code, kind = str(p.get("키") or ""), p["갈래"]
|
||
hit = _SUB[kind].match(code)
|
||
here = refs.here()
|
||
if hit:
|
||
n = int(hit.group(1))
|
||
made = (
|
||
self.sk.keys.get(sangeun.mark(self.key, n))
|
||
if kind == "단가산출"
|
||
else hbuild.confirmed(self.folder, refs.where(self.source)).get(
|
||
hbuild.mark(self.key, n)
|
||
)
|
||
)
|
||
if not made:
|
||
raise Stuck(f"{code} 먼저 옮김")
|
||
return {"층": here[0], "주인": here[1], "키": made}
|
||
head = "SK-" if kind == "단가산출" else "SC-"
|
||
ref = {"층": p.get("층") or here[0], "주인": p.get("주인"), "키": code}
|
||
if p.get("층") is None:
|
||
ref["주인"] = here[1]
|
||
if not code.startswith(head) or refs.read_ref(ref) is None:
|
||
raise Stuck(f"없는 {kind} 「{code}」")
|
||
return ref
|
||
|
||
def pick(self, p: dict, line: dict | None, where: str) -> dict[str, Any]:
|
||
"""고른 것 하나 재검사 · 다시 셈 → `{갈래, 키, 입력값, 배, 몫, 수량, 참조?, …}`."""
|
||
kind = p["갈래"]
|
||
got = {k: p[k] for k in ("갈래", "키", "몫", "비목", "기준", "별도계상") if k in p}
|
||
if kind == "기초":
|
||
full, need = judge.inputs(self.m, str(p.get("키") or ""), p.get("입력값"))
|
||
if need:
|
||
raise Stuck(f"{NEED} {len(need)}", need)
|
||
got["입력값"] = full
|
||
if kind in _SUB:
|
||
got["참조"] = self._ref(p)
|
||
else:
|
||
plain = {k: v for k, v in got.items() if k in ledger.PICK_KEYS}
|
||
ledger.check_pair(plain, where, self.known)
|
||
if kind in ("재료", "유가") and not got.get("키"):
|
||
raise Stuck(f"{kind} 키 없음")
|
||
if line is not None:
|
||
qty = judge.quantity(self.m, line, p)
|
||
if qty["배"] is None:
|
||
raise Stuck("단위 환산 입력")
|
||
got["배"], got["수량"] = qty["배"], Decimal(str(qty["우리"] or 0))
|
||
return got
|
||
|
||
# ── SC ──
|
||
def hopyo(self, no: int, rows: dict[int, list[dict]]) -> None:
|
||
unit = self.units[no]
|
||
lines = unit.get("줄") or []
|
||
checked = {}
|
||
for at, picks in sorted(rows.items()):
|
||
if not 0 <= at < len(lines):
|
||
raise store.StoreError(400, f"없는 줄 「제{no}호표 {at}」")
|
||
where = f"제{no}호표 {at + 1}째 줄"
|
||
if not picks:
|
||
continue
|
||
try:
|
||
checked[at] = {"고른": [self.pick(p, lines[at], where) for p in picks]}
|
||
except Stuck as e:
|
||
self.out["막힘"].append({"호표": no, "차례": at, "까닭": e.why, "칸": e.cells})
|
||
b = hbuild.Builder(self.folder, self.source, self.key, no, self.who)
|
||
b.strict, b.same = False, self.same
|
||
made = b.build(checked, [])
|
||
ref = made["참조"]
|
||
head = {"번": no, "키": ref["키"], "이름": unit.get("이름")}
|
||
head |= {k: made[k] for k in ("다시씀", "재사용")}
|
||
self.out["만듦"].append({**head, "비교": compare(ref, unit)})
|
||
self.out["장부"]["씀"] += made["장부"]["씀"]
|
||
|
||
def kid(self, no: int) -> None:
|
||
"""확인 안 된 하위 호표 — 모두 고정(705-3 길)으로 먼저."""
|
||
b = hbuild.Builder(self.folder, self.source, self.key, no, self.who)
|
||
b.strict, b.same = False, self.same
|
||
if b.done.get(hbuild.mark(self.key, no)):
|
||
return
|
||
made = b.build({}, [])
|
||
self.out["하위"].append({"번": no, "키": made["참조"]["키"], "새로": True})
|
||
|
||
# ── SK ──
|
||
def block(self, no: int, picks: list[dict]) -> None:
|
||
blk = self.sk.blocks[no]
|
||
items = blk.get("항목") or []
|
||
us = self.sk.units()
|
||
made: list[dict] = []
|
||
rows: list[dict] = []
|
||
for p in picks:
|
||
got = self.pick(p, None, f"단산{no}")
|
||
name = str(p.get("이름") or "").strip() or str(blk.get("이름") or f"단산{no}")
|
||
share = got.get("몫") or 1
|
||
at = p.get("항목")
|
||
item = items[at] if isinstance(at, int) and 0 <= at < len(items) else None
|
||
if got["갈래"] == "기초":
|
||
made.append({"종류": "로직", "로직": got["키"], "입력값": got["입력값"],
|
||
"이름": name, "수량": share}) # fmt: skip
|
||
continue
|
||
if got["갈래"] in _SUB:
|
||
qty = float(item.get("품") or 1) if item else share
|
||
made.append({"참조": got["참조"], "이름": name, "수량": qty})
|
||
continue
|
||
if item is None:
|
||
raise Stuck(f"{got['갈래']} — 블록 단위 줄(항목) 고름")
|
||
if got["갈래"] == "고정":
|
||
parts = hfix._parts(item.get("금액"))
|
||
fx = {"이름": name, "단가": {k: float(v) for k, v in parts.items()}, "이음": {}}
|
||
made.append({"고정": fx, "이름": name, "수량": 1})
|
||
continue
|
||
old = {"기준": got.get("기준"), "비목": got.get("비목")}
|
||
if got["갈래"] in OLD_PICK:
|
||
old = {OLD_PICK[got["갈래"]]: got["키"], "별도계상": got.get("별도계상")}
|
||
unit = skunit._picked(item, old, us)["단위"]
|
||
rows.append({"차례": at, "행": item.get("행"), "상태": units.LINKED, "단위": unit})
|
||
extra, _ = skunit.adds(blk, rows, us, self.sk.ref_of)
|
||
doc = {"키": "SK-000000", "이름": "옮김", "단위": blk.get("단위") or "식", "바탕": None}
|
||
try:
|
||
adds = sd.clean({**doc, "더한줄": made + extra}, None, self.sk.whole)["더한줄"]
|
||
except (ValueError, store.StoreError) as e:
|
||
raise Stuck(f"더한줄 틀림 — {e}") from e
|
||
plan = {"갈래": "고름", "바탕": None, "입력값": {}, "덮개": {}, "자재표시": {}, "뺀줄": []}
|
||
plan |= {"더한줄": adds, "못옮김": [], "놓음": []}
|
||
mark_ = sangeun.mark(self.key, no)
|
||
key = self.sk.write(no, plan, self.sk.keys.get(mark_))
|
||
self.sk.keys[mark_] = key
|
||
self.out["단가산출"].append({"번": no, "키": key, "이름": blk.get("이름")})
|
||
|
||
def blocks(self, sure: dict[int, list[dict]]) -> None:
|
||
"""하위 단산(블록 단위 줄 하위) 먼저."""
|
||
done: set[int] = set()
|
||
|
||
def visit(no: int, path: tuple = ()) -> None:
|
||
if no in done or no in path:
|
||
return
|
||
for one in self.sk.blocks[no].get("항목") or []:
|
||
sub = one.get("하위") or {}
|
||
if sub.get("종류") == "단산" and int(sub.get("번") or 0) in sure:
|
||
visit(int(sub["번"]), (*path, no))
|
||
done.add(no)
|
||
try:
|
||
self.block(no, sure[no])
|
||
except Stuck as e:
|
||
self.out["막힘"].append({"블록": no, "까닭": e.why, "칸": e.cells})
|
||
|
||
for no in sorted(sure):
|
||
visit(no)
|
||
|
||
|
||
def move(
|
||
folder: Path,
|
||
source: Path,
|
||
key: str,
|
||
units_: dict[int, dict[int, list[dict]]],
|
||
blocks: dict[int, list[dict]],
|
||
who: Any = None,
|
||
cover: bool = False,
|
||
) -> dict[str, Any]:
|
||
"""[옮기기] → `{만듦, 단가산출, 막힘, 하위, 장부}` — 블록 SK 먼저 · 호표 하위 먼저 ·
|
||
이미 옮긴 호표 · 블록(같은 HP 출처 SC · SK)은 `cover`(덮음) 때만 덮음 · 아니면 409 아무것도 안 씀."""
|
||
mv = Move(folder, source, key, who)
|
||
bad = [f"제{n}호표" for n in units_ if n not in mv.units]
|
||
bad += [f"단산{n}" for n in blocks if n not in mv.sk.blocks]
|
||
if bad:
|
||
raise store.StoreError(404, f"없는 {bad[0]}")
|
||
done = hbuild.confirmed(mv.folder, refs.where(mv.source))
|
||
had = {
|
||
"호표": [
|
||
{"번": n, "키": done[k]} for n in sorted(units_) if (k := hbuild.mark(key, n)) in done
|
||
]
|
||
}
|
||
had["블록"] = [
|
||
{"번": n, "키": mv.sk.keys[k]}
|
||
for n in sorted(blocks)
|
||
if (k := sangeun.mark(key, n)) in mv.sk.keys
|
||
]
|
||
if not cover and (had["호표"] or had["블록"]):
|
||
raise store.StoreError(409, {"까닭": COVER, **had})
|
||
mv.blocks(blocks)
|
||
wanted = {
|
||
int(hit.group(1))
|
||
for rows in units_.values()
|
||
for picks in rows.values()
|
||
for p in picks
|
||
if p.get("갈래") == "일위대가" and (hit := _SUB["일위대가"].match(str(p.get("키") or "")))
|
||
}
|
||
order = hbuild._order(mv.units)
|
||
for no in order: # 고른 것이 부르는 확인 안 된 하위 = 모두 고정으로 먼저
|
||
if no in wanted and no not in units_:
|
||
mv.kid(no)
|
||
for no in order:
|
||
if no in units_:
|
||
mv.hopyo(no, units_[no])
|
||
return mv.out
|