Merge remote-tracking branch 'origin/sub_laptop_4' into main_laptop_1
This commit is contained in:
@@ -16,6 +16,7 @@ from M01_MasterData import M01_MasterData_Store as store
|
||||
from M01_MasterData import M01_MasterData_Store_Combo as combo_store
|
||||
from M01_MasterData import M01_MasterData_Store_Flow as flow
|
||||
from M01_MasterData import M01_MasterData_Store_Make as make
|
||||
from M01_MasterData import M01_MasterData_Store_Revise as revise
|
||||
|
||||
import master_basis as mbs # noqa: E402 (`store` 가 scripts 를 길에 둠)
|
||||
|
||||
@@ -49,6 +50,7 @@ class FileChanges(BaseModel):
|
||||
|
||||
class SaveBody(BaseModel):
|
||||
files: list[FileChanges]
|
||||
개정확인: bool = False # 로직 정본 줄 고침 = 「원문 개정 반영」 확인(713-1) · 없으면 409
|
||||
|
||||
|
||||
class NewLogic(BaseModel):
|
||||
@@ -196,7 +198,9 @@ def get_logics(
|
||||
|
||||
@router.get("/logic")
|
||||
def get_logic(key: str) -> dict:
|
||||
return _call(store.logic, key)
|
||||
"""`지문` = 줄 바탕 지문(계산 칸 · `Store_Revise.row_print`) — M02 덮개 「M01 개정」 비교 값."""
|
||||
out = _call(store.logic, key)
|
||||
return {**out, "지문": revise.row_print(out["logic"])}
|
||||
|
||||
|
||||
@router.get("/logic/source")
|
||||
@@ -275,7 +279,7 @@ def post_text(body: CalcBody) -> dict:
|
||||
@router.post("/save")
|
||||
def post_save(body: SaveBody) -> dict:
|
||||
"""「흐름」 줄은 검사 · 서버 다시 셈 · 새 줄(키 없음)은 자체 GX — {files, added, warnings}."""
|
||||
return _call(flow.save, [f.model_dump() for f in body.files])
|
||||
return _call(flow.save, [f.model_dump() for f in body.files], body.개정확인)
|
||||
|
||||
|
||||
@router.get("/logic/flow")
|
||||
|
||||
@@ -21,6 +21,7 @@ from typing import Any
|
||||
|
||||
from M01_MasterData import M01_MasterData_Store as store
|
||||
from M01_MasterData import M01_MasterData_Store_Make as make
|
||||
from M01_MasterData import M01_MasterData_Store_Revise as revise
|
||||
from M02_MasterTemplete.DataFlow import M02_DF_Compute as dc
|
||||
from M02_MasterTemplete.DataFlow import M02_DF_Values as dv
|
||||
|
||||
@@ -375,8 +376,10 @@ def _bare(row: dict) -> dict:
|
||||
return {"끝수": None, **kept, "입력": inputs, "출처": row.get("출처") or "자체"}
|
||||
|
||||
|
||||
def save(batch: list[dict]) -> dict:
|
||||
"""`/save` — 흐름 줄은 검사 · 다시 셈 · 새 줄은 자체 GX · 그 밖은 `Store.save` 그대로."""
|
||||
def save(batch: list[dict], confirmed: bool = False) -> dict:
|
||||
"""`/save` — 흐름 줄은 검사 · 다시 셈 · 새 줄은 자체 GX · 그 밖은 `Store.save` 그대로 ·
|
||||
로직 정본 줄 고침은 `confirmed`(「원문 개정 반영」) 없으면 409(`Store_Revise` · 713-1)."""
|
||||
revise.guard(batch, confirmed)
|
||||
warns, fresh = [], False
|
||||
for part in batch:
|
||||
for ch in part.get("changes") or []:
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
"""M01 로직 정본 개정 확인 (PLAN 713-1 · 요구서 내역12 4장 10 · 17).
|
||||
|
||||
- 로직 정본 파일(`로직_*.json` · 자체 `로직_자체.json` 뺌) 줄을 실제로 바꾸는 `/save`(흐름 저장 ·
|
||||
지우기 포함)는 「원문 개정 반영」 확인 값(`개정확인`)이 있어야 함 — 없으면 409 + 까닭.
|
||||
- 요소 파일(인력 · 재료 · 표 …) · 자체 로직 · 같은 줄 그대로 다시 보냄은 대상 밖.
|
||||
- 줄 바탕 지문(`row_print`) = 계산 칸만 정렬 JSON(수는 Decimal 정규 글) sha256 앞 16 자 —
|
||||
M02 덮개가 담아 두고 「M01 개정 — 확인」 배지를 띄울 값.
|
||||
- 판 칸 · 옛 줄 사본은 안 둠 — 정본 이력은 git.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from decimal import Decimal
|
||||
|
||||
from M01_MasterData import M01_MasterData_Store as store
|
||||
|
||||
OWN = "로직_자체.json"
|
||||
PRINT_FIELDS = ("결과단위", "입력", "중간", "호표", "덧줄", "결과", "끝수", "흐름")
|
||||
WHY = "로직 정본(품셈 원문) 줄을 바꿈 — 「원문 개정 반영」 확인 뒤 저장(개정확인)"
|
||||
|
||||
|
||||
def is_canon(file: str) -> bool:
|
||||
return file.startswith("로직_") and file.endswith(".json") and file != OWN
|
||||
|
||||
|
||||
def _norm(v):
|
||||
"""수는 int · float · Decimal 어느 꼴로 와도 같은 글(「10」 · 「10.0」 → 「10」)."""
|
||||
if isinstance(v, bool) or v is None or isinstance(v, str):
|
||||
return v
|
||||
if isinstance(v, (int, float, Decimal)):
|
||||
return format(Decimal(str(v)).normalize(), "f")
|
||||
if isinstance(v, list):
|
||||
return [_norm(x) for x in v]
|
||||
if isinstance(v, dict):
|
||||
return {k: _norm(x) for k, x in v.items()}
|
||||
return str(v)
|
||||
|
||||
|
||||
def row_print(row: dict) -> str:
|
||||
"""줄 바탕 지문 — 계산 칸(`PRINT_FIELDS`)만 · 이름 · 별칭 · 비고 고침은 지문을 안 바꿈."""
|
||||
calc = {k: _norm(row[k]) for k in PRINT_FIELDS if row.get(k) is not None}
|
||||
raw = json.dumps(calc, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
|
||||
return hashlib.sha256(raw.encode("utf-8")).hexdigest()[:16]
|
||||
|
||||
|
||||
def changed(batch: list[dict]) -> list[dict]:
|
||||
"""정본 줄을 바꾸는 고침 — `[{file, key, op, 이름}]` · 같은 줄을 그대로 다시 보낸 edit 은 뺌."""
|
||||
out = []
|
||||
for part in batch:
|
||||
file = str(part.get("file") or "")
|
||||
if not is_canon(file):
|
||||
continue
|
||||
try:
|
||||
rows = {str(r.get("키")): r for r in store.read(file)[0].get("줄") or []}
|
||||
except store.StoreError:
|
||||
continue # 없는 파일 — `store.save` 가 404
|
||||
for ch in part.get("changes") or []:
|
||||
op, key, row = ch.get("op"), str(ch.get("key") or ""), ch.get("row")
|
||||
old = rows.get(key)
|
||||
if op == "edit" and old is not None and store._dec(row) == old:
|
||||
continue
|
||||
name = (row or {}).get("이름") if isinstance(row, dict) else None
|
||||
out.append(
|
||||
{
|
||||
"file": file,
|
||||
"key": key or None,
|
||||
"op": op,
|
||||
"이름": name or (old or {}).get("이름"),
|
||||
}
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def guard(batch: list[dict], confirmed: bool) -> None:
|
||||
if confirmed:
|
||||
return
|
||||
hits = changed(batch)
|
||||
if hits:
|
||||
raise store.StoreError(409, {"개정": hits, "까닭": WHY})
|
||||
Reference in New Issue
Block a user