Merge commit '43e945568' into sub_laptop_1
# Conflicts: # M02_MasterTemplete/M02_MasterTemplete_Hopyo_MatPick.ts # M02_MasterTemplete/M02_San_MatPick.py
This commit is contained in:
@@ -186,8 +186,10 @@ _LIST_COST = ("M", "L", "S", "T")
|
||||
|
||||
|
||||
def hopyo(saved: dict) -> dict[str, Any]:
|
||||
"""원파일 → 실무자료 읽기 꼴 — 단산(산출근거 머리)만 · 일위대가 안 만듦(요구 743-2 · HeadMainDat 안 씀)."""
|
||||
out: dict[str, Any] = {"꼴": "원파일", "시트": saved["꼴"], "호표": [], "검산": {"어긋남": []}}
|
||||
"""원파일 → 실무자료 읽기 꼴 — 단산(산출근거 머리) · 호표 = STmate 구성표 일위 줄(828-1 · 엑셀 일위대가표
|
||||
길과 같은 꼴) · EST 일위대가 안 만듦(요구 743-2 · HeadMainDat 안 씀)."""
|
||||
out: dict[str, Any] = {"꼴": "원파일", "시트": saved["꼴"], "호표": _ilwi(saved)}
|
||||
out["검산"] = {"어긋남": []}
|
||||
out["단산"] = [
|
||||
{"번": n, "이름": b.get("이름"), "단위": b.get("단위"), "금액": _cost(b.get("저장"))}
|
||||
for n, b in numbers(saved).items()
|
||||
@@ -195,6 +197,23 @@ def hopyo(saved: dict) -> dict[str, Any]:
|
||||
return out
|
||||
|
||||
|
||||
def _ilwi(saved: dict) -> list[dict]:
|
||||
"""STmate 일위 구성 줄 → 호표(행 = 격자 「일위대가표」 행 · 줄 재현 · 덮음 = `M02_Hopyo_UnitLine`)."""
|
||||
if saved.get("꼴") != "STmate" or not saved.get("일위"):
|
||||
return []
|
||||
from M02_MasterTemplete import M02_Hopyo_UnitLine as ul
|
||||
from M02_MasterTemplete import M02_San_Ilwi as il
|
||||
|
||||
out, at = il.hopyo(saved["일위"]), 2
|
||||
for h in out:
|
||||
h["행"] = [at, at + len(h["줄"])]
|
||||
for i, line in enumerate(h["줄"], 1):
|
||||
line["행"] = at + i
|
||||
at += len(h["줄"]) + 2
|
||||
ul.check_hopyo(h)
|
||||
return out
|
||||
|
||||
|
||||
def grid(saved: dict) -> dict[str, Any]:
|
||||
"""원파일 → 단가산출표 한 시트 격자(`M02_Hopyo_Grid.read` 꼴) — 산출근거 머리(코드 · 이름 · 규격 ·
|
||||
단위 · 저장 금액) 한 줄씩 · 행 = 1 머리 + 블록 차례."""
|
||||
@@ -214,7 +233,7 @@ def grid(saved: dict) -> dict[str, Any]:
|
||||
sheet = {"id": "s1", "이름": LIST, "칸": cells, "틀고정": {"행": 1}}
|
||||
sheet["열"] = {c: {"폭": w} for c, w in zip(cols, (10, 40, 18, 6, 12, 12, 12, 12))}
|
||||
book = {"종류": "통합문서", "판": 1, "열": "", "서식": [{}], "시트": [sheet], "활성": "s1"}
|
||||
return {
|
||||
out = {
|
||||
"책": book,
|
||||
"호표": [],
|
||||
"단산": rows,
|
||||
@@ -223,6 +242,18 @@ def grid(saved: dict) -> dict[str, Any]:
|
||||
"목록": {"호표": [], "단산": [{"번": r["번"], "행": r["행"][0]} for r in rows]},
|
||||
"줄": [],
|
||||
}
|
||||
if hp := _ilwi(saved): # 828-1 STmate 구성표 일위대가 = 「일위대가표」 시트(앞)
|
||||
from M02_MasterTemplete import M02_San_Ilwi as il
|
||||
|
||||
book["시트"].insert(0, il.sheet(hp, "s2"))
|
||||
book["활성"] = "s2"
|
||||
out["호표"] = [{"번": h["번"], "이름": h["이름"], "행": h["행"]} for h in hp]
|
||||
out["보기"][il.SHEET] = "s2"
|
||||
out["원본"][il.SHEET] = il.SHEET
|
||||
out["줄"] = [
|
||||
{"번": h["번"], "차례": ln["차례"], "행": ln["행"]} for h in hp for ln in h["줄"]
|
||||
]
|
||||
return out
|
||||
|
||||
|
||||
def _dec(v: Any) -> int | float | None:
|
||||
|
||||
@@ -41,9 +41,9 @@ interface MatKind {
|
||||
단서: string[];
|
||||
별도계상: boolean;
|
||||
블록: number[];
|
||||
/** 일위대가 구성 줄이 있는 호표(828-1 · 코드 숫자) */
|
||||
호표?: number[];
|
||||
줄: number;
|
||||
/** 828-2 이 종이 든 일위대가 구성 줄의 호표 코드(STmate 엑셀 숨은 칸 · 원파일 구성표) */
|
||||
일위?: string[];
|
||||
고름?: MatCand;
|
||||
}
|
||||
|
||||
@@ -135,13 +135,12 @@ export function createMatPick(opt: { scope: HopyoScope; key: string; target: Hop
|
||||
|
||||
function kindRow(k: MatKind): HTMLElement {
|
||||
const what = `${label(k)}${k.단위 ? ` (${k.단위})` : ""}` || "—";
|
||||
const hops = k.일위?.length ? ` · 일위대가 ${k.일위.length} 호표` : "";
|
||||
const sub = [k.방법, `${k.블록.length} 블록${hops} · ${k.줄} 줄`, ...k.단서]
|
||||
const hp = k.호표?.length ? ` · 일위대가 ${k.호표.length}` : "";
|
||||
const sub = [k.방법, `${k.블록.length} 블록${hp} · ${k.줄} 줄`, ...k.단서]
|
||||
.filter(Boolean)
|
||||
.join(" · ");
|
||||
const badge = createBadge(k.별도계상 ? `별도계상 · ${k.판정}` : k.판정, TONE[k.판정] ?? "warning", { size: 18 }); // prettier-ignore
|
||||
const title = `단산 ${k.블록.join(" · ")}${hops ? ` / 일위대가 ${k.일위?.join(" · ")}` : ""}`;
|
||||
const row = createListRow({ label: what, sub, trailing: badge, size: 36, title }); // prettier-ignore
|
||||
const row = createListRow({ label: what, sub, trailing: badge, size: 36, title: `단산 ${k.블록.join(" · ")}${hp && ` · 호표 ${k.호표?.join(" · ")}`}` }); // prettier-ignore
|
||||
const box = el("div", { attrs: { "data-mat-kind": k.종 }, children: [row, controls(k)] });
|
||||
if (edits.has(k.종)) box.dataset.matEdited = "";
|
||||
return box;
|
||||
|
||||
@@ -415,7 +415,9 @@ class _Stm:
|
||||
if not one_ and len(sole) == 1: # 비목 꼬리 없어도 값 든 비목 하나면 단가 줄(셈 같음)
|
||||
one_, to = sole[0], None
|
||||
res |= {"비목": one_, "옮김": to} if one_ else {}
|
||||
parts = machine_parts(res) if self.keys else [] # 812-9 기계 찾기 · 조합 = 줄 둘
|
||||
parts = (
|
||||
machine_parts(res, pr.get("같은구성")) if self.keys else []
|
||||
) # 812-9 · 816-2 같은 파일 구성(828-1)
|
||||
if parts:
|
||||
res["찾기"], link, many = parts[0], parts[0]["키"], None
|
||||
res |= {"조합첫": True} if len(parts) > 1 else {}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
일위 구성 줄 꼴(원파일 · 엑셀 같은 꼴 — 읽기기 답 `일위` 블록 `줄`):
|
||||
{차례, 코드(자원 · PR · RR · C1 · T1 · 빈), 갈래(M L S X B D W · 율 · 닫기 · 구분 · 글), 이름, 규격, 단위,
|
||||
수량(1 차), 단가 {T L M S}?(셈에 쓴 자원 단가 · 중기 조종원 = 시간 환산), 금액 {T L M S}?(줄 금액),
|
||||
율? {밑, 대상(M L S T), 곱(RR)}, 밑줄?(율 밑수에 듦), 관급?, 별도계상?(자원 규격 「별도계상」)}
|
||||
율? {밑, 대상(M L S T), 곱(RR)}, 밑줄?(율 밑수에 듦), 관급?, 별도계상?(자재 이름 · 규격 「별도계상」 · 「별산」)}
|
||||
블록 = {코드, 이름, 규격, 단위, 저장, 줄, 줄합 {T L M S}(다시 셈 · 끝전 뒤), 표지}
|
||||
율 줄 이름 = 지은 글(「노무비의 3%」 · 원문 이름 칸은 못 읽음).
|
||||
HP 호표(`hopyo`) = 엑셀 일위대가표 읽기(`M02_Hopyo_Stmate.read`)와 같은 꼴 — 글 · 구분 · 닫기 줄 뺌.
|
||||
@@ -123,7 +123,9 @@ def lines(rows: list[dict], res: dict[str, dict], opt: dict) -> tuple[list[dict]
|
||||
one["밑줄"] = True
|
||||
if row.get("KWNGP") == "1":
|
||||
one["관급"] = True
|
||||
if "별도계상" in one["규격"]:
|
||||
if kind == "M" and re.search(
|
||||
r"별도계상|별산", one["이름"] + one["규격"]
|
||||
): # 엑셀 비고 「별산…」 짝
|
||||
one["별도계상"] = True
|
||||
out.append(one)
|
||||
before = dict(s)
|
||||
@@ -144,7 +146,9 @@ def lines(rows: list[dict], res: dict[str, dict], opt: dict) -> tuple[list[dict]
|
||||
continue
|
||||
if kind not in KIND:
|
||||
continue
|
||||
saved = head.get("저장") or {}
|
||||
saved = (
|
||||
head.get("원단가") or head.get("저장") or {}
|
||||
) # 낙찰률 줄 = 2 칸(smt_engine costSlot)
|
||||
unit = one["단위"].strip()
|
||||
own = "L" if code[0] == "L" else "S" if code[0] == "S" else "M"
|
||||
pct = re.fullmatch(r"([MLST])%", unit)
|
||||
@@ -161,8 +165,8 @@ def lines(rows: list[dict], res: dict[str, dict], opt: dict) -> tuple[list[dict]
|
||||
p = {k: _f(saved.get(k)) for k in "MLS"}
|
||||
if code in opt["환산"]:
|
||||
p = {"M": 0.0, "L": _wage(p["L"], opt["환산식"]), "S": 0.0}
|
||||
if unit == "$":
|
||||
p = {k: v * opt["달러"] / 1000 for k, v in p.items()}
|
||||
if unit == "$": # 달러 → 천원(엑셀 「중기사용료」 값 · 원 미만 버림)
|
||||
p = {k: _tr(v * opt["달러"] / 1000) for k, v in p.items()}
|
||||
elif unit in ("€", "¥", "¥"):
|
||||
p = dict.fromkeys("MLS", 0.0)
|
||||
one["단가"] = {"T": p["M"] + p["L"] + p["S"], **p}
|
||||
@@ -187,7 +191,7 @@ def make_of(name: str, spec: str, rows: list[dict]) -> dict[str, Any]:
|
||||
make: dict[str, Any] = {"이름": name, "규격": spec, "손료": []}
|
||||
for one in rows:
|
||||
g, q, unit = one["갈래"], one["수량"], one["단위"].strip().upper()
|
||||
if g == "S" and unit == "천원" and q and (one.get("단가") or {}).get("S"):
|
||||
if g == "S" and unit in ("천원", "$") and q and (one.get("단가") or {}).get("S"):
|
||||
make["손료"].append([q * 10_000, one["단가"]["S"] * 1000, False])
|
||||
elif g == "L" and q > 0 and not make.get("조종원"):
|
||||
make["조종원"], make["조종원직종"] = q, one["이름"].strip()
|
||||
@@ -222,7 +226,7 @@ def compose(
|
||||
if parent[:1] not in ("B", "X") or not head:
|
||||
continue
|
||||
got, tot = lines(group, res, options(parent, ini, sbm.get(parent)))
|
||||
saved = head.get("저장") or {}
|
||||
saved = head.get("원단가") or head.get("저장") or {}
|
||||
ok = not _f(saved.get("T")) or same(tot, saved)
|
||||
if parent[0] == "B":
|
||||
blocks[parent] = {"줄": got, "줄합": tot, "표지": [] if ok else [MISMATCH]}
|
||||
@@ -272,3 +276,53 @@ def hopyo(blocks: list[dict]) -> list[dict[str, Any]]:
|
||||
head |= {k: str(b.get(k) or "") for k in ("이름", "규격", "단위")}
|
||||
out.append({**head, "금액": _hp(b.get("저장")), "줄": rows})
|
||||
return out
|
||||
|
||||
|
||||
SHEET = "일위대가표"
|
||||
_COLS = "ABCDEFGHI"
|
||||
_HEAD = ("품명", "규격", "수량", "단위", "재료비", "노무비", "경비", "합계", "코드")
|
||||
|
||||
|
||||
def sheet(hps: list[dict], sid: str) -> dict[str, Any]:
|
||||
"""HP 호표(행 있음) → 격자 「일위대가표」 시트(머리 줄 = 「제N호표」 · 금액 = 저장 · 줄 금액 = 다시 셈)."""
|
||||
cells: dict[str, dict] = {f"{c}1": {"값": h} for c, h in zip(_COLS, _HEAD)}
|
||||
|
||||
def put(at: int, vals: list) -> None:
|
||||
for c, v in zip(_COLS, vals):
|
||||
if v not in (None, ""):
|
||||
cells[f"{c}{at}"] = {"값": v}
|
||||
|
||||
for h in hps:
|
||||
money = [h["금액"].get(k) for k in ("재료비", "노무비", "경비", "계")]
|
||||
put(
|
||||
h["행"][0],
|
||||
[f"제{h['번']}호표 {h['이름']}", h["규격"], None, h["단위"], *money, h["코드"]],
|
||||
)
|
||||
for ln in h["줄"]:
|
||||
money = [ln["금액"].get(k) for k in ("재료비", "노무비", "경비", "계")]
|
||||
put(ln["행"], [ln["이름"], ln["규격"], ln["수량"], ln["단위"], *money, ln["코드"]])
|
||||
widths = (36, 22, 10, 6, 12, 12, 12, 12, 10)
|
||||
return {
|
||||
"id": sid,
|
||||
"이름": SHEET,
|
||||
"칸": cells,
|
||||
"틀고정": {"행": 1},
|
||||
"열": {c: {"폭": w} for c, w in zip(_COLS, widths)},
|
||||
}
|
||||
|
||||
|
||||
def materials(saved: dict):
|
||||
"""일위 구성 자재 줄(M) → (블록, 줄, 자원 {이름 · 규격 · 단위 · 코드 · 재료찾기?}) — 재료 찾기 = 산근 줄과 같은
|
||||
길(827-2 `M02_San_Link.m01` · 파일 단서 · 머리 = 부모 일위 이름 규격) · 유가 · 환율 줄 = 재료찾기 없음."""
|
||||
from M02_MasterTemplete import M02_San_Link as link
|
||||
from M02_MasterTemplete import M02_San_MatFind as mf
|
||||
|
||||
for b in saved.get("일위") or []:
|
||||
head = f"{b.get('이름') or ''} {b.get('규격') or ''}"
|
||||
for ln in b.get("줄") or []:
|
||||
if ln.get("갈래") != "M":
|
||||
continue
|
||||
res = {k: ln.get(k) or "" for k in ("이름", "규격", "단위", "코드")} | {"종류": "자재"}
|
||||
with mf.clues_on(saved.get("단서"), head):
|
||||
link.m01("재료", res, "")
|
||||
yield b, ln, res
|
||||
|
||||
@@ -180,9 +180,8 @@ def entry(item: dict, sym: str, excel: Any) -> dict | None:
|
||||
# ── 판(그 파일 재료 종) ──────────────────────────────────────────────────
|
||||
def census(folder: str | Path, key: str) -> list[dict]:
|
||||
"""HP 블록마다 재료 줄 판정 → 종 [{종, 이름, 규격, 단위, 판정, 방법, 키, 후보, 단서, 별도계상, 블록, 줄,
|
||||
일위?}] · 일위 = 그 종이 든 일위대가 구성 줄의 호표 코드(828-2 · 고름은 산근 블록 만들기에만 얹음)."""
|
||||
호표?(828-1 일위 구성 줄 · 코드 숫자)}]."""
|
||||
from M02_MasterTemplete import M02_Doc_FromNative as fn
|
||||
from M02_MasterTemplete import M02_San_MatFind as mf
|
||||
from M02_MasterTemplete import M02_Sangeun_FromHopyo as fh
|
||||
|
||||
hopyo_store.read(folder, key) # 없는 HP = 404
|
||||
@@ -208,34 +207,37 @@ def census(folder: str | Path, key: str) -> list[dict]:
|
||||
found = res.get("재료찾기")
|
||||
if not found:
|
||||
continue
|
||||
g = _group(groups, res)
|
||||
k = kind_of(res)
|
||||
g = groups.setdefault(
|
||||
k,
|
||||
{"종": k, **{x: _text(res.get(x)) for x in ("이름", "규격", "단위")},
|
||||
**{x: found.get(x) for x in ("판정", "방법", "키", "후보", "단서")},
|
||||
"별도계상": _apart(res), "블록": [], "줄": 0},
|
||||
) # fmt: skip
|
||||
g["줄"] += 1
|
||||
if no not in g["블록"]:
|
||||
g["블록"].append(no)
|
||||
for blk, res in (
|
||||
mf.ilwi_found(src) if src else []
|
||||
): # 828-2 일위대가 구성 줄 재료(일위 = 호표 코드)
|
||||
if not res.get("재료찾기"):
|
||||
from M02_MasterTemplete import M02_San_Ilwi as il
|
||||
|
||||
for b, _, res in il.materials(src) if src else (): # 828-1 일위 구성 줄(호표 = 코드 숫자)
|
||||
if not (found := res.get("재료찾기")):
|
||||
continue
|
||||
g = _group(groups, res)
|
||||
k = kind_of(res)
|
||||
g = groups.setdefault(
|
||||
k,
|
||||
{"종": k, **{x: _text(res.get(x)) for x in ("이름", "규격", "단위")},
|
||||
**{x: found.get(x) for x in ("판정", "방법", "키", "후보", "단서")},
|
||||
"별도계상": _apart(res), "블록": [], "줄": 0},
|
||||
) # fmt: skip
|
||||
g["줄"] += 1
|
||||
if blk["코드"] not in (hops := g.setdefault("일위", [])):
|
||||
hops.append(blk["코드"])
|
||||
hp = g.setdefault("호표", [])
|
||||
if (n := int(str(b["코드"])[1:])) not in hp:
|
||||
hp.append(n)
|
||||
out_ = sorted(groups.values(), key=lambda g: (KINDS.index(g["판정"]) if g["판정"] in KINDS else 9, -len(g["블록"]), g["이름"])) # fmt: skip
|
||||
atomic_write_json(path, {**saved, CENSUS: out_})
|
||||
return out_
|
||||
|
||||
|
||||
def _group(groups: dict[str, dict], res: dict) -> dict:
|
||||
found, k = res["재료찾기"], kind_of(res)
|
||||
return groups.setdefault(
|
||||
k,
|
||||
{"종": k, **{x: _text(res.get(x)) for x in ("이름", "규격", "단위")},
|
||||
**{x: found.get(x) for x in ("판정", "방법", "키", "후보", "단서")},
|
||||
"별도계상": _apart(res), "블록": [], "줄": 0},
|
||||
) # fmt: skip
|
||||
|
||||
|
||||
def fingerprint(folder: str | Path, key: str) -> tuple[str, str]:
|
||||
hp = hopyo_store.read(folder, key)
|
||||
return str(hp.get("지문") or ""), str(hp.get("파일") or "")
|
||||
|
||||
@@ -151,7 +151,7 @@ def _stm_sheet(rows: list[list]) -> dict[str, dict]:
|
||||
cur["손료"].append([_num(v[2]) * 10_000, _num(v[4]) * 1000, False])
|
||||
elif len(v) > 2 and str(v[2]) == "인" and (_num(v[1]) or 0) > 0 and not cur.get("조종원"):
|
||||
cur["조종원"], cur["조종원직종"] = _num(v[1]), str(v[0]).strip()
|
||||
elif first == "잡품" and len(v) > 2 and cur.get("잡재료율") is None:
|
||||
elif first in ("잡품", "잡재료", "잡재료비") and len(v) > 2 and cur.get("잡재료율") is None:
|
||||
cur["잡재료율"] = _num(v[2])
|
||||
elif len(v) > 3 and str(v[3]).upper() in ("L", "ℓ"):
|
||||
_fuel(cur, v[0], v[2])
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
<HII 꼬리표 · 길이 · 블록 번호>) · BOQ11.DBF(내역).
|
||||
BDQTY.DBF(일위대가 · 중기 구성) = 828-1 `M02_San_Bdqty`(믿을 칸 · 스스로 검사 · 어긋나면 물러남 = 일위 줄 없음 +
|
||||
표지) → `M02_San_Ilwi`(일위 구성 줄 · 다시 셈 · 중기 구성 `기계구성` · 같은 파일 구성 대조 `같은구성`).
|
||||
SYSINFO = 난독화 → 표지. 금액 칸 = 차수 1 · 낙찰률 줄(NAGCD > 0) = 2 칸(원단가 · 828-1 ㉱ · 2~4 차 수량은 못 읽음).
|
||||
SYSINFO = 난독화 → 표지. 금액 칸 = 차수 1(산근 · 자원 = 엑셀 단가산출근거와 같음) · 낙찰률 줄(NAGCD > 0) 2 칸 =
|
||||
`원단가`(828-1 ㉱ — 구성 셈 · 일위 머리 = 엑셀 일위대가표와 같음 · 2~4 차 수량은 못 읽음).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -160,7 +161,7 @@ def read(data: bytes) -> dict[str, Any]:
|
||||
code = r.get("CODE") or ""
|
||||
if not code:
|
||||
continue
|
||||
saved = _amt(r, AMT2 if _f(r.get("NAGCD")) > 0 else AMT)
|
||||
saved = _amt(r, AMT)
|
||||
res[code] = {
|
||||
"종류": KIND.get(code[:1]),
|
||||
"이름": r.get("NAME"),
|
||||
@@ -168,6 +169,8 @@ def read(data: bytes) -> dict[str, Any]:
|
||||
"단위": r.get("UNIT"),
|
||||
"저장": saved,
|
||||
}
|
||||
if _f(r.get("NAGCD")) > 0: # 낙찰률 줄 원단가(828-1 ㉱ — 구성 셈 · 일위 머리)
|
||||
res[code]["원단가"] = _amt(r, AMT2)
|
||||
prices[code] = {**saved, "meta": {k: r.get(f) for k, f in META.items()}}
|
||||
out: dict[str, Any] = {"꼴": "STmate", "산근": [], "일위": [], "자원": res, "단가": prices}
|
||||
ini = z.read(names["START8X.INI"]).decode("cp949", "replace") if "START8X.INI" in names else ""
|
||||
@@ -179,7 +182,8 @@ def read(data: bytes) -> dict[str, Any]:
|
||||
continue
|
||||
tags: list[str] = []
|
||||
head = {k: res[code][k] for k in ("이름", "규격", "단위", "저장")}
|
||||
if text is None and code in comp:
|
||||
if text is None and code in comp: # 일위 머리 = 원단가(엑셀 일위대가표와 같음)
|
||||
head["저장"] = res[code].get("원단가") or head["저장"]
|
||||
out["일위"].append({"코드": code, **head, **comp[code]})
|
||||
continue
|
||||
if text is None:
|
||||
@@ -210,7 +214,7 @@ def read(data: bytes) -> dict[str, Any]:
|
||||
*([read_ok] if why is None else [UNREAD[0], FALLBACK + why]),
|
||||
UNREAD[1],
|
||||
*out.pop("구성표지", []),
|
||||
"금액 = 차수 1 칸(TAMT1 · LAMT1 · MAMT1 · SAMT1) · 낙찰률 줄 = 2 칸(원단가)",
|
||||
"금액 = 차수 1 칸(TAMT1 · LAMT1 · MAMT1 · SAMT1) · 일위 머리 · 구성 셈의 낙찰률 줄 = 2 칸(원단가)",
|
||||
"블록 끝전 갈래 = 옵션 SAN_TYP 후보(뜻 미확정)",
|
||||
*(f"모르는 코드 머리 {c}" for c in odd),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user