Files
Aislo/B08_Quantity/B08_Quantity_Summary_Data.ts
T
eomsangdonandClaude Sonnet 5.5 c3af85113c feat(B08): 탭 구조물 · 토공 · 수리 + 구조물 보조 넷 · 구조물도 탭 지움 · 보조 탭 위 간격 8px(95-1 · 95-2)
구조물도 모달(집계표 행 누름 · 그 측점 조합 전부 · 복사본 먼저) · 공용 표 onRowClick opt-in(95-3) ·
수리 상세 폭 제한 풂 · 카드 높이 화면 따라 · @container(95-4). 화면 검증 전.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W4uKaV4ZBhsJN4GHG7yRj4
2026-10-08 13:46:09 +09:00

128 lines
4.7 KiB
TypeScript

/* =============================================================================
* B08_Quantity_Summary_Data.ts
* 구조물 집계표의 줄 · 열 고르기(화면 없음 · PLAN 61-1).
*
* 줄 = 구조물 · 배관이 놓인 측점(같은 측점은 한 줄 · 오름차순)
* 열 = 저장된 구조물 조합(본체 `summary_item` + 그룹 `group_items`) — 같은 층 · 주인 · 키는 한 열
*
* 읽기만 — B05/B06 저장 자료(structures.json · pipe_points.json)를 고치지 않는다.
* 런타임 import 는 `common_util_summary_item` 하나 — 시험이 두 파일을 이어 붙여 돌림.
* ========================================================================== */
import {
readGroupItems,
readSummaryItem,
type SummaryItem,
} from "../common_util/common_util_summary_item";
/** 구조물 · 관 지점 한 건 — 필요한 칸만. 구간형은 기준점(`chainage_m`) · 없으면 시점. */
export interface PlacedItem {
/** 복사본 폴더를 찾는 구조물 id(관 지점은 `pipe@측점`) — 없으면 복사본 없이 원본 */
structure_id?: string | null;
chainage_m?: number | null;
start_m?: number | null;
summary_item?: unknown;
group_items?: unknown;
}
/** M02 조합 정보에서 읽은 구분 · 이름 — 못 찾으면 null(저장된 이름으로 보임). */
export interface ComboLabel {
group: string;
name: string;
}
export interface ComboColumn {
/** `{층}|{주인}|{키}` — 항목 고르기 `rowId` 와 같은 꼴 */
id: string;
item: SummaryItem;
group: string;
name: string;
/** M02 에 그 조합이 없음 */
missing: boolean;
}
export const comboId = (item: SummaryItem): string =>
`${item.layer}|${item.owner ?? ""}|${item.key}`;
function stationOf(thing: PlacedItem): number | null {
const value = thing.chainage_m ?? thing.start_m;
return typeof value === "number" && Number.isFinite(value) ? value : null;
}
/** 놓인 측점 — 1cm 안이면 같은 측점 · 오름차순. */
export function summaryStations(things: readonly PlacedItem[]): number[] {
const seen = new Map<number, number>();
for (const thing of things) {
const station = stationOf(thing);
if (station !== null) seen.set(Math.round(station * 100), station);
}
return [...seen.values()].sort((a, b) => a - b);
}
/** 저장된 조합 열 — 구분 · 이름 차례(같은 구분은 붙어 머리 칸을 합침). */
export function summaryColumns(
things: readonly PlacedItem[],
describe: (item: SummaryItem) => ComboLabel | null,
): ComboColumn[] {
const found = new Map<string, SummaryItem>();
for (const thing of things) {
const own = readSummaryItem(thing.summary_item);
for (const item of [...(own ? [own] : []), ...Object.values(readGroupItems(thing.group_items))])
if (!found.has(comboId(item))) found.set(comboId(item), item);
}
const columns = [...found].map(([id, item]): ComboColumn => {
const label = describe(item);
return label
? { id, item, ...label, missing: false }
: { id, item, group: "", name: item.name || item.key, missing: true };
});
return columns.sort(
(a, b) =>
a.group.localeCompare(b.group, "ko") ||
a.name.localeCompare(b.name, "ko") ||
a.id.localeCompare(b.id),
);
}
/** 한 측점에 놓인 조합 한 건 — 어느 구조물 것인지(복사본 찾기용)까지. */
export interface StationCombo {
id: string;
item: SummaryItem;
structureId: string | null;
}
/** 측점(1cm 안 같은 측점)에 놓인 조합들 — 구조물마다 본체 → 그룹 차례 · 같은 구조물의 같은 조합은 한 건. */
export function combosAt(things: readonly PlacedItem[], station: number): StationCombo[] {
const seen = new Set<string>();
const found: StationCombo[] = [];
for (const thing of things) {
const at = stationOf(thing);
if (at === null || Math.round(at * 100) !== Math.round(station * 100)) continue;
const own = readSummaryItem(thing.summary_item);
for (const item of [
...(own ? [own] : []),
...Object.values(readGroupItems(thing.group_items)),
]) {
const structureId = thing.structure_id ?? null;
const key = `${structureId}
${comboId(item)}`;
if (!seen.has(key)) {
seen.add(key);
found.push({ id: comboId(item), item, structureId });
}
}
}
return found;
}
/** 머리 첫 줄 — 이어진 같은 구분을 한 칸으로 `[구분, 열 수]`. */
export function groupSpans(columns: readonly ComboColumn[]): Array<[string, number]> {
const spans: Array<[string, number]> = [];
for (const column of columns) {
const last = spans[spans.length - 1];
if (last && last[0] === column.group) last[1] += 1;
else spans.push([column.group, 1]);
}
return spans;
}