fix(ui): 화면 글 「손입력 · 손 입력 · 손 줄」 → 「수기 입력 · 수기 줄」 전역(171-1)

- 화면 글 10 자리(8 파일) — 세월교 힌트 · B08 재료 엑셀 알림 · B09 내역 수기 줄 까닭(거울 둘) · 산근 쓰는 곳 · 단가 힌트 · 총괄 수기 입력 칸 · 로케일 둘
- 저장 키 · 주석 · 손료는 그대로 · 로케일 줄 수 그대로
- 걸린 시험 셋 글 맞춤 · 새 시험 test_171_1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KYZ2HWWdUwZWYnn3tQz7X
This commit is contained in:
2026-10-10 08:34:28 +09:00
co-authored by Claude Opus 5.5
parent 1f1646781f
commit ac1961c0a3
12 changed files with 71 additions and 13 deletions
@@ -27,5 +27,5 @@ export function fordHeightM(designFlowM3s: number | null, widthM: number): numbe
export function fordHeightHint(designFlowM3s: number | null, widthM: number): string {
const need = fordHeightM(designFlowM3s, widthM);
if (need !== null) return `필요 수심 ${need.toFixed(2)} 이상`;
return designFlowM3s ? "월류 폭을 넣으면 셈" : "설계유량 없음 — 손 입력";
return designFlowM3s ? "월류 폭을 넣으면 셈" : "설계유량 없음 — 수기 입력";
}
+3 -1
View File
@@ -188,7 +188,9 @@ function excelDiffs(s: MaterialSheets): string {
if (want != null && (row.surcharge ?? 0) !== want)
out.push(`${row.spec} 할증 ${row.surcharge ?? 0}%(엑셀 ${want}%)`);
}
return out.length ? `2공구 엑셀 손값과 다름 — ${out.join(" · ")} · 지금 값 = 품셈 근거` : "";
return out.length
? `2공구 엑셀 수기 입력값과 다름 — ${out.join(" · ")} · 지금 값 = 품셈 근거`
: "";
}
/** 콘크리트소요 원천 = 콘크리트(기계) · 모르터 1:3 열 + 그 둘에 맞는 사본 자재 열(짝 계산 `matched` 그대로) */
+1 -1
View File
@@ -119,7 +119,7 @@ def _material_items(inputs: dict[str, Any], compare: list[dict[str, Any]]) -> li
PriceOf = Callable[[str], dict[str, Any] | None]
WHY_HAND = "손 줄 — 연결 없음(우클릭 [연결])"
WHY_HAND = "수기 줄 — 연결 없음(우클릭 [연결])"
WHY_LOGIC = "새 산근 — [저장] 뒤 단가(서버 셈)"
+1 -1
View File
@@ -108,7 +108,7 @@ function materialItems(inputs: Row, compare: Row[]): Row[] {
/** 사본 주인 id → 대가 1 단위 단가(145-2 `daegaPrice`) */
export type PriceOf = (owner: string) => Row | null;
const WHY_HAND = "손 줄 — 연결 없음(우클릭 [연결])";
const WHY_HAND = "수기 줄 — 연결 없음(우클릭 [연결])";
const WHY_LOGIC = "새 산근 — [저장] 뒤 단가(서버 셈)";
/** 연결 → 1 단위 단가 {no, key, labor, material, expense, sum} — 없으면 null */
+1 -1
View File
@@ -483,7 +483,7 @@ def hand_sources(user: dict[str, Any] | None) -> list[tuple[str, str, dict[str,
continue
src = {"code": str(ref["code"]), "given": _clean_given(ref.get("given")),
"rate": ref.get("rate"), "picks": ref.get("picks")} # fmt: skip
out.append((row["key"], f"손 줄 {row.get('name') or row['key']}", src))
out.append((row["key"], f"수기 줄 {row.get('name') or row['key']}", src))
return out
@@ -157,7 +157,7 @@ export const buildCompare: TabBuild = ({ sheets, user, setUser }): TabTable => {
],
rows: out,
frozen: 2,
hint: "적용 단가 = 출처 중 최저(0 · 빈칸 뺌 · 견적 포함) 자동 · 칸에 넣으면 사용자 값 · 지우면 다시 최저 · 「출처」 칸 누름 = 고를 출처 돌림(값 있는 출처 · 견적이 높아도 고름) · 견적 단가 · 면 = 손입력 · 「관급 · 별도」 칸 누름 = 없음 → 관급 → 별도 · 「견적서」 칸 누름 = 파일 올리기 · 보기",
hint: "적용 단가 = 출처 중 최저(0 · 빈칸 뺌 · 견적 포함) 자동 · 칸에 넣으면 사용자 값 · 지우면 다시 최저 · 「출처」 칸 누름 = 고를 출처 돌림(값 있는 출처 · 견적이 높아도 고름) · 견적 단가 · 면 = 수기 입력 · 「관급 · 별도」 칸 누름 = 없음 → 관급 → 별도 · 「견적서」 칸 누름 = 파일 올리기 · 보기",
editable: (_i, key) => key === "applied" || key === "p:견적" || key === "g:견적",
onEdit: (i, key, value) => {
const row = out[i];
@@ -191,7 +191,7 @@ export const buildTotal: TabBuild = (ctx): TabTable => {
money("material", "재료비"),
money("expense", "경비"),
qty("rate", "요율(%)", 5),
money("hand", "손입력", undefined, 2),
money("hand", "수기 입력", undefined, 2),
text("note", "산식", 320),
text("candidate", "요율 출처", 260),
],
@@ -200,7 +200,7 @@ export const buildTotal: TabBuild = (ctx): TabTable => {
hint:
"요율 = M01 조달청 제비율에서 자동(맨 위 고르는 값 · 공종 토목) — 칸에 적으면 사용자 값이 앞섬 · 지우거나 출처 칸 누르면 자동 · " +
"2024 엑셀 요율과 연도 차로 다를 수 있음 · 자동도 없으면 그 줄 · 그 줄을 쓰는 소계 빈칸 · 끝수 = 엑셀 그대로(줄마다 원 버림 · 관급 천원 올림) · " +
"손입력 = 안전관리비 기초액(빈칸 = 자동 구간 기초액) · 이윤 보정 · 부가세 조정 · 조달수수료율(빈칸 = 0) · " +
"수기 입력 = 안전관리비 기초액(빈칸 = 자동 구간 기초액) · 이윤 보정 · 부가세 조정 · 조달수수료율(빈칸 = 0) · " +
"공종 제목 줄은 설계내역서(145-3)가 들어오면 붙음",
editable: (i, key) =>
(key === "rate" && RATE_KEYS.has(String(rows[i]?.key))) ||
@@ -41,7 +41,7 @@ def test_엑셀_검산_시멘트_모래_자갈_레미콘():
def test_엑셀_손값과_다른_상수는_알림만():
ui = (ROOT / "B08_Quantity" / "B08_Quantity_UI_Material.ts").read_text(encoding="utf-8")
assert "2공구 엑셀 손값과 다름" in ui and "b08MaterialExcel" in ui
assert "2공구 엑셀 수기 입력값과 다름" in ui and "b08MaterialExcel" in ui
# 기본 배합은 품셈 근거 그대로(124)
per = {r["key"]: r["per"] for r in compute(SUMMARY, clean_overrides({}))["concrete"]["rows"]}
assert per["c40"] == {"cement": 323, "sand": 0.456, "gravel": 0.612}
+1 -1
View File
@@ -177,7 +177,7 @@ def test_새_산근_사본_뒤_번호_호표_탭_집계표(tmp_path):
base = compute_base(prices.build_inputs(None, keys), {})
got = sg.collect([("s1", a)], base, sg.hand_sources(user))
listed = [(r["no"], r["logic"], r["uses"]) for r in got["sg_list"]]
assert listed == [(1, NET, ["s1", "손 줄 새 산근"]), (2, STONE, ["손 줄 메쌓기"])]
assert listed == [(1, NET, ["s1", "수기 줄 새 산근"]), (2, STONE, ["수기 줄 메쌓기"])]
assert got["sg_hand"] == {
"hand:c": got["sg_list"][1]["key"],
"hand:d": got["sg_list"][0]["key"],
@@ -0,0 +1,52 @@
"""171-1 「손입력 · 손 입력 · 손 줄」 → 「수기 입력 · 수기 줄」 화면 글 — 소스 시험.
화면 글(따옴표 안)만 바꿈 · 저장 키(`{손: 수}` · 수량출처 「손」 · 표 `손` 깃) · 주석 · 손료는 그대로.
"""
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
SPOTS = {
"A00_Common/design/design_structure_ford_calc.ts": ["설계유량 없음 — 수기 입력"],
"B08_Quantity/B08_Quantity_UI_Material.ts": ["2공구 엑셀 수기 입력값과 다름"],
"B09_Estimation/B09_Estimation_Calc_Bill.ts": ['"수기 줄 — 연결 없음(우클릭 [연결])"'],
"B09_Estimation/B09_Estimation_Calc_Bill.py": ['"수기 줄 — 연결 없음(우클릭 [연결])"'],
"B09_Estimation/B09_Estimation_Sangeun.py": ['f"수기 줄 {'],
"B09_Estimation/B09_Estimation_UI_Tabs_Base.ts": ["견적 단가 · 면 = 수기 입력"],
"B09_Estimation/B09_Estimation_UI_Tabs_Total.ts": ['"수기 입력"', '"수기 입력 = 안전관리비'],
"ui_template/ui_template_locale_b2.ts": [
"수량 원천: 수기 입력(직접비)",
"수량 원천: 수기 입력 공종",
],
}
# 따옴표 안 화면 글에 남은 옛 이름(주석 · 문서 글 빼고)
OLD = re.compile(r"[\"'`][^\"'`\n]*(손 ?입력|손 줄|손값)")
NOTE = re.compile(r'"""[\s\S]*?"""|/\*[\s\S]*?\*/|^\s*(//|#).*$', re.M)
def _text(rel: str) -> str:
return (ROOT / rel).read_text(encoding="utf-8")
def test_새_이름_자리():
for rel, wants in SPOTS.items():
src = _text(rel)
for w in wants:
assert w in src, f"{rel}: {w}"
def test_화면_글에_옛_이름_없음():
for rel in SPOTS:
for line in NOTE.sub("", _text(rel)).splitlines():
assert not OLD.search(line), f"{rel}: {line.strip()}"
def test_저장_키는_그대로():
calc = _text("M02_MasterTemplete/M02_MasterTemplete_Labor_Calc.ts")
assert 'export type QtySource = "손" | "칸" | "없음";' in calc
assert "export type Qty = { 손: number } | null;" in calc
assert "손?: boolean;" in _text("ui_template/sheet/ui_template_sheet_types.ts")
assert "rows[] 손 줄" in _text("B09_Estimation/B09_Estimation_Calc_Bill.ts") # 주석 그대로
@@ -100,7 +100,11 @@ def test_월류_높이는_폭과_연동_필요_수심_올림(out):
row = [h for q, b, h in out["table"] if q == 2.54]
assert row == sorted(row, reverse=True) and len(set(row)) == len(row)
assert out["none"] == [None, None] # 설계유량 · 폭이 없으면 손 입력
assert out["hints"] == ["필요 수심 0.08 이상", "월류 폭을 넣으면 셈", "설계유량 없음 — 손 입력"]
assert out["hints"] == [
"필요 수심 0.08 이상",
"월류 폭을 넣으면 셈",
"설계유량 없음 — 수기 입력",
]
def test_정의_파일에_세월교_이름_표가_올라_있음():
+2 -2
View File
@@ -973,9 +973,9 @@ export const ui_locales_b2 = {
"공종별 수량 (한 줄에 「공종코드=수량」)",
'Quantities (one "code=qty" per line)',
],
B09_Estimation_Src_Manual: ["수량 원천: 손입력(직접비 직접 입력)", "Source: manual direct costs"],
B09_Estimation_Src_Manual: ["수량 원천: 수기 입력(직접비)", "Source: manual direct costs"],
B09_Estimation_Src_Quantities: [
"수량 원천: 손입력 공종 수량 × 일위대가",
"수량 원천: 수기 입력 공종 수량 × 일위대가",
"Source: manual quantities × unit prices",
],
B09_Estimation_Missing_UP: [