- M01_LB_Calc.py — POST /api/m01/lb/calc {row, inputs, tables}: /calc 와 같은 셈 · 부를 때마다 새 사본에 표를 바꿔 끼움(전역 캐시 · 파일 안 건드림 · 저장 안 함)
- GET /api/m01/lb/table-users — 표 「용도.로직키」 + 로직 글 대조
- main.py 등록 한 줄(시스템 관리자 · 폴더째 지우면 이 줄도 지움)
- M01_LB_Api.ts — lbCalcLater(0.3초 · 최신만 · 쉬운 말) · tableUsers · fetchLbTable
- 시험 test_m01_lb_calc.py — 견본 표 덮어 값 바뀜 · 캐시 · 파일 그대로 · 표 없으면 /calc 와 같음 · 404 · 사용처 · 403
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmELpdXFDRZHhfbGuRhSmL
104 lines
4.2 KiB
TypeScript
104 lines
4.2 KiB
TypeScript
/* =============================================================================
|
|
* M01_LB_Api.ts
|
|
* 로직 만들기 판 Api — 고친 표로 예시값 계산(0.3초 · 최신만) · 표를 쓰는 로직(사용처) · 표 읽기
|
|
* 서버 = `M01_LB_Calc.py`(`/api/m01/lb/*`) · 모양은 `docs/raw/M01_쉬운단가/로직판_A_정함.md` 「맞출 모양」
|
|
* 시험: `resources/tester/test_m01_lb_calc.py`
|
|
* ========================================================================== */
|
|
|
|
import { API_BASE_URL } from "@config/config_frontend";
|
|
import { fetchTable, fetchTables } from "../M01_MasterData_Api_Fetch";
|
|
import { ApiError, type CalcAnswer, type LogicRow } from "../M01_MasterData_UI_Logic_Api";
|
|
import { safeReason } from "../M01_MasterData_UI_Logic_Text";
|
|
import { plainError } from "../EasyCalc/M01_EasyCalc_Api";
|
|
|
|
/** 표 키 → 표 사본(고친 값 포함 · 표 통째) — 서버가 메모리에서만 바꿔 끼움 */
|
|
export type LbTables = Record<string, unknown>;
|
|
|
|
/** 표를 쓰는 로직 한 줄 — `via` = 표 「용도」 에 적힘 · 로직 글에 표 키가 있음 · 둘 다 */
|
|
export interface LbTableUser {
|
|
key: string;
|
|
이름: string;
|
|
via: "용도" | "글" | "둘";
|
|
}
|
|
|
|
/** 표 한 벌 — `file` · `version` 은 표가 든 파일 */
|
|
export interface LbTable {
|
|
file: string;
|
|
version: string;
|
|
table: Record<string, unknown> & { 줄: Record<string, unknown>[] };
|
|
}
|
|
|
|
const CALC_WAIT_MS = 300;
|
|
const TABLE_GROUPS = ["소요량", "계수"]; // 표가 드는 그룹(계수 표가 소요량 파일에 든 것도 있음)
|
|
|
|
async function send<T>(path: string, body?: unknown): Promise<T> {
|
|
let response: Response;
|
|
try {
|
|
response = await fetch(`${API_BASE_URL}/m01/lb${path}`, {
|
|
method: body === undefined ? "GET" : "POST",
|
|
credentials: "include",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: body === undefined ? undefined : JSON.stringify(body),
|
|
});
|
|
} catch {
|
|
throw new ApiError(0, "");
|
|
}
|
|
const data = (await response.json().catch(() => ({}))) as { detail?: unknown } & T;
|
|
if (!response.ok) throw new ApiError(response.status, data.detail ?? response.statusText);
|
|
return data;
|
|
}
|
|
|
|
/** 로직이 받는 입력값만 — 모르는 이름을 보내면 서버가 멈춤 */
|
|
function inputsFor(row: LogicRow, inputs: Record<string, unknown>): Record<string, unknown> {
|
|
return Object.fromEntries(
|
|
(row.입력 ?? []).filter((i) => i.이름 in inputs).map((i) => [i.이름, inputs[i.이름]]),
|
|
);
|
|
}
|
|
|
|
/** 계산 멈춤 까닭 — 표 키(`QF000421`)가 섞인 글은 뭉뚱그림 */
|
|
function plainStop(reason: string): string {
|
|
const plain = safeReason(reason);
|
|
return /[A-Z]{2}\d{6}/.test(plain) ? safeReason("{}") : plain;
|
|
}
|
|
|
|
let calcSeq = 0;
|
|
let calcTimer: ReturnType<typeof setTimeout> | undefined;
|
|
|
|
/** 0.3초 뒤 고친 표로 서버 계산 · 최신 응답만 `onDone` · 멈춤 · 실패는 `plain` 쉬운 말 */
|
|
export function lbCalcLater(
|
|
row: LogicRow,
|
|
inputs: Record<string, unknown>,
|
|
tables: LbTables,
|
|
onDone: (ans: CalcAnswer | null, plain: string | null) => void,
|
|
): void {
|
|
const mine = ++calcSeq;
|
|
clearTimeout(calcTimer);
|
|
calcTimer = setTimeout(() => {
|
|
send<CalcAnswer>("/calc", { row, inputs: inputsFor(row, inputs), tables })
|
|
.then((ans) => {
|
|
if (mine === calcSeq) onDone(ans, ans.ok ? null : plainStop(ans.reason));
|
|
})
|
|
.catch((e: unknown) => {
|
|
if (mine === calcSeq) onDone(null, plainError(e));
|
|
});
|
|
}, CALC_WAIT_MS);
|
|
}
|
|
|
|
/** 사용처 — 그 표를 쓰는 로직(표 「용도」 + 로직 글 대조) */
|
|
export async function tableUsers(key: string): Promise<LbTableUser[]> {
|
|
const got = await send<{ users: LbTableUser[] }>(
|
|
`/table-users?${new URLSearchParams({ key }).toString()}`,
|
|
);
|
|
return got.users;
|
|
}
|
|
|
|
/** 표 키 하나로 표 통째 — 든 파일을 찾아(소요량 · 계수) Api_Fetch 의 표 읽기로 */
|
|
export async function fetchLbTable(key: string): Promise<LbTable> {
|
|
for (const group of TABLE_GROUPS) {
|
|
const page = await fetchTables(group, "", "", key, 1, 5);
|
|
const head = page.tables.find((t) => t.키 === key);
|
|
if (head) return { file: head.file, ...(await fetchTable(head.file, key)) };
|
|
}
|
|
throw new ApiError(404, `없는 표 「${key}」`);
|
|
}
|