fix(778-2): 못옮김 기계 잇기 = 기계경비 기초 로직 줄 · 목록 밖 기계 셈 문맥 고침
- SkPatch LINK 기계 = 로직 · _Plan.link: EQ 키 → 기계경비 기초(그 기계를 고르기에 둔 기초 먼저) + 입력 기계 = 원문번호 · EQ 키 바로 더한줄 셈 오류 없앰 - master_pick resolve · machine · master_formula _inputs: 셈에 넘어온 마스터로 기계 찾음 — 까닭 = mp.EQ ContextVar 는 마스터를 지은 문맥에만 있어 서버 둘째 요청(스레드 풀 · 새 문맥)부터 목록 밖 기계(굴착기(타이어) 0.6㎥ 등)가 「고르기 밖」으로 막힘 · ContextVar 는 대신 길로 남김 - 새 시험 test_778_2_hopyo_machine_link(잇기 뒤 SK 셈 · 서버 두 요청 · 문맥 밖 스레드) - 여덟 파일 기초 80 · SK 37 · 고름 대기 89 그대로 · SK 셈 오류 0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,8 @@
|
||||
기계경비 로직(굴착기 GC000267 · 그 밖 GC000268 · 기계 = 옵션 거꾸로 찾기) · 남은 우리 = 뺀줄
|
||||
자재 = 같은 이름 우리 줄 수량 덮개 · 옮기지 못한 차이 = `못옮김`(버리지 않고 본뜬에 적음)
|
||||
못옮김 = `[{id, 갈래 노임|기계|자재, 이름, 규격, 단위, 수량(엑셀 1 단위 품), 글, 후보}]` · 사용자 `잇기`
|
||||
`[{id, 노임|기계|자재: 키}]` = SK 더한줄 인력 · 기계 · 재료(엑셀 수량 그대로) · `{id, 뺌: 까닭}` = 놓음.
|
||||
`[{id, 노임|기계|자재: 키}]` = SK 더한줄 인력 · 재료 · 기계(EQ = 기계경비 기초 로직 줄 · 778-2)
|
||||
(엑셀 수량 그대로) · `{id, 뺌: 까닭}` = 놓음.
|
||||
SK 단위(728-2) = 엑셀 줄 단위 · 바탕 기준단위와 성격 다름 = 환산 계수 = 우리 품 ÷ 엑셀 품(인력 · 장비
|
||||
둘 이상이 같은 비일 때만 · `환산` {계수}) · 못 구함 = 고름 대기(까닭 「단위 성격 다름 · 계수 필요」).
|
||||
고름 대기 = 후보 · 숫자 없음 — SK 안 만듦 · 후보 다섯(사용자가 고른 기초 `pick` = 견줘 SK · 기초) ·
|
||||
@@ -49,7 +50,7 @@ LOOKS = ("같음", "품다름", "엑셀만", "기초만") # 줄비교 꼴(746-1
|
||||
COMPARE = 3 # 고름 대기 후보 앞 몇에 줄비교
|
||||
LINK = {
|
||||
"노임": ("인력", "LB"),
|
||||
"기계": ("기계", "EQ"),
|
||||
"기계": ("로직", "EQ"), # EQ = 기계경비 기초 로직 줄 + 기계 입력(778-2)
|
||||
"자재": ("재료", "MT"),
|
||||
} # 잇기 칸 → 더한줄 · 키 머리
|
||||
|
||||
@@ -228,9 +229,17 @@ class _Plan:
|
||||
raise LinkError(f"「{got['id']}」 · 없는 키 「{key}」") from e
|
||||
self.lost.remove(got)
|
||||
name = str(elem.get("이름") or got["이름"])
|
||||
qty = _num(float(got["수량"]) * self.tb)
|
||||
if kind == "로직": # EQ 키 바로 = 셈 못 함 → 기계경비 기초(기계 = 그 EQ)
|
||||
kinds = mtypes(f"{elem.get('상세구분')} {elem.get('이름')}")
|
||||
code = _mach_logics(self.whole, kinds, elem)[0]
|
||||
name = f"{name} {elem.get('규격') or ''}".strip()
|
||||
self.add({"종류": kind, "로직": code, "입력값": {MACH_IN: elem.get("원문번호")},
|
||||
"이름": name, "단위": got["단위"] or "시간", "수량": qty}) # fmt: skip
|
||||
continue
|
||||
unit = got["단위"] or ("인" if kind == "인력" else str(elem.get("단위") or ""))
|
||||
self.add({"종류": kind, "요소": key, "이름": name, "단위": unit,
|
||||
"수량": _num(float(got["수량"]) * self.tb)}) # fmt: skip
|
||||
"수량": qty}) # fmt: skip
|
||||
|
||||
def labor(self, excel: dict[str, float]) -> None:
|
||||
mine = [x for x in self.lines if x["갈래"] == "인력" and not x["cid"].startswith("add:")]
|
||||
|
||||
@@ -512,7 +512,7 @@ def fill_empty(row: dict, given: dict) -> dict:
|
||||
return {**given, **gap}
|
||||
|
||||
|
||||
def _inputs(row: dict, given: dict) -> dict:
|
||||
def _inputs(row: dict, given: dict, master=None) -> dict:
|
||||
env = {}
|
||||
for spec in row.get("입력", []):
|
||||
name = spec["이름"]
|
||||
@@ -529,8 +529,8 @@ def _inputs(row: dict, given: dict) -> dict:
|
||||
pass
|
||||
if isinstance(value, (int, float)):
|
||||
value = Decimal(str(value))
|
||||
value = mp.resolve(spec, value) # 기계 EQ 키 → 원문 분류번호
|
||||
if "고르기" in spec and value not in spec["고르기"] and not mp.machine(spec, value):
|
||||
value = mp.resolve(spec, value, master) # 기계 EQ 키 → 원문 분류번호
|
||||
if "고르기" in spec and value not in spec["고르기"] and not mp.machine(spec, value, master):
|
||||
raise FormulaError(f"입력 「{name}」={value} 고르기 밖")
|
||||
if "범위" in spec:
|
||||
low, high = spec["범위"]
|
||||
@@ -567,7 +567,7 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict:
|
||||
if isinstance(row.get("흐름"), dict): # 흐름 로직 — 깔림에 기대지 않고 스스로 깖
|
||||
_flow_hooks()
|
||||
return FLOW_RUN(master, ref, row, given)
|
||||
env = _inputs(row, given)
|
||||
env = _inputs(row, given, master)
|
||||
for step in row.get("중간", []):
|
||||
try:
|
||||
env[step["이름"]] = evaluate(parse(step["식"]), env, master, depth)
|
||||
|
||||
@@ -33,22 +33,27 @@ def choosing(picks: dict | None):
|
||||
CHOSEN.reset(token)
|
||||
|
||||
|
||||
def _numbers() -> dict:
|
||||
return EQ.get().numbers.get("EQ", {}) if EQ.get() else {}
|
||||
def _eq(master=None):
|
||||
"""기계를 찾을 마스터 — 셈에 넘어온 것(기계 줄 있음) › 지은 문맥의 것(`EQ` · 다른 스레드는 빔)."""
|
||||
return master if (getattr(master, "numbers", None) or {}).get("EQ") else EQ.get()
|
||||
|
||||
|
||||
def resolve(spec: dict, value):
|
||||
def _numbers(master=None) -> dict:
|
||||
return _eq(master).numbers.get("EQ", {}) if _eq(master) else {}
|
||||
|
||||
|
||||
def resolve(spec: dict, value, master=None):
|
||||
"""기계 입력이 EQ 키로 오면 원문 분류번호로 풂 — 고르기 목록(원문번호)과 같은 꼴."""
|
||||
if not (isinstance(value, str) and value.strip()[:2] == "EQ" and EQ.get()):
|
||||
if not (isinstance(value, str) and value.strip()[:2] == "EQ" and _eq(master)):
|
||||
return value
|
||||
row = EQ.get().index.get("EQ", {}).get(value.strip())
|
||||
row = _eq(master).index.get("EQ", {}).get(value.strip())
|
||||
number = str(row.get("원문번호") or "") if row else ""
|
||||
return number if number and machine(spec, number) else value
|
||||
return number if number and machine(spec, number, master) else value
|
||||
|
||||
|
||||
def machine(spec: dict, value) -> bool:
|
||||
def machine(spec: dict, value, master=None) -> bool:
|
||||
"""기계 입력 — 고르기 목록 밖 원문 분류번호도 기계 마스터에 있으면 받음."""
|
||||
known = _numbers()
|
||||
known = _numbers(master)
|
||||
picks = spec.get("고르기") or []
|
||||
return bool(picks) and str(value).strip() in known and all(str(o) in known for o in picks)
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
"""못옮김 기계 잇기 = 기계경비 기초 로직 줄(PLAN 778-2 · 분석 621-B 4-2).
|
||||
|
||||
EQ 키 바로 더한줄은 셈 오류 → 사용자 잇기 `{id, 기계: EQ…}` = SK 더한줄
|
||||
`{종류: 로직, 로직: 기계경비 기초, 입력값: {기계: 기계 번호}}` · SK 셈 됨(그 기계 시간당 기계경비 ×
|
||||
엑셀 시간 × 배) · 갈래 다른 키 · 없는 키 = LinkError. 고르기 목록 밖 기계 = 서버 둘째 요청 ·
|
||||
지은 문맥 밖 스레드에도 셈 됨(마스터를 셈에 넘김). master 읽기만.
|
||||
"""
|
||||
|
||||
import threading
|
||||
|
||||
import pytest
|
||||
|
||||
from M01_MasterData import M01_MasterData_Store as m01
|
||||
from M02_MasterTemplete import M02_Hopyo_Combine as combine
|
||||
from M02_MasterTemplete import M02_Hopyo_SkPatch as skpatch
|
||||
from M02_MasterTemplete import M02_Sangeun_Calc as calc
|
||||
from M02_MasterTemplete import M02_Sangeun_Router as sangeun_router
|
||||
from resources.tester.test_m02_hopyo_excel import client # noqa: F401
|
||||
|
||||
BASE = "GC000267" # 바탕 아무 기초(시간 단위) — 셈만 봄
|
||||
|
||||
|
||||
def _plan(whole) -> skpatch._Plan:
|
||||
doc = {"키": "SK-000000", "이름": "견줌", "단위": "시간", "바탕": {"로직": BASE},
|
||||
"입력값": {}, "덮개": {}, "뺀줄": [], "자재표시": {}, "더한줄": []} # fmt: skip
|
||||
work = skpatch._Plan(doc, whole)
|
||||
one = {"id": "기계:굴착기", "갈래": "기계", "이름": "굴착기", "규격": "0.6", "단위": "시간",
|
||||
"수량": 0.25, "글": "엑셀만 장비 — 기계 못 찾음", "후보": []} # fmt: skip
|
||||
work.lost = [one, {**one, "id": "기계:굴착기#1"}]
|
||||
return work
|
||||
|
||||
|
||||
def test_기계_잇기_뒤_SK_셈_됨():
|
||||
whole = m01.loaded()[1]
|
||||
work = _plan(whole)
|
||||
work.link([{"id": "기계:굴착기", "기계": "EQ000026"}])
|
||||
add = work.doc["더한줄"][-1]
|
||||
assert add["종류"] == "로직" and add["로직"] in combine.MACHINE_SET - {
|
||||
combine.MACHINE["굴착기"]
|
||||
}
|
||||
assert add["입력값"] == {skpatch.MACH_IN: "0211-0060"} and "요소" not in add
|
||||
assert add["수량"] == pytest.approx(0.25 * work.tb) and add["단위"] == "시간"
|
||||
ran = calc.run(work.doc, whole) # 전 = 「Decimal × dict」 셈 오류
|
||||
line = next(x for x in ran["줄"] if x.get("cid") == "add:#0")
|
||||
assert float(line["금액"]) > 0
|
||||
one = {"종류": "로직", "로직": add["로직"], "입력값": add["입력값"], "수량": 1}
|
||||
hour = calc.run({**work.doc, "바탕": None, "더한줄": [one]}, whole)["기준"]
|
||||
assert float(hour["경비"]) == 27120 # 굴착기(타이어) 0.6㎥ 시간당 경비(엑셀 14-6 과 같음)
|
||||
# 무한궤도 = 굴착기(무한궤도) 기계경비 · 갈래 다른 키 · 없는 키 = 틀림
|
||||
work.link([{"id": "기계:굴착기#1", "기계": "EQ000019"}])
|
||||
assert work.doc["더한줄"][-1]["로직"] == combine.MACHINE["굴착기"]
|
||||
work.lost = _plan(whole).lost
|
||||
with pytest.raises(skpatch.LinkError):
|
||||
work.link([{"id": "기계:굴착기", "기계": "MT000010"}])
|
||||
with pytest.raises(skpatch.LinkError):
|
||||
work.link([{"id": "기계:굴착기", "기계": "EQ999999"}])
|
||||
|
||||
|
||||
def test_목록_밖_기계_서버_둘째_요청에도_셈(client): # noqa: F811
|
||||
m01.loaded() # 마스터 = 이 스레드에서 지음 · 요청 = 다른 스레드(문맥 안 따라감)
|
||||
client.app.include_router(sangeun_router.router)
|
||||
one = {"종류": "로직", "로직": "GC000268", "입력값": {skpatch.MACH_IN: "0211-0060"},
|
||||
"이름": "굴착기(타이어) 0.6㎥", "단위": "시간", "수량": 1} # fmt: skip
|
||||
doc = {"키": "SK-000000", "이름": "t", "단위": "시간", "바탕": None, "입력값": {}, "덮개": {},
|
||||
"뺀줄": [], "자재표시": {}, "더한줄": [one]} # fmt: skip
|
||||
for _ in range(2):
|
||||
got = client.post("/api/m02/layers/personal/sangeun/calc", json={"문서": doc})
|
||||
assert got.status_code == 200, got.text
|
||||
assert got.json()["기준"]["경비"] == 27120, got.json().get("까닭")
|
||||
whole, out = m01.loaded()[1], {} # 지은 문맥 밖 스레드(서버 요청 스레드 풀과 같음)
|
||||
run = threading.Thread(target=lambda: out.update(calc.run(doc, whole)))
|
||||
run.start()
|
||||
run.join()
|
||||
assert out["기준"] and float(out["기준"]["경비"]) == 27120, out.get("까닭")
|
||||
Reference in New Issue
Block a user