feat(785-5): 만들기 · 길 — 확인한 고른 것 → SC · SK · 장부 · 새 M02_Hopyo_Router · 판정 · 검산 · 옮기기
- 새 M02_Hopyo_Router — 옛 hopyo 길 모두 M02_Labor_Router 에서 옮김(옛 화면 785-7 까지 돎) · 새 길 hopyo-judge 목록 · 호표 · 블록 · hopyo/{키}/check · hopyo/{키}/move · main 등록 · 시동 warm · Fit.ensure 걷음 · 올림 때 엑셀 사전(Row.lexicon) · 블록 풀이 뒤 셈 · 끝내기 때 저장본 지움
- 새 M02_Hopyo_Judge — 1차 줄 판(Row) + 단산 줄 2차 풀이(Clause → Solve) · 엑셀 칸(이름 · 규격 · 단위 · 단가) · 블록 금액 · 절 몫 · 블록 풀이 파생 저장본(HP 폴더 _derived · 입력 지문 · 지문 다를 때만) · judged(folder, key) · 검산(입력값 = 엔진이 쓴 칸 모두 · 지역 · 자재지역 · 조종원직종 비움 · 맡은 절 단위 줄 · 몫과 품 대조 · 금액 = 일위대가 로직 줄과 같은 셈)
- 새 M02_Hopyo_Make — 고른 것 → SC 줄(같은 별칭 · 줄 본뜬 {호표, 차례, 행} · 수량 = 엑셀 × 몫 × 배 · 같은 기초 둘째 = 한 줄 SC 사본 · 별도계상 자재 · 이음 · 율 · 고정 · 참조) · SK 더한줄(바탕 없음 · SkUnit.adds · Sangeun.Maker.write) · 하위 먼저 · 입력 필요 · 단위 환산 못 정함 = 그 줄(블록)만 막힘 · 장부 고른 것 꼴 짝 · 이미 옮긴 것 = 덮음 없으면 409(남의 SC 덮지 않음) · 확인한 줄 몸(Lines) = Build 에서 옮김
- Build 나눔 — 모두 고정 길 · Builder · _order · _origins · build_all 그대로(B09 빌림) · 확인한 줄 몸 → Make.Lines
- SkUnit · Sangeun — SkPatch · Fit · Basis 부름 걷음(기계경비 기초 차례 SkUnit 으로 · 근거 꼬리표 = Clause.basis · 맞춤 셈 409 없앰 · 재료 고름 MO 됨) · Kids 가 _same_mark 가짐
- 시험 새 test_642_5_make(판정 · 검산 · 옮기기 · 예시 1 · 2 · 3 · 덮음 409 · 막는 길) · test_m02_hopyo_excel 마당에 새 Router 더함 · HOPYO_MAX_BYTES 자리(길 옮긴 까닭)
- 낡은 시험 고침: test_718_1 · test_729 「맞춤 셈 전 409」 뺌(맞춤 뒤 셈 안 기다림) · test_628 맞춤셈 캐시 넣기 뺌(근거 = 조항 색인) · test_729 근거 없음 꼴 · 고정 이음 유가 칸(785-3 LINKS 늘어난 까닭)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,590 @@
|
||||
"""M02 일위대가 실무자료 길(PLAN 785-5 · 요구 642 · 설계 642-A 2-9) — `/api/m02/layers/{층}/hopyo…`.
|
||||
|
||||
새 길(글 먼저 · 결정적 매칭) — 판정 `hopyo-judge`(1차 `M02_Hopyo_Row` + 2차 `M02_Hopyo_Solve` ·
|
||||
`M02_Hopyo_Judge`) · 검산 `hopyo/{키}/check`(저장 없음) · 옮기기 `hopyo/{키}/move`(확인한 것 →
|
||||
SC · SK · 장부 · `M02_Hopyo_Make`) · 경로 층 = 파일 층(판정 · 검산 · `to` = 만들 층) / 만들 층(옮기기).
|
||||
올림 = Book + 격자 + 엑셀 사전(`Row.lexicon`) + 블록 풀이 뒤 셈(`Judge.ensure` · 파생 저장본) ·
|
||||
옛 맞춤 뒤 셈 · 시동 데움 없음 · [끝내기] = 저장본도 지움.
|
||||
옛 길(`M02_Labor_Router` 에서 옮김 · 옛 화면 = 785-6 끝까지 · 지움 = 785-7) · 그대로 쓰는 길(격자 ·
|
||||
지우기 · 장부 · 붙여넣기 · 다시 잇기 · 구조물 조합 · 칠 = B09 · M01 이 씀).
|
||||
층 권한 = `Router_Items._root` · ⚠ 등록은 `main.py`(로그인만).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal
|
||||
|
||||
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
|
||||
from common_util.common_util_auth import verify_session
|
||||
from M02_MasterTemplete import M02_Hopyo_Build as hopyo_build
|
||||
from M02_MasterTemplete import M02_Hopyo_Excel as hopyo_excel
|
||||
from M02_MasterTemplete import M02_Hopyo_Forget as hopyo_forget
|
||||
from M02_MasterTemplete import M02_Hopyo_Grid as hopyo_grid
|
||||
from M02_MasterTemplete import M02_Hopyo_Item as hopyo_item
|
||||
from M02_MasterTemplete import M02_Hopyo_Judge as hopyo_judge
|
||||
from M02_MasterTemplete import M02_Hopyo_Ledger as hopyo_ledger
|
||||
from M02_MasterTemplete import M02_Hopyo_Make as hopyo_maker
|
||||
from M02_MasterTemplete import M02_Hopyo_Marks as hopyo_marks
|
||||
from M02_MasterTemplete import M02_Hopyo_Match as hopyo_match
|
||||
from M02_MasterTemplete import M02_Hopyo_Paste as hopyo_paste
|
||||
from M02_MasterTemplete import M02_Hopyo_Relink as hopyo_relink
|
||||
from M02_MasterTemplete import M02_Hopyo_Row as hopyo_row
|
||||
from M02_MasterTemplete import M02_Hopyo_Sangeun as hopyo_sangeun
|
||||
from M02_MasterTemplete import M02_Hopyo_Store as hopyo_store
|
||||
from M02_MasterTemplete import M02_Labor_Ref as refs
|
||||
from M02_MasterTemplete import M02_Layer_Access as access
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||||
from M02_MasterTemplete.M02_MasterTemplete_Router_Items import Layer, Session, _call, _root
|
||||
|
||||
router = APIRouter(prefix="/api/m02", tags=["M02 Hopyo"])
|
||||
|
||||
|
||||
# ── 새 길(785-5) — 판정 · 검산 · 옮기기 ─────────────────────────────────────
|
||||
def _to(session: Session, folder: Path, to: Layer | None, to_owner: str | None) -> Path:
|
||||
return _root(session, to, to_owner) if to is not None else folder
|
||||
|
||||
|
||||
@router.get("/layers/{layer}/hopyo-judge/{key}")
|
||||
def hopyo_judge_list(
|
||||
layer: Layer,
|
||||
key: str,
|
||||
owner: str | None = None,
|
||||
to: Layer | None = None,
|
||||
to_owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""호표 목록 + 1차 판정 수 · 블록 목록 `{키, 파일, 호표 [...], 블록 [...]}`(읽기만)."""
|
||||
folder = _root(session, layer, owner)
|
||||
target = _to(session, folder, to, to_owner)
|
||||
return _call(folder, lambda: hopyo_judge.listing(folder, key, target))
|
||||
|
||||
|
||||
@router.get("/layers/{layer}/hopyo-judge/{key}/{no}")
|
||||
def hopyo_judge_one(
|
||||
layer: Layer,
|
||||
key: str,
|
||||
no: int,
|
||||
owner: str | None = None,
|
||||
to: Layer | None = None,
|
||||
to_owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""호표 하나 — 줄 판(1차) · 단산 줄 `풀이`(2차 블록 풀이)."""
|
||||
folder = _root(session, layer, owner)
|
||||
target = _to(session, folder, to, to_owner)
|
||||
return _call(folder, lambda: hopyo_judge.hopyo(folder, key, no, target))
|
||||
|
||||
|
||||
@router.get("/layers/{layer}/hopyo-judge/{key}/block/{no}")
|
||||
def hopyo_judge_block(
|
||||
layer: Layer,
|
||||
key: str,
|
||||
no: int,
|
||||
owner: str | None = None,
|
||||
to: Layer | None = None,
|
||||
to_owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""단산 블록 하나 — 블록 풀이(`Solve.block`) + 이름 · 단위 · 부름 · 근거 · 만든."""
|
||||
folder = _root(session, layer, owner)
|
||||
target = _to(session, folder, to, to_owner)
|
||||
return _call(folder, lambda: hopyo_judge.block(folder, key, no, target))
|
||||
|
||||
|
||||
class Pick(BaseModel):
|
||||
"""고른 것(785-3 꼴) — 금액 · 품 칸 없음(서버가 셈) · 고정 단가 = 엑셀 줄 것."""
|
||||
|
||||
model_config = ConfigDict(extra="ignore")
|
||||
갈래: Literal["기초", "단가산출", "일위대가", "인력", "재료", "유가", "기계", "율", "고정"]
|
||||
키: str | None = Field(None, max_length=40)
|
||||
이름: str | None = Field(None, max_length=200)
|
||||
단위: str | None = Field(None, max_length=40)
|
||||
입력값: dict[str, Any] = Field(default_factory=dict)
|
||||
몫: float | None = None
|
||||
배: float | None = None
|
||||
비목: str | None = None
|
||||
기준: str | list[str] | None = None
|
||||
별도계상: bool | None = None
|
||||
층: Layer | None = None
|
||||
주인: str | None = None
|
||||
항목: int | None = None # 블록 고른 것 — 블록 단위 줄 차례(수량 = 그 줄 품)
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def _no_money(cls, raw: Any) -> Any:
|
||||
"""금액 · 품 · 수량 칸 = 400(서버가 셈 · 브라우저 값 안 받아 적음)."""
|
||||
if isinstance(raw, dict) and {"금액", "품", "수량"} & set(raw):
|
||||
raise ValueError("금액 · 품 · 수량은 서버가 셈")
|
||||
return raw
|
||||
|
||||
|
||||
class CheckBody(BaseModel):
|
||||
"""`호표` + `차례` = 일위대가 줄 · `블록` = 단산 블록(항목 줄과 대조) · `고른` = 고른 것."""
|
||||
|
||||
호표: int | None = None
|
||||
차례: int | None = None
|
||||
블록: int | None = None
|
||||
고른: list[Pick] = Field(default_factory=list, max_length=20)
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/hopyo/{key}/check")
|
||||
def hopyo_check(
|
||||
layer: Layer,
|
||||
key: str,
|
||||
body: CheckBody,
|
||||
owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""검산(저장 없음) — 고른 것마다 입력값(엔진이 쓴 칸 모두) · 입력필요 · 줄 대조 · 금액(서버 셈)."""
|
||||
folder = _root(session, layer, owner)
|
||||
picks = [p.model_dump(exclude_none=True) for p in body.고른]
|
||||
args = (folder, key, body.호표, body.차례, body.블록, picks)
|
||||
return _call(folder, hopyo_judge.check, *args)
|
||||
|
||||
|
||||
class MoveBody(BaseModel):
|
||||
"""확인한 것 — 파일 층 `층` · `주인` · `호표 {번: {차례: [고른 것]}}` · `블록 {번: [고른 것]}`."""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
층: Layer
|
||||
주인: str | None = None
|
||||
호표: dict[int, dict[int, list[Pick]]] = Field(default_factory=dict, max_length=500)
|
||||
블록: dict[int, list[Pick]] = Field(default_factory=dict, max_length=500)
|
||||
덮음: bool = False # 이미 옮긴 호표 · 블록(같은 HP 출처) 덮기 — 아니면 409 {까닭, 호표, 블록}
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/hopyo/{key}/move")
|
||||
def hopyo_move(
|
||||
layer: Layer,
|
||||
key: str,
|
||||
body: MoveBody,
|
||||
owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""[옮기기] — 확인한 것 → 이 층 SC · SK · 장부(하위 먼저 · 서버가 다시 셈) →
|
||||
`{만듦, 단가산출, 막힘, 하위, 장부}`."""
|
||||
folder = _root(session, layer, owner, write=True)
|
||||
source = _root(session, body.층, body.주인)
|
||||
dump = lambda ps: [p.model_dump(exclude_none=True) for p in ps] # noqa: E731
|
||||
units = {no: {at: dump(ps) for at, ps in rows.items()} for no, rows in body.호표.items()}
|
||||
blocks = {no: dump(ps) for no, ps in body.블록.items()}
|
||||
args = (folder, source, key, units, blocks, _who(session), body.덮음)
|
||||
return _call(folder, hopyo_maker.move, *args)
|
||||
|
||||
|
||||
# ── 옛 길(785-7 까지 · 옛 화면) · 그대로 쓰는 길 ─────────────────────────────
|
||||
HOPYO_MAX_BYTES = 20 * 1024 * 1024
|
||||
|
||||
|
||||
def _who(session: Session) -> dict[str, Any]:
|
||||
return {"id": session.get("user_id"), "이름": session.get("name")}
|
||||
|
||||
|
||||
def _upload(name: str, data: bytes, who: dict[str, Any]) -> dict[str, Any]:
|
||||
mark = hopyo_store.fingerprint(data)
|
||||
hopyo_forget.sweep(store.root()) # 하루 지난 작업 중 HP 끝냄(743-1)
|
||||
if same := next((r for r in hopyo_store.rows(store.root()) if r.get("지문") == mark), None):
|
||||
hopyo_judge.ensure(store.root(), same["키"])
|
||||
return {**same, "이어서": True}
|
||||
try:
|
||||
parsed = hopyo_excel.read_book(name, data)
|
||||
except hopyo_excel.ExcelError as error:
|
||||
raise store.StoreError(400, str(error)) from error
|
||||
try:
|
||||
grid = hopyo_grid.read(name, data, parsed)
|
||||
except Exception: # 격자는 보기용 덤 — 못 만들어도 올리기는 됨(보기 창 404)
|
||||
grid = None
|
||||
sheets, _ = hopyo_excel._open(name, data, hopyo_row.SHEETS)
|
||||
parsed["사전"] = hopyo_row.lexicon(sheets) # 엑셀 사전(785-3 · 1차 줄 판정)
|
||||
row = hopyo_store.save(store.root(), name, parsed, who, grid, mark)
|
||||
hopyo_judge.ensure(store.root(), row["키"]) # 블록 풀이 뒤 셈(파생 저장본 · 화면 안 기다림)
|
||||
return row
|
||||
|
||||
|
||||
@router.get("/layers/{layer}/hopyo")
|
||||
def hopyo_list(
|
||||
layer: Layer, owner: str | None = None, session: Session = Depends(verify_session)
|
||||
) -> dict:
|
||||
"""실무자료 목록 `{줄: [{키, 파일, 올린때, 올린이, 시트, 호표수, 어긋남}]}`."""
|
||||
return {"줄": hopyo_store.rows(_root(session, layer, owner))}
|
||||
|
||||
|
||||
@router.get("/layers/{layer}/hopyo-picks")
|
||||
def hopyo_picks(
|
||||
layer: Layer, owner: str | None = None, session: Session = Depends(verify_session)
|
||||
) -> dict:
|
||||
"""[로직 추가] 「실무 호표」 줄 — 이 층 · 위 층 파일의 호표마다
|
||||
`{층, 주인, 키, 파일, 번, 이름, 규격, 단위, 맞춤, 확인}`(확인 = 이 층에 확인된 사전조합)."""
|
||||
folder = _root(session, layer, owner)
|
||||
return {"줄": _call(folder, _picks, folder)}
|
||||
|
||||
|
||||
def _picks(folder: Path) -> list[dict[str, Any]]:
|
||||
rows, here, done = hopyo_match.picks(folder), refs.where(folder), {}
|
||||
for row in rows:
|
||||
src = (row["층"], row.get("주인"))
|
||||
if src not in done:
|
||||
done[src] = hopyo_build.confirmed(folder, src)
|
||||
key = done[src].get(hopyo_build.mark(row["키"], row["번"]))
|
||||
row["확인"] = {"층": here[0], "주인": here[1], "키": key} if key else None
|
||||
return rows
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/hopyo")
|
||||
def hopyo_upload(
|
||||
layer: Layer,
|
||||
file: UploadFile = File(...),
|
||||
owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""엑셀(xls · xlsx) 올리기 — 「일위대가」 시트를 읽어 결과 · 격자 저장 · 20 MB ·
|
||||
엉터리 · 시트 없음 400."""
|
||||
folder = _root(session, layer, owner, write=True)
|
||||
data = file.file.read(HOPYO_MAX_BYTES + 1)
|
||||
if len(data) > HOPYO_MAX_BYTES:
|
||||
raise HTTPException(status_code=400, detail="파일이 너무 큼(20 MB 넘음)")
|
||||
name = Path(file.filename or "실무자료").name[:120]
|
||||
return _call(folder, _upload, name, data, _who(session))
|
||||
|
||||
|
||||
@router.get("/layers/{layer}/hopyo/{key}")
|
||||
def hopyo_read(
|
||||
layer: Layer, key: str, owner: str | None = None, session: Session = Depends(verify_session)
|
||||
) -> dict:
|
||||
"""실무자료 하나 — 검산 · 호표마다 `맞춤` [들어감, 줄 수] · 파일 `맞춤` 합."""
|
||||
folder = _root(session, layer, owner)
|
||||
return _call(folder, hopyo_match.view, folder, key)
|
||||
|
||||
|
||||
@router.get("/layers/{layer}/hopyo-grid/{key}")
|
||||
def hopyo_grid_read(
|
||||
layer: Layer, key: str, owner: str | None = None, session: Session = Depends(verify_session)
|
||||
) -> dict:
|
||||
"""엑셀꼴 격자(329-1) `{책: Workbook, 호표: [{번, 이름, 행}], 단산: [{번, 이름, 행}]}` ·
|
||||
옛 파일 404."""
|
||||
folder = _root(session, layer, owner)
|
||||
return _call(folder, hopyo_store.grid, folder, key)
|
||||
|
||||
|
||||
@router.get("/layers/{layer}/hopyo/{key}/{no}")
|
||||
def hopyo_one(
|
||||
layer: Layer,
|
||||
key: str,
|
||||
no: int,
|
||||
owner: str | None = None,
|
||||
to: Layer | None = None,
|
||||
to_owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""호표 하나 — 줄마다 맞춤(상태 · 체크 제안 · 대상 · 후보 5) · `to` = 만들 층(없으면 파일 층 ·
|
||||
호표 참조 줄 체크 · 만든)."""
|
||||
folder = _root(session, layer, owner)
|
||||
target = _root(session, to, to_owner) if to is not None else None
|
||||
return _call(folder, lambda: hopyo_match.view(folder, key, no, to=target))
|
||||
|
||||
|
||||
class GuessBody(BaseModel):
|
||||
"""`차례` = 호표 안 줄(0부터) · `대상` = [{로직 | 자재, 입력값?, 몫?}](낱개도 받음)."""
|
||||
|
||||
차례: int
|
||||
대상: list[dict[str, Any]] | dict[str, Any]
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/hopyo/{key}/{no}/guess")
|
||||
def hopyo_guess(
|
||||
layer: Layer,
|
||||
key: str,
|
||||
no: int,
|
||||
body: GuessBody,
|
||||
owner: str | None = None,
|
||||
to: Layer | None = None,
|
||||
to_owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""미리 셈(저장 안 함 · 설계 3-4) — 고른 대상 · 옵션으로 엔진 한 번 → `{대상, 품, 글}` ·
|
||||
`to` = 만들 층(없으면 파일 층)."""
|
||||
folder = _root(session, layer, owner)
|
||||
target = _root(session, to, to_owner) if to is not None else None
|
||||
args = (folder, key, no, body.차례, body.대상)
|
||||
return _call(folder, lambda: hopyo_match.guess(*args, to=target))
|
||||
|
||||
|
||||
class _Strict(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid") # 금액 · 품 칸 = 400(서버가 셈)
|
||||
|
||||
|
||||
class BuildTarget(_Strict):
|
||||
"""대상 하나 — 로직 · 자재 · 호표 중 하나 · 배 = 우리 수량 ÷ 엑셀 수량 · 몫 = 섞인 줄 몫."""
|
||||
|
||||
로직: str | None = None
|
||||
자재: str | None = None
|
||||
호표: int | None = None
|
||||
전국: bool | None = None # 「전국」 자재(361-1 · 키 없음 · 서버가 줄에서 다시 셈)
|
||||
노임: str | None = None # 729-2 사용자 이음 — LB 키(엑셀 숫자 + 이음)
|
||||
기계: str | None = None # EQ 키
|
||||
비율: Literal["노무비", "재료비", "경비"] | None = None # % 줄 밑수
|
||||
입력값: dict[str, Any] = Field(default_factory=dict)
|
||||
배: float = 1
|
||||
몫: float | None = None
|
||||
|
||||
|
||||
class BuildLine(_Strict):
|
||||
"""확인한 줄 — `차례` = 호표 안 줄(0부터) · `대상` 목록 또는 `뺌` 까닭."""
|
||||
|
||||
차례: int
|
||||
대상: list[BuildTarget] = Field(default_factory=list)
|
||||
뺌: Literal["우리에 없음", "안 씀"] | None = None
|
||||
|
||||
|
||||
class BuildWhole(_Strict):
|
||||
대상: list[BuildTarget]
|
||||
|
||||
|
||||
class BuildBody(_Strict):
|
||||
"""실무 호표 → 이 층 사전조합 — 파일 층 `층` · `주인` · `키` + 호표 `번` + 확인한 `줄` ·
|
||||
직접 줄 호표 = `통째` · 같은 본뜬은 늘 덮음."""
|
||||
|
||||
층: Layer
|
||||
주인: str | None = None
|
||||
키: str
|
||||
번: int
|
||||
줄: list[BuildLine] = Field(default_factory=list, max_length=500)
|
||||
통째: BuildWhole | None = None
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/hopyo-build")
|
||||
def hopyo_make(
|
||||
layer: Layer,
|
||||
body: BuildBody,
|
||||
owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""[확인하고 사전조합 만들기] — 확인한 줄만(이 층 쓰기 · 파일 층 읽기) · 별칭 장부 씀 →
|
||||
`{참조, 이름, 다시씀, 재사용, 빠진줄, 하위, 비교, 장부}` · 하위 호표 확인 전 = 함께 만듦(실패 409)."""
|
||||
folder = _root(session, layer, owner, write=True)
|
||||
source = _root(session, body.층, body.주인)
|
||||
lines = [one.model_dump(exclude_none=True) for one in body.줄]
|
||||
whole = body.통째.model_dump(exclude_none=True) if body.통째 else None
|
||||
args = (folder, source, body.키, body.번, lines, whole, _who(session))
|
||||
return _call(folder, hopyo_build.build, *args)
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/hopyo-unlinked")
|
||||
def hopyo_unlinked(
|
||||
layer: Layer,
|
||||
body: BuildBody,
|
||||
owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""안 이은 줄 미리 보기(729-2) — 만들기 몸 그대로 → `{남은줄 [{차례, 이름, 규격, 단위, 갈래, 까닭,
|
||||
후보}], 수}`(저장 없음)."""
|
||||
folder = _root(session, layer, owner, write=True)
|
||||
source = _root(session, body.층, body.주인)
|
||||
lines = [one.model_dump(exclude_none=True) for one in body.줄]
|
||||
whole = body.통째.model_dump(exclude_none=True) if body.통째 else None
|
||||
args = (folder, source, body.키, body.번, lines, whole)
|
||||
return _call(folder, hopyo_build.unlinked, *args)
|
||||
|
||||
|
||||
class BuildAllBody(_Strict):
|
||||
"""시트 통째(705-3) — 파일 층 `층` · `주인` · `키` 만(숫자 · 줄 칸 없음 · 서버가 파일에서 읽음)."""
|
||||
|
||||
층: Layer
|
||||
주인: str | None = None
|
||||
키: str
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/hopyo-build-all")
|
||||
def hopyo_make_all(
|
||||
layer: Layer,
|
||||
body: BuildAllBody,
|
||||
owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""[모두 고정으로 들여오기] — 시트 호표 모두 고정 줄로 · 하위 먼저 · 확인된 호표 그대로 →
|
||||
`{호표, 줄, 품셈, 고정, 호표참조, 빠짐, 이음, 이음후보, 같음, 그대로, 다름, 실패}`."""
|
||||
folder = _root(session, layer, owner, write=True)
|
||||
source = _root(session, body.층, body.주인)
|
||||
args = (folder, source, body.키, _who(session), True) # 729-2 — 남은 줄 있는 호표 = 실패
|
||||
return _call(folder, hopyo_build.build_all, *args)
|
||||
|
||||
|
||||
@router.get("/layers/{layer}/hopyo-ledger")
|
||||
def hopyo_ledger_read(
|
||||
layer: Layer, owner: str | None = None, session: Session = Depends(verify_session)
|
||||
) -> dict:
|
||||
"""별칭 장부 — 이 층 + 위 층 줄 `{열쇠, 원래, 갈래, 짝, 누가, 때, 처음파일, 층, 주인, 고침}`
|
||||
(고침 = 이 층 · 쓰기 권한)."""
|
||||
folder = _root(session, layer, owner)
|
||||
writable = access.grant(session, layer, owner).write
|
||||
return {"줄": _call(folder, hopyo_ledger.rows, folder, writable)}
|
||||
|
||||
|
||||
class LedgerBody(_Strict):
|
||||
"""`줄` = {열쇠: 장부 줄 | null(지움)} — 이 층만 · 서버가 짝을 다시 검사."""
|
||||
|
||||
줄: dict[str, Any]
|
||||
|
||||
|
||||
@router.put("/layers/{layer}/hopyo-ledger")
|
||||
def hopyo_ledger_write(
|
||||
layer: Layer,
|
||||
body: LedgerBody,
|
||||
owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""별칭 장부 [저장] — 고친 줄 · 지운 줄(층 쓰기) → `{씀, 줄}`."""
|
||||
folder = _root(session, layer, owner, write=True)
|
||||
return _call(folder, hopyo_ledger.put, folder, body.줄, _who(session))
|
||||
|
||||
|
||||
@router.delete("/layers/{layer}/hopyo/{key}")
|
||||
def hopyo_delete(
|
||||
layer: Layer, key: str, owner: str | None = None, session: Session = Depends(verify_session)
|
||||
) -> dict:
|
||||
"""실무자료 지우기 = [끝내기](718-1 · 743-1 · 화면 떠남도) — HP · 격자 · 맞춤셈 캐시 지움 ·
|
||||
만든 일위대가 · 단가산출은 엑셀 사본만 걷고 남음 · 별칭 장부 그대로 → `{지움, 만든}`."""
|
||||
folder = _root(session, layer, owner, write=True)
|
||||
got = _call(folder, hopyo_forget.finish, folder, key)
|
||||
hopyo_judge.forget(folder, key) # 블록 풀이 저장본(785-5)
|
||||
return got
|
||||
|
||||
|
||||
class SangeunBody(_Strict):
|
||||
"""`확인` = 사용자가 확인한 블록만 단위 SK `[{번, 고름?: {차례: 고름}}]`(778-5 · 자동 없음)."""
|
||||
|
||||
확인: list[dict[str, Any]] = Field(default_factory=list, max_length=500)
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/hopyo/{key}/sangeun")
|
||||
def hopyo_make_sangeun(
|
||||
layer: Layer,
|
||||
key: str,
|
||||
body: SangeunBody | None = None,
|
||||
owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""불러오기 결과 「단가산출」(718-1 · 778-5) — 블록마다 줄 판정 · 확인한 블록만 단위 SK
|
||||
(파일 층 = 이 층) → `{블록, 판정, 만듦, 못만듦, 그대로, 같은것씀, 위층같음}` · 맞춤 셈 전 409."""
|
||||
folder = _root(session, layer, owner, write=True)
|
||||
picks = body.확인 if body else []
|
||||
return _call(folder, hopyo_sangeun.build, folder, key, _who(session), picks)
|
||||
|
||||
|
||||
class PasteBody(_Strict):
|
||||
"""엑셀 붙여넣기(705-2) — `글` = 붙인 글 그대로 · `열` = 기억한 열 짝(없으면 머리 글 · 짐작 + 물음) ·
|
||||
`줄만` = 열린 문서에 줄로 · `있음` = 문서의 고정 줄 가름(이름 겹침 피함)."""
|
||||
|
||||
글: str = Field(max_length=hopyo_paste.TEXT_MAX)
|
||||
열: dict[str, int] | None = None
|
||||
줄만: bool = False
|
||||
있음: list[str] = Field(default_factory=list, max_length=500)
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/hopyo-paste")
|
||||
def hopyo_paste_run(
|
||||
layer: Layer,
|
||||
body: PasteBody,
|
||||
owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""[Ctrl+V] — `갈래` 물음(열 짝) · 줄(고정 줄 · 저장은 문서 [저장]) · 만듦(실무자료 + 호표마다 사전조합 ·
|
||||
층 쓰기 · `build_all` 답 + 키)."""
|
||||
folder = _root(session, layer, owner, write=True)
|
||||
args = (folder, body.글, body.열, _who(session), body.줄만, body.있음)
|
||||
return _call(folder, hopyo_paste.paste, *args)
|
||||
|
||||
|
||||
class RelinkRowsBody(BaseModel):
|
||||
"""다시 잇기 표(705-2) — `키` = 이 층 사전조합들 · `문서` = 열린 문서(저장 전 · 줄 · 후보만)."""
|
||||
|
||||
키: list[str] = Field(default_factory=list, max_length=500)
|
||||
문서: dict[str, Any] | None = None
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/hopyo-relink/rows")
|
||||
def hopyo_relink_rows(
|
||||
layer: Layer,
|
||||
body: RelinkRowsBody,
|
||||
owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""「이 공사 단가로 다시 잇기」 표 — 고정 줄마다 붙인 단가 · 이은 단가 · 후보(층 읽기)."""
|
||||
folder = _root(session, layer, owner)
|
||||
return _call(folder, hopyo_relink.rows, body.키, body.문서)
|
||||
|
||||
|
||||
class RelinkBody(_Strict):
|
||||
"""바꿈 `[{키, 줄, 노임?, 자재?}]` — 이음 키만(숫자 칸 없음 · 값 null = 풂)."""
|
||||
|
||||
바꿈: list[dict[str, Any]] = Field(max_length=hopyo_relink.CHANGES_MAX)
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/hopyo-relink")
|
||||
def hopyo_relink_apply(
|
||||
layer: Layer,
|
||||
body: RelinkBody,
|
||||
owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""[바꿈] · [되돌리기] — 이 층 사전조합 이음 칸 고쳐 씀(층 쓰기) → `{바뀜, 문서, 되돌림}`."""
|
||||
folder = _root(session, layer, owner, write=True)
|
||||
return _call(folder, hopyo_relink.apply, body.바꿈)
|
||||
|
||||
|
||||
@router.get("/layers/{layer}/hopyo/{key}/{no}/siblings")
|
||||
def hopyo_siblings(
|
||||
layer: Layer,
|
||||
key: str,
|
||||
no: int,
|
||||
owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""규격 묶음 제안(704-4) — 이름 · 단위 · 줄 구성 같고 규격만 다른 호표 `{묶음 [{번, 규격, 열}], 이름}`."""
|
||||
folder = _root(session, layer, owner)
|
||||
return _call(folder, hopyo_item.siblings, folder, key, no)
|
||||
|
||||
|
||||
class ItemBody(BuildBody):
|
||||
"""[구조물 조합으로](704-4) — 확인 창 몸 그대로 + `묶음` = 규격 열로 묶을 호표 번 · `이름` = 항목 이름."""
|
||||
|
||||
묶음: list[int] = Field(default_factory=list, max_length=20)
|
||||
이름: str | None = Field(None, max_length=120)
|
||||
새로: bool = False # 같은 내용 항목 409 뒤 — 그래도 새로(733-2)
|
||||
씀: str | None = Field(None, max_length=80) # 같은 내용 409 뒤 — 그 항목 씀
|
||||
|
||||
|
||||
@router.post("/layers/{layer}/hopyo-item")
|
||||
def hopyo_to_item(
|
||||
layer: Layer,
|
||||
body: ItemBody,
|
||||
owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""실무 호표 → 이 층 구조물 조합 항목(사전조합 안 만듦 · 이 층 쓰기 · 파일 층 읽기) →
|
||||
`{key, 열, 이름, 줄수, 열수, 빠진줄}`."""
|
||||
folder = _root(session, layer, owner, write=True)
|
||||
source = _root(session, body.층, body.주인)
|
||||
lines = [one.model_dump(exclude_none=True) for one in body.줄]
|
||||
whole = body.통째.model_dump(exclude_none=True) if body.통째 else None
|
||||
args = (folder, source, body.키, body.번, lines, whole, body.묶음, body.이름, _who(session))
|
||||
args += (body.새로, body.씀)
|
||||
return _call(folder, hopyo_item.make_item, *args)
|
||||
|
||||
|
||||
@router.get("/layers/{layer}/hopyo-marks/{key}")
|
||||
def hopyo_marks_read(
|
||||
layer: Layer,
|
||||
key: str,
|
||||
owner: str | None = None,
|
||||
to: Layer | None = None,
|
||||
to_owner: str | None = None,
|
||||
session: Session = Depends(verify_session),
|
||||
) -> dict:
|
||||
"""하이라이트 재료(779-2 · 읽기만) — `블록 [{번, 판정, SK, 갈래, 못옮김}]` · `to` = SK 를 볼 층."""
|
||||
folder = _root(session, layer, owner)
|
||||
target = _root(session, to, to_owner) if to else folder
|
||||
return _call(folder, lambda: {"블록": hopyo_marks.blocks(folder, key, target)})
|
||||
Reference in New Issue
Block a user