- master_symbol.py(이름 끝말 → 머리글자 규칙 · 아래첨자 · 갈래별 찾기 위 「표」 · 요소 P · w · 합 C · 결과 P · Q · 한 로직 안 겹침 숫자) + build_변수기호_800_1.py(--check · 채우기만 · 원문 796-3 · 사용자 고침 안 덮음) - 로직 줄 변수표 6,664 칸(1,403 로직) · master_equation.export 끝 한 줄 msym.apply(보이기만 · 저장 안 함) · 셈 열쇠 · 식 · 값 그대로(1,519 로직 셈 지문 전후 같음) - 검토 목록 xlsx 첫 시트 「기호 기본값(800-1)」 · 읽는 법(관례 줄 위 · 원문 줄 아래) - test_800_1_var_symbols 8 · 지문 파일 마스터 해시만 갱신(결과 같음) - 낡은 시험 고침: test_796_1(작업시간 T · 운반량 Q · 운반회수 n · 운반량 N 겹침은 기본 아래 비움) · test_796_2b(형틀목공 입력글 Q_형틀목공) — 기호 기본값이 생겨 기대 글이 바뀜 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
266 lines
10 KiB
Python
266 lines
10 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""변수 기호 기본값 — 국문 이름 → 기호 · 아래 · 위 (PLAN 800-1 · 요구 660-1 · 659-2).
|
||
|
||
보이기만 — 셈 · 엔진 열쇠 · 식에 안 씀. 기호 정하는 틀:
|
||
- 이름 끝말로 머리글자(관례): 율 r · 계수 k · 효율 E · 단가 P · 노임 w · 시간 T · 횟수 n · 개수 N ·
|
||
수량 · 소요량 · 품 Q · 길이 · 거리 L · 높이 H · 폭 B · 두께 t · 면적 A · 부피 V · 무게 W · 지름 D …
|
||
- 아래첨자 = 이름에서 끝말을 뺀 알맹이(6 자까지) · 같은 이름 = 같은 기호(로직이 달라도).
|
||
- 갈래별: 찾기(표 칸) = 위 「표」 · 요소 단가 P · 노임 w · 합(금액 줄) C · 결과 = 단위가 원이면 P 아니면 Q.
|
||
- 규칙만(자료 파일 없음) — 한 로직 안 겹침은 쓰는 쪽(빌더 · 내보내기)이 아래첨자 숫자로 가름.
|
||
- 이미 기호 꼴(영문 · 그리스 ≤ 6 자)이거나 사용자가 정한 기호는 건드리지 않음.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
|
||
SYMBOL = re.compile(r"^[A-Za-zα-ωΑ-Ω][A-Za-z0-9′']{0,5}(?:_[A-Za-z0-9′']{1,6})?$") # 이미 기호 꼴
|
||
LATIN_HEAD = re.compile(r"^([A-Za-zα-ω]{1,2})([가-힣].*)$") # 「E입력」 「f분류」 = 기호 + 말
|
||
FAMILY = ("변수", "찾기", "요소", "합", "결과")
|
||
SUBMAX = 6 # master_equation_edit.SUB_FORM 과 같게
|
||
|
||
# (끝말들, 머리글자) — 먼저 맞는 것이 이김(긴 끝말 · 특수 먼저)
|
||
SUFFIX: list[tuple[tuple[str, ...], str]] = [
|
||
(("배율", "계수", "상수"), "k"),
|
||
(("중량", "무게"), "W"),
|
||
(("용량", "능력", "출력"), "q"),
|
||
(("효율",), "E"),
|
||
(
|
||
(
|
||
"함수비",
|
||
"배합비",
|
||
"용적비",
|
||
"인력비",
|
||
"기계비",
|
||
"감률",
|
||
"증가율",
|
||
"할증율",
|
||
"가산율",
|
||
"비율",
|
||
),
|
||
"r",
|
||
),
|
||
(("율", "률", "할증", "가산", "증감", "가감", "감산", "증가", "보정", "감"), "r"),
|
||
(("노임",), "w"),
|
||
(("단가", "가격"), "P"),
|
||
(("재료비", "노무비", "경비", "손료", "비용"), "C"),
|
||
(("시간",), "T"),
|
||
(("일수", "기간", "연수"), "d"),
|
||
(("횟수", "회수"), "n"),
|
||
(("품",), "Q"),
|
||
(("수량", "소요량", "사용량", "적재량", "용수", "량"), "Q"),
|
||
(("개수", "대수", "본수", "매수", "점수", "단수", "층수", "인원", "수"), "N"),
|
||
(("거리", "길이", "연장", "간격"), "L"),
|
||
(("반경",), "R"),
|
||
(("폭",), "B"),
|
||
(("높이", "깊이", "심도", "표고", "수심", "수고", "수직고", "직고"), "H"),
|
||
(("두께",), "t"),
|
||
(("면적",), "A"),
|
||
(("부피", "체적", "용적", "재적"), "V"),
|
||
(("직경", "지름", "치수", "경"), "D"),
|
||
(("속도", "유속"), "v"),
|
||
(("경사도", "경사", "구배", "기울기"), "i"),
|
||
(("각도",), "θ"),
|
||
(("강도",), "σ"),
|
||
(("밀도",), "ρ"),
|
||
(("압력",), "p"),
|
||
(
|
||
(
|
||
"규격",
|
||
"종류",
|
||
"구분",
|
||
"방식",
|
||
"방법",
|
||
"유형",
|
||
"조건",
|
||
"상태",
|
||
"분류",
|
||
"형식",
|
||
"형태",
|
||
"지역",
|
||
"종별",
|
||
"재질",
|
||
"직종",
|
||
"기종",
|
||
"등급",
|
||
"구조",
|
||
"토질",
|
||
"지반",
|
||
"대상",
|
||
"위치",
|
||
"장소",
|
||
"부위",
|
||
"명칭",
|
||
),
|
||
"S",
|
||
),
|
||
(("인부", "기사", "기능사", "기술자", "기술인", "조수", "반장", "신호수", "측량사"), "Q"),
|
||
]
|
||
EXACT = { # 끝말 틀에 안 맞는 이름
|
||
"공수": ("Q", ""),
|
||
"총품": ("Q", ""),
|
||
"방폭": ("S", "방폭"),
|
||
"급수": ("S", "급수"),
|
||
"세밀도": ("S", "세밀도"),
|
||
"완제품": ("S", "완제품"),
|
||
"단위용수": ("Q", "단위용수"),
|
||
}
|
||
TRADE_END = ("공", "원", "사", "부") # 일꾼 직종 끝말(3 자 이상 이름만)
|
||
SHORT = ( # 긴 말 줄임
|
||
("플랜트", "플"),
|
||
("건설기계", "기계"),
|
||
("크롤러", "크롤"),
|
||
("전기용접기", "전용"),
|
||
("산소용접기", "산용"),
|
||
("콘크리트", "콘"),
|
||
("트럭탑재형", "트탑"),
|
||
("조종원", "조종"),
|
||
("작업", "작"),
|
||
)
|
||
FALLBACK = {"변수": "X", "찾기": "Q", "요소": "P", "합": "C", "결과": "Q"}
|
||
|
||
|
||
def short(text: str) -> str:
|
||
"""아래첨자 글 — 낱말 아닌 글 걷고 첫 낱말(+ 이어지는 낱말 · 크기 숫자) 6 자 안으로."""
|
||
parts = [p for p in (re.sub(r"[^\w′']|_", "", p) for p in text.split()) if p]
|
||
if not parts:
|
||
return "·"
|
||
out = parts[0]
|
||
for part in parts[1:]:
|
||
num = re.match(r"\d+", part)
|
||
if len(out + part) <= SUBMAX:
|
||
out += part
|
||
elif num and len(out + num.group()) <= SUBMAX:
|
||
out += num.group()
|
||
break
|
||
for long, cut in SHORT:
|
||
if len(out) > SUBMAX:
|
||
out = out.replace(long, cut)
|
||
return out[:SUBMAX]
|
||
|
||
|
||
def _norm(name: str) -> str:
|
||
"""분석용 이름 — 「입력」 「(…)」 걷기."""
|
||
out = re.sub(r"\(.*?\)", "", name)
|
||
out = re.sub(r"입력$", "", out) if len(out) > 3 else out
|
||
return out.strip()
|
||
|
||
|
||
def rule(name: str, family: str = "변수", hint: str = "") -> tuple[str, str, str, str]:
|
||
"""이름 → (기호, 아래, 위, 규칙). hint: 「고름」 = 고르기 입력 · 「중간」."""
|
||
up = "표" if family == "찾기" else ""
|
||
if family == "결과":
|
||
return ("P" if "원" in name else "Q"), "", "", "결과 단위"
|
||
m = LATIN_HEAD.match(name)
|
||
if m:
|
||
return m.group(1), short(m.group(2)), up, "앞 영문 = 기호"
|
||
base = _norm(name) or name
|
||
if base in EXACT:
|
||
letter, stem = EXACT[base]
|
||
return letter, short(stem or base), up, "이름 따로"
|
||
if family == "요소" and base.endswith("노임"):
|
||
return "w", short(base[:-2] or base), up, "노임 w"
|
||
for kws, letter in SUFFIX:
|
||
for kw in kws:
|
||
if base.endswith(kw) and len(base) >= len(kw) + (len(kw) < 2):
|
||
stem = base[: -len(kw)] if len(base) > len(kw) and letter != "S" else base
|
||
if letter == "Q" and kw in (
|
||
"인부",
|
||
"기사",
|
||
"기능사",
|
||
"기술자",
|
||
"기술인",
|
||
"조수",
|
||
"반장",
|
||
):
|
||
stem = base
|
||
if family == "합":
|
||
return "C", short(base), up, "금액 C"
|
||
return letter, short(stem), up, f"끝말 「{kw}」 → {letter}"
|
||
if len(base) >= 3 and base.endswith(TRADE_END) and family != "합":
|
||
return "Q", short(base), up, "직종 → Q(품)"
|
||
if family == "합":
|
||
return "C", short(base), up, "금액 C"
|
||
letter = "S" if hint == "고름" else "Q" if hint == "중간" else FALLBACK[family]
|
||
return letter, short(base), up, "끝말 없음 — 기본"
|
||
|
||
|
||
def lookup(name: str, family: str = "변수", hint: str = "") -> tuple[str, str, str]:
|
||
return rule(name, family, hint)[:3]
|
||
|
||
|
||
def is_symbol(sign: str) -> bool:
|
||
return bool(SYMBOL.match(sign or ""))
|
||
|
||
|
||
# ── 수식 내보내기 결과에 씌우기(찾기 · 요소 · 합 · 결과 — 정의 칸이 없는 것) ─────────────
|
||
def family_name(node: dict) -> tuple[str, str, str] | None:
|
||
"""변수 마디 → (갈래, 이름, 같은 것 가름 열쇠) — 찾기 · 요소 · 합만 · 아니면 None."""
|
||
kind = node.get("갈래")
|
||
if kind not in ("찾기", "요소", "합") or "기호" not in node or node.get("열쇠") == "결과":
|
||
return None
|
||
if kind == "찾기": # 칸이 같아도 표 · 조건이 다르면 다른 변수
|
||
return kind, str(node.get("칸") or ""), str(node.get("열쇠"))
|
||
name = str(node.get("이름") or node.get("열쇠") or "")
|
||
if kind == "요소":
|
||
if name.startswith("줄."): # 다른 호표 줄 금액 참조
|
||
name = name[2:].strip("'")
|
||
return "합", name, name
|
||
if m := re.match(
|
||
r"^[A-Z]{1,3}:\{(.+?)\}\.?(.*)$", name
|
||
): # EQ:{기계}.조종원 — 풀리지 않은 열쇠
|
||
return kind, m.group(1) + m.group(2).strip("'"), name
|
||
return kind, name, name
|
||
|
||
|
||
def _text(sign: str, low: str, up: str) -> str:
|
||
return sign + (f"_{low}" if low else "") + (f"^{up}" if up else "")
|
||
|
||
|
||
def apply(out: dict) -> dict:
|
||
"""내보낸 수식(export 결과) 안 변수 마디 기호가 한글 그대로면 기본 기호로 — 기호 꼴 · 고친 것은 그대로.
|
||
|
||
한 내보내기(= 한 로직) 안에서 다른 이름이 같은 글이 되면 뒤 것 아래에 숫자(`Q_크레인2`).
|
||
"""
|
||
seen: dict[str, tuple[str, str]] = {} # 글 → (갈래, 이름)
|
||
for v in out.get("변수표") or []:
|
||
seen.setdefault(
|
||
_text(str(v.get("기호") or ""), str(v.get("아래") or ""), str(v.get("위") or "")),
|
||
("변수", str(v.get("열쇠"))),
|
||
)
|
||
chosen: dict[tuple[str, str], tuple[str, str, str]] = {}
|
||
|
||
def pick(fam: str, name: str, got: tuple[str, str, str]) -> tuple[str, str, str]:
|
||
one = chosen.get((fam, name))
|
||
if one is None:
|
||
sign, low, up = got
|
||
n, base = 1, low
|
||
while seen.get(_text(sign, low, up), (fam, name)) != (fam, name):
|
||
n += 1
|
||
low = base[: SUBMAX - len(str(n))] + str(n)
|
||
seen[_text(sign, low, up)] = (fam, name)
|
||
one = chosen[(fam, name)] = (sign, low, up)
|
||
return one
|
||
|
||
for line in out.get("줄") or []:
|
||
head = line.get("결과") if line.get("갈래") == "결과" else None
|
||
if isinstance(head, dict) and not is_symbol(str(head.get("기호") or "")):
|
||
head["기호"], head["아래"], head["위"] = pick(
|
||
"결과", "결과", rule(out.get("결과단위") or "", "결과")[:3]
|
||
)
|
||
_walk(out, pick)
|
||
return out
|
||
|
||
|
||
def _walk(tree, pick) -> None:
|
||
if isinstance(tree, list):
|
||
for one in tree:
|
||
_walk(one, pick)
|
||
elif isinstance(tree, dict):
|
||
got = family_name(tree)
|
||
if got and got[1] and not is_symbol(str(tree["기호"])):
|
||
tree["기호"], tree["아래"], tree["위"] = pick(got[0], got[2], lookup(got[1], got[0]))
|
||
for v in tree.values():
|
||
if isinstance(v, (dict, list)):
|
||
_walk(v, pick)
|