feat(일위대가실무): 338-1 맞춤 핵심 서버 2 단계 — 줄 새 상태 · 체크 제안 · 대상 목록 · 품 비교 · 호표 통째 · 셈 · 만든 · guess 목록 + 골든 시험
- 새 Line — 상태 차례(% · 호표 · 노임 · 장부 뺌 · 별칭 · 맞춤셈 판정 · 이름 근거 후보) · 체크 제안(맞음 · 별칭 규칙 셋 · 확인된 하위 호표) · 대상(입력값 출처 별칭 > 블록 > 규격 · 환산 · 몫 · 배) · 품 비교 · 직접 줄 호표 통째 · guess(대상 목록 · 낱개도 받음 · 엔진 한 번) - Match — view(to = 만들 층 · 셈) · picks · guess 를 Line 으로 · 옛 층 기억 · confirm · 들어감 없앰 · b_order 후보에 근거 칸 - Verdict — 위 다섯 딱 맞음 먼저(조합 없는 줄) · 후보 근거 = 근거 맞춤 꼴 · 지문차례(조합 전) - Fit — 맞춤셈 캐시 이름에 셈 판(CALC_VERSION) - 골든 시험(느림 · 1 분 54 초) — 사람 126 1순위 118 · 다섯 안 122 · 맞음 57 틀림 0 · 다른 점 틀림 1 · 옵션까지 57 · 조합 맞음 62/83 · 새 파일 51 맞음 틀림 2 · 씨앗 끔 · 북부청 조합 전 통과 - 근거 시험(나 창) 세 곳 — 옛 match_line → b_order(새 API) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
This commit is contained in:
@@ -1,18 +1,14 @@
|
||||
"""M02 일위대가 실무자료 맞춤(PLAN 324-2) — 엑셀 구성 줄 → 우리 돈 로직 · 자재 · 입력값 · 환산.
|
||||
"""M02 일위대가 실무자료 맞춤(PLAN 324-2 · 338) — 엑셀 구성 줄 → 우리 로직 · 자재 · 입력값 · 환산.
|
||||
|
||||
차례 = ① 층 기억(같은 층 → 위 층 · 확정분) ② 이름 같음(정규화) ·
|
||||
씨앗(일위대가조합.json 메모 · 읽기만)
|
||||
③ 정리 이름 같음(괄호 · 기계 낱말) ④ 후보 5(글자 두 개 묶음 점수 + 단위 맞음 + 산림품셈 덤 ·
|
||||
뜻 낱말이 하나도 안 겹치면 뺌 — 「잔토처리」 ↔ 「소금처리」) —
|
||||
+ 근거 후보(330-2 · `M02_Hopyo_Basis` · 단산 줄 출처 · 직종 · 이름 거름 안 거침 ·
|
||||
로직 키로 합쳐 점수 더함) ·
|
||||
③ ④ 는 사용자 확정(층 기억) 뒤에만 들어감.
|
||||
줄 갈래: 단가산출 · 기계 · 기타 = 로직 / 자재 · 별도계상 = 자재(M01 자재품목) / 호표 = 그 호표 /
|
||||
% = 안 들어감(로직 덧줄이 셈).
|
||||
이름 · 근거 차례(`b_order` · 338 설계 2-4 B) = 이름 같음(정규화) · 씨앗(일위대가조합.json 메모 ·
|
||||
읽기만)이 1위 · 단 엑셀 인용 조항이 다른 로직을 가리키면(근거 검사 · 옛 판 글이면 그대로) 후보 ·
|
||||
그 밖 = 이름 후보(글자 두 개 묶음 + 단위 + 산림 덤 · 뜻 낱말 안 겹치면 뺌 · 로직은 접지 않음) +
|
||||
근거 후보(`M02_Hopyo_Basis` · 조항 같음 · 윗마디 · 직종) 합침 — 숫자 맞춤(`M02_Hopyo_Fit`)의 덤.
|
||||
줄 · 호표 맞춤 응답(상태 · 체크 제안 · 대상 · 품 비교) = `M02_Hopyo_Line` · 별칭 장부 = 다 창.
|
||||
규격 → 입력값 = 고르기 글이 규격(품명 괄호 글 포함)에 있으면 그 값(기계 = 고르기이름) ·
|
||||
이름 붙은 수 D= H= H: B= T= L= L3= 는 입력 단위로 · 못 읽으면 빈 값(자동값) + 표시.
|
||||
환산(우리 수량 = 엑셀 수량 × 배): 같은 단위 1 · 같은 성격 = 단위표 · ㎡→㎥ × T · m→㎡ × B ·
|
||||
m→㎥ × B × T = 「추정」(확정 뒤에만) · 두 단위 로직 = 단위 입력으로 고르고 1 · 그 밖 = 사용자 입력.
|
||||
m→㎥ × B × T = 「추정」(체크 안 함) · 두 단위 로직 = 단위 입력으로 고르고 1 · 그 밖 = 사용자 입력.
|
||||
M01 은 읽기만(로직 별칭 안 씀).
|
||||
"""
|
||||
|
||||
@@ -21,7 +17,6 @@ from __future__ import annotations
|
||||
import json
|
||||
import re
|
||||
import unicodedata
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
@@ -41,7 +36,6 @@ mm = mp.mm
|
||||
SEED_NAME = "일위대가조합.json"
|
||||
LOGIC_KINDS = ("단가산출", "기계", "기타")
|
||||
MATERIAL_KINDS = ("자재", "별도계상")
|
||||
READY = ("기억", "같음", "호표")
|
||||
MACH_WORDS = (
|
||||
("불도우저", "불도저"),
|
||||
("굴삭기", "굴착기"),
|
||||
@@ -377,41 +371,108 @@ def _strong_basis(line: dict[str, Any], cand: dict[str, Any]) -> bool:
|
||||
|
||||
|
||||
def b_order(line: dict[str, Any], got: dict[str, Any] | None = None, text: Any = ()) -> dict:
|
||||
"""이름 · 근거 차례 `{상태: 같음 | 후보 | 없음, top: [로직 5], 합침: [로직 15]}` — 이름 후보 +
|
||||
근거 후보 합침(접힘 없음) · 「같음」도 근거 검사(인용 조항이 다른 로직을 가리키면 후보) ·
|
||||
옛 판 글이면 같음 그대로 · 씨앗은 이름 같음 자리."""
|
||||
"""이름 · 근거 차례 `{상태: 같음 | 후보 | 없음, top: [로직 5], 합침: [로직 15], 후보: [5],
|
||||
근거: {로직: 근거}}` — 이름 후보 + 근거 후보 합침(접힘 없음) · 「같음」도 근거 검사(인용 조항이
|
||||
다른 로직을 가리키면 후보) · 옛 판 글이면 같음 그대로 · 씨앗은 이름 같음 자리."""
|
||||
got = got or targets()
|
||||
basis_ = dict(line.get("근거") or {})
|
||||
if basis_.get("출처"):
|
||||
line = {**line, "근거": {**basis_, "출처": fix_typo(basis_["출처"])}}
|
||||
basis = hopyo_basis.candidates(line, got)
|
||||
named = candidates(line, "로직", got, None)
|
||||
merged = [c.get("로직") for c in hopyo_basis.merge(basis, named) if c.get("로직")]
|
||||
merged_c = [c for c in hopyo_basis.merge(basis, named) if c.get("로직")]
|
||||
merged = [c["로직"] for c in merged_c]
|
||||
why = {c["로직"]: c["근거"] for c in merged_c if c.get("근거")}
|
||||
by = {c["로직"]: c for c in merged_c}
|
||||
pub = {x["로직"]: x for x in got["로직"]}
|
||||
|
||||
def done(state: str, top: list[str]) -> dict:
|
||||
cands = [by.get(k) or {**_public(pub[k]), "점수": None} for k in top[:5] if k in pub]
|
||||
return {"상태": state, "top": top[:5], "합침": merged[:15], "후보": cands, "근거": why}
|
||||
|
||||
found = _exact(line, "로직", got)
|
||||
if len(found) == 1:
|
||||
tgt = found[0]["로직"]
|
||||
theirs = (line.get("근거") or {}).get("출처") or []
|
||||
our = hopyo_basis.ours().get(tgt)
|
||||
if _OLD_TEXT.search(" ".join(str(t) for t in list(text)[:5])) or not theirs:
|
||||
return {"상태": "같음", "top": [tgt], "합침": merged[:15]}
|
||||
return done("같음", [tgt])
|
||||
if our and clause(theirs, our)[0] >= 0.9:
|
||||
return {"상태": "같음", "top": [tgt], "합침": merged[:15]}
|
||||
strong = [c for c in basis if c["근거"]["까닭"].startswith("조항 같음")]
|
||||
return done("같음", [tgt])
|
||||
strong = [c for c in basis if c["근거"]["까닭"].startswith(("조항 같음", "윗마디 같음"))]
|
||||
strong = [c for c in strong if _strong_basis(line, c)]
|
||||
if not strong:
|
||||
return {"상태": "같음", "top": [tgt], "합침": merged[:15]}
|
||||
return done("같음", [tgt])
|
||||
top = list(dict.fromkeys([strong[0]["로직"], tgt] + [c["로직"] for c in strong[1:]]))
|
||||
return {"상태": "후보", "top": top[:5], "합침": merged[:15]}
|
||||
return done("후보", top)
|
||||
top = [x["로직"] for x in found]
|
||||
rest = [c for c in hopyo_basis.merge(basis, named) if c.get("로직") not in top]
|
||||
rest = [c for c in merged_c if c["로직"] not in top]
|
||||
first = next((c.get("로직") for c in named if c.get("로직") not in top), None)
|
||||
top += [c["로직"] for c in hopyo_basis.pick(rest, first if basis else None, 5 - len(top))]
|
||||
return {"상태": "후보" if top else "없음", "top": top[:5], "합침": merged[:15]}
|
||||
return done("후보" if top else "없음", top)
|
||||
|
||||
|
||||
# ── 줄 · 호표 맞춤(338 · 설계 3-3 · 3-4 — 몸은 `M02_Hopyo_Line`) ─────────────────
|
||||
def _logic_specs(code: str) -> list[dict[str, Any]]:
|
||||
got = labor_inputs.logic_inputs([code])["로직"]
|
||||
return got[0]["입력"] if got else []
|
||||
|
||||
|
||||
def _line_mod():
|
||||
from M02_MasterTemplete import M02_Hopyo_Line as line_mod # 줄 맞춤이 이 파일을 부름
|
||||
|
||||
return line_mod
|
||||
|
||||
|
||||
def units_of(doc: dict[str, Any]) -> dict[int, str]:
|
||||
return {int(h["번"]): str(h.get("단위") or "") for h in doc.get("호표") or []}
|
||||
|
||||
|
||||
def view(folder: Path, key: str, no: int | None = None, to: Path | None = None) -> dict[str, Any]:
|
||||
"""실무자료 보기 — `no` 없으면 호표마다 [체크 제안 수, 줄 수] · 있으면 그 호표 줄마다 맞춤(3-3)
|
||||
·
|
||||
`to` = 만들 층(장부 · 확인된 사전조합 · 없으면 파일 층) · 맞춤셈이 없으면 뒤 셈을 넣음."""
|
||||
lines = _line_mod()
|
||||
ctx = lines.Ctx(folder, key, to)
|
||||
doc = ctx.doc
|
||||
if no is None:
|
||||
hopyos = [lines.hopyo_view(h, ctx, False) for h in doc.get("호표") or []]
|
||||
keep = ("번", "이름", "규격", "단위", "금액", "맞춤", "행")
|
||||
brief = [{k: h.get(k) for k in keep if k in h} for h in hopyos]
|
||||
ready = [sum(h["맞춤"][0] for h in hopyos), sum(h["맞춤"][1] for h in hopyos)]
|
||||
head = {**hopyo_store.brief(doc), "검산": doc.get("검산"), "셈": ctx.calc}
|
||||
return {**head, "호표": brief, "맞춤": ready}
|
||||
hopyo = ctx.units.get(int(no))
|
||||
if hopyo is None:
|
||||
raise store.StoreError(404, f"없는 호표 「제{no}호표」")
|
||||
return {**hopyo_store.brief(doc), "호표": lines.hopyo_view(hopyo, ctx)}
|
||||
|
||||
|
||||
def guess(
|
||||
folder: Path, key: str, no: int, at: int, target: Any, to: Path | None = None
|
||||
) -> dict[str, Any]:
|
||||
"""미리 셈(3-4 · 저장 안 함) — 대상 목록(낱개도 받음) → {대상, 품, 글}."""
|
||||
return _line_mod().guess(folder, key, no, at, target, to)
|
||||
|
||||
|
||||
def picks(folder: Path) -> list[dict[str, Any]]:
|
||||
"""[로직 추가] 「실무 호표」 줄 — 이 층 · 위 층 실무자료 호표마다 맞춤 [체크 제안 수, 줄 수]
|
||||
(장부 · 확인된 사전조합 = 이 층)."""
|
||||
lines, out = _line_mod(), []
|
||||
for one in chain(folder):
|
||||
layer, owner = refs.where(one)
|
||||
for row in hopyo_store.rows(one):
|
||||
ctx = lines.Ctx(one, row["키"], folder)
|
||||
for hopyo in ctx.doc.get("호표") or []:
|
||||
ready = lines.hopyo_view(hopyo, ctx, False)["맞춤"]
|
||||
head = {"층": layer, "주인": owner, "키": row["키"], "파일": row["파일"]}
|
||||
keep = ("번", "이름", "규격", "단위")
|
||||
out.append({**head, **{k: hopyo.get(k) for k in keep}, "맞춤": ready})
|
||||
return out
|
||||
|
||||
|
||||
# ── 층 기억 ───────────────────────────────────────────────────────────
|
||||
def chain(folder: Path) -> list[Path]:
|
||||
"""기억을 볼 층 폴더 — 같은 층 → 위 층(개인 → 회사 → 마스터)."""
|
||||
"""실무자료를 볼 층 폴더 — 같은 층 → 위 층(개인 → 회사 → 마스터)."""
|
||||
layer, owner = refs.where(folder)
|
||||
out = [Path(folder)]
|
||||
if layer == "personal":
|
||||
@@ -419,196 +480,3 @@ def chain(folder: Path) -> list[Path]:
|
||||
if layer in ("personal", "company"):
|
||||
out.append(Path(store.FOLDER))
|
||||
return out
|
||||
|
||||
|
||||
def memories(folder: Path) -> list[dict[str, dict[str, Any]]]:
|
||||
return [hopyo_store.memory(one) for one in chain(folder)]
|
||||
|
||||
|
||||
def recall(line: dict[str, Any], memo: list[dict[str, dict[str, Any]]]) -> dict[str, Any] | None:
|
||||
key = line_key(line)
|
||||
return next((one[key] for one in memo if key in one), None)
|
||||
|
||||
|
||||
# ── 줄 하나 맞춤 ───────────────────────────────────────────────────────
|
||||
def _logic_specs(code: str) -> list[dict[str, Any]]:
|
||||
got = labor_inputs.logic_inputs([code])["로직"]
|
||||
return got[0]["입력"] if got else []
|
||||
|
||||
|
||||
def _target_of(remembered: dict[str, Any], got: dict[str, Any]) -> dict[str, Any] | None:
|
||||
if remembered.get("로직"):
|
||||
return next((x for x in got["로직"] if x["로직"] == remembered["로직"]), None)
|
||||
if remembered.get("자재"):
|
||||
return next((x for x in got["자재"] if x["자재"] == remembered["자재"]), None)
|
||||
return None
|
||||
|
||||
|
||||
def match_line(
|
||||
line: dict[str, Any],
|
||||
memo: list[dict[str, dict[str, Any]]],
|
||||
units: dict[int, str],
|
||||
full: bool = True,
|
||||
got: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""줄 → `{열쇠, 상태, 대상, 입력값, 못읽음, 환산, 들어감, 후보?}` —
|
||||
`units` = {호표 번: 단위}(일위N 참조) · `full` = 입력값 · 후보까지(아니면 상태 · 환산만)."""
|
||||
out: dict[str, Any] = {"열쇠": line_key(line), "대상": None, "입력값": {}, "못읽음": []}
|
||||
kind = line.get("갈래")
|
||||
pool = "자재" if kind in MATERIAL_KINDS else "로직"
|
||||
remembered = recall(line, memo)
|
||||
got = got or targets()
|
||||
if kind == "%":
|
||||
return {**out, "상태": "%", "환산": None, "들어감": False}
|
||||
if remembered and remembered.get("빼기"):
|
||||
return {**out, "상태": "빼기", "환산": None, "들어감": False}
|
||||
if kind == "호표":
|
||||
no = int((line.get("참조") or {}).get("번") or 0)
|
||||
conv = factor(line.get("단위"), units.get(no), line.get("규격"))
|
||||
target = {"호표": no, "단위": units.get(no)}
|
||||
ready = no in units and conv["배"] is not None and conv["갈래"] != "추정"
|
||||
return {**out, "상태": "호표", "대상": target, "환산": conv, "들어감": ready}
|
||||
if remembered and (target := _target_of(remembered, got)):
|
||||
conv = {"배": remembered.get("환산"), "갈래": "기억"}
|
||||
state = {"상태": "기억", "대상": _public(target), "입력값": remembered.get("입력값") or {}}
|
||||
return {**out, **state, "환산": conv, "들어감": conv["배"] is not None}
|
||||
found = _exact(line, pool, got)
|
||||
if len(found) == 1:
|
||||
target = found[0]
|
||||
conv = factor(line.get("단위"), target["단위"], line.get("규격"))
|
||||
out.update(상태="같음", 대상=_public(target), 환산=conv)
|
||||
out["들어감"] = conv["배"] is not None and conv["갈래"] != "추정"
|
||||
if full and "로직" in target:
|
||||
out["입력값"], out["못읽음"] = spec_inputs(line, _logic_specs(target["로직"]))
|
||||
return out
|
||||
out.update(상태="후보" if found else "없음", 환산=None, 들어감=False)
|
||||
if not full:
|
||||
return out
|
||||
top = [{**_public(x), "점수": 9} for x in found]
|
||||
same = [_public(x) for x in found]
|
||||
basis = hopyo_basis.candidates(line, got) if pool == "로직" else []
|
||||
named = candidates(line, pool, got, None if basis else 5)
|
||||
rest = [c for c in hopyo_basis.merge(basis, named) if _public(c) not in same]
|
||||
first = next((c.get("로직") for c in named if _public(c) not in same), None)
|
||||
out["후보"] = top[:5] + hopyo_basis.pick(rest, first if basis else None, 5 - len(top))
|
||||
return {**out, "상태": "후보" if out["후보"] else "없음"}
|
||||
|
||||
|
||||
def guess(folder: Path, key: str, no: int, at: int, target: dict[str, Any]) -> dict[str, Any]:
|
||||
"""후보 · 고른 대상 하나 미리 채움 — `{대상, 입력값, 못읽음, 환산}`(확정 전 화면 보기)."""
|
||||
line = _line(folder, key, no, at)
|
||||
found = _target_of(target if isinstance(target, dict) else {}, targets())
|
||||
if found is None:
|
||||
raise store.StoreError(400, "돈 로직 · 우리 자재가 아님")
|
||||
target = _public(found)
|
||||
conv = factor(line.get("단위"), target.get("단위"), line.get("규격"))
|
||||
if not target.get("로직"):
|
||||
return {"대상": target, "입력값": {}, "못읽음": [], "환산": conv}
|
||||
values, unread = spec_inputs(line, _logic_specs(str(target["로직"])))
|
||||
return {"대상": target, "입력값": values, "못읽음": unread, "환산": conv}
|
||||
|
||||
|
||||
def _line(folder: Path, key: str, no: int, at: int) -> dict[str, Any]:
|
||||
doc = hopyo_store.read(folder, key)
|
||||
hopyo = next((h for h in doc.get("호표") or [] if int(h["번"]) == int(no)), None)
|
||||
lines = (hopyo or {}).get("줄") or []
|
||||
if not 0 <= int(at) < len(lines):
|
||||
raise store.StoreError(404, f"없는 줄 「제{no}호표 {at}」")
|
||||
return lines[int(at)]
|
||||
|
||||
|
||||
def match_hopyo(
|
||||
hopyo: dict[str, Any],
|
||||
memo: list[dict[str, dict[str, Any]]],
|
||||
units: dict[int, str],
|
||||
full: bool = True,
|
||||
got: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""호표 하나 — 줄마다 맞춤 · `맞춤` = [들어감 수, 줄 수]."""
|
||||
got = got or targets()
|
||||
lines = [
|
||||
{**one, "맞춤": match_line(one, memo, units, full, got)} for one in hopyo.get("줄") or []
|
||||
]
|
||||
ready = sum(1 for one in lines if one["맞춤"]["들어감"])
|
||||
return {**hopyo, "줄": lines, "맞춤": [ready, len(lines)]}
|
||||
|
||||
|
||||
def units_of(doc: dict[str, Any]) -> dict[int, str]:
|
||||
return {int(h["번"]): str(h.get("단위") or "") for h in doc.get("호표") or []}
|
||||
|
||||
|
||||
def view(folder: Path, key: str, no: int | None = None) -> dict[str, Any]:
|
||||
"""실무자료 보기 — `no` 없으면 호표마다 맞춤 수만 · 있으면 그 호표 줄마다 맞춤 · 후보."""
|
||||
doc = hopyo_store.read(folder, key)
|
||||
memo, units = memories(folder), units_of(doc)
|
||||
if no is None:
|
||||
got = targets()
|
||||
hopyos = [match_hopyo(h, memo, units, False, got) for h in doc.get("호표") or []]
|
||||
keep = ("번", "이름", "규격", "단위", "금액", "맞춤")
|
||||
brief = [{k: h.get(k) for k in keep} for h in hopyos]
|
||||
ready = [sum(h["맞춤"][0] for h in hopyos), sum(h["맞춤"][1] for h in hopyos)]
|
||||
return {**hopyo_store.brief(doc), "검산": doc.get("검산"), "호표": brief, "맞춤": ready}
|
||||
hopyo = next((h for h in doc.get("호표") or [] if int(h["번"]) == int(no)), None)
|
||||
if hopyo is None:
|
||||
raise store.StoreError(404, f"없는 호표 「제{no}호표」")
|
||||
return {**hopyo_store.brief(doc), "호표": match_hopyo(hopyo, memo, units)}
|
||||
|
||||
|
||||
def picks(folder: Path) -> list[dict[str, Any]]:
|
||||
"""[로직 추가] 「실무 호표」 줄 — 이 층 · 위 층 실무자료 호표마다 맞춤 n/m
|
||||
(이 층 기억 · 만드는 층)."""
|
||||
memo, got, out = memories(folder), targets(), []
|
||||
for one in chain(folder):
|
||||
layer, owner = refs.where(one)
|
||||
for row in hopyo_store.rows(one):
|
||||
doc = hopyo_store.read(one, row["키"])
|
||||
units = units_of(doc)
|
||||
for hopyo in doc.get("호표") or []:
|
||||
ready = match_hopyo(hopyo, memo, units, False, got)["맞춤"]
|
||||
head = {"층": layer, "주인": owner, "키": row["키"], "파일": row["파일"]}
|
||||
keep = ("번", "이름", "규격", "단위")
|
||||
out.append({**head, **{k: hopyo.get(k) for k in keep}, "맞춤": ready})
|
||||
return out
|
||||
|
||||
|
||||
# ── 확정 저장 ─────────────────────────────────────────────────────────
|
||||
def confirm(folder: Path, entries: dict[str, Any]) -> dict[str, Any]:
|
||||
"""확정 · 빼기 · 지움 → 층 기억 — `{열쇠: {로직|자재|빼기, 입력값, 환산} | None}`.
|
||||
로직 = 돈 로직 · 자재 = M01 자재품목 · 환산 = 0 넘는 수(빼기는 안 봄)."""
|
||||
if not isinstance(entries, dict) or not entries:
|
||||
raise store.StoreError(400, "확정할 줄이 없음")
|
||||
got = targets()
|
||||
logics = {x["로직"] for x in got["로직"]}
|
||||
mats = {x["자재"] for x in got["자재"]}
|
||||
stamp = datetime.now().isoformat(timespec="seconds")
|
||||
clean: dict[str, Any] = {}
|
||||
for key, raw in entries.items():
|
||||
if raw is None:
|
||||
clean[str(key)] = None
|
||||
continue
|
||||
if not isinstance(raw, dict) or str(key).count("|") != 2:
|
||||
raise store.StoreError(400, f"확정 꼴이 틀림 「{key}」")
|
||||
if raw.get("빼기") is True:
|
||||
clean[str(key)] = {"빼기": True, "확정": stamp}
|
||||
continue
|
||||
code, mat = str(raw.get("로직") or ""), str(raw.get("자재") or "")
|
||||
if (code and code not in logics) or (mat and mat not in mats) or bool(code) == bool(mat):
|
||||
raise store.StoreError(400, f"돈 로직 · 우리 자재 하나만 「{key}」")
|
||||
times = raw.get("환산")
|
||||
number = None if isinstance(times, bool) else _number(times)
|
||||
if number is None or number <= 0:
|
||||
raise store.StoreError(400, f"환산은 0 넘는 수 「{key}」")
|
||||
values = raw.get("입력값") or {}
|
||||
if not isinstance(values, dict):
|
||||
raise store.StoreError(400, f"입력값이 묶음 아님 「{key}」")
|
||||
head = {"로직": code} if code else {"자재": mat}
|
||||
clean[str(key)] = {**head, "입력값": values, "환산": _json(number), "확정": stamp}
|
||||
return {"기억": hopyo_store.remember(folder, clean)}
|
||||
|
||||
|
||||
def _number(raw: Any) -> Decimal | None:
|
||||
try:
|
||||
got = Decimal(str(raw).replace(",", "").strip())
|
||||
except ArithmeticError:
|
||||
return None
|
||||
return got if got.is_finite() else None
|
||||
|
||||
Reference in New Issue
Block a user