From c05139a9711644f697ae8865426ec9c886cdb238 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 5 Nov 2026 17:03:36 +0900 Subject: [PATCH] =?UTF-8?q?feat(791-1):=20=EC=97=94=EC=A7=84=20=EC=85=88?= =?UTF-8?q?=20=EC=9D=98=EB=AF=B8=20=EB=84=93=ED=9E=98=20=E2=80=94=20?= =?UTF-8?q?=EA=B8=88=EC=95=A1=EB=B2=88=EC=A7=80=20=C2=B7=20=EC=86=8C?= =?UTF-8?q?=EA=B3=84=20=EB=8B=A8=EA=B3=84=20=C2=B7=20=EC=A0=9C=EC=99=B8=20?= =?UTF-8?q?=C2=B7=20=EC=A4=84=20=EC=9E=90=EB=A6=AC=20=C2=B7=20=EB=B8=94?= =?UTF-8?q?=EB=A1=9D=20=EB=81=9D=EC=A0=84=20=C2=B7=20=EA=B2=B0=EA=B3=BC=20?= =?UTF-8?q?=EC=A1=B0=EC=A0=95=20=C2=B7=20=EB=B3=80=EC=88=98=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 새 모듈 master_formula_sum.py — 덧줄 고리(master_formula.run 에서 옮김) · 번지 · 단계 · 합밖 · 조정 · 표시 · 검사 - master_cut.py — 줄 자리 {방법, 자리} · 블록 끝전(비목버림 | 큰나머지) · 결과 조정 · 답 단계 목록 - master_formula_check.py — 새 칸 검사(없는 번지 · 단계 순서 · 자리 범위 · 끝전 · 조정) - _틀.md 7장 「셈 의미 넓힘」 · 8장 덧줄 이름 - 지킴 시험 test_791_1 — M01 로직 1,515 지문 전후 같음 · 새 의미 단위 · 실무 견본 넷(봉화 D00102 · 울진소광 D01489 · 동부청영월 D00003 · 북부청임도 18) = 엑셀 - master_formula.py 690 → 669 줄 Co-Authored-By: Claude Opus 5.5 --- resources/master_data/_틀.md | 17 +- resources/master_data/scripts/master_cut.py | 51 +- .../master_data/scripts/master_formula.py | 33 +- .../scripts/master_formula_check.py | 5 +- .../master_data/scripts/master_formula_sum.py | 267 +++ .../fixtures/m01_791_1_logic_fingerprint.json | 1520 +++++++++++++++++ .../tester/test_791_1_engine_semantics.py | 497 ++++++ 7 files changed, 2352 insertions(+), 38 deletions(-) create mode 100644 resources/master_data/scripts/master_formula_sum.py create mode 100644 resources/tester/fixtures/m01_791_1_logic_fingerprint.json create mode 100644 resources/tester/test_791_1_engine_semantics.py diff --git a/resources/master_data/_틀.md b/resources/master_data/_틀.md index 8170ce235..fde188bcd 100644 --- a/resources/master_data/_틀.md +++ b/resources/master_data/_틀.md @@ -145,7 +145,7 @@ ``` - 장 파일 줄은 갈래를 스스로 지님 — `구분` = 원문 + 부문(「산림품셈」 · 「건설품셈 공통」 · 「건설품셈 토목」 …) · `상세구분` = 「NN장 장 제목」(파일 이름 그대로 · 「13장 구조물」). 파일 머리의 `부문` · `차례` 는 목록 차례로 남김. -- 열 차례 — 표는 키 · 원문번호 · 구분 · 상세구분 · 이름 · 기준 · 출처 · 비고 · 용도 · 조건 · 범위규칙 · 값칸 · 줄 · 주 · `그룹`(소요량 파일 안의 계수 표 표시 · 맨 뒤) · 로직은 키 · 원문번호 · 구분 · 상세구분 · 이름 · 결과단위 · 출처 · 소유 · 비고 · 입력 · 중간 · 호표 · 덧줄 · 결과 · 끝수. +- 열 차례 — 표는 키 · 원문번호 · 구분 · 상세구분 · 이름 · 기준 · 출처 · 비고 · 용도 · 조건 · 범위규칙 · 값칸 · 줄 · 주 · `그룹`(소요량 파일 안의 계수 표 표시 · 맨 뒤) · 로직은 키 · 원문번호 · 구분 · 상세구분 · 이름 · 결과단위 · 출처 · 소유 · 비고 · 입력 · 중간 · 호표 · 덧줄 · 결과 · 끝수 · 단계 · 끝전 · 조정. - 갈래 목록은 서버가 장 파일에서 모아 줌(`GET /subs?group=…` · 장 차례대로) — 화면이 이름을 품지 않음. - 건설품셈 부문은 `구분` 칸 — 「건설품셈 건축」 · 원문번호는 절 번호만(「1-1-1」). - 조건 종류 — `수`(같은 값) · `고르기`(글자) · `범위`(줄에 `[아래, 위]` · 끝 열림은 `null` · 포함 여부는 `범위규칙` 에 「초과·이하」처럼). @@ -204,6 +204,20 @@ - `결과단위` 가 돈이 아닌 로직(㎥/시간)은 `호표` 없이 `중간` + `결과` 식. - `끝수` = 묶음 `{대상, 자리, 방법, 출처?, 비고?}` — 엔진이 계산 맨 끝에 붙임. `대상` 은 `계`(비목 합 셋을 각각 끊고 계 = 그 합) · `결과`(돈 아닌 로직) · `자리` 는 소수 자리(0 = 원 미만) · `방법` 은 `버림`·`올림`·`반올림`. 글자 `끝수`(「손료 원 미만 버림(…)」)는 원문 근거를 적은 설명일 뿐 — 그 끊기는 식 안에 이미 있음. - `종류` 가 로직이고 `비목` 칸이 없는 호표 줄은 불러온 결과를 비목별로 나눠 얹음. + +셈 의미 넓힘 — 쓰는 로직에만 먹음 · 안 쓰면 옛 셈 그대로 · 엔진 `scripts/master_formula_sum.py` · 끝전 `master_cut.settle`: + +- `번지`(호표 · 덧줄 줄 칸) = 금액번지 이름(「A01」 · 글자 · 숫자) — 줄 비목 금액이 그 번지에 쌓임 · 덧줄 식 `번지.A01.재료비`(칸 = 노무비 · 재료비 · 경비 · 계). +- `합밖: true`(줄 칸) = 번지엔 쌓되 비목 합 · 단계에서 뺌(STmate `->`). +- `단계`(로직 칸) = 소계 줄 목록 `[{이름, 단, 줄, 제외}]` — `단` 1~5(소계 · 계 · 합계 …) · `줄` = 담을 호표 · 덧줄 이름(이름 겹침 금지 · 한 줄은 한 단계) · 단 k = 제 `줄` + 앞에서 아직 안 담긴 아래 단 단계 · `제외: true` = 찍되 위 단계 · 비목 합에서 뺌 · 덧줄 식 `단계.'이름'.계` · 셈 답에 `단계` 목록(이름 · 단 · 노 · 재 · 경 · 계). +- 덧줄이 부르는 번지 · 단계 = 그 덧줄 앞 줄까지 — 그 줄이나 뒤 줄이 들면 검사 「단계 순서」. +- 특수 % — L% · M% · S%(처음부터 그 비목 누계 × n %) = 덧줄 `노무비 * n / 100`(덧줄 식의 비목 합 = 그 덧줄 앞 누계) · T%(바로 앞 소계 × 수량) = `단계.'소계'.계 * 수량`. +- `자리`(줄 칸) = `{방법: 버림 | 반올림 | 올림, 자리: 0~3}` — 줄 비목 금액 끝수(없으면 0.1 원 버림) · 뒤 덧줄이 보는 값도 이것. +- `끝전`(로직 칸) = `비목버림`(없음과 같음 · 비목마다 원 버림) | `큰나머지`(계 원 버림 · 모자란 원을 원 미만 큰 비목부터 1 원씩 · 같으면 경 › 재 › 노). +- `조정`(로직 칸) = `{배율: {비목: 수}, 옮김: 비목}`(둘 다 골라 씀) — 끝전 뒤 비목 × 배율 → 계 전부를 `옮김` 비목으로(「경비로 적용」) → 원 버림. +- `표시`(중간 칸) = 0~6 — 값을 그 자리 반올림해 뒤 줄에 넘김(STmate `X = 식 =`). +- 날 셈(`master_formula.run`)은 번지 · 단계 · 합밖 · 조정 · 표시까지 · 줄 자리 · 끝전은 끝수 셈(`master_cut.engine`). +- 계약단가 · 낙찰률 · 운전사 노임 5/24 는 이 의미 밖(B09 · 기계). 재료 고르기 — 호표 재료 줄의 `요소` 를 키 대신 조건 묶음으로 적음: ```json @@ -252,6 +266,7 @@ - 로직 부르기 — `로직(GC000871, 기계='0201-0070')` · 돈 로직의 결과는 비목 묶음 `.노무비` `.재료비` `.경비` `.계` · 돈 아닌 로직은 수 하나. - 입력 · 중간 · 비목 합은 이름 그대로. - 덧줄에서만 `줄.'호표 줄 이름'` = 그 호표 줄의 금액 — 비목 합에서 한 줄만 빼는 식에 씀(`GF000185` 콘크리트다짐). +- 덧줄에서만 `번지.A01.칸` · `단계.'이름'.칸` — 7장 셈 의미 넓힘. - 지역 고르기 조건(7장 `지역: "입력"`)을 쓰는 로직(부르는 로직 포함)은 입력 `자재지역` 을 두고 하위 로직에 `자재지역=자재지역` 으로 넘김 — `지역` 은 유가 고르기. - 쓸 수 있는 것 — `+ - * /` · 괄호 · 비교 `< <= > >= == !=` · `그리고` `또는` · `찾기` · `로직` · `만약(조건, 참, 거짓)` · `올림` `버림` `반올림`(자리) · `최소` `최대`. 그 밖은 쓰지 않음. - 로직이 돌고 도는 참조를 만들지 않음. diff --git a/resources/master_data/scripts/master_cut.py b/resources/master_data/scripts/master_cut.py index dba59b443..b3919abec 100644 --- a/resources/master_data/scripts/master_cut.py +++ b/resources/master_data/scripts/master_cut.py @@ -22,6 +22,7 @@ from decimal import Decimal from pathlib import Path import master_formula as mf +import master_formula_sum as ms _ROOT = str(Path(__file__).resolve().parents[3]) if _ROOT not in sys.path: # 스크립트 · 시험 길 — 서버는 이미 뿌리에서 돎 @@ -39,9 +40,19 @@ def down(value, places: int): return Decimal(str(rounddown(float(value), places))) -def _parts(split: dict) -> tuple[dict, Decimal | None]: - """줄 비목 → (비목마다 0.1 원 버림, 금액 = 그 합 · 모두 빈 값 = None).""" - cut = {k: down(v, 1) for k, v in split.items()} +def _line(value, rule: dict | None): + """줄 값 끝수 — 줄 `자리` {방법, 자리}(791-1) · 없으면 0.1 원 버림.""" + if value is None or not rule: + return down(value, 1) + way, places = rule.get("방법"), int(rule.get("자리") or 0) + if way == "버림": + return down(value, places) + return mf._round(Decimal(str(value)), places, mf.ROUND_WAYS[way]) + + +def _parts(split: dict, rule: dict | None = None) -> tuple[dict, Decimal | None]: + """줄 비목 → (비목마다 0.1 원 버림 · 줄 `자리` 면 그것, 금액 = 그 합 · 모두 빈 = None).""" + cut = {k: _line(v, rule) for k, v in split.items()} kept = [v for v in cut.values() if v is not None] return cut, (sum(kept, Decimal(0)) if kept else None) @@ -84,6 +95,9 @@ def engine(master, ref: str, got: dict, depth: int = 0) -> dict: if isinstance(logic.get("흐름"), dict): return got items, adds = logic.get("호표") or [], logic.get("덧줄") or [] + rules = [x.get("자리") for x in (*items, *adds)] # 줄 자리(791-1) + new = ms.uses(logic) + skip = ms.skipped(logic) if new else set() env = dict(got.get("중간") or {}) total = dict.fromkeys(COSTS, Decimal(0)) lines = [] @@ -100,21 +114,40 @@ def engine(master, ref: str, got: dict, depth: int = 0) -> dict: elif n - len(items) < len(adds) and line.get("금액") is not None: add = adds[n - len(items)] env.update(total) + if new: # 덧줄이 부르는 번지 · 단계 = 버린 줄로 + env.update(ms.scope(logic, lines)) try: value = mf.evaluate(mf.parse(add["식"]), env, master, depth) except _ERRORS: value = line["금액"] - env[add["이름"]] = down(value, 1) # 뒤 덧줄 · 중간 칸 = 버린 값 + env[add["이름"]] = _line(value, rules[n]) # 뒤 덧줄 · 중간 칸 = 버린 값 split = {add["비목"]: value} - cut, money = _parts(split) + cut, money = _parts(split, rules[n] if n < len(rules) else None) for k, v in cut.items(): - total[k] += v or 0 + total[k] += 0 if n in skip else v or 0 if n >= len(items) and money is not None: more = {"단가": money} lines.append({**line, "비목": cut, "금액": money, **more}) - costs = {k: mf.cut(logic, down(v, 0), "계") for k, v in total.items()} - # 중간 = 덧줄이 본 값(버린 줄 합 · 덧줄 값 · `줄`) — 시험 계산 중간 칸 - return {**got, "줄": lines, **costs, "계": sum(costs.values()), "중간": env} + if not new: + costs = {k: mf.cut(logic, down(v, 0), "계") for k, v in total.items()} + # 중간 = 덧줄이 본 값(버린 줄 합 · 덧줄 값 · `줄`) — 시험 계산 중간 칸 + return {**got, "줄": lines, **costs, "계": sum(costs.values()), "중간": env} + costs = settle(logic, total) + more = {"단계": ms.steps(logic, lines)} + return {**got, "줄": lines, **costs, "계": sum(costs.values()), "중간": env, **more} + + +def settle(logic: dict, total: dict) -> dict: + """블록 끝전(791-1) — `끝전` 비목버림(비목마다 원 버림 · 옛 규칙) · 큰나머지(계 원 버림 뒤 + 모자란 원을 소수 큰 비목부터 · 같으면 경 › 재 › 노) → 로직 `끝수` 묶음 → `조정` → 원 버림.""" + costs = {k: down(v, 0) for k, v in total.items()} + if logic.get("끝전") == "큰나머지": + rest = int(down(sum(total.values()), 0) - sum(costs.values())) + order = sorted(COSTS, key=lambda k: (costs[k] - total[k], COSTS[::-1].index(k))) + for k in order[:rest]: + costs[k] += 1 + costs = ms.adjust(logic, {k: mf.cut(logic, v, "계") for k, v in costs.items()}) + return {k: down(v, 0) for k, v in costs.items()} def plain(got: dict) -> dict: diff --git a/resources/master_data/scripts/master_formula.py b/resources/master_data/scripts/master_formula.py index 04e849bec..41bd5edd6 100644 --- a/resources/master_data/scripts/master_formula.py +++ b/resources/master_data/scripts/master_formula.py @@ -570,7 +570,8 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict: env = _inputs(row, given, master) for step in row.get("중간", []): try: - env[step["이름"]] = evaluate(parse(step["식"]), env, master, depth) + value = evaluate(parse(step["식"]), env, master, depth) + env[step["이름"]] = ms.shown(step, value) # `표시` 자리 반올림(791-1) except EmptyInput as e: # 못 구한 중간값 — 쓰는 줄만 비움 env[step["이름"]] = Blank(str(e)) if "결과" in row: @@ -632,34 +633,11 @@ def run(master: Master, ref: str, given: dict, depth: int = 0) -> dict: **(extra or {}), } ) - # 덧줄이 호표 줄 금액 하나를 빼고 더할 수 있게 — `줄.'호표 줄 이름'`(빈 줄은 뺌) - env["줄"] = {x["이름"]: x["금액"] for x in lines if x["이름"] and x["금액"] is not None} - blank = {x["이름"]: x["까닭"] for x in lines if x.get("까닭")} - for extra in row.get("덧줄", []): - env.update(sums) - try: # 덧줄도 값 빈 품목·빈 줄을 부르면 그 줄만 비움 - value, why = evaluate(parse(extra["식"]), env, master, depth), None - env[extra["이름"]] = value - except FormulaError as e: - gone = next((n for n in blank if n in extra["식"]), None) - value, why = None, f"「{gone}」 줄 비어 못 셈 — {blank[gone]}" if gone else str(e) - blank[extra["이름"]] = why - sums[extra["비목"]] += value or 0 - lines.append( - { - "이름": extra["이름"], - "단위": "식", - "수량": Decimal(1), - "단가": value, - "금액": value, - "비목": {extra["비목"]: value}, - **({"까닭": why} if why else {}), - } - ) - sums = {k: cut(row, v, "계") for k, v in sums.items()} # 비목 합마다 끊고 계는 그 합 + # 덧줄 · 비목 합 끊기 · 번지 · 단계 · 조정(791-1) = `master_formula_sum.extras` + sums, more = ms.extras(master, row, lines, env, sums, depth) mb.tag(master, row, lines, env) # 줄마다 인력 · 재료 · 경비 · 하위 묶음(712-1) mg.tag(master, row, lines, env) # 옵션으로 빠진 줄 `옵션꺼짐`(753-1) - return {"줄": lines, **sums, "계": sum(sums.values()), "중간": _shown(env)} + return {"줄": lines, **sums, "계": sum(sums.values()), "중간": _shown(env), **more} # 품셈재료 단가 — 서로 부르는 모듈이라 끝에서 읽음 @@ -680,6 +658,7 @@ from master_material import ( # noqa: E402, F401 import master_pick as mp # noqa: E402 — 검색어 · 고름 · 단위 import master_bundle as mb # noqa: E402 — 인력 · 재료 · 경비 묶음 import master_gate as mg # noqa: E402 — 옵션으로 빠진 줄 +import master_formula_sum as ms # noqa: E402 — 덧줄 · 번지 · 단계 · 조정 · 표시(791-1) from master_formula_check import ( # noqa: E402, F401 — 검사는 나눈 파일 · 이름은 여기서 그대로 _check_cut, _check_node, diff --git a/resources/master_data/scripts/master_formula_check.py b/resources/master_data/scripts/master_formula_check.py index 25aec63f8..c0b3f189f 100644 --- a/resources/master_data/scripts/master_formula_check.py +++ b/resources/master_data/scripts/master_formula_check.py @@ -9,6 +9,7 @@ from __future__ import annotations import itertools from decimal import Decimal +import master_formula_sum as ms import master_keys as mk from master_formula import ( _HOLE, @@ -175,12 +176,14 @@ def check_logic(master: Master, row: dict) -> tuple[list[str], set]: if item.get("종류") != "로직" and group != item.get("종류"): out.append(f"{where} · 요소 그룹이 종류와 다름") names = known | set(COST_ITEMS) | {"줄"} # 「줄」 = 호표 줄 금액 묶음(덧줄에서만) + if ms.uses(row): # 금액번지 · 소계 단계(791-1) — 덧줄에서만 + names = names | {ms.ADDR, ms.STEP} for extra in row.get("덧줄", []): one(extra["식"], f"{key} 덧줄 {extra['이름']}", names) names = names | {extra["이름"]} if extra.get("비목") not in COST_ITEMS: out.append(f"{key} 덧줄 {extra['이름']} · 비목 「{extra.get('비목')}」") - out += _check_cut(row, key) + out += _check_cut(row, key) + ms.check(row, key) return out, calls diff --git a/resources/master_data/scripts/master_formula_sum.py b/resources/master_data/scripts/master_formula_sum.py new file mode 100644 index 000000000..584967d4c --- /dev/null +++ b/resources/master_data/scripts/master_formula_sum.py @@ -0,0 +1,267 @@ +# -*- coding: utf-8 -*- +"""로직 셈 의미 넓힘(PLAN 791-1 · `_틀.md` 7장 「셈 의미」) — 덧줄 고리 · 금액번지 · 소계 단계 · +제외 · 결과금액 조정 · 변수 표시 반올림 · 검사. + +`master_formula.run` 이 부름(덧줄 고리를 여기로 옮김) · 끝전(줄 자리 · 블록 끝전)은 `master_cut`. +새 칸을 안 쓰는 로직은 옛 셈 그대로 — 지킴 = M01 로직 지문(`test_791_1_engine_semantics.py`). +eval 없음 · 수는 Decimal. +""" + +from __future__ import annotations + +from decimal import ROUND_HALF_UP, Decimal + +import master_formula as mf + +COSTS = mf.COST_ITEMS +ROW_KEYS = ("단계", "끝전", "조정") # 로직 줄 칸 +LINE_KEYS = ("번지", "합밖", "자리") # 호표 · 덧줄 칸 +ENDS = ("비목버림", "큰나머지") +MAX_PLACE = 3 # 줄 자리 0~3 +MAX_SHOW = 6 # 변수 표시 0~6 +MAX_STEP = 5 # 소계 단 1~5(STmate `(=)`~`(=====)`) +ADDR, STEP = "번지", "단계" # 덧줄 식 이름 — `번지.A01.재료비` · `단계.'소계'.계` + + +def items(row: dict) -> list[dict]: + """엔진 줄 차례 = 호표 하나씩 뒤 덧줄 하나씩.""" + return [*row.get("호표", []), *row.get("덧줄", [])] + + +def uses(row: dict) -> bool: + """새 의미(번지 · 단계 · 끝전 · 조정 · 합밖 · 줄 자리)를 쓰는 로직 — 아니면 옛 셈 그대로.""" + return any(k in row for k in ROW_KEYS) or any(k in x for x in items(row) for k in LINE_KEYS) + + +def shown(step: dict, value): + """중간 `표시` = 값을 n 자리 반올림해 뒤 줄에 넘김(STmate `X = 식 =`).""" + if step.get("표시") is None or not isinstance(value, Decimal): + return value + return mf._round(value, step["표시"], ROUND_HALF_UP) + + +def _add(acc: dict, money: dict | None) -> None: + for k, v in (money or {}).items(): + if k in acc: + acc[k] += v or 0 + + +def _bundle(acc: dict) -> dict: + return {**acc, "계": sum(acc[k] for k in COSTS)} + + +def _cover(row: dict) -> list[tuple[dict, set]]: + """단계마다 (단계, 담은 줄 차례) — 단 1 = 제 `줄` · 단 k = 제 `줄` + 아직 안 담긴 아래 단 단계. + 제외 단계는 위로 안 올라감.""" + where = {x.get("이름"): i for i, x in enumerate(items(row)) if x.get("이름")} + out, open_ = [], [] + for st in row.get(STEP) or []: + level, mine = int(st.get("단") or 1), {where[n] for n in st.get("줄") or [] if n in where} + keep = [] + for low, got in open_: + if low < level: + mine |= got + else: + keep.append((low, got)) + open_ = keep if st.get("제외") else [*keep, (level, mine)] + out.append((st, mine)) + return out + + +def skipped(row: dict) -> set: + """비목 합에서 빼는 줄 차례 — `합밖` 줄(번지엔 쌓음) · 제외 단계가 담은 줄.""" + out = {i for i, x in enumerate(items(row)) if x.get("합밖")} + for st, mine in _cover(row): + if st.get("제외"): + out |= mine + return out + + +def total(row: dict, lines: list[dict]) -> dict: + """비목 합 — 뺄 줄을 빼고 지금까지 줄.""" + skip, acc = skipped(row), dict.fromkeys(COSTS, Decimal(0)) + for i, line in enumerate(lines): + if i not in skip: + _add(acc, line.get("비목")) + return acc + + +def steps(row: dict, lines: list[dict]) -> list[dict]: + """단계 값 — 지금까지 셈한 줄만(뒤 줄은 0).""" + out = [] + for st, mine in _cover(row): + acc = dict.fromkeys(COSTS, Decimal(0)) + for i in sorted(mine): + if i < len(lines): + _add(acc, lines[i].get("비목")) + out.append( + {"이름": st.get("이름"), "단": st.get("단"), **_bundle(acc)} + | ({"제외": True} if st.get("제외") else {}) + ) + return out + + +def scope(row: dict, lines: list[dict]) -> dict: + """덧줄 식 이름 — `번지` {A01: 묶음} · `단계` {이름: 묶음} (지금까지 줄).""" + places = {} + for item, line in zip(items(row), lines): + if item.get(ADDR): + _add(places.setdefault(item[ADDR], dict.fromkeys(COSTS, Decimal(0))), line.get("비목")) + return { + ADDR: {k: _bundle(v) for k, v in places.items()}, + STEP: {x["이름"]: {k: x[k] for k in (*COSTS, "계")} for x in steps(row, lines)}, + } + + +def adjust(row: dict, sums: dict) -> dict: + """결과금액 조정 — `배율` {비목: 수} 곱 → `옮김` 비목으로 계 전부(STmate 「경비로 적용」).""" + rule = row.get("조정") + if not rule: + return sums + out = dict(sums) + for k, rate in (rule.get("배율") or {}).items(): + out[k] = out[k] * Decimal(str(rate)) + if rule.get("옮김"): + whole = sum(out.values()) + out = {k: whole if k == rule["옮김"] else Decimal(0) for k in COSTS} + return out + + +def extras(master, row: dict, lines: list[dict], env: dict, sums: dict, depth: int): + """덧줄 셈(옛 `run` 고리 그대로) + 새 의미 → (비목 합, 덧칸 `{단계}`).""" + new = uses(row) + # 덧줄이 호표 줄 금액 하나를 빼고 더할 수 있게 — `줄.'호표 줄 이름'`(빈 줄은 뺌) + env["줄"] = {x["이름"]: x["금액"] for x in lines if x["이름"] and x["금액"] is not None} + blank = {x["이름"]: x["까닭"] for x in lines if x.get("까닭")} + for extra in row.get("덧줄", []): + if new: + sums = total(row, lines) + env.update(scope(row, lines)) + env.update(sums) + try: # 덧줄도 값 빈 품목·빈 줄을 부르면 그 줄만 비움 + value, why = mf.evaluate(mf.parse(extra["식"]), env, master, depth), None + env[extra["이름"]] = value + except mf.FormulaError as e: + gone = next((n for n in blank if n in extra["식"]), None) + value, why = None, f"「{gone}」 줄 비어 못 셈 — {blank[gone]}" if gone else str(e) + blank[extra["이름"]] = why + sums[extra["비목"]] += value or 0 + lines.append( + { + "이름": extra["이름"], + "단위": "식", + "수량": Decimal(1), + "단가": value, + "금액": value, + "비목": {extra["비목"]: value}, + **({"까닭": why} if why else {}), + } + ) + if new: + sums = total(row, lines) + sums = {k: mf.cut(row, v, "계") for k, v in sums.items()} # 비목 합마다 끊고 계는 그 합 + if not new: + return sums, {} + return adjust(row, sums), {STEP: steps(row, lines)} + + +# ── 검사 ────────────────────────────────────────────────────────────── +def _whole(v, low: int, high: int) -> bool: + return isinstance(v, (int, Decimal)) and v == int(v) and low <= int(v) <= high + + +def _refs(node): + """식 안 `번지.X.칸` · `단계.'X'.칸` → (이름, X, 칸).""" + for part in mf.walk(node): + if part[0] == "field" and part[1][0] == "field" and part[1][1][0] == "name": + head = part[1][1][1] + if head in (ADDR, STEP): + yield head, part[1][2], part[2] + + +def check(row: dict, key: str) -> list[str]: + """새 의미 칸 — 끝전 · 조정 · 줄 자리 · 번지 · 표시 · 단계 · 덧줄이 부르는 번지 · 단계 차례.""" + out, lines = [], items(row) + end = row.get("끝전") + if end is not None and end not in ENDS: + out.append(f"{key} 끝전 「{end}」 — {' · '.join(ENDS)}") + rule = row.get("조정") + if rule is not None: + if not isinstance(rule, dict) or set(rule) - {"옮김", "배율"}: + out.append(f"{key} 조정 · 칸은 옮김 · 배율만") + else: + if rule.get("옮김") not in (None, *COSTS): + out.append(f"{key} 조정 옮김 「{rule['옮김']}」 — {' · '.join(COSTS)}") + for k, rate in (rule.get("배율") or {}).items(): + if k not in COSTS or not isinstance(rate, (int, Decimal)) or rate < 0: + out.append(f"{key} 조정 배율 「{k}」={rate}") + for step in row.get("중간", []): + if step.get("표시") is not None and not _whole(step["표시"], 0, MAX_SHOW): + out.append(f"{key} 중간 {step['이름']} · 표시 「{step['표시']}」 — 0∼{MAX_SHOW} 정수") + names = [x.get("이름") for x in lines] + for item in lines: + where = f"{key} 줄 {item.get('이름', '')}" + place = item.get("자리") + if place is not None and ( + not isinstance(place, dict) + or set(place) - {"방법", "자리"} + or place.get("방법") not in mf.ROUND_WAYS + or not _whole(place.get("자리", 0), 0, MAX_PLACE) + ): + out.append( + f"{where} · 자리 「{place}」 — {{방법: 버림·반올림·올림, 자리: 0∼{MAX_PLACE}}}" + ) + if ADDR in item and not (isinstance(item[ADDR], str) and mf._NAME.fullmatch(item[ADDR])): + out.append(f"{where} · 번지 「{item[ADDR]}」 — 글자 · 숫자만") + if "합밖" in item and not isinstance(item["합밖"], bool): + out.append(f"{where} · 합밖 「{item['합밖']}」 — 참 · 거짓") + out += _check_steps(row, key, names) + return out + + +def _check_steps(row: dict, key: str, names: list) -> list[str]: + out, seen, taken = [], set(), {} + for st in row.get(STEP) or []: + name = st.get("이름") if isinstance(st, dict) else None + where = f"{key} 단계 {name}" + if not isinstance(name, str) or not name.strip(): + out.append(f"{key} 단계 · 이름 없음") + continue + if name in seen: + out.append(f"{where} · 같은 이름") + seen.add(name) + if set(st) - {"이름", "단", "줄", "제외"}: + out.append(f"{where} · 모르는 칸 {sorted(set(st) - {'이름', '단', '줄', '제외'})}") + if not _whole(st.get("단"), 1, MAX_STEP): + out.append(f"{where} · 단 「{st.get('단')}」 — 1∼{MAX_STEP} 정수") + for line in st.get("줄") or []: + if line not in names: + out.append(f"{where} · 없는 줄 「{line}」") + elif names.count(line) > 1: + out.append(f"{where} · 줄 이름 「{line}」 겹침 — 줄 이름을 가를 것") + elif line in taken: + out.append(f"{where} · 줄 「{line}」 이 단계 「{taken[line]}」 에도 있음") + taken.setdefault(line, name) + if out: + return out + cover = {st["이름"]: mine for st, mine in _cover(row)} + places = {} + for i, item in enumerate(items(row)): + if item.get(ADDR): + places.setdefault(item[ADDR], set()).add(i) + first = len(row.get("호표", [])) + for n, extra in enumerate(row.get("덧줄", [])): + try: + node = mf.parse(extra["식"]) + except mf.FormulaError: + continue # 못 읽는 식은 `check_logic` 이 알림 + for head, name, slot in _refs(node): + where = f"{key} 덧줄 {extra.get('이름')}" + group = (places if head == ADDR else cover).get(name) + if group is None: + out.append(f"{where} · 없는 {head} 「{name}」") + elif any(i >= first + n for i in group): + out.append(f"{where} · {head} 「{name}」 에 이 줄이나 뒤 줄이 듦 — 단계 순서") + if slot not in mf.BUNDLE: + out.append(f"{where} · {head} 칸 「{slot}」 — {' · '.join(mf.BUNDLE)}") + return out diff --git a/resources/tester/fixtures/m01_791_1_logic_fingerprint.json b/resources/tester/fixtures/m01_791_1_logic_fingerprint.json new file mode 100644 index 000000000..4195975dd --- /dev/null +++ b/resources/tester/fixtures/m01_791_1_logic_fingerprint.json @@ -0,0 +1,1520 @@ +{ + "결과": { + "GC000001": "7d4315a2e57faccf", + "GC000002": "80490ffd46bdae1b", + "GC000003": "fc0976937690096a", + "GC000004": "5cd8f6b509cb63ba", + "GC000005": "8f1f86153b65923d", + "GC000006": "ddf5033d1bd2509a", + "GC000007": "553403dda3ab31a0", + "GC000008": "0ac6b48f9d954b72", + "GC000009": "8d6c81186a43cde2", + "GC000010": "a088272376704a38", + "GC000011": "5e5a0a95b476657f", + "GC000012": "9cf6be2ec277e6a2", + "GC000013": "c89b367028599f4c", + "GC000014": "87958bd41c33d96c", + "GC000015": "08f6644a6182218a", + "GC000016": "bfb81b345bd8a6a0", + "GC000017": "6cad0db0ed40263c", + "GC000018": "bfb81b345bd8a6a0", + "GC000019": "0a7e43c3d979c6b1", + "GC000020": "d42ef536820ee847", + "GC000021": "de367438e0c57c50", + "GC000022": "f63fc47171b4d9d7", + "GC000023": "da46b5ea55791653", + "GC000024": "507fbe4f1a6aeb5c", + "GC000025": "8fb514ac60852f63", + "GC000026": "885231b92e1ec4f6", + "GC000027": "c2fab8d107e4f459", + "GC000028": "6c237aa8d841c9f4", + "GC000029": "1a4b933ba0915dcd", + "GC000030": "b1f93779c53c10b5", + "GC000031": "19fb2b3f060a403f", + "GC000032": "8d12915b31886478", + "GC000033": "40db920dd4a5196b", + "GC000034": "a3741cec025edb17", + "GC000035": "b71369fe75a4b524", + "GC000036": "daadaa89de50d0fb", + "GC000037": "fc1a463c1eb95bdf", + "GC000038": "ee6573b583d06de6", + "GC000039": "be616cde585b00d3", + "GC000040": "e7301c8f26bd3e89", + "GC000041": "f43bf1c4efa68ff8", + "GC000042": "ab81c28667d6ca6d", + "GC000043": "c9cda16cf63f465e", + "GC000044": "d162eec28934f0bb", + "GC000045": "ebc7ca1d1df3cf14", + "GC000046": "772e0ea10fe9d076", + "GC000047": "de37431b0a7212c9", + "GC000048": "4ebbecd1978cb5d6", + "GC000049": "768f96c588e58cc5", + "GC000050": "20be2e907b6199c5", + "GC000051": "8975480ffbc241a9", + "GC000052": "b62987293805c837", + "GC000053": "44b7e04a7ca59616", + "GC000054": "8c9a8ec4605bb950", + "GC000055": "817f5ce8e0657df1", + "GC000056": "3f785c308f782466", + "GC000057": "00ed3f6ccc42d576", + "GC000058": "e46c97809efb7ae7", + "GC000059": "e46c97809efb7ae7", + "GC000060": "5bb952313838ecd7", + "GC000061": "cd28fc332349f88d", + "GC000062": "62cc79d4cf67aef3", + "GC000063": "afbc888625061ac0", + "GC000064": "76fdd68435511ac1", + "GC000065": "e4e8d81dd320304d", + "GC000066": "c8af6791ccf6b573", + "GC000067": "94915f55811ac00d", + "GC000068": "84aa511954797da9", + "GC000069": "3651f131d15e7285", + "GC000070": "f351693f4068a226", + "GC000071": "ebd014c62661585e", + "GC000072": "435bcae8429e2c02", + "GC000073": "e23a64b95f41ecec", + "GC000074": "84b75ca4a1b8795e", + "GC000075": "4efd1e289f112c69", + "GC000076": "3f942af62ac7ab1f", + "GC000077": "45a97187a616dcc5", + "GC000078": "dc46b5900d098ecd", + "GC000079": "9b1b96115595f4f8", + "GC000080": "04d79a59a861c2a8", + "GC000081": "ffee855c85eb2eb5", + "GC000082": "207ba2aad6d7e897", + "GC000083": "69e7634523137c9e", + "GC000084": "457f1f82a889f107", + "GC000085": "788737f4c20ca2af", + "GC000086": "8462d2f751e6b96f", + "GC000087": "e9a85bc4ee941c66", + "GC000088": "21a08ba083032c59", + "GC000089": "07164e3ad3a1b366", + "GC000090": "3e57391df5b0c39f", + "GC000091": "525b1d44d0f29909", + "GC000092": "dbcd8db9e280d8d2", + "GC000093": "8558a524c82b70b6", + "GC000094": "41b50de14cc61fb0", + "GC000095": "2733ce5f163adea7", + "GC000096": "03fda01e29f3c071", + "GC000097": "2f9c3b00f1a5ee47", + "GC000098": "9cfe01fec9cb8c42", + "GC000099": "eb10e55dc952f897", + "GC000100": "b8f43a31bff36b76", + "GC000101": "3421450503e00246", + "GC000102": "8d5767cc0256f0eb", + "GC000103": "c75814c9b633394e", + "GC000104": "9569c65648ea532b", + "GC000105": "9a4adb7e2192f33f", + "GC000106": "b902ac2249efbf8c", + "GC000107": "b741aec2a3b01ae1", + "GC000108": "ebe7317e11b671e4", + "GC000109": "f5d34edfb423809f", + "GC000110": "58e3550db4ef72da", + "GC000111": "6a618afca1cf9380", + "GC000112": "b2734007ec3b6a2d", + "GC000113": "76dcf488dadb26af", + "GC000114": "9e31c47b4dcbb6cc", + "GC000115": "03d9429e03ad9c6e", + "GC000116": "3353091a5eb7cfaa", + "GC000117": "a3d6cc9fb0dfef85", + "GC000118": "9ea0717f80e9306b", + "GC000119": "919627582b73efff", + "GC000120": "d77f4deb4d339132", + "GC000121": "dbc6833625d331e4", + "GC000122": "eee3a6a9dc3d77b8", + "GC000123": "27931deb9e861cbb", + "GC000124": "1f2793f478b52093", + "GC000125": "ad8bb518824c9166", + "GC000126": "efe6e42c00c8a277", + "GC000127": "22bfa31ad4846a1a", + "GC000128": "3d4a3b6c7c69a3d5", + "GC000129": "3d1e7fae7d93468c", + "GC000130": "f7eac9082c989025", + "GC000131": "054e716eb9e8f6fd", + "GC000132": "f63f83a9b3775fda", + "GC000133": "891824d21d03cc64", + "GC000134": "f63f83a9b3775fda", + "GC000135": "891824d21d03cc64", + "GC000136": "42b086acd9faf73c", + "GC000137": "10188216c2bdcd54", + "GC000138": "56e7a7446cab2155", + "GC000139": "3d0aa6a2ad7552bd", + "GC000140": "0b50a7e5f0bcd592", + "GC000141": "e9da8f094d8e8d90", + "GC000142": "7850caecb40cdc94", + "GC000143": "5d53c30e20b325f5", + "GC000144": "9c94b3edeec51f4d", + "GC000145": "450d8a92f438e197", + "GC000146": "9da178d350502807", + "GC000147": "dece099c43814b73", + "GC000148": "7d460fb358a1b230", + "GC000149": "63a1228dea21e66e", + "GC000150": "0b2401e37bf584d9", + "GC000151": "7c76662f24c9fc09", + "GC000152": "975beb2e1bfbe36d", + "GC000153": "1b8aed605cf79438", + "GC000154": "26731a6c0b2e18b5", + "GC000155": "dabfd6e2cfd6f2c1", + "GC000156": "03f8b8b63aceb103", + "GC000157": "b6348d67357ecc3a", + "GC000158": "3b8aa5a3b4f77ec9", + "GC000159": "baf8cf68043d30e4", + "GC000160": "fb859a7d6686343d", + "GC000161": "b2d8ed6a1da732e2", + "GC000162": "87cab124a3eecac3", + "GC000163": "caba1a165786097e", + "GC000164": "f10b1d5f1c100fc0", + "GC000165": "81bbf22a88e858c7", + "GC000166": "f59e061172144650", + "GC000167": "f79b56b66e2e1ed9", + "GC000168": "17706e4e780e9aed", + "GC000169": "1b7bef5161eaa578", + "GC000170": "2c827c8f8239f566", + "GC000171": "57461898c49242e8", + "GC000172": "73755963d5a9a355", + "GC000173": "de42eb0f8107f234", + "GC000174": "67715fda44e114f8", + "GC000175": "615383a5a8b4c2ce", + "GC000176": "ad627b606f64d0ab", + "GC000177": "c48c7849eae1fb1c", + "GC000178": "a2d4bca71a7244af", + "GC000179": "aa0273537429d949", + "GC000180": "c42fae8bc78ff89f", + "GC000181": "dedf6c30302ac3ac", + "GC000182": "f8dd24c0ebbe771a", + "GC000183": "c3322fc50ae93fdc", + "GC000184": "cebe9c91474ac374", + "GC000185": "2623e56d0c7fe9c2", + "GC000186": "ea70b7c4fd7e980a", + "GC000187": "e16ba1d2451c06dd", + "GC000188": "1af7de5322af4ac4", + "GC000189": "20acc8af9d883979", + "GC000190": "0557f98fa18bf5bc", + "GC000191": "ce3afd445e92a458", + "GC000192": "2925bfc60a52b5c3", + "GC000193": "e0b9082d6f39c479", + "GC000194": "ea70b7c4fd7e980a", + "GC000195": "b01f7d344a35f8a9", + "GC000196": "6407822d4207652f", + "GC000197": "98edb70b616372f4", + "GC000198": "341ac37370b92438", + "GC000199": "13c737b4f4a26735", + "GC000200": "3f981f6df1d36185", + "GC000201": "35950594d9c84c5e", + "GC000202": "cccd52da5310ead1", + "GC000203": "a87ed8ad678ae521", + "GC000204": "5fa98634166a788b", + "GC000205": "5fbd636612132145", + "GC000206": "720bf63c8ab22044", + "GC000207": "4a259801b941a05a", + "GC000208": "e7f9cc5e0a734a4f", + "GC000209": "e199487f8d011e60", + "GC000210": "55d80d2222df5649", + "GC000211": "ff37a68cd5d56c82", + "GC000212": "ee602e699f5b7b51", + "GC000213": "e82dc2b5b576eff4", + "GC000214": "406666ef873ece33", + "GC000215": "f066baac792f1dee", + "GC000216": "f2413394a7755175", + "GC000217": "09a1df7ea42252d1", + "GC000218": "6dd1f8e8f9d2cbe3", + "GC000219": "776c529d1584955f", + "GC000220": "514b2746ef1abc91", + "GC000221": "67f0c8d90182ac51", + "GC000222": "5a0e91ff710663cf", + "GC000223": "b2d52429bc550767", + "GC000224": "2f9ac2a2130d294f", + "GC000225": "137b699f0b4da17a", + "GC000226": "b926069644f1a2d8", + "GC000227": "7cc66a21bc56756f", + "GC000228": "d1cd922abccecf30", + "GC000229": "40e3b3d655e974b2", + "GC000230": "3cbf3a1a3dc1243f", + "GC000231": "8a8d8f48684eea6b", + "GC000232": "00959c359c472c35", + "GC000233": "c88e2b47e90b6022", + "GC000234": "e120c71ff285238f", + "GC000235": "afd2823828bd2eac", + "GC000236": "87fbb46fa763a532", + "GC000237": "0b6f08eb7d7c4ab3", + "GC000238": "2a8985fccd78ef60", + "GC000239": "87a1bc2be76c9753", + "GC000240": "2535307dfaabf2a1", + "GC000241": "a47ae052f428616e", + "GC000242": "771e04764d87aaea", + "GC000243": "4f6a1c69d5b02d95", + "GC000244": "9490d5de415640e5", + "GC000245": "b10bd1061122917d", + "GC000246": "62692a8527c5d47d", + "GC000247": "0ddd22193e16d629", + "GC000248": "36b35a081681749f", + "GC000249": "187ec67d3a365397", + "GC000250": "939340b132920eb7", + "GC000251": "51a9c36ab1ea7538", + "GC000252": "efb4b94d4f23442b", + "GC000253": "99d137e5144ed348", + "GC000254": "73e103b7a51b161d", + "GC000255": "4fd7d55e792a2332", + "GC000256": "f662402af22cc884", + "GC000257": "3b9fd26679cdf702", + "GC000258": "69b4f229a9a686c1", + "GC000259": "a0e45ba570f5a1db", + "GC000260": "e91cd0a97ed72649", + "GC000261": "dad84d307a423562", + "GC000262": "539dddb2c4adb75a", + "GC000263": "4e1d2a34b4456c69", + "GC000264": "edd7d1b501dc016a", + "GC000265": "93a676d703990def", + "GC000266": "f73c91fdf63b7b2c", + "GC000267": "47be327e7d9b0859", + "GC000268": "47a774da333f01bb", + "GC000269": "21fcc5f6cc6a5189", + "GC000270": "d316c138ac531c5b", + "GC000271": "7ef79cb4d13247e4", + "GC000272": "85d8c11839035bad", + "GC000273": "9d452fb25423a1fc", + "GC000274": "88a19fbd041d094d", + "GC000275": "a355da735a5bd80d", + "GC000276": "f756a060dfde9022", + "GC000277": "6f33339fcb3d9efd", + "GC000278": "5b3132c093bbfa5e", + "GC000279": "c4bac2b47a52b7e4", + "GC000280": "d9d571c7e80d10aa", + "GC000281": "799574151cd2de72", + "GC000282": "b6ab5d54a6148f4f", + "GC000283": "bc2ddd008d1aaf12", + "GC000284": "4eff859d8640732c", + "GC000285": "bd87c96d47e35a5b", + "GC000286": "8c2152ca9c5233ec", + "GC000287": "f66887911776131a", + "GC000288": "9247fbc3ee26a174", + "GC000289": "40c3837dccd21820", + "GC000290": "04948cbd574e2b25", + "GC000291": "9e447f72494aee1b", + "GC000292": "1e768fb4b88f1c77", + "GC000293": "9ed4f59e59aaae9d", + "GC000294": "d2deaaac3ee9da22", + "GC000295": "2c7c68432ab641b4", + "GC000296": "86a0a7fa4021356a", + "GC000297": "d7b58bc42f8593a9", + "GC000298": "c3c9a98069c5bfc9", + "GC000299": "ca4d6d768fd6fce5", + "GC000300": "336ca09285614dc9", + "GC000301": "fafce48c96b8b04e", + "GC000302": "b6f1683c81bce129", + "GC000303": "fb89190822ff2102", + "GC000304": "824d01f6179f216f", + "GC000305": "98a266dee8372229", + "GC000306": "6f387f6390e125ea", + "GC000307": "73406e9a4ce7e279", + "GC000308": "495cdb5291b84dad", + "GC000309": "c34ecad3b1f8c3c5", + "GC000310": "2117bc583e2656d4", + "GC000311": "0d3c9e12647ea427", + "GC000312": "c63de29972a053a5", + "GC000313": "a7998138cdad5b80", + "GC000314": "21170a8334edb82b", + "GC000315": "57a5421f1e1e57c9", + "GC000316": "1a91a87dfcb20911", + "GC000317": "50633e6604d59291", + "GC000318": "a91369072c45e272", + "GC000319": "6430dcfa8416d547", + "GC000320": "5b7a4afc49c66b45", + "GC000321": "a6006dffe54f848f", + "GC000322": "6b594f44971b14f9", + "GC000323": "298514ef118c812d", + "GC000324": "7a318b9916465377", + "GC000325": "2a1e30dbc05fc94a", + "GC000326": "0351c694db3718e0", + "GC000327": "5f98de3ba837824d", + "GC000328": "f7506c2e6f25358e", + "GC000329": "56c90d8705a72fac", + "GC000330": "ff3e3873fb3457f1", + "GC000331": "9e37a24848816b9f", + "GC000332": "7961e0927b34b819", + "GC000333": "a0fdb96ec8156889", + "GC000334": "3107fa69146f3da3", + "GC000335": "319d6de01353d757", + "GC000336": "6e443bfe87ba8ac9", + "GC000337": "b7e5896a41b00bea", + "GC000338": "28f8eee137f98700", + "GC000339": "337307b2ae1f789a", + "GC000340": "f1b795b868397bc1", + "GC000341": "d9ab39537a34a974", + "GC000342": "c7f20ccf4d71a1c1", + "GC000343": "5ebf8b2c6d13ce01", + "GC000344": "8fe4b98db736e9dc", + "GC000345": "abd241840ebf9a26", + "GC000346": "b7172c64f854a9de", + "GC000347": "9a9ea8fee2da6d1d", + "GC000348": "2b465f9b3d89a61e", + "GC000349": "22e1354d8ac05109", + "GC000350": "d7d9c87aca729e38", + "GC000351": "098d7657740db30a", + "GC000352": "feea563364e0f392", + "GC000353": "be3d3a8a77e8e626", + "GC000354": "bd0ea2fc069a451c", + "GC000355": "41fbf3c04dad9b63", + "GC000356": "335597af08c18bc2", + "GC000357": "0b85bf1d665b8e03", + "GC000358": "90fbc25ac6c477ca", + "GC000359": "267cfc953ddb41fa", + "GC000360": "8443e47377de0f0b", + "GC000361": "7014dee182a1baed", + "GC000362": "989d3804642fd56f", + "GC000363": "d200b4eaa58e9679", + "GC000364": "b224a93c307ab5b0", + "GC000365": "8e735c9eccd3084b", + "GC000366": "04cb78fbfe85be9a", + "GC000367": "4ddf6ed74deec53a", + "GC000368": "230b9fe5910cbb8f", + "GC000369": "dc2cfd4131611fe0", + "GC000370": "5448c808d97d5f2b", + "GC000371": "6de8664db4a2236d", + "GC000372": "80151d97116612dc", + "GC000373": "6fac06fe457c121f", + "GC000374": "01fc967be9e3a68f", + "GC000375": "ae8e8a4912a78d00", + "GC000376": "01264a6bb28b620a", + "GC000377": "c5435aeda2be8ffc", + "GC000378": "d8e635b2b9a551e0", + "GC000379": "cee6034c827932ee", + "GC000380": "3c2d047a59dc7784", + "GC000381": "7a295c780d74dc35", + "GC000382": "127d37b6139aff57", + "GC000383": "4d93e13d6da83a46", + "GC000384": "18e29c55cb864b98", + "GC000385": "2ffe431115673986", + "GC000386": "ba669eeae64dfcba", + "GC000387": "f22ae0be142f2f19", + "GC000388": "5cf3c3ad8ee2818f", + "GC000389": "90998b7f0e6611db", + "GC000390": "a31228850286eca3", + "GC000391": "8272ea0f96d280bf", + "GC000392": "b530fb0e1586808c", + "GC000393": "95e50db868f3ea39", + "GC000394": "118f0e72b5d291cb", + "GC000395": "e28762f8b2fbddf1", + "GC000396": "b9e9dac144853b7e", + "GC000397": "a7e8fd68ef585dd3", + "GC000398": "4c68b2677eeecc96", + "GC000399": "110f49c7e393a5cc", + "GC000400": "7993645a30dc0098", + "GC000401": "34cb1623751c932d", + "GC000402": "5fa3793801797b51", + "GC000403": "aaf14fc52fb059ad", + "GC000404": "b28e965576dca519", + "GC000405": "f8d2aa99a40d28d5", + "GC000406": "702921e69417e8a8", + "GC000407": "95395e343a036fb2", + "GC000408": "d999302c8fc5faf5", + "GC000409": "266483ded4357b94", + "GC000410": "00ed3325b5381cb7", + "GC000411": "3dceccd1da0ed7f6", + "GC000412": "cae7b6d438256f4d", + "GC000413": "4529ca47c5a24540", + "GC000414": "bf6bc2f190980995", + "GC000415": "6f29f03e5b520ac4", + "GC000416": "be3d0741dc3dd97f", + "GC000417": "25c962d659335495", + "GC000418": "364e0c78fed800c7", + "GC000419": "258553d537f9e5b2", + "GC000420": "59d808e04cb3ab0e", + "GC000421": "e0f695205ba2183e", + "GC000422": "ef487e4050204cc5", + "GC000423": "4179607e274f6411", + "GC000424": "4179607e274f6411", + "GC000425": "15bed50901c1019c", + "GC000426": "4bc609ef025f197d", + "GC000427": "5b63ac3d8db2586e", + "GC000428": "4b3f07c0fc31d19d", + "GC000429": "93377cf125c035ec", + "GC000430": "bbdef53fce7994ba", + "GC000431": "a350e17865b6b2b3", + "GC000432": "a6d4d0c489afcc54", + "GC000433": "74b583b5b7479b3e", + "GC000434": "7f0817261c451b9d", + "GC000435": "78bf4fb8024385d3", + "GC000436": "6f73a1a31b678d08", + "GC000437": "6cbb755b5a022229", + "GC000438": "591e037e913cded0", + "GC000439": "9339b9b7ea138d9e", + "GC000440": "625a48be3718d6c0", + "GC000441": "f5135178b946544e", + "GC000442": "668eb8f20b67846f", + "GC000443": "631fb22b91a01cfc", + "GC000444": "eb00fca3240bc15f", + "GC000445": "f33ca1ffbfbdf4d5", + "GC000446": "b9421e104677733b", + "GC000447": "c03c96e86e4d1047", + "GC000448": "c1be5c13b879bb16", + "GC000449": "9ac17e95ceb6ec13", + "GC000450": "438cb694e3223dbc", + "GC000451": "f3502c5d8982a8ab", + "GC000452": "32b9a39992d166e5", + "GC000453": "2f5297f32b24bcd1", + "GC000454": "fcd295a3761e2492", + "GC000455": "543cb1e489b67cf1", + "GC000456": "a93cd6a2064ef130", + "GC000457": "bc48ff6fd00f9311", + "GC000458": "b1d1b21b99080e93", + "GC000459": "3dbba47036cd7fba", + "GC000460": "41c46659cbb0fcb5", + "GC000461": "c1d5e66064f1a9a0", + "GC000462": "03004966fccd4546", + "GC000463": "03004966fccd4546", + "GC000464": "0014307847af414b", + "GC000465": "d17708ca6bcfc084", + "GC000466": "31f2fcb7e7f9b388", + "GC000467": "a3d3a7b7b1f27c1e", + "GC000468": "ec3c4c83165132c5", + "GC000469": "6476ed4d52b2569e", + "GC000470": "564744f71baf82f1", + "GC000471": "06df8bfcdf37cb67", + "GC000472": "9a3dec4b9f97d25d", + "GC000473": "b2299042e212d3e2", + "GC000474": "3d7d53d4a88e38a0", + "GC000475": "881f89182734e745", + "GC000476": "191e05e12ba465a1", + "GC000477": "144b35e4c782f14a", + "GC000478": "cbbca31a70591a03", + "GC000479": "6e4f5b19b6f8df33", + "GC000480": "7b4a0234124397cf", + "GC000481": "c7e24cce4a47d8d5", + "GC000482": "49772c415355f820", + "GC000483": "6470dfd588dbda51", + "GC000484": "cba8841e22ccc4ab", + "GC000485": "cba8841e22ccc4ab", + "GC000486": "bc37b80cf3ad7aa8", + "GC000487": "08171171cf4a9f6c", + "GC000488": "746c023ddfb209dc", + "GC000489": "64c23e1e1b6e3766", + "GC000490": "b26e705bfbd6d12c", + "GC000491": "61b0f0f06c2d3c4c", + "GC000492": "93b999f7c9b3f2cd", + "GC000493": "cd4c5e665dcb7ba2", + "GC000494": "bc4e4ab4de820763", + "GC000495": "1ea8d9e68807aaac", + "GC000496": "bddb6621f2941814", + "GC000497": "bfae88f96e508ba2", + "GC000498": "e78d4147d870ec63", + "GC000499": "2c7f6a50f2e9a375", + "GC000500": "54e4f5735ac48b4f", + "GC000501": "437a52c093f31e27", + "GC000502": "db40830ed66ecc6d", + "GC000503": "799b8da82f6dd55f", + "GC000504": "ec4f77e974d74c92", + "GC000505": "cd753e41069aa704", + "GC000506": "086ef04f8ad79402", + "GC000507": "646fb939abd43050", + "GC000508": "c396954a18f8cb3c", + "GC000509": "3855c14edd73212e", + "GC000510": "cd10a693163d8eaa", + "GC000511": "d2dd547733d37305", + "GC000512": "9c39a4857d3146e8", + "GC000513": "872460719b4e012d", + "GC000514": "42f7e441222c2825", + "GC000515": "587f8181321b2395", + "GC000516": "7ddc53c29b62f3eb", + "GC000517": "a8689753217700a9", + "GC000518": "3ef8a595a3ae0413", + "GC000519": "d4151d45335f3125", + "GC000520": "1dd38701117cc5a0", + "GC000521": "8713d35595baece2", + "GC000522": "8c49a2422667d864", + "GC000523": "cb261dddba4d0c72", + "GC000524": "305d0eabd38387a2", + "GC000525": "3beb60cfc59d2819", + "GC000526": "3beb60cfc59d2819", + "GC000527": "3eeb406bd4fb73e9", + "GC000528": "345179e8207da9e2", + "GC000529": "90e240fc17a05325", + "GC000530": "e42001b50b277a45", + "GC000531": "4899f663c4ab3848", + "GC000532": "86d4f366ac7998a9", + "GC000533": "af9eb45e587705cc", + "GC000534": "ac4bab982c4ae32b", + "GC000535": "f507f9abdd98ea71", + "GC000536": "2f7610c304794f1c", + "GC000537": "ff6cfc76680805ef", + "GC000538": "d7bee768af685f23", + "GC000539": "580d3019ea89d56f", + "GC000540": "f507f9abdd98ea71", + "GC000541": "2f7610c304794f1c", + "GC000542": "b8c7276496276510", + "GC000543": "d101cbadff6371d8", + "GC000544": "c092017ae51a04ea", + "GC000545": "0f7e3969e26d1954", + "GC000546": "77205f911fc590eb", + "GC000547": "ea3f9ff666de9eb1", + "GC000548": "45d97e3520cd8589", + "GC000549": "1dbb32830082af19", + "GC000550": "90a18f4a30e85ab0", + "GC000551": "b8b7dedb0a842a7e", + "GC000552": "f8e012307bc777ab", + "GC000553": "1e39d96cba493c53", + "GC000554": "cee78998e5a09ea9", + "GC000555": "1cbfe115ba226174", + "GC000556": "196033b96083bca5", + "GC000557": "ce0c0a8758e8624c", + "GC000558": "bb53ec117abbfc34", + "GC000559": "d3f1952d0e45683a", + "GC000560": "a75c377290ab5688", + "GC000561": "5392b86533f4cfc7", + "GC000562": "0e24bb979692f61f", + "GC000563": "f4910cc8080bd692", + "GC000564": "ea83ebd5c7107bcc", + "GC000565": "7dffc4bf6a3c7ba0", + "GC000566": "cc6a969af98b4845", + "GC000567": "35352260168021ce", + "GC000568": "29e326c17821f705", + "GC000569": "ef042784eea44e37", + "GC000570": "39e0e8f417a930b4", + "GC000571": "041e8e3df3ba5ba3", + "GC000572": "c039eab0555281e2", + "GC000573": "8653e0949bb42b97", + "GC000574": "37b40ba2d7463d6f", + "GC000575": "5d3854cd54310827", + "GC000576": "642a954c647addb5", + "GC000577": "4c8005e1d5d3d905", + "GC000578": "fd50edf1bf4e57e4", + "GC000579": "3e0d3862f0cb7394", + "GC000580": "b6a064c4c077efa9", + "GC000581": "7783ee039ad4f206", + "GC000582": "5a6d83c0a8277104", + "GC000583": "de23757b451580b6", + "GC000584": "11c9e93e5c5ccc3b", + "GC000585": "055b5cb03f031a3b", + "GC000586": "32a452dfd347a9a4", + "GC000587": "b4e3e76ebea9cd7d", + "GC000588": "2a81166e6ac05e79", + "GC000589": "3559f4cf5a8c27a9", + "GC000590": "ae1d56b089222e28", + "GC000591": "19cafea7a90a30f6", + "GC000592": "30f83549db5b5c8f", + "GC000593": "f2df3e29df805c1d", + "GC000594": "8ca3771b774f5527", + "GC000595": "5805d9712fc1b88b", + "GC000596": "d801ec3c4c4558d8", + "GC000597": "ea5ffdd62442cafc", + "GC000598": "21dbeb540efa3aca", + "GC000599": "65a886732073c963", + "GC000600": "8e30e4c6daa74174", + "GC000601": "9d274397d7added3", + "GC000602": "e838cde23071877a", + "GC000603": "d2ec5ccd539e681b", + "GC000604": "3f4f94bbf4d6bfa3", + "GC000605": "455a68d92e8c0cea", + "GC000606": "744dda0cf8b27a92", + "GC000607": "4211a4cff262f4b3", + "GC000608": "501f3334a7fd5f5f", + "GC000609": "5de979fbc41c8ad3", + "GC000610": "0de017446ba99b5f", + "GC000611": "6dcfe61b32546835", + "GC000612": "9d1ef73d1b2dc3a5", + "GC000613": "ddd76ef5b957052a", + "GC000614": "7d54163a76e70199", + "GC000615": "ac83b0652db0ea1c", + "GC000616": "19e2ff79d46ea40d", + "GC000617": "08002aaf584c5066", + "GC000618": "cd13166e23575ab8", + "GC000619": "03c5ad96b55964c7", + "GC000620": "aa379d523ced0677", + "GC000621": "0cdbf1c79de28a18", + "GC000622": "a493adad5c0bb11f", + "GC000623": "2c840d248fa72294", + "GC000624": "3269d67dc11b8b66", + "GC000625": "935e740ec4257979", + "GC000626": "43161d07d2a1f3a1", + "GC000627": "f2e7f01d715e56d1", + "GC000628": "a1ddec0580f01aeb", + "GC000629": "925f6d7520a6f1e9", + "GC000630": "c521bd33447ebc36", + "GC000631": "420c25ace98a26fb", + "GC000632": "1940510ca50d3021", + "GC000633": "0d17fa8e1339e9ce", + "GC000634": "9941e6ed207d158f", + "GC000635": "61d1b415f4fb3da9", + "GC000636": "4f2d9777d81db2ea", + "GC000637": "9b41cffafe8454c5", + "GC000638": "fac4ee1c170e5e27", + "GC000639": "23568dba95325e52", + "GC000640": "9f6ef3108725d0a1", + "GC000641": "2555fffc0fef8280", + "GC000642": "b44f3ad4b3eb9de7", + "GC000643": "dc195f1f9f071c41", + "GC000644": "7789ab3505915d32", + "GC000645": "0bd97d55926cc7f2", + "GC000646": "666b4fbbcbe824d9", + "GC000647": "741e03d1aced25f5", + "GC000648": "2979142019a724dc", + "GC000649": "53651582b9a5d821", + "GC000650": "91f3d2955bc7abf2", + "GC000651": "fa0b44a00d0c46fe", + "GC000652": "96089b3ac28f22bd", + "GC000653": "5f54e3268457bb3a", + "GC000654": "0c598ee6a25210a2", + "GC000655": "0f1c88126863925a", + "GC000656": "527b8e1e4a604e5a", + "GC000657": "c686a29299f10c17", + "GC000658": "34eda6c57f33d324", + "GC000659": "885301c41dceb88b", + "GC000660": "5dc6d7892098fb0d", + "GC000661": "e40cd0df8f117662", + "GC000662": "4c6d832cc29cb16a", + "GC000663": "87b81aa7bb73ce71", + "GC000664": "cfd8049292bd8144", + "GC000665": "df1f5269b2dbb1ab", + "GC000666": "3bdc025f3213baca", + "GC000667": "80f2a471f3491dd9", + "GC000668": "5f3e8d8a280c10ae", + "GC000669": "00c52adaf4fc2212", + "GC000670": "fa693328cc5b2a5c", + "GC000671": "e57766f1c2f9fb13", + "GC000672": "ae04be8d8173b997", + "GC000673": "2e284ee5363741c6", + "GC000674": "948713f482396bbf", + "GC000675": "0ddd18f561f0d7ca", + "GC000676": "532e261b29d25757", + "GC000677": "17c86455c4813e62", + "GC000678": "fc4b05f4d5f1d1c0", + "GC000679": "bbefcb9d31664efc", + "GC000680": "49ac6e0a8f8c6984", + "GC000681": "8835a121da94796f", + "GC000682": "53cf95205f5fae46", + "GC000683": "d18c3124134dfa73", + "GC000684": "dedb790d4e1d0e72", + "GC000685": "def3b08dc883d3f1", + "GC000686": "565a08b083b4483a", + "GC000687": "112c990eaabaac64", + "GC000688": "ff679e877394399b", + "GC000689": "dd0d32d7ae8e05d5", + "GC000690": "0809478f5b38e662", + "GC000691": "d0db7428f2f2c990", + "GC000692": "c986679d50f9fb9b", + "GC000693": "5ef0e39aa75f4059", + "GC000694": "5e066c87ad819054", + "GC000695": "6427aeb87d35996b", + "GC000696": "940f7ee41c5511c0", + "GC000697": "a038f227482d0855", + "GC000698": "9b6a8c13d0234ce5", + "GC000699": "e1a9b800ab69faa2", + "GC000700": "1e43cf8d87e52cf4", + "GC000701": "7081c12c25780562", + "GC000702": "52c1cd7aa6fdcb8f", + "GC000703": "4d98025f62a24422", + "GC000704": "3e76d76bf9a0320d", + "GC000705": "467edeaa9d7991e6", + "GC000706": "b40d43c12426bbde", + "GC000707": "4647ade4a2bf87c2", + "GC000708": "d9849fbb6a3ee9ed", + "GC000709": "8c320639d2487638", + "GC000710": "a46ccb4d6cb5a60b", + "GC000711": "7a2d1c06a6563e76", + "GC000712": "a9e21cc764bd518c", + "GC000713": "8d2446f322c15e54", + "GC000714": "09e409a6af9b549b", + "GC000715": "3ada611c3022fafd", + "GC000716": "bc5ff223f3107b94", + "GC000717": "e1620cc70fbe8e4d", + "GC000718": "7fbf536a731b0652", + "GC000719": "99f8e88a6e04cc18", + "GC000720": "2aaa7b6ca31e2a8b", + "GC000721": "776ed06b4c669392", + "GC000722": "cd08f27610741e3f", + "GC000723": "30de68b46d9670a0", + "GC000724": "3537d39e3ca02293", + "GC000725": "bc45cee23fb0c36f", + "GC000726": "e84a4a1846b92165", + "GC000727": "b6dd8b9de43fca35", + "GC000728": "f99be96f361cecfa", + "GC000729": "537e3f872123d900", + "GC000730": "e634cd4dfab4b850", + "GC000731": "2b76b7e2ec662f07", + "GC000732": "19f1e2478d514d0b", + "GC000733": "63db5a6326bc41ce", + "GC000734": "98c10c130d569dd8", + "GC000735": "10528c24600de01e", + "GC000736": "599a42c0e8ecbb34", + "GC000737": "162d33f4ad920d64", + "GC000738": "bf12583dd6e63603", + "GC000739": "dc6ea79fae1a8406", + "GC000740": "0379727e2703bba1", + "GC000741": "f6a94b2b6ee84c61", + "GC000742": "214853ffec6d9788", + "GC000743": "1d7100ebb82969bd", + "GC000744": "a3eca57a24110571", + "GC000745": "f390f79d6983ff2d", + "GC000746": "b638bf6d0b7dee23", + "GC000747": "b3af012cede76f0b", + "GC000748": "b9dee9eed0c6756e", + "GC000749": "1c335b0d8b638119", + "GC000750": "9a490d207a52ef11", + "GC000751": "e93238a820365be0", + "GC000752": "e4c82dd5125e8052", + "GC000753": "759f1518a0a5550d", + "GC000754": "b6120f24a1c7ff13", + "GC000755": "e69201ee13e2d17d", + "GC000756": "da7e374f67f75094", + "GC000757": "1de554438a04e13f", + "GC000758": "1de554438a04e13f", + "GC000759": "47a3bae0e7999328", + "GC000760": "9d6598ca0097be93", + "GC000761": "1a4b3c6d1b094e64", + "GC000762": "dc6fd8be7142ec78", + "GC000763": "1d11334e7e807743", + "GC000764": "4809b1c550a4392f", + "GC000765": "2426b8cab1cfa58f", + "GC000766": "dc3ad5742e10e4a7", + "GC000767": "9eab2cc60b2f5dd8", + "GC000768": "d902c3215a9ad1d4", + "GC000769": "3f3becc981d40ad0", + "GC000770": "e4ae9116775f2651", + "GC000771": "36442258c858b00f", + "GC000772": "41e7840b225b37e0", + "GC000773": "a783576779c45c1c", + "GC000774": "3e1a7c7cb54acc28", + "GC000775": "d77e69ef37ddd3a1", + "GC000776": "397a1de8fad539d5", + "GC000777": "cba4d52f09463034", + "GC000778": "aca971c92425343c", + "GC000779": "cefc66b29b099417", + "GC000780": "76297e7e6de9a111", + "GC000781": "b6960cfe0889fc2e", + "GC000782": "fee47de6869c4b17", + "GC000783": "9eec0c13e19d7faf", + "GC000784": "db6127e3506e5a75", + "GC000785": "9585f36814478474", + "GC000786": "10574f7db8ae7058", + "GC000787": "172083fc3726d8f5", + "GC000788": "bb1794f1492cb556", + "GC000789": "5921ff7d63744baa", + "GC000790": "072464f6b58c1470", + "GC000791": "afee9536d05b99a7", + "GC000792": "6373536f8dbaede9", + "GC000793": "5a2bb7f8e161519a", + "GC000794": "712ed9d1e9472ce9", + "GC000795": "324539721a3d6af8", + "GC000796": "95a290f547dfbdb7", + "GC000797": "185b1104807dd78c", + "GC000798": "44506bdac06bbc93", + "GC000799": "775fdd8fb1e88c8b", + "GC000800": "472f8adc042fffbd", + "GC000801": "b0d21e99ad578283", + "GC000802": "11ff6d1963a93a62", + "GC000803": "c43140077abd7ef4", + "GC000804": "215c812c342376ad", + "GC000805": "4128727f928119de", + "GC000806": "fdbbb7bb82693f55", + "GC000807": "842677cdb73ebb49", + "GC000808": "234bc33ae078efac", + "GC000809": "091f29b4519bab9e", + "GC000810": "e111263e61e9f08a", + "GC000811": "0014cfeca71fc7f1", + "GC000812": "8d7e25f4f5956412", + "GC000813": "039c20813b4963de", + "GC000814": "960aaf37506fe2ae", + "GC000815": "caea1507822f0cf3", + "GC000816": "f078c95ba268dea6", + "GC000817": "9e95b6a27d402699", + "GC000818": "5c44381e288a394e", + "GC000819": "fd1db564b3634e9a", + "GC000820": "59778499d3aedde1", + "GC000821": "4ad29411f1e07961", + "GC000822": "61a775ac96448087", + "GC000823": "4a84ea2059e6d1cd", + "GC000824": "b22ed260dfb7c91f", + "GC000825": "bf539279ebd0ed9d", + "GC000826": "a75649d288334ea2", + "GC000827": "769be1c9404b1dd8", + "GC000828": "1ace55e39e8e14fc", + "GC000829": "56169b7f655ecb24", + "GC000830": "9f13f5bcfbf3aae5", + "GC000831": "d2595e6b2d059e4f", + "GC000832": "966c2fe113e3fd9b", + "GC000833": "169ad3db1ee687d1", + "GC000834": "0e19e5f58852150d", + "GC000835": "b15ee1abae0d8b86", + "GC000836": "370f15f5065568ca", + "GC000837": "761216da7068cc0e", + "GC000838": "93f73566d47ba27e", + "GC000839": "f332a8edb15b5b53", + "GC000840": "47797dfb881399d2", + "GC000841": "2998e18eb999f394", + "GC000842": "38e62fc0e5327606", + "GC000843": "440261adb3336d6c", + "GC000844": "f52a09d0e9a28c95", + "GC000845": "676e4b86680de1ec", + "GC000846": "10084dfb0eeddc7f", + "GC000847": "adcbbe5e694b914e", + "GC000848": "dd59e808e8c50df1", + "GC000849": "143aee7e6328eba6", + "GC000850": "2a4c8b2fb379de37", + "GC000851": "b39881ea25a692f7", + "GC000852": "21cda18200ca6153", + "GC000853": "bcfc1475534ed380", + "GC000854": "faf5f80411f815c2", + "GC000855": "00276e84b6afc49e", + "GC000856": "dfa2dbfc64c00ab0", + "GC000857": "7062f238bf119e3c", + "GC000858": "275797ae676b31d0", + "GC000859": "45c141eba4d04e06", + "GC000860": "855bafb5d733b455", + "GC000861": "a933bcbb18ac58bf", + "GC000862": "52124ac7e7967958", + "GC000863": "ca163771d11eefe8", + "GC000864": "b268211f21b72a72", + "GC000865": "5c3378ee9f7af327", + "GC000866": "653f430a3526b349", + "GC000867": "b30a0c7a7983e241", + "GC000868": "b8e0c0eb0f40657d", + "GC000869": "5d16a4a4784b55be", + "GC000870": "bf848a89b12e1f55", + "GC000871": "b2baf204e325a7df", + "GC000876": "0e7381dccc193c10", + "GC000877": "6330fddfb1dbac3f", + "GC000878": "b25cc13faa545ba4", + "GC000879": "25e485712a54838a", + "GC000880": "3daeebe190772d71", + "GC000881": "d5e9d4e7fc0337f7", + "GC000882": "41f7e88f725232c0", + "GC000883": "04a5c5921e82601d", + "GC000884": "b43354b03d8b8c3a", + "GC000885": "d111179a1d12cee8", + "GC000886": "e3c6eaaca33cfb5f", + "GC000887": "4b7aba05e78691c9", + "GC000888": "05f24ee28b535f07", + "GC000889": "c519e74ffe52b7a8", + "GC000890": "cccd70af3605b067", + "GC000891": "43c365a04d302c91", + "GC000892": "bb65cba2d9553b79", + "GC000893": "6fce1e70bd923096", + "GC000894": "b3a8a6381fc57d60", + "GC000895": "2144517a19378ca5", + "GC000896": "c3d79af44e4359f4", + "GC000897": "a7d551bf23b637f2", + "GC000898": "6944b6b4e1ae3af2", + "GC000899": "fbfbfb7a878ffdde", + "GC000900": "8e31b8f888b93016", + "GC000901": "a5ea3f73e8e20d6d", + "GC000902": "5d5cd4bde9d0068a", + "GC000903": "d32706ab68b37561", + "GC000904": "ccb55cde594dca49", + "GC000905": "3ce597226dc4532d", + "GC000906": "987dd5df984b32e3", + "GC000907": "962e9f9b8f401949", + "GC000908": "21acda1f3b2a2c08", + "GC000909": "e90add81ff13f031", + "GC000910": "32434b15208e4dd9", + "GC000911": "273a3e1b2b80196b", + "GC000912": "135fb09f45726fbb", + "GC000913": "a177a4fc5f2ff9cc", + "GC000914": "1c2b089b2d3703bb", + "GC000915": "3d8d6ba87e0cf8fb", + "GC000916": "500ef8730d2d4543", + "GC000917": "eeff71bdf1a29f81", + "GC000918": "88afd9f049cced17", + "GC000919": "c64432778bc72ef7", + "GC000920": "65587719ceead5ac", + "GC000921": "a8720619f80561f6", + "GC000922": "9c4556fbe6cca33b", + "GC000923": "16b4d4a59b5ddb19", + "GC000924": "a3a7c17b28f0eda1", + "GC000925": "55e8a1d67e50d2fb", + "GC000926": "2284e9abb8d6e215", + "GC000927": "a295358244ebfc74", + "GC000928": "abfada19ff003e89", + "GC000929": "98194b7d6f9624e1", + "GC000930": "befe278af5a705ca", + "GC000931": "4694429d4d9645e8", + "GC000932": "f8a8cf62c535ed6c", + "GC000933": "cb3c3d82fe0e88b2", + "GC000934": "5a5ffc0e84e858e4", + "GC000935": "2a924859a3f2d8ef", + "GC000936": "f1f3fb17254f6240", + "GC000937": "2cd2f70e5130012e", + "GC000938": "bd23fcd42360d46a", + "GC000939": "3c9e994e2ab5c164", + "GC000940": "01959b03d398e482", + "GC000941": "67625ba7df4f4dd4", + "GC000942": "6a3515aa6d3a0d30", + "GC000943": "77ba5b8ee8299442", + "GC000944": "19c5d698dd87bc10", + "GC000945": "8b27a845fb8e8387", + "GC000946": "217015662be10524", + "GC000947": "aec5813b0fa15137", + "GC000948": "fa3e2e3e6d965fad", + "GC000949": "26470a9ec29acbd4", + "GC000950": "4f8f45052fe9cf6e", + "GC000951": "01dbc2d05f7112ed", + "GC000952": "cd096d607d055a97", + "GC000953": "6b23eea11ff95216", + "GC000954": "f305d8c31f2fe27e", + "GC000955": "e1ae6c0fa5fa5456", + "GC000956": "d43582f8f07d6b6f", + "GC000957": "cafdcc1289a72016", + "GC000958": "c065c3305ba9d811", + "GC000959": "46215ac29b8cc119", + "GC000960": "f3b6bdd613046fb0", + "GC000961": "57143faa6b050e81", + "GC000962": "13c4498a90a9fe02", + "GC000963": "fe0c52cf24a0acc5", + "GC000964": "9b0372e4fcd26691", + "GC000965": "a9a46bd81493944e", + "GC000966": "302698b05ec94eab", + "GC000967": "e3a309899f3ab881", + "GC000968": "833e20bb5024fd3f", + "GC000969": "4ed65f02552229c9", + "GC000970": "33bf521702a2add5", + "GC000971": "33bf521702a2add5", + "GC000972": "3b46dd05bbfae1d8", + "GC000973": "8e4ba1c41cc24d04", + "GC000974": "968ccd0a121965f9", + "GC000975": "b9736e9ce4da4265", + "GC000976": "215b0373c463bcd5", + "GC000977": "d5073bb102eb4550", + "GC000978": "d44a7e7c41b68346", + "GC000979": "66b8cdbac3a18a24", + "GC000980": "d8c188c2017d8be9", + "GC000981": "cf25ecb60e6e7ba6", + "GC000982": "dcd4d266f6d4a4cc", + "GC000983": "7f66351ed5bc6b48", + "GC000984": "9a5deace93d997d4", + "GC000985": "0c1679033a780b22", + "GC000986": "83bc7d3df4b34029", + "GC000987": "967c0d5aba8a5677", + "GC000988": "52c0e18edbca9717", + "GC000989": "a4b9839706716820", + "GC000990": "98dd7432bdf51bf9", + "GC000991": "c7d5c882eeeb7c4c", + "GC000992": "3ab53b186d32b387", + "GC000993": "b764994ea7e63623", + "GC000994": "c0fd19a449b4aa12", + "GC000995": "346ee8f43484a87b", + "GC000996": "5113840c59b410ab", + "GC000997": "2947bcbb72eda4fb", + "GC000998": "e599d578b9916145", + "GC000999": "2e5eda6da8036edc", + "GC001000": "4af3ac8ecd1443f4", + "GC001001": "640065aa8944ee57", + "GC001002": "cc0d593b69540945", + "GC001003": "2dac95327c392c49", + "GC001004": "7bb31e271f90595a", + "GC001005": "31a3ec44adbc5e2f", + "GC001006": "8773c1e4b7bd39c6", + "GC001007": "e9148209c731d414", + "GC001008": "4f6b26ff60a6e2bb", + "GC001009": "666155e171a9291c", + "GC001010": "ac406dce03689656", + "GC001011": "b932292b01a4f392", + "GC001012": "bae7b595ccf0aa28", + "GC001013": "00a53e7291d18aab", + "GC001014": "0aeeb439dfe90cc8", + "GC001015": "214f95011fbe63b3", + "GC001016": "f2eac67750dbd364", + "GC001017": "58fcddc06f11d9cd", + "GC001018": "150e60b62aeca1c2", + "GC001019": "594ef955c7188b45", + "GC001020": "d3fe6ca3a31b9bd9", + "GC001021": "5083e28f2b6fb432", + "GC001022": "4bc2cbc0138bd260", + "GC001023": "7531e7005bc374b8", + "GC001024": "d6c59aa933c7da08", + "GC001025": "fabc4044409f4248", + "GC001026": "f3f0e384da9e5f68", + "GC001027": "f230ef0182960ece", + "GC001028": "b025e0d7cf6bb1d2", + "GC001029": "cad0a5a4630bc6e5", + "GC001030": "6b1d9e371f18247e", + "GC001031": "625f09c97c8914ed", + "GC001032": "4e29044db8a32463", + "GC001033": "1c236222b001ee16", + "GC001034": "f4629cc8bc295643", + "GC001035": "a8ef0abc89a34e95", + "GC001036": "08adea0fc8c53a13", + "GC001037": "a7113a795984a5f3", + "GC001038": "0000b62a8ef9caaa", + "GC001039": "778e3e9e6be01ca9", + "GC001040": "2601dd50ffb4bd69", + "GC001041": "a6838df30a1081db", + "GC001042": "adc0baf00a2ff439", + "GC001043": "59773e9ccf18672b", + "GC001044": "1d375fb510ca1f53", + "GC001045": "c2b37c4414a91f4d", + "GC001046": "a6b89761bba43099", + "GC001047": "25110541c24176ea", + "GC001048": "b04b02b0d52bcc9f", + "GC001049": "f3f6828e91d7abbe", + "GC001050": "35c6dae4af71653e", + "GC001051": "e23e7d382b82d6a4", + "GC001052": "382fc1cd55899963", + "GC001053": "4638a7514238a7d1", + "GC001054": "213c958b4c0610b4", + "GC001055": "fe7d7bbb71ce33ff", + "GC001056": "0df5f51e9f1a8b6c", + "GC001057": "eb66412ba029d46a", + "GC001058": "83be7233448e4a83", + "GC001059": "51ff2942e90e45ff", + "GC001060": "451fe26d14b3a4df", + "GC001061": "11c4517010881b0b", + "GC001062": "321b26784a33578e", + "GC001063": "3784d44c9a55d7ba", + "GC001064": "4f16160479dc7edc", + "GC001065": "62ec81680c4b83a0", + "GC001066": "6f1482dee9012330", + "GC001067": "d3ea286295aafaf4", + "GC001068": "0435e299b40773c3", + "GC001069": "44908b617230b346", + "GC001070": "e7b02016afb1d5c9", + "GC001071": "8c6f3adf5141871c", + "GC001072": "d13e3d28a3ac9760", + "GC001073": "99ddf5db02e89eb7", + "GC001074": "d1bb34c70923c513", + "GC001075": "023184e3ad6e0ee7", + "GC001076": "be2eb9644885ff84", + "GC001077": "0058c22b4bae45c8", + "GC001078": "92df44ab97d399ea", + "GC001079": "cd17f889793f7291", + "GC001080": "3b3ab1d074b992cb", + "GC001081": "62fa249d89e1bcba", + "GC001082": "8d11ea260802ec8c", + "GC001083": "2c4c28a8158e2cc3", + "GC001084": "b61e9f6c365b1e0c", + "GC001085": "1fffe2c8fd00ff8d", + "GC001086": "dbf85e913e4ec1e8", + "GC001087": "b14c9b0f2ac6db16", + "GC001088": "a7edf01a02f60cf3", + "GC001089": "d472cbecb2608431", + "GC001090": "87f293dc65c9a99d", + "GC001091": "4f55f881feab810b", + "GC001092": "2e43caea98075638", + "GC001093": "81183e21f308e1c1", + "GC001094": "9dba8e2ddeea2473", + "GC001095": "4414eac42667f97f", + "GC001096": "c0bfef9c900113da", + "GC001097": "4b6db055132d8a19", + "GC001098": "64d0425d7e213c02", + "GC001099": "19a0ae17152cd4fa", + "GC001100": "6703e1a3a74e8c82", + "GC001101": "9791731acaa1c1e8", + "GC001102": "b9a5b3e4bc8417ac", + "GC001103": "14aa2fa4c1ca2d7b", + "GC001104": "a0ef0d1557fe43aa", + "GC001105": "d7077e78af2a270a", + "GC001106": "80b4d644a378c9df", + "GC001107": "52851f7eacd234a6", + "GC001108": "b451a6460d26f30e", + "GC001109": "0eb23607caba10e8", + "GC001110": "bf9b73f391f25287", + "GC001111": "1585c33422d29b17", + "GC001112": "d8eb10f9f1b994e8", + "GC001113": "060cc332c664a296", + "GC001114": "edb5299f8649f904", + "GC001115": "caa862ecb662f4e9", + "GC001116": "f8c0d8d29e23f25e", + "GC001117": "7cb35dd128452016", + "GC001118": "137a6c1c9651b0cd", + "GC001119": "7cf5bed7d5104df4", + "GC001120": "493ba494273555a4", + "GC001121": "d3c4f53ade95f4bb", + "GC001122": "098c8ffe67b9b606", + "GC001123": "58ee4b940bd7bea3", + "GC001124": "beff66eff9ff83c0", + "GC001125": "e588f4a830eaf293", + "GC001126": "b8e6de0222b0ebb8", + "GC001127": "6f27d0e0613cef68", + "GC001128": "67c7fb33df6e4d98", + "GC001129": "260d26a5c52c8bc6", + "GC001130": "fa580923424b2791", + "GC001131": "27a97f83e94c280c", + "GC001132": "0423524d3bf64a69", + "GC001133": "8bd27203b8c1d9da", + "GC001134": "4996d563b9a9d6f5", + "GC001135": "b6e0b935b99ea9a8", + "GC001136": "44b78b3e3adf2c2b", + "GC001137": "d1d64ede12fd57bc", + "GC001138": "97baf6cbfe8ef653", + "GC001139": "bfbb103f6c8d581e", + "GC001140": "331481c81b2c16f9", + "GC001141": "b7a9afb31c7367fe", + "GC001142": "a71c65aaf3eaa7fb", + "GC001143": "67408ddb506d5fff", + "GC001144": "7b207014a8648623", + "GC001145": "c84f872700bca009", + "GC001146": "d0cb3999201a30ac", + "GC001147": "a0b23be6a9736a77", + "GC001148": "9adaa6b9f1d8c1d0", + "GC001149": "aeed5c7c9e52efef", + "GC001150": "1375791d7dd5ce45", + "GC001151": "1890f61af6e6e49f", + "GC001152": "6ae99c99ea7e4c9f", + "GC001153": "813d46800b48b946", + "GC001154": "cbbd35887f079546", + "GC001155": "0b6c9c3dc014fc51", + "GC001156": "1e7e8101574b5f87", + "GC001157": "19c727d732d3c6c2", + "GC001158": "108ec420791670b2", + "GC001159": "f867477915fd53c9", + "GC001160": "3ca4cdd646f5e7b1", + "GC001161": "0cb1fa1eeda920fb", + "GC001162": "c3d278e87f2fded4", + "GC001163": "c1216e4fdd0db9e4", + "GC001164": "ac5b787938ec3ca2", + "GC001165": "798aee41ed132310", + "GC001166": "b6940f94ad685bae", + "GC001167": "ab6999dcf7f7dfe6", + "GC001168": "29e3a9b048eaf277", + "GC001169": "6cda5f96e9b1895b", + "GC001170": "ffa182f36c870ff5", + "GC001171": "9f75127a01bd86bb", + "GC001172": "5c6541add2107560", + "GC001173": "cf591c7b9110fc61", + "GC001174": "f4460eb1a5bbf5a8", + "GC001175": "e977653544776b8d", + "GC001176": "b2ee9367c2e76297", + "GC001177": "cd96d482611e3fac", + "GC001178": "6f0ce9eb75c93485", + "GC001179": "31ca6315d407d18e", + "GC001180": "5d64ee0703bd9fd8", + "GC001181": "d710dc2e5d6209cd", + "GC001182": "39728686a32b586b", + "GC001183": "ba58b75e8ae7d4ad", + "GC001184": "270066ef9e54a1ff", + "GC001185": "7b25fdf0a39b285b", + "GC001186": "f3e1dcf656c28da6", + "GC001187": "fa3afeea5bd94cea", + "GC001188": "b9d0c9c443d613be", + "GC001189": "e024ae7a407a41bc", + "GC001190": "8a48901776c6c318", + "GC001191": "46df05f842017dd6", + "GC001192": "c5fb416fcdc88610", + "GC001193": "28c7498794e07f1b", + "GF000001": "6d4852b3bed4d630", + "GF000002": "7fb3cc42d7f9c1e4", + "GF000003": "4103e0624c021e21", + "GF000004": "077753726c853fb4", + "GF000005": "15e14570263e541b", + "GF000006": "f5fbc5599593fcdd", + "GF000007": "2678b09e837335c1", + "GF000008": "5ed4709df6f16245", + "GF000009": "b44147fc04463d85", + "GF000010": "12c25f3eca199e16", + "GF000011": "1081dfe5c1331344", + "GF000012": "8921be8954b4fcdc", + "GF000013": "0c12bfb2dd486fda", + "GF000014": "118833bb184bc013", + "GF000015": "fba610f24d522df0", + "GF000016": "cf1c33d1cc0f9348", + "GF000017": "994933579971e473", + "GF000018": "731972f51e56b0e9", + "GF000019": "fe8d8e751a7bccf6", + "GF000020": "25b14d3f6724212a", + "GF000021": "08788b2c37e34073", + "GF000022": "74a3ed3fb09d7fa2", + "GF000023": "25d42895c4995860", + "GF000024": "11ce9a4bd88ab492", + "GF000025": "18dbf8245039dddc", + "GF000026": "b2e73a3bdd289658", + "GF000027": "ecae5aed219de4af", + "GF000028": "8ca5b5802c337b50", + "GF000029": "2479e0215c0b84b2", + "GF000030": "9e904a1678d15291", + "GF000031": "ccd1bfe7a062ecdb", + "GF000032": "0ab83b189e8f29cc", + "GF000033": "d1a7b8d3198701bd", + "GF000034": "56c3e7c30ccb927c", + "GF000035": "c7aa32b1a1869cf7", + "GF000036": "e1445cde895ec900", + "GF000037": "9d6a0b7a5f8353bf", + "GF000038": "9d6a0b7a5f8353bf", + "GF000039": "c62f002c5f43b0e3", + "GF000040": "e51dccdea617671b", + "GF000041": "cd47ee154f3244b5", + "GF000042": "0e4bfc8fcd8874a2", + "GF000043": "1fe78a8a6f7f2801", + "GF000044": "779f3ed515c49772", + "GF000045": "fc41f3f6207badb8", + "GF000046": "3bdd18d6ed956517", + "GF000047": "aa470cc5c869f950", + "GF000048": "08d73d3e9f5c8bde", + "GF000049": "71e918278313da9a", + "GF000050": "ac1c906a5c34bd64", + "GF000051": "d3ab5af8a67fb3aa", + "GF000052": "0735e0e635e43dc8", + "GF000053": "cee8d87f37d4a83c", + "GF000054": "6510bf1a5443fc69", + "GF000055": "106a26812f74428f", + "GF000056": "201e2ecc830249ab", + "GF000057": "3e4d697f5d8e2ac3", + "GF000058": "4ce1a48d78b20135", + "GF000059": "2468dc593e62b609", + "GF000060": "32795025f32c2ea1", + "GF000061": "3e2dbd0238c650ae", + "GF000062": "82ac20b13db525a6", + "GF000063": "2399a8a6f2370ebb", + "GF000064": "3691c3f0dddc23e3", + "GF000065": "c93c75cbdb7d0a29", + "GF000066": "645e77d01bf12ca2", + "GF000067": "6e61c79c3cebf06a", + "GF000068": "f5efe1c734ae0d65", + "GF000069": "11875a02cf158989", + "GF000070": "ae73e25475a3f518", + "GF000071": "accdad34835606ec", + "GF000072": "46fc9472329cfde2", + "GF000073": "04351d792ec7cb57", + "GF000074": "1784012fed5c2a60", + "GF000075": "2fefa5dfd69f6c54", + "GF000076": "d8d214a95bf066b2", + "GF000077": "29da9b241ebba44c", + "GF000078": "dadd4d6b1ebdde10", + "GF000079": "025467d91fd4d3e9", + "GF000080": "cdc0e256130ae2d6", + "GF000081": "570e10034d48d167", + "GF000082": "0ac9a5dbf19782f1", + "GF000083": "138801569f4c0b4c", + "GF000084": "c47b5525c6999de8", + "GF000085": "3d77f5a2ee6c6686", + "GF000086": "f51782051f807b28", + "GF000087": "04d75ab523e838c5", + "GF000088": "428bae72d8a01f50", + "GF000089": "7e58c196a1055a36", + "GF000090": "9454c89101d85c73", + "GF000091": "a12b65a3d655b55b", + "GF000092": "e0f4984208153657", + "GF000093": "69c2d44e10283591", + "GF000094": "239a41f878dea121", + "GF000095": "1e165bec50520755", + "GF000096": "d2b46146a28d1b43", + "GF000097": "539d92d88ee79dc1", + "GF000098": "07a3e69abc721577", + "GF000099": "cb7d2f17a290243b", + "GF000101": "db317f695afed2b2", + "GF000103": "deac82bbd8832a03", + "GF000105": "d159c5453de90795", + "GF000107": "db2019a9acb968b1", + "GF000109": "8e4f58911b220102", + "GF000111": "b0d55497b049d9ca", + "GF000112": "08e31ea2787ab8a3", + "GF000113": "96b811faf59c723a", + "GF000114": "da1f6dbb0e97b062", + "GF000115": "1c9e47c2facb0a0f", + "GF000116": "34393ed51f08780d", + "GF000117": "9ab2c67374fb2ade", + "GF000118": "8fdb03e7ac44ad66", + "GF000119": "4723c568a4dd0770", + "GF000120": "95901768fd7e0b75", + "GF000121": "ae3ff4722ab44895", + "GF000122": "aab1cd310b948ada", + "GF000123": "d29f5e6eafdc5e19", + "GF000124": "33cdd0187bfc8cee", + "GF000125": "ba310d5129e09bf9", + "GF000126": "7787895938016575", + "GF000127": "a8e707e848abff01", + "GF000128": "4d5e616c017a72fc", + "GF000129": "755fd0bc92640528", + "GF000130": "9d100c36239e8fc6", + "GF000131": "a9559ccc964a7182", + "GF000132": "e88ed19943884de0", + "GF000133": "45218c4076224b49", + "GF000134": "ac9bd137ba90ed31", + "GF000135": "ac9bd137ba90ed31", + "GF000136": "ac9bd137ba90ed31", + "GF000137": "fecab379718d0537", + "GF000138": "af68f29c14d86a80", + "GF000139": "b75d001c209ca4d5", + "GF000140": "5bd79368c766024b", + "GF000141": "1e0ca0edefd1e7ab", + "GF000142": "845d686f40d694ba", + "GF000143": "6f402786f10fc588", + "GF000144": "236be8ca6d0e968a", + "GF000145": "5cae634d5f28ba66", + "GF000146": "d24fd59d8ce81498", + "GF000147": "a2be3e52ade7132f", + "GF000148": "d622d3f62faa6e91", + "GF000149": "c827b77a6a931472", + "GF000150": "070b408ca4265d5d", + "GF000151": "02ba4b5ba5fcdb70", + "GF000152": "ca7e15de45166964", + "GF000153": "ecfa88d93e333e7a", + "GF000154": "4723c568a4dd0770", + "GF000155": "e906e78633adf07c", + "GF000156": "6c94a1416e351867", + "GF000157": "0257cea6572ee314", + "GF000158": "16d341cdfc67195b", + "GF000159": "8817e4b04dadf2ce", + "GF000160": "00d98cee92de7d27", + "GF000161": "11bb248ad80468cd", + "GF000162": "b7687c7e06f196d3", + "GF000163": "0c0ad21be9f89a8e", + "GF000164": "aa2b36be6b628505", + "GF000165": "70fdffa5f34564de", + "GF000166": "82f956e679b07bd6", + "GF000167": "7ca81aa29f6d9f0c", + "GF000168": "563811f75d6d3342", + "GF000169": "aa000e0c740ecb8c", + "GF000170": "f82e819bc314f81e", + "GF000171": "99a718b6e7355d20", + "GF000172": "68dcf3c5d0c57383", + "GF000173": "679da6395273d77d", + "GF000174": "ff03d4337a5f2281", + "GF000175": "2d16f478d0cafd8c", + "GF000176": "13f47cf6de6d0f0e", + "GF000177": "1c9db6b4ce1e99f9", + "GF000178": "3b25e607a8aed569", + "GF000179": "32cc377894b839cd", + "GF000180": "3a40d6c2684741ca", + "GF000181": "0ce74e3b9f2dbdf7", + "GF000182": "a0308f901047ff7c", + "GF000183": "d00dfada55ec7bd9", + "GF000184": "a8576736c4fae350", + "GF000185": "cdebcbcc593d8d4a", + "GF000186": "d6dc63e6e42748c3", + "GF000187": "178afffb640781b9", + "GF000188": "1c26d242f7586962", + "GF000189": "2a84b8cb96b392bc", + "GF000190": "adaa715de4fb64ce", + "GF000191": "01503f3286ef00ac", + "GF000192": "5aaf34d8ddb4127c", + "GF000193": "e5d2faa2b7c1a355", + "GF000194": "423dc18d67c4665c", + "GF000195": "a7ff93639d2a6969", + "GF000196": "cd1e1e2b4cfe3069", + "GF000197": "fbbf865f1f6e8a14", + "GF000198": "d97fd61a1d84f17b", + "GF000199": "461c811c8ffd1517", + "GF000200": "f403fedba481ee7e", + "GF000201": "536dc3187b013c5b", + "GF000202": "1cd4cc49bc7f5194", + "GF000203": "99d36c97bb09860e", + "GF000204": "80e60a85357eabd1", + "GF000205": "eae96eb030e20485", + "GF000206": "ed3fd1e3a713e64b", + "GF000207": "cfa07d686c60012a", + "GF000208": "8d087cb2ca6e8c87", + "GF000209": "4a5f3f10efc42cd0", + "GF000210": "8d557e55abdfd170", + "GF000211": "8b437a7bce97fbbb", + "GF000212": "dd3d0739262e95b5", + "GF000213": "7dfdf0edfc9b6c82", + "GF000214": "15f0256c9db37da3", + "GF000215": "dca1425caff1e684", + "GF000218": "7571c4ac6166793b", + "GF000219": "1314eaa6c0b9b1d6", + "GF000220": "3b4654013468fcc8", + "GF000221": "b0293813b344e36e", + "GF000222": "e2d934fae1deb595", + "GF000223": "706dfd279976c254", + "GF000224": "ba3c5cef3e5de148", + "GF000225": "428629cd64e4f788", + "GF000226": "1994024b7f4bdd19", + "GF000227": "c7e9353623931b23", + "GF000228": "aebe49cee8070517", + "GF000229": "0bceb0832a67ec41", + "GF000230": "bba2fd51094ff47d", + "GF000231": "e9f30b4b57c24fc3", + "GF000232": "5cfb6ad5ed394f59", + "GF000233": "afc940f2974b4eb1", + "GF000234": "9b1cc563b400659a", + "GF000235": "3d440a702953c58f", + "GF000236": "4262cb6bab517270", + "GF000237": "411bd04576041ac1", + "GF000238": "165045a50a36af5f", + "GF000239": "82ad8bc99f26511b", + "GF000240": "4a6c40e405dbe3b2", + "GF000241": "b7013a513cca5d0e", + "GF000242": "c13eb53d9a442cb7", + "GF000243": "3a2d983f69ad6139", + "GF000244": "4fed4c9600592005", + "GF000245": "bc96ad734f651e7e", + "GF000246": "7be48038f25080fa", + "GF000247": "c984fc1fc5a63f49", + "GF000248": "ab92ee738d86dadc", + "GF000249": "56dd25ae6b4db7e9", + "GF000250": "8de00901d02be001", + "GF000251": "a440d676787d52ae", + "GF000252": "2ac24ef5c72c5d96", + "GF000253": "2ac24ef5c72c5d96", + "GF000254": "f1272c2df5e41eb7", + "GF000255": "55558a307ba31119", + "GF000256": "a8d81316e7ec1231", + "GF000257": "fe78865aa0a44b5f", + "GF000258": "a8611574f25c026c", + "GF000259": "3cda324f976d401a", + "GF000260": "a8d81316e7ec1231", + "GF000261": "e35b8ea9a34f2e4d", + "GF000262": "32f20e99e73ea19b", + "GF000263": "2a6ecea5c98ec26c", + "GF000264": "5bc0db7a59a5b681", + "GF000265": "d2e16acba1fd8133", + "GF000266": "f9c882ddf20a98fa", + "GF000267": "ba329b38b823a1cb", + "GF000268": "528eb599e471b675", + "GF000269": "a8e18e9a275bd3f2", + "GF000270": "6b50a02dcc443911", + "GF000271": "909756409f10d449", + "GF000272": "0d1f915802311cc0", + "GF000273": "2777f16bfdb4bded", + "GF000274": "28af883d6a5a9f5d", + "GF000275": "98624eee7ede5f6e", + "GF000276": "88f1911eba353a99", + "GF000277": "54860a2a318e2993", + "GF000278": "6015fc19f3c9f605", + "GF000279": "428ba10ebe5140fd", + "GF000280": "0c3cb103e27efb38", + "GF000281": "0e04b83c9ad7fec9", + "GF000282": "0078b51a88b7eb8e", + "GF000283": "cef74d92887ff33d", + "GF000284": "fcd03ba312687b70", + "GF000285": "f26e296cff8a8cc0", + "GF000286": "11a75298fc653945", + "GF000287": "9ee12abca8b5bfcc", + "GF000288": "f9d2c71240e043d7", + "GF000289": "2e9e9dffdb46a00d", + "GF000290": "ed94f2d9f60b491e", + "GF000291": "b780ce32357fa512", + "GF000292": "4a20fb8a1a94e467", + "GF000293": "acddae8d16934885", + "GF000294": "0280cba84e6f1050", + "GF000295": "b5f021410b0ed71e", + "GF000296": "8569615eaadd9ab8", + "GF000297": "0f21feb9cfdff97a", + "GF000298": "9f9dede4548bb44b", + "GF000299": "cd6f50a4a75e1ca3", + "GF000300": "715c7eb299facb34", + "GF000301": "5e44caf34081088d", + "GF000302": "834f8680374da428", + "GF000303": "a5cd786eb3f88618", + "GF000304": "19c69b0a4d4f3f52", + "GF000305": "8d5ac33a52c3ff59", + "GF000306": "64c378b290cc1d6e", + "GF000307": "fa7f1545a07adce3", + "GF000308": "88fc63e7085f5a60", + "GF000309": "91094fc70c509ca8", + "GF000310": "949a0a6daa90ff60", + "GF000311": "b03d8424eac362dc", + "GF000312": "7b0d09c29cae4134", + "GF000313": "74c35a9511039242", + "GF000314": "9527af2499f448b2", + "GF000315": "99cbd7c430433895", + "GF000316": "268f057e2b039bfc", + "GF000317": "087ab95a315be873", + "GF000318": "39eff01c2c26c798", + "GF000319": "9aa5293a954f06c6", + "GF000320": "d68a6a7e6a1ac5d4", + "GF000321": "4d282a27209fe4d6", + "GF000322": "473ad156ac2fe2da", + "GF000323": "4821e510e47ff308", + "GF000324": "5c82316f03b41c6a", + "GF000325": "51d18c3cac6d2573", + "GF000326": "db2a93fbee97bc6c", + "GF000327": "38729fe0ac79a42b", + "GF000328": "6c85d6dcb2f86f5e", + "GF000329": "91ae6615b093f63d", + "GF000330": "c2c9ecfb55f5a1a1", + "GF000331": "a180544f2ae28361", + "GF000332": "9a8ef8dfe5b2edc5", + "GF000333": "86799e690947928f", + "GF000334": "05e8796025a4fe06" + }, + "마스터": "daa2dbee639aaf56" +} diff --git a/resources/tester/test_791_1_engine_semantics.py b/resources/tester/test_791_1_engine_semantics.py new file mode 100644 index 000000000..c4962d674 --- /dev/null +++ b/resources/tester/test_791_1_engine_semantics.py @@ -0,0 +1,497 @@ +"""791-1 엔진 셈 의미 지킴 시험 — 금액번지 · 소계 단계 · 제외 · 줄 자리 · 블록 끝전 · 결과 조정 · +특수 % · 변수 표시. + +① 전후 같음 — M01 로직 전부(입력 = 자동값 + 기본값)의 날 셈 · 끝수 셈 지문 = 고치기 전 뜬 지문 + (`fixtures/m01_791_1_logic_fingerprint.json` · 마스터 데이터가 바뀌면 건너뜀 → 이 파일을 + 바로 돌려 다시 뜸). +② 새 의미 단위 시험 · 검사. +③ 실무 손 옮김 견본 넷 — 원문 글(STmate 숨은 G 열 · EST 보이는 글)을 사람이 로직 꼴로 옮김 · + 단가 = 그 엑셀 목록표 값 · 노 · 재 · 경 · 계 = 엑셀(`helper_m02_hopyo_golden.FILES` · 649-G2). +""" + +import hashlib +import importlib +import json +import sys +from decimal import Decimal +from pathlib import Path + +import pytest + +from M01_MasterData import M01_MasterData_Store as m01 # scripts 길을 둠 + +mcut = importlib.import_module("master_cut") +mf = importlib.import_module("master_formula") +ms = importlib.import_module("master_formula_sum") + +ROOT = Path(__file__).resolve().parents[2] +FINGER = Path(__file__).resolve().parent / "fixtures" / "m01_791_1_logic_fingerprint.json" +COSTS = ("노무비", "재료비", "경비", "계") +D = Decimal + + +# ── ① 전후 같음 ──────────────────────────────────────────────────────── +def _master_hash() -> str: + h = hashlib.sha256() + for path in sorted((ROOT / "resources" / "master_data").glob("*.json")): + if not path.name.startswith("_"): + h.update(path.name.encode() + path.read_bytes()) + return h.hexdigest()[:16] + + +def _given(whole, row) -> dict: + import master_auto as ma + + given = ma.values(whole, row) + given.update({k: v for k, v in ma.defaults(whole, row).items() if v is not None}) + return given + + +def _print(whole, row) -> str: + key = str(row["키"]) + try: + raw = mf.run(whole, key, _given(whole, row)) + text = json.dumps(raw, default=str, sort_keys=True, ensure_ascii=False) + cut = mcut.engine(whole, key, raw) + text += json.dumps(cut, default=str, sort_keys=True, ensure_ascii=False) + except Exception as e: # 멈춤도 지문 — 멈춤 까닭이 바뀌어도 잡음 + text = f"ERR {type(e).__name__} {e}" + return hashlib.sha256(text.encode()).hexdigest()[:16] + + +def fingerprints() -> dict: + files, whole = m01.loaded() + rows = [r for d in files.values() if d.get("그룹") == "로직" for r in d.get("줄", [])] + return {str(r["키"]): _print(whole, r) for r in rows} + + +def test_M01_로직_전부_전후_같음(): + saved = json.loads(FINGER.read_text(encoding="utf-8")) + if saved["마스터"] != _master_hash(): + pytest.skip("마스터 데이터 바뀜 — 지문 다시 뜸: python resources/tester/test_791_1_…py") + now = fingerprints() + assert len(now) == len(saved["결과"]) >= 1515 + diff = sorted(k for k in now if now[k] != saved["결과"].get(k)) + assert not diff, f"셈 결과 바뀐 로직 {len(diff)} 개: {diff[:10]}" + + +# ── 시험 마스터 ─────────────────────────────────────────────────────── +def _master(logics: list[dict], labor: dict, mats: dict | None = None) -> mf.Master: + """시험 전용 색인 — 인력 LB99 · 자재품목 MT99(물가자료) · 로직 GX99.""" + files = { + "인력.json": { + "그룹": "인력", + "줄": [ + {"키": k, "이름": n, "단위": "원/일", "값": D(v)} for k, (n, v) in labor.items() + ], + }, + "재료_자재품목.json": { + "그룹": "재료", + "줄": [ + {"키": k, "이름": n, "단위": u, "물가자료": D(v)} + for k, (n, u, v) in (mats or {}).items() + ], + }, + "로직_시험.json": {"그룹": "로직", "줄": logics}, + } + return mf.Master(files) + + +def _calc(master, key: str, given: dict | None = None) -> dict: + return mcut.engine(master, key, mf.run(master, key, given or {})) + + +def _money(got: dict) -> dict: + return {k: got[k] for k in COSTS} + + +def _line(got: dict, name: str) -> dict: + return next(x for x in got["줄"] if x["이름"] == name) + + +def _step(got: dict, name: str) -> dict: + return next(x for x in got["단계"] if x["이름"] == name) + + +def _man(key, qty, name, **more): + return { + "종류": "인력", + "요소": key, + "이름": name, + "단위": "인", + "수량": qty, + "비목": "노무비", + **more, + } + + +def _mat(key, qty, name, **more): + return { + "종류": "재료", + "요소": key, + "이름": name, + "단위": "식", + "수량": qty, + "비목": "재료비", + **more, + } + + +def _machine(qty, name, bundle): + """기계 시간당 노 · 재 · 경 = 엑셀 중기목록표 값 그대로(M02 `값묶음` 길).""" + return { + "종류": "로직", + "요소": "로직(GX990009)", + "이름": name, + "단위": "hr", + "수량": qty, + "값묶음": dict(zip(("노무비", "재료비", "경비"), map(D, bundle))), + } + + +_HOUR = { + "키": "GX990009", + "이름": "시간당 기계경비(시험)", + "결과단위": "원/시간", + "입력": [], + "호표": [], +} + + +def _logic(key: str, **body) -> dict: + return {"키": key, "이름": key, "결과단위": "원/식", "입력": [], **body} + + +# ── ② 새 의미 단위 시험 ──────────────────────────────────────────────── +LAB = {"LB990001": ("보통인부", 100000), "LB990002": ("특별인부", 200000)} +MAT = {"MT990001": ("자재 갑", "식", 1000), "MT990002": ("자재 을", "식", 3000)} + + +def _base(**body) -> mf.Master: + row = _logic( + "GX990001", + 호표=[ + _mat("MT990001", "1.23", "갑", 번지="A01"), + _mat("MT990002", "0.5", "을", 번지="A01"), + _man("LB990001", "0.0123", "보통", 번지="A02"), + _man("LB990002", "0.01", "특별", 번지="A02"), + ], + **body, + ) + return _master([row], LAB, MAT) + + +def test_옛_로직은_새_칸_없음(): + master = _base() + for item in master.logic("GX990001")["호표"]: + del item["번지"] + got = _calc(master, "GX990001") + assert "단계" not in got and not ms.uses(master.logic("GX990001")) + assert _money(got) == {"노무비": 3230, "재료비": 2730, "경비": 0, "계": 5960} + + +def test_번지_율_밑(): + master = _base(덧줄=[{"이름": "잡재료", "식": "번지.A01.재료비 * 10 / 100", "비목": "재료비"}]) + got = _calc(master, "GX990001") + assert _line(got, "잡재료")["금액"] == D("273.0") + assert got["재료비"] == 3003 + + +def test_단계_누계_제외_합밖(): + master = _base( + 덧줄=[{"이름": "공구손료", "식": "단계.'인부 소계'.노무비 * 3 / 100", "비목": "경비"}], + 단계=[ + {"이름": "자재 소계", "단": 1, "줄": ["갑", "을"]}, + {"이름": "인부 소계", "단": 1, "줄": ["보통", "특별"]}, + {"이름": "경비 소계", "단": 1, "줄": ["공구손료"], "제외": True}, + {"이름": "계", "단": 2}, + ], + ) + got = _calc(master, "GX990001") + assert _step(got, "인부 소계")["계"] == D("3230.0") + assert _line(got, "공구손료")["금액"] == D("96.9") + assert _step(got, "경비 소계") == { + "이름": "경비 소계", + "단": 1, + "노무비": 0, + "재료비": 0, + "경비": D("96.9"), + "계": D("96.9"), + "제외": True, + } + assert _step(got, "계")["계"] == D("5960.0") # 제외 단계는 위로 안 올라감 + assert _money(got) == {"노무비": 3230, "재료비": 2730, "경비": 0, "계": 5960} + # 합밖 = 번지엔 쌓되 합에서 뺌 + row = master.logic("GX990001") + row["호표"][1]["합밖"] = True + got = _calc(master, "GX990001") + assert got["재료비"] == 1230 and _step(got, "자재 소계")["재료비"] == D("2730.0") + + +def test_줄_자리_셋(): + for way, want in (("반올림", 124), ("올림", 124), ("버림", 123)): # 123.5 + master = _base( + 덧줄=[{"이름": "덧", "식": "123.5", "비목": "경비", "자리": {"방법": way, "자리": 0}}] + ) + assert _line(_calc(master, "GX990001"), "덧")["금액"] == want + master = _base( + 덧줄=[ + {"이름": "덧", "식": "123.456", "비목": "경비", "자리": {"방법": "반올림", "자리": 2}} + ] + ) + assert _line(_calc(master, "GX990001"), "덧")["금액"] == D("123.46") + + +def test_블록_끝전_둘(): + body = {"덧줄": [{"이름": "경", "식": "0.6", "비목": "경비"}]} # 노 3230.0 · 재 2730.0 · 경 0.6 + assert _calc(_base(**body), "GX990001")["계"] == 5960 + got = _calc(_base(끝전="큰나머지", **body), "GX990001") + assert _money(got) == {"노무비": 3230, "재료비": 2730, "경비": 0, "계": 5960} + # 원 미만이 모여 1 원이 되면 큰 나머지 비목에 · 같으면 경 › 재 › 노 + body = { + "덧줄": [ + {"이름": "노", "식": "0.6", "비목": "노무비"}, + {"이름": "경", "식": "0.6", "비목": "경비"}, + ] + } + assert _calc(_base(**body), "GX990001")["계"] == 5960 + got = _calc(_base(끝전="큰나머지", **body), "GX990001") + assert _money(got) == {"노무비": 3230, "재료비": 2730, "경비": 1, "계": 5961} + + +def test_결과금액_조정(): + got = _calc(_base(조정={"옮김": "경비"}), "GX990001") + assert _money(got) == {"노무비": 0, "재료비": 0, "경비": 5960, "계": 5960} + got = _calc(_base(조정={"배율": {"노무비": D("0.5")}}), "GX990001") + assert _money(got) == {"노무비": 1615, "재료비": 2730, "경비": 0, "계": 4345} + assert mf.run(_base(조정={"옮김": "재료비"}), "GX990001", {})["재료비"] == D("5960.0") + + +def test_특수_퍼센트_누계와_앞_소계(): + master = _base( + 덧줄=[ + {"이름": "L%", "식": "노무비 * 2 / 100", "비목": "경비"}, # 처음부터 노무비 누계 + {"이름": "T%", "식": "단계.'소계'.계 * 0.8", "비목": "경비"}, # 바로 앞 소계 × 수량 + ], + 단계=[{"이름": "소계", "단": 1, "줄": ["갑", "을"]}], + ) + got = _calc(master, "GX990001") + assert _line(got, "L%")["금액"] == D("64.6") + assert _line(got, "T%")["금액"] == D("2184.0") + + +def test_변수_표시_반올림(): + row = _logic( + "GX990002", + 중간=[{"이름": "f", "식": "1 / 1.3", "표시": 2}, {"이름": "g", "식": "1 / 1.3"}], + 결과={"식": "f * 100"}, + ) + got = mf.run(_master([row], LAB), "GX990002", {}) + assert got["결과"] == D("77.00") and got["중간"]["g"] != D("0.77") + + +def test_검사(): + def problems(**body) -> list[str]: + master = _base(**body) + return mf.check_logic(master, master.logic("GX990001"))[0] + + assert problems(덧줄=[{"이름": "덧", "식": "번지.A01.재료비 * 1", "비목": "경비"}]) == [] + assert any( + "없는 번지" in x + for x in problems(덧줄=[{"이름": "덧", "식": "번지.B09.계", "비목": "경비"}]) + ) + order = problems( + 덧줄=[{"이름": "덧", "식": "단계.'소'.계", "비목": "경비"}], + 단계=[{"이름": "소", "단": 1, "줄": ["갑", "덧"]}], + ) + assert any("단계 순서" in x for x in order) + assert any( + "자리" in x + for x in problems( + 덧줄=[{"이름": "덧", "식": "1", "비목": "경비", "자리": {"방법": "버림", "자리": 4}}] + ) + ) + assert any("끝전" in x for x in problems(끝전="합버림")) + assert any("단 「6」" in x for x in problems(단계=[{"이름": "소", "단": 6, "줄": ["갑"]}])) + assert any("없는 줄" in x for x in problems(단계=[{"이름": "소", "단": 1, "줄": ["없음"]}])) + assert any("조정 옮김" in x for x in problems(조정={"옮김": "계"})) + assert any( + "이 단계" in x + for x in problems( + 단계=[{"이름": "가", "단": 1, "줄": ["갑"]}, {"이름": "나", "단": 1, "줄": ["갑"]}] + ) + ) + + +# ── ③ 실무 손 옮김 견본 넷 ───────────────────────────────────────────── +def test_봉화_D00102_합판거푸집_STmate_번지_율(): + """봉화 단가산출근거 2909~2943 행 — `&M00081& * 1.03 * 0.327 :=A01` · `&PA01M:M& * 11'%'` · + `&PA02L:S& * 1'%'` · `(=)` · `(==)` · 총계 45,652 · 34,119 · 11,192 · 341.""" + row = _logic( + "GX990101", + 호표=[ + _mat("MT990101", "1.03 * 0.327", "합판(내수)", 번지="A01"), + _mat("MT990102", "0.038 * 0.327", "각재", 번지="A01"), + _man("LB990101", "5 / 50", "형틀목공", 번지="A02"), + _man("LB990102", "2 / 50", "보통인부", 번지="A02"), + ], + 덧줄=[ + {"이름": "소모자재(박리재 등)", "식": "번지.A01.재료비 * 11 / 100", "비목": "재료비"}, + { + "이름": "공구손료 및 경장비 기계경비", + "식": "번지.A02.노무비 * 1 / 100", + "비목": "경비", + }, + ], + 단계=[ + {"이름": "자재비 소계", "단": 1, "줄": ["합판(내수)", "각재", "소모자재(박리재 등)"]}, + { + "이름": "설치 소계", + "단": 1, + "줄": ["형틀목공", "보통인부", "공구손료 및 경장비 기계경비"], + }, + {"이름": "계", "단": 2}, + ], + ) + master = _master( + [row], + {"LB990101": ("형틀목공", 274978), "LB990102": ("보통인부", 165545)}, + {"MT990101": ("합판(내수)", "㎡", 11468), "MT990102": ("각재", "㎥", 500661)}, + ) + assert mf.check_logic(master, row)[0] == [] + got = _calc(master, "GX990101") + assert _money(got) == {"노무비": 34119, "재료비": 11192, "경비": 341, "계": 45652} + assert [x["금액"] for x in got["줄"]] == [ + D(x) for x in ("3862.5", "6221.2", "27497.8", "6621.8", "1109.2", "341.1") + ] + assert [x["계"] for x in got["단계"]] == [D("11192.9"), D("34460.7"), D("45653.6")] + + +def test_울진소광_D01489_낙석방지책_STmate_L퍼센트(): + """울진소광 32 호표(D01489) — `'공구손료 :'&S01045& * 2'%'`(S01045 단위 L% = 처음부터 + 노무비 누계) · `L = Q/10 =` 표시 2 · 계 줄 420,734 · 182,223 · 230,359 · 8,152 + (계약단가 87.745 % 는 B09 몫).""" + row = _logic( + "GX990102", + 중간=[ + {"이름": "N", "식": "40"}, + {"이름": "Q", "식": "200"}, + {"이름": "L", "식": "Q / 10", "표시": 2}, + {"이름": "Q1", "식": "360"}, + {"이름": "A", "식": "5.2"}, + ], + 호표=[ + _mat("MT990103", "1", "낙석방지책"), + _man("LB990103", "1 / N", "용접공"), + _man("LB990104", "3 / N", "특별인부(지주)"), + _man("LB990105", "2 / N", "보통인부(지주)"), + _machine("1 / N * 8", "트럭탑재형 크레인 10톤", (49479, 16797, 22539)), + _man("LB990104", "4 / Q * L", "특별인부(와이어)"), + _man("LB990105", "2 / Q * L", "보통인부(와이어)"), + _man("LB990104", "4 / Q1 * A", "특별인부(철망)"), + _man("LB990105", "2 / Q1 * A", "보통인부(철망)"), + ], + 덧줄=[{"이름": "공구손료", "식": "노무비 * 2 / 100", "비목": "경비"}], + 단계=[ + {"이름": "재료비 계", "단": 2, "줄": ["낙석방지책"]}, + {"이름": "인건비 소계", "단": 1, "줄": ["용접공", "특별인부(지주)", "보통인부(지주)"]}, + {"이름": "크레인 소계", "단": 1, "줄": ["트럭탑재형 크레인 10톤"]}, + {"이름": "지주 계", "단": 2}, + {"이름": "와이어 계", "단": 2, "줄": ["특별인부(와이어)", "보통인부(와이어)"]}, + {"이름": "철망 계", "단": 2, "줄": ["특별인부(철망)", "보통인부(철망)"]}, + {"이름": "공구손료 계", "단": 2, "줄": ["공구손료"]}, + {"이름": "합계", "단": 3}, + ], + ) + master = _master( + [row, _HOUR], + { + "LB990103": ("용접공", 278326), + "LB990104": ("특별인부", 221506), + "LB990105": ("보통인부", 169804), + }, + {"MT990103": ("낙석방지책", "경간", 227000)}, + ) + assert mf.check_logic(master, row)[0] == [] + got = _calc(master, "GX990102") + assert _money(got) == {"노무비": 182223, "재료비": 230359, "경비": 8152, "계": 420734} + assert _line(got, "공구손료")["금액"] == D("3644.4") + assert _step(got, "지주 계")["계"] == D("49824.2") + assert _step(got, "합계")["계"] == D("420735.3") + + +def test_동부청영월_D00003_터파기_STmate_큰나머지_끝전(): + """동부청영월 단가산출근거 54~103 행(D00003) — `f = 1/1.3 =` · `E=…=` · + `Q = 3600*q*K*f*E/Cm =`(표시 2) · `&X00205L/M/S& / Q * 0.9` · 계 원 버림 + 큰 나머지 + 5,471 · 4,764 · 310 · 397.""" + row = _logic( + "GX990103", + 중간=[ + {"이름": "q", "식": "0.7"}, + {"이름": "f", "식": "1 / 1.3", "표시": 2}, + {"이름": "K", "식": "0.9"}, + {"이름": "Cm", "식": "20"}, + {"이름": "E", "식": "(0.7 + 0.6) / 2 - 0.05", "표시": 2}, + {"이름": "Q", "식": "3600 * q * K * f * E / Cm", "표시": 2}, + ], + 호표=[ + _man("LB990106", "(0.2 + 0.26) / 2 * 0.1", "인부"), + _machine("1 / Q * 0.9", "유압식 백호우 0.7㎥", (55700, 18015, 23128)), + ], + 단계=[ + {"이름": "인력 소계", "단": 1, "줄": ["인부"]}, + {"이름": "기계 소계", "단": 1, "줄": ["유압식 백호우 0.7㎥"]}, + {"이름": "계", "단": 2}, + ], + 끝전="큰나머지", + ) + master = _master([row, _HOUR], {"LB990106": ("보통인부", 165545)}) + assert mf.check_logic(master, row)[0] == [] + got = _calc(master, "GX990103") + assert got["중간"]["Q"] == D("52.39") + assert _money(got) == {"노무비": 4764, "재료비": 310, "경비": 397, "계": 5471} + assert _step(got, "계")["계"] == D("5471.0") + row["끝전"] = "비목버림" # 비목 원 버림이면 1 원 모자람 + assert _calc(master, "GX990103")["계"] == 5470 + + +def test_북부청임도_18_문양거푸집_EST_줄_자리(): + """북부청임도 단가산출 18 번(문양거푸집) — `5,786.1 × 2% = 116`(그 줄만 반올림 0) · + `인부소계 : 24,467.3` · 노무비 24,467 · 재료비 5,902 · 계 30,369.""" + row = _logic( + "GX990104", + 호표=[ + _mat("MT990104", "1 * (1 / 0.72)", "문양거푸집(ABS코팅폼)"), + _man("LB990107", "0.07", "형틀목공"), + _man("LB990108", "0.03", "보통인부"), + ], + 덧줄=[ + { + "이름": "잡재료비", + "식": "재료비 * 2 / 100", + "비목": "재료비", + "자리": {"방법": "반올림", "자리": 0}, + } + ], + 단계=[{"이름": "인부소계", "단": 1, "줄": ["형틀목공", "보통인부"]}], + ) + master = _master( + [row], + {"LB990107": ("형틀목공", 275790), "LB990108": ("보통인부", 172068)}, + {"MT990104": ("문양거푸집(ABS코팅폼)", "㎡", 4166)}, + ) + assert mf.check_logic(master, row)[0] == [] + got = _calc(master, "GX990104") + assert _line(got, "잡재료비")["금액"] == 116 + assert _step(got, "인부소계")["계"] == D("24467.3") + assert _money(got) == {"노무비": 24467, "재료비": 5902, "경비": 0, "계": 30369} + del row["덧줄"][0]["자리"] # 기본 0.1 버림이면 엑셀과 1 원 다름 + assert _calc(master, "GX990104")["계"] == 30368 + + +if __name__ == "__main__": # 지문 다시 뜸 — 마스터 데이터가 바뀐 뒤 · 엔진 고치기 전 커밋에서만 + data = {"마스터": _master_hash(), "결과": fingerprints()} + FINGER.write_text( + json.dumps(data, ensure_ascii=False, indent=1, sort_keys=True) + "\n", encoding="utf-8" + ) + print(len(data["결과"]), "개 지문", FINGER, file=sys.stderr)