- 엔진 = old_code B08_Quantity_Engine_EarthworkTable.py 복사(B08_Quantity_Engine_Earthwork.py) · 계산식 그대로 - 고친 곳 = 입력부(암 종류 없으면 암 지반 유형) · 줄 cut_rock_kind · 합계 리핑암 · 발파암 칸 - 길 = GET /quantity/earthwork — B06 횡단 설계 저장 단면적(확정 노선 · 없으면 최신) · 환산계수 = 유토곡선과 같은 mixed_conversion_factors · 아무것도 안 씀 - 화면 = 측점 · 거리 · 절토(토사 · 리핑암 · 발파암) · 측구터파기(토사 · 암) 각 단면적 · 입적 · 보정량 · 보정량계 · 성토 · 유용토 · 차인토량 · 누가토량 · 합계 줄 - 옛 시험 test_b08_earthwork_table.py 는 가져오는 경로만 새 엔진으로 · 새 시험 test_61_3_b08_earthwork_kinds.py Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UoSr2sqvYtBf1dtQdus88s
215 lines
7.7 KiB
TypeScript
215 lines
7.7 KiB
TypeScript
/* =============================================================================
|
|
* B08_Quantity_UI_Earthwork.ts
|
|
* 3탭 토적표(PLAN 61-3) — 줄 = 측점 · 열 = 옛 토적 엔진 출력 칸 그대로 · 맨 아래 합계 줄.
|
|
*
|
|
* GET …/quantity/earthwork (서버 단독 · 읽기만) — 평균단면적법 · 보정량 = 입적 × 다짐 환산계수
|
|
* 절토 암은 그 측점이 센 갈래(`cut_rock_kind`)대로 리핑암 · 발파암 열에 놓음.
|
|
* 표 모양은 1탭 집계표 것(`b08-summary__*`)을 다시 씀.
|
|
* ========================================================================== */
|
|
|
|
import { API_BASE_URL } from "@config/config_frontend";
|
|
import { stationLabel } from "../common_util/common_util_station";
|
|
import { stationInterval } from "./B08_Quantity_UI_Summary";
|
|
|
|
interface EarthworkRow {
|
|
chainage_m: number;
|
|
distance_m: number;
|
|
cut_soil_area_m2: number;
|
|
cut_soil_volume_m3: number;
|
|
cut_soil_adjusted_m3: number;
|
|
cut_rock_area_m2: number;
|
|
cut_rock_volume_m3: number;
|
|
cut_rock_adjusted_m3: number;
|
|
cut_rock_kind: string;
|
|
ditch_soil_area_m2: number;
|
|
ditch_soil_volume_m3: number;
|
|
ditch_soil_adjusted_m3: number;
|
|
ditch_rock_area_m2: number;
|
|
ditch_rock_volume_m3: number;
|
|
ditch_rock_adjusted_m3: number;
|
|
adjusted_total_m3: number;
|
|
fill_area_m2: number;
|
|
fill_volume_m3: number;
|
|
diverted_m3: number;
|
|
balance_m3: number;
|
|
cumulative_m3: number;
|
|
notes: string[];
|
|
}
|
|
|
|
interface EarthworkTable {
|
|
rows: EarthworkRow[];
|
|
totals: Record<string, number>;
|
|
conversion_factors: Record<string, { compacted: number }>;
|
|
station_count: number;
|
|
route_id: number;
|
|
confirmed: boolean;
|
|
}
|
|
|
|
const ROCKS = ["ripping_rock", "blasting_rock"] as const;
|
|
const GROUPS: Array<[string, number]> = [
|
|
["절토 토사", 3],
|
|
["절토 리핑암", 3],
|
|
["절토 발파암", 3],
|
|
["측구터파기 토사", 3],
|
|
["측구터파기 암", 3],
|
|
];
|
|
const TRIPLE = ["단면적", "입적", "보정량"];
|
|
|
|
const num = (value: number | undefined | null): string =>
|
|
value == null || !Number.isFinite(value) ? "" : value.toFixed(2);
|
|
|
|
function cell(tag: "th" | "td", text: string, span?: { row?: number; col?: number }) {
|
|
const node = document.createElement(tag);
|
|
node.textContent = text;
|
|
if (span?.row) node.rowSpan = span.row;
|
|
if (span?.col) node.colSpan = span.col;
|
|
return node;
|
|
}
|
|
|
|
function note(text: string): HTMLElement {
|
|
const p = document.createElement("p");
|
|
p.className = "ui-general-block__empty";
|
|
p.textContent = text;
|
|
return p;
|
|
}
|
|
|
|
function head(): HTMLTableSectionElement {
|
|
const top = document.createElement("tr");
|
|
top.append(cell("th", "측점", { row: 2 }), cell("th", "거리", { row: 2 }));
|
|
for (const [label, span] of GROUPS) top.append(cell("th", label, { col: span }));
|
|
top.append(
|
|
cell("th", "보정량계", { row: 2 }),
|
|
cell("th", "성토", { col: 2 }),
|
|
cell("th", "유용토", { row: 2 }),
|
|
cell("th", "차인토량", { row: 2 }),
|
|
cell("th", "누가토량", { row: 2 }),
|
|
);
|
|
top.firstElementChild?.classList.add("b08-summary__station");
|
|
const sub = document.createElement("tr");
|
|
for (const _ of GROUPS) sub.append(...TRIPLE.map((t) => cell("th", t)));
|
|
sub.append(cell("th", "단면적"), cell("th", "입적"));
|
|
const thead = document.createElement("thead");
|
|
thead.append(top, sub);
|
|
return thead;
|
|
}
|
|
|
|
/** 절토 암 세 칸 — 그 측점 갈래 열에만 · 다른 갈래 열은 빈 칸 */
|
|
function rockCells(row: EarthworkRow, kind: string): string[] {
|
|
return row.cut_rock_kind === kind
|
|
? [num(row.cut_rock_area_m2), num(row.cut_rock_volume_m3), num(row.cut_rock_adjusted_m3)]
|
|
: ["", "", ""];
|
|
}
|
|
|
|
function bodyRow(row: EarthworkRow, interval: number): HTMLTableRowElement {
|
|
const tr = document.createElement("tr");
|
|
tr.dataset.b08Earthwork = String(row.chainage_m);
|
|
const station = cell(
|
|
"th",
|
|
stationLabel(row.chainage_m, interval) + (row.notes.length ? " *" : ""),
|
|
);
|
|
station.className = "b08-summary__station";
|
|
if (row.notes.length) station.title = row.notes.join("\n");
|
|
const values = [
|
|
num(row.distance_m),
|
|
num(row.cut_soil_area_m2),
|
|
num(row.cut_soil_volume_m3),
|
|
num(row.cut_soil_adjusted_m3),
|
|
...ROCKS.flatMap((kind) => rockCells(row, kind)),
|
|
num(row.ditch_soil_area_m2),
|
|
num(row.ditch_soil_volume_m3),
|
|
num(row.ditch_soil_adjusted_m3),
|
|
num(row.ditch_rock_area_m2),
|
|
num(row.ditch_rock_volume_m3),
|
|
num(row.ditch_rock_adjusted_m3),
|
|
num(row.adjusted_total_m3),
|
|
num(row.fill_area_m2),
|
|
num(row.fill_volume_m3),
|
|
num(row.diverted_m3),
|
|
num(row.balance_m3),
|
|
num(row.cumulative_m3),
|
|
];
|
|
tr.append(station, ...values.map((v) => cell("td", v)));
|
|
return tr;
|
|
}
|
|
|
|
/** 합계 줄 — 단면적은 합이 뜻이 없어 비움(옛 엔진 `totals` 와 같음) · 누가토량 = 마지막 줄 */
|
|
function totalRow(table: EarthworkTable): HTMLTableRowElement {
|
|
const t = table.totals;
|
|
const last = table.rows[table.rows.length - 1];
|
|
const values = [
|
|
num(t.distance_m),
|
|
"",
|
|
num(t.cut_soil_volume_m3),
|
|
num(t.cut_soil_adjusted_m3),
|
|
...ROCKS.flatMap((kind) => [
|
|
"",
|
|
num(t[`cut_${kind}_volume_m3`]),
|
|
num(t[`cut_${kind}_adjusted_m3`]),
|
|
]),
|
|
"",
|
|
num(t.ditch_soil_volume_m3),
|
|
num(t.ditch_soil_adjusted_m3),
|
|
"",
|
|
num(t.ditch_rock_volume_m3),
|
|
num(t.ditch_rock_adjusted_m3),
|
|
num(t.adjusted_total_m3),
|
|
"",
|
|
num(t.fill_volume_m3),
|
|
num(t.diverted_m3),
|
|
num(t.balance_m3),
|
|
num(last?.cumulative_m3),
|
|
];
|
|
const tr = document.createElement("tr");
|
|
tr.className = "b08-earthwork__total";
|
|
tr.dataset.b08EarthworkTotal = "";
|
|
const label = cell("th", "합계");
|
|
label.className = "b08-summary__station";
|
|
tr.append(label, ...values.map((v) => cell("td", v)));
|
|
return tr;
|
|
}
|
|
|
|
export async function renderEarthwork(host: HTMLElement, projectId: string): Promise<void> {
|
|
host.replaceChildren(note("토적표를 만드는 중…"));
|
|
let table: EarthworkTable;
|
|
let interval = 20;
|
|
try {
|
|
const [response, gap] = await Promise.all([
|
|
fetch(`${API_BASE_URL}/projects/${projectId}/quantity/earthwork`, { credentials: "include" }),
|
|
stationInterval(projectId).catch(() => 20),
|
|
]);
|
|
const body = (await response.json().catch(() => null)) as
|
|
(EarthworkTable & { detail?: unknown }) | null;
|
|
if (!response.ok || !body) {
|
|
const detail = body?.detail;
|
|
throw new Error(typeof detail === "string" ? detail : `HTTP ${response.status}`);
|
|
}
|
|
table = body;
|
|
interval = gap;
|
|
} catch (error) {
|
|
host.replaceChildren(note(`토적표를 불러오지 못했습니다 — ${String(error)}`));
|
|
return;
|
|
}
|
|
if (!table.rows.length) {
|
|
host.replaceChildren(note("측점 단면적이 아직 없습니다 — 횡단 설계를 먼저 마치세요."));
|
|
return;
|
|
}
|
|
const c = (kind: string): string => num(table.conversion_factors[kind]?.compacted);
|
|
const info = note(
|
|
`평균단면적법 · 노선 ${table.route_id}${table.confirmed ? "(확정)" : "(확정 전 최신 노선)"} · ` +
|
|
`측점 ${table.station_count} · 다짐 환산계수 토사 ${c("soil")} · 리핑암 ${c("ripping_rock")} · ` +
|
|
`발파암 ${c("blasting_rock")} · 단위 ㎡ · ㎥ · * = 사유 있음(마우스를 올림)`,
|
|
);
|
|
const tbody = document.createElement("tbody");
|
|
tbody.append(...table.rows.map((row) => bodyRow(row, interval)));
|
|
const tfoot = document.createElement("tfoot");
|
|
tfoot.append(totalRow(table));
|
|
const el = document.createElement("table");
|
|
el.className = "ui-general-block__table b08-summary__table b08-earthwork__table";
|
|
el.dataset.b08Earthwork = "table";
|
|
el.append(head(), tbody, tfoot);
|
|
const wrap = document.createElement("div");
|
|
wrap.className = "ui-general-block__table-wrap b08-summary__wrap";
|
|
wrap.append(el);
|
|
host.replaceChildren(info, wrap);
|
|
}
|