Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_4
This commit is contained in:
@@ -34,6 +34,8 @@ export interface CfQuery {
|
||||
};
|
||||
value?: unknown;
|
||||
pick?: string;
|
||||
/** 다른 로직 입력 상자 — 서버가 그 로직을 받은 설계 값으로 셈(/logic-value · 화면 queriesOf 가 만듦 · /values 로는 안 감) */
|
||||
logic?: { key: string; designs: Record<string, unknown> };
|
||||
}
|
||||
|
||||
/** 입력 값 답 — 표는 `rows` · 그 밖은 `value` · 못 구하면 ok false + 쉬운 말 */
|
||||
|
||||
@@ -647,7 +647,9 @@ def run(lg: dict, values: dict, load: Loader, local: list[dict], stack: list | N
|
||||
at = _at(out.get("cell") or "A1")[2]
|
||||
hit = (calc.get(sheet.get("id")) or {}).get(at)
|
||||
value = (
|
||||
_plain(hit)
|
||||
hit["오류"] # 오류 값을 넘김(Eval fromCalc 와 같음 · 받는 칸은 그 글/NA())
|
||||
if isinstance(hit, dict) and "오류" in hit
|
||||
else _plain(hit)
|
||||
if hit is not None
|
||||
else ((sheet.get("칸") or {}).get(at) or {}).get("값")
|
||||
)
|
||||
|
||||
@@ -87,11 +87,17 @@ class _Flow:
|
||||
self.links.append(one | ({"rule": rule} if rule else {}))
|
||||
|
||||
def design(self) -> None:
|
||||
"""설계 값 상자 — 예시값 = 줄의 시험입력 · 없으면 check_master.시험입력 기본."""
|
||||
given = store.cm.시험입력(self.row)
|
||||
try:
|
||||
given = mf._inputs(self.row, given) # 글로 적힌 수 → 수(옛 엔진과 같음)
|
||||
except mf.FormulaError:
|
||||
pass
|
||||
variables = []
|
||||
for s in self.row.get("입력") or []:
|
||||
pick = s.get("고르기")
|
||||
one = {"name": s["이름"], "type": "choice" if pick else "number"}
|
||||
one |= {"unit": str(s.get("단위") or "-"), "example": plain(pick[0]) if pick else None}
|
||||
one |= {"unit": str(s.get("단위") or "-"), "example": plain(given.get(s["이름"]))}
|
||||
if pick:
|
||||
one["choices"] = plain(pick)
|
||||
if "범위" in s:
|
||||
@@ -329,6 +335,7 @@ def convert(row: dict, ctx: dict | None = None) -> dict:
|
||||
flow = {"key": row.get("키"), "name": row.get("이름") or ""}
|
||||
flow |= {"resultUnit": row.get("결과단위") or "", "boxes": list(f.boxes.values())}
|
||||
flow |= {"links": f.links, "userElements": []}
|
||||
flow["totalRound"] = None # 옛 계는 끝수 없음(줄.끝수 묶음은 출력 상자 끝수로 실음)
|
||||
return {"flow": flow, "notes": list(dict.fromkeys(f.notes))}
|
||||
|
||||
|
||||
|
||||
@@ -170,8 +170,12 @@ export async function unitChoices(): Promise<string[]> {
|
||||
return units.length ? units : ["원/㎡"];
|
||||
}
|
||||
|
||||
/** 조건 찾기를 쓰는 그룹 — 재료 밖은 인력 · 기계 · 유가전력(field 키 · 서버 Values.GROUP_RULE) */
|
||||
const RULE_GROUPS = ["인력", "기계", "유가전력"];
|
||||
|
||||
/** 입력 값 물음 — 그룹 입력 상자의 요소(알약 = 요소 이름) · 재료 자리(알약 = 자리 이름 · 조건 찾기는 이어진 알약 값을
|
||||
* pill 로 읽어 넘김) · 표(알약 「표」) · 사용자 요소 · 변수는 Eval 이 스스로 봄 */
|
||||
* pill 로 읽어 넘김 · 인력 · 기계 · 유가 조건 찾기도) · 표(알약 「표」) · 다른 로직(서버가 받은 설계 값으로 셈 · port 「*」)
|
||||
* · 사용자 요소 · 변수는 Eval 이 스스로 봄 */
|
||||
export function queriesOf(
|
||||
lg: CfLogic,
|
||||
pill: (p: CfPort) => unknown = () => null,
|
||||
@@ -179,10 +183,25 @@ export function queriesOf(
|
||||
): { q: CfQuery; box: string; port: string }[] {
|
||||
return lg.boxes.flatMap((b) => {
|
||||
const inp = b.input;
|
||||
if (b.kind === "input" && inp?.mode === "logic" && inp.logic?.key) {
|
||||
const ref = inp.logic;
|
||||
const designs: Record<string, unknown> = {};
|
||||
for (const d of ref.designs) {
|
||||
const l = lg.links.find((x) => x.to.box === b.id && x.to.name === d.name);
|
||||
const v = l ? pill(l.from) : ref.examples?.[d.name];
|
||||
if (v !== null && v !== undefined) designs[d.name] = v;
|
||||
}
|
||||
const q: CfQuery = { id: `${b.id}*`, group: "기계", logic: { key: ref.key, designs } };
|
||||
return [{ q, box: b.id, port: "*" }];
|
||||
}
|
||||
if (b.kind !== "input" || !inp || inp.mode !== "group" || !inp.group) return [];
|
||||
const group = inp.group;
|
||||
const slots = inp.elements
|
||||
.filter((e) => group === "재료" && e.mode && e.mode !== "fixed")
|
||||
.filter(
|
||||
(e) =>
|
||||
(group === "재료" && e.mode && e.mode !== "fixed") ||
|
||||
(RULE_GROUPS.includes(group) && e.mode === "rule"),
|
||||
)
|
||||
.map((e) => {
|
||||
const port = elementPort(e);
|
||||
const q: CfQuery = {
|
||||
@@ -222,14 +241,35 @@ export async function fetchValues(
|
||||
pill?: (p: CfPort) => unknown,
|
||||
elementPort?: (e: CfElementRef) => string,
|
||||
): Promise<{ values: CfValues; errors: string[] }> {
|
||||
const asks = queriesOf(lg, pill, elementPort);
|
||||
if (!asks.length) return { values: {}, errors: [] };
|
||||
const all = queriesOf(lg, pill, elementPort);
|
||||
const asks = all.filter((a) => !a.q.logic);
|
||||
const values: CfValues = {};
|
||||
const errors: string[] = [];
|
||||
// 다른 로직 — 서버가 받은 설계 값으로 셈(그 안 조건 찾기 · 단가까지) → 출력 이름마다 값
|
||||
const logicAsks = all.filter((a) => a.q.logic);
|
||||
const logicDone = logicAsks.length
|
||||
? import("./M02_DF_Api").then(({ cfLogicValue }) =>
|
||||
Promise.all(
|
||||
logicAsks.map((a) =>
|
||||
cfLogicValue(a.q.logic!.key, a.q.logic!.designs).then(
|
||||
(got) => {
|
||||
values[a.box] = { ...(values[a.box] ?? {}), ...(got.outputs as CfValues[string]) };
|
||||
},
|
||||
(e: unknown) => errors.push(`${a.q.logic!.key} — ${String(e)}`),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Promise.resolve();
|
||||
if (!asks.length) {
|
||||
await logicDone;
|
||||
return { values, errors };
|
||||
}
|
||||
const answers = await ask(
|
||||
asks.map((a) => a.q),
|
||||
lg.userElements as never,
|
||||
);
|
||||
const values: CfValues = {};
|
||||
const errors: string[] = [];
|
||||
await logicDone;
|
||||
for (const a of asks) {
|
||||
const got = answers.find((x) => x.id === a.q.id);
|
||||
if (!got?.ok) errors.push(`${a.port}${got?.reason ? ` — ${got.reason}` : ""}`);
|
||||
|
||||
@@ -413,6 +413,11 @@ function otherLogic(
|
||||
): Record<string, CfValue> {
|
||||
const ref = box.input?.logic;
|
||||
const out: Record<string, CfValue> = {};
|
||||
// 서버가 받은 설계 값으로 셈한 출력(Entry_Detail 다른 로직 물음)이 있으면 그것 — 그 안 단가 · 조건 찾기까지 정본
|
||||
if (ref?.outputs.some((p) => given[p.name] !== undefined)) {
|
||||
for (const p of ref.outputs) out[p.name] = given[p.name] ?? null;
|
||||
return out;
|
||||
}
|
||||
const other = ref ? opts.logics?.[ref.key] : undefined;
|
||||
if (!other || (opts.depth ?? 0) > 8) {
|
||||
if (ref && opts.logics && !other) errors.push(tl("NoLogic", { box: box.name }));
|
||||
|
||||
@@ -208,6 +208,8 @@ export interface CfLogic {
|
||||
boxes: CfBox[];
|
||||
links: CfLink[];
|
||||
userElements: CfUserElement[];
|
||||
/** 원가 합계 끝수 — 없으면 기본(일위대가표 계금 1원 미만 버림) · null = 끝수 없음(값 그대로 · 옛 로직 변환) */
|
||||
totalRound?: CfRound | null;
|
||||
}
|
||||
|
||||
/** 알약 하나의 모습 */
|
||||
|
||||
@@ -70,7 +70,8 @@ export async function defaultTotalRound(): Promise<CfRound> {
|
||||
}
|
||||
|
||||
/** 끝수 적용 — 소수 자리 · 방법(부동소수 오차는 아주 작은 값으로 눌러 줌) */
|
||||
export function roundBy(r: CfRound, v: number): number {
|
||||
export function roundBy(r: CfRound | null | undefined, v: number): number {
|
||||
if (!r) return v;
|
||||
const k = 10 ** r.places;
|
||||
const x = v * k;
|
||||
const eps = Math.abs(x) * 1e-12 + 1e-9;
|
||||
@@ -84,7 +85,8 @@ export function roundBy(r: CfRound, v: number): number {
|
||||
}
|
||||
|
||||
/** 「0.1원 미만 버림」 꼴 쉬운 말 */
|
||||
export function roundText(r: CfRound): string {
|
||||
export function roundText(r: CfRound | null): string {
|
||||
if (!r) return "끝수 없음(값 그대로)";
|
||||
const step = r.places > 0 ? Number((10 ** -r.places).toFixed(r.places)) : 1;
|
||||
return `${step}원 미만 ${r.method}`;
|
||||
}
|
||||
@@ -108,14 +110,11 @@ let totalNow: CfRound = { places: 0, method: "버림" };
|
||||
void defaultTotalRound().then((r) => (totalNow = r));
|
||||
export const totalRoundNow = (): CfRound => totalNow;
|
||||
|
||||
/** 그 로직의 합계 끝수 — 비용 출력 상자에 끝수가 실려 있으면 그것(옛 로직을 옮긴 흐름은 자신의 끝수) ·
|
||||
* 없으면 기본(새 로직 = 일위대가표 계금 1원 미만 버림) */
|
||||
/** 그 로직의 합계 끝수 — 정본 = CfLogic.totalRound(옛 로직을 옮긴 흐름 = 자신의 끝수 · null = 끝수 없음) ·
|
||||
* 정하지 않았으면 fallback(새 로직 = 일위대가표 계금 1원 미만 버림) */
|
||||
export function totalRoundOf(
|
||||
lg: { boxes: { kind: string; output?: { cost: string; round?: CfRound } }[] },
|
||||
fallback: CfRound,
|
||||
): CfRound {
|
||||
const hit = lg.boxes.find(
|
||||
(b) => b.kind === "output" && b.output?.cost !== "값" && b.output?.round,
|
||||
);
|
||||
return hit?.output?.round ?? fallback;
|
||||
lg: { totalRound?: CfRound | null },
|
||||
fallback: CfRound = totalNow,
|
||||
): CfRound | null {
|
||||
return lg.totalRound === undefined ? fallback : lg.totalRound;
|
||||
}
|
||||
|
||||
@@ -321,7 +321,11 @@ def one_value(item: dict, local: list[dict] | None = None) -> dict:
|
||||
if group == "기계" and isinstance(ref, str) and MACHINE_SEP in ref:
|
||||
ref, _, field = ref.partition(MACHINE_SEP)
|
||||
try:
|
||||
if isinstance(item.get("slot"), dict): # 재료 자리(고정 · 선택 · 조건 찾기)
|
||||
if (
|
||||
isinstance(item.get("slot"), dict) and group in GROUP_RULE
|
||||
): # 인력 · 기계 · 유가 조건 찾기
|
||||
got = group_find(str(group), item["slot"], item.get("value"))
|
||||
elif isinstance(item.get("slot"), dict): # 재료 자리(고정 · 선택 · 조건 찾기)
|
||||
got = slot_price(item["slot"], item.get("value"), item.get("pick"))
|
||||
elif isinstance(ref, str) and _UKEY.match(ref):
|
||||
got = _user(ref, local or [])
|
||||
|
||||
Reference in New Issue
Block a user