Files
Aislo/B08_Quantity/B08_Quantity_Milestone_Calc.ts
T
eomsangdonandClaude Opus 5.5 2afb1af1ca feat(B08): 거리이정표 등록 모달 2열 · 소재지 다음 지도 · 연결선 모달 길찾기 · 도로 구분 갈래 열(244-13)
- 등록 모달 한 벌(추가 · 상자 고침 · 삭제) — 분류(기준위치 · 거래처) · 항목 고르기 · 거래처 이름 · 목적지 2열 · 큰 창
- 소재지 = 주소 찾기 · 다음 지도 누름 · 표지 끌기(직접 입력 없음) · 좌표 → VWorld 주소 서버 길
- 다이어그램 [기준점 +] 없앰 · 연결선 누름 = 도로 구분 · 거리 · 다음 지도 경로 · 끝점 끌기 = 다시 길찾기
- 시점 → 중점 = 손 입력만 · 자동 그림 = 거래처 쪽 구간
- 도로 구분 = 기준점 거침 2차 · 1차 · 비포장 · 그 밖 포장 · 비포장(파이썬 · TS 짝) · 표 열 기준점 따라

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz
2026-10-10 23:36:39 +09:00

152 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =============================================================================
* B08_Quantity_Milestone_Calc.ts
* B08 거리이정표 표 합(PLAN 244-1) — 화면 없음 · 저장 안 함 · import 없음(시험이 node 로 돌림).
*
* ⚠ 짝 = `B08_Quantity_Milestone_Calc.py`(같은 식) — 한쪽을 고치면 다른 쪽도 ·
* 거울 시험 `resources/tester/test_244_1_b08_milestone.py`.
* 조작 중은 이 파일이 바로 셈 · [저장] · 읽기 때 서버가 파이썬 짝으로 다시 셈.
*
* 표 한 줄 = 경로(업체) 구간 + (기준점 거침이면 기준점 → 시점 공통 구간) + (목적지 = 중점이면
* 시점 → 중점 = 중점 측점 km) · 도로 구분 셋마다 합 · 포장 = 2차 + 1차 · 합계 = 포장 + 비포장 ·
* 끝수 = 소수 셋째 자리(반올림)
* 구간 km = 사용자 값 · 없으면 [길찾기] 자동 값(244-7) · 도로 구분 안 고른 구간 = 합에 안 넣고
* pick = 참(화면 「도로 구분 고르기」)
* 도로 구분(244-13) = 기준점 거쳐 시점 들어가는 구간(거래처 → 기준점 · 기준점 → 시점) = 2차포장 · 1차포장 ·
* 비포장 · 그 밖(거래처 → 시점 · 시점 → 중점) = 포장 · 비포장 · 포장 = 2차 + 1차 + 포장
* ========================================================================== */
export const ROADS = ["2차포장", "1차포장", "포장", "비포장"] as const;
export type Road = (typeof ROADS)[number];
/** 구간 도로 구분 고르기 — 기준점 거침 = 2차 · 1차 · 비포장 · 아니면 포장 · 비포장(244-13) */
export const roadsFor = (viaBase: boolean): Road[] =>
viaBase ? ["2차포장", "1차포장", "비포장"] : ["포장", "비포장"];
/** 8방 — 기준점(없으면 공사지) 기준 지도 방위 */
export const DIRS = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"] as const;
export type Dir = (typeof DIRS)[number];
export type Dest = "start" | "mid";
export interface Leg {
/** null = 사용자가 안 고름(자동 판정 없음 · 244-7) */
road: Road | null;
/** 사용자 값 — 있으면 우선 */
km: number | null;
/** [길찾기] 자동 값(km 만 저장 · 경로 좌표 · 응답은 저장 안 함) */
auto_km?: number | null;
}
export interface MilestoneRow {
item: string;
dest: Dest;
note: string;
}
export interface MilestoneRoute {
id: string;
/** 업체 이름 = 하위 탭 이름 */
name: string;
address: string;
/** 기준점 거침(기준점 없으면 무시 · 시점으로 바로) */
via_base: boolean;
dir: Dir | null;
lon?: number | null;
lat?: number | null;
/** 업체 → 기준점(거침) · 시점 구간 */
legs: Leg[];
rows: MilestoneRow[];
}
export interface MilestoneData {
site: { mid_station_m: number | null; mid_road: Road };
base: {
enabled: boolean;
name: string;
address: string;
dir: Dir | null;
lon?: number | null;
lat?: number | null;
/** 기준점 → 시점 공통 구간 */
legs: Leg[];
};
routes: MilestoneRoute[];
}
export interface MilestoneTableRow {
route: string;
index: number;
item: string;
company: string;
dest: Dest;
s2: number;
s1: number;
pv: number;
un: number;
paved: number;
unpaved: number;
total: number;
note: string;
/** 도로 구분 안 고른 구간이 있음 */
pick: boolean;
}
type Key = "s2" | "s1" | "pv" | "un";
const KEYS: Record<string, Key> = { "2차포장": "s2", "1차포장": "s1", 포장: "pv", 비포장: "un" };
/** 소수 셋째 자리 반올림(파이썬 짝 floor(x × 1000 + 0.5) 과 같음) */
export const r3 = (value: number): number => Math.floor(value * 1000 + 0.5) / 1000;
/** 구간 km — 사용자 값 우선 · 없으면 자동 값 */
export const legKm = (leg: Leg): number | null => leg.km ?? leg.auto_km ?? null;
/** 도로 구분마다 더함 — 안 고른 구간이 있으면 참 */
function addLegs(sums: Record<Key, number>, legs: Leg[] | undefined): boolean {
let pick = false;
for (const leg of legs ?? []) {
const key = leg.road ? KEYS[leg.road] : undefined;
if (key) sums[key] += Number(legKm(leg) ?? 0);
else pick = true;
}
return pick;
}
/** 시점 → 중점 km — 사용자 측점(없으면 기본 = 노선 ÷ 2) ÷ 1000 */
export function midKm(data: MilestoneData, midDefaultM: number | null): number {
const station = data.site?.mid_station_m ?? midDefaultM;
return Number(station ?? 0) / 1000;
}
/** 표 줄 — 경로 차례 · 경로 안 줄 차례 */
export function computeMilestone(
data: MilestoneData,
midDefaultM: number | null,
): MilestoneTableRow[] {
const base = data.base;
const mid: Leg = { road: data.site?.mid_road ?? "비포장", km: midKm(data, midDefaultM) };
const out: MilestoneTableRow[] = [];
for (const route of data.routes ?? []) {
(route.rows ?? []).forEach((row, index) => {
const sums = { s2: 0, s1: 0, pv: 0, un: 0 };
let pick = addLegs(sums, route.legs);
if (base?.enabled && route.via_base !== false) pick = addLegs(sums, base.legs) || pick;
if (row.dest === "mid") addLegs(sums, [mid]);
const paved = sums.s2 + sums.s1 + sums.pv;
out.push({
route: route.id,
index,
item: row.item ?? "",
company: route.name ?? "",
dest: row.dest ?? "start",
s2: r3(sums.s2),
s1: r3(sums.s1),
pv: r3(sums.pv),
un: r3(sums.un),
paved: r3(paved),
unpaved: r3(sums.un),
total: r3(paved + sums.un),
note: row.note ?? "",
pick,
});
});
}
return out;
}