Files
frontend/apps/sn/app/components/lib/utils/excelToJson.ts
T

212 lines
7.5 KiB
TypeScript

/**
* 📄 Excel to JSON Utility
* 엑셀 데이터 JSON 변환 유틸리티
*
* - 엑셀 파일 데이터 파싱 및 JSON 변환 기능
* - 날짜 및 시간 포맷팅 및 유효성 검사
* - Zod 스키마를 통한 데이터 타입 검증 및 안전성 보장
* - 수용가-맨홀 매칭결과, 시간패턴, 유량자료 등 데이터 변환
* - 강우자료 및 수용가 물사용량 데이터 처리
* - 다양한 날짜 형식(M/D/YYYY, YYYY-MM-DD 등) 지원
*/
export const TEST_UUID = '2d4b151d-1828-41e0-91d9-4cd28ea1cc7e'
import { z } from "zod";
// "YYYY-MM-DDTHH:MM:SS" 형식으로 포맷팅 하는 함수
function parseAndFormatDateTimeToISOCompatibleString(
dateInput: string,
timeInputForMDY: string | null,
inputType: 'YYYY-MM-DD_H:MM' | 'M/D/YYYY_H:MM' | 'M/D/YY_H:MM'
): string | null {
let year: number, month: number, day: number, hours: number, minutes: number;
let datePartStr: string;
let timePartStr: string;
if (inputType === 'YYYY-MM-DD_H:MM') {
const parts = String(dateInput).split(' ');
if (parts.length !== 2) {
console.warn(`Invalid dateTime format for ${inputType} (split space):`, dateInput);
return null;
}
datePartStr = parts[0] ?? ""; // "YYYY-MM-DD"
timePartStr = parts[1] ?? ""; // "H:MM"
const dateParts = datePartStr.split('-');
if (dateParts.length !== 3) {
console.warn(`Invalid date format for ${inputType} (datePart):`, datePartStr);
return null;
}
year = parseInt(dateParts[0] ?? '', 10);
month = parseInt(dateParts[1] ?? '', 10);
day = parseInt(dateParts[2] ?? '', 10);
} else if (inputType === 'M/D/YYYY_H:MM') {
datePartStr = String(dateInput); // "M/D/YYYY"
if (!timeInputForMDY) {
console.warn(`Time input is missing for ${inputType}:`, dateInput);
return null;
}
timePartStr = String(timeInputForMDY); // "H:MM"
const dateParts = datePartStr.split('/');
if (dateParts.length !== 3) {
console.warn(`Invalid date format for ${inputType} (datePart):`, datePartStr);
return null;
}
month = parseInt(dateParts[0] ?? '', 10);
day = parseInt(dateParts[1] ?? '', 10);
year = parseInt(dateParts[2] ?? '', 10);
} else if (inputType === 'M/D/YY_H:MM') { // "M/D/YY H:MM" 형식 처리
const parts = String(dateInput).split(' ');
if (parts.length !== 2) {
console.warn(`Invalid dateTime format for ${inputType} (split space):`, dateInput);
return null;
}
datePartStr = parts[0] ?? ""; // "M/D/YY"
timePartStr = parts[1] ?? ""; // "H:MM"
const dateParts = datePartStr.split('/');
if (dateParts.length !== 3) {
console.warn(`Invalid date format for ${inputType} (datePart):`, datePartStr);
return null;
}
month = parseInt(dateParts[0] ?? '', 10);
day = parseInt(dateParts[1] ?? '', 10);
year = parseInt(dateParts[2] ?? '', 10);
// YY to YYYY conversion (assuming 2000s)
if (year < 100) {
year += 2000;
}
} else {
console.warn("Invalid inputType for date parsing.");
return null;
}
// 시간 파싱 (H:MM) - 공통 로직
const timeParts = timePartStr.split(':');
if (timeParts.length !== 2) {
console.warn(`Invalid time format (timePart):`, timePartStr);
return null;
}
hours = parseInt(timeParts[0] ?? '', 10);
minutes = parseInt(timeParts[1] ?? '', 10);
if (isNaN(year) || isNaN(month) || isNaN(day) || isNaN(hours) || isNaN(minutes)) {
console.warn("Invalid parsed date or time numeric values:", { year, month, day, hours, minutes });
return null;
}
// "YYYY-MM-DD HH:MM:SS" 형식으로 포맷팅 - 공통 로직
const formattedYear = String(year).padStart(4, '0'); // 연도는 4자리로 보장 (M/D/YYYY의 경우)
const formattedMonth = String(month).padStart(2, '0');
const formattedDay = String(day).padStart(2, '0');
const formattedHours = String(hours).padStart(2, '0');
const formattedMinutes = String(minutes).padStart(2, '0');
return `${formattedYear}-${formattedMonth}-${formattedDay} ${formattedHours}:${formattedMinutes}:00`;
}
// 수용가-맨홀 매칭결과 (수정 필요)
export const MatchingResultZod = z.object({
anlsSn: z.number(), // 분석 일련번호
mhlMngNo: z.string(), // 맨홀 번호 (임시값이라 추후 수정 필요)
rcptrNo: z.string(), // 수용가 번호
});
export type MatchingResultZodType = z.infer<typeof MatchingResultZod>;
export const excelToMatchingResultData = (excelData: any) => {
const MatchingResultData: MatchingResultZodType[] = excelData.map((item: any) => {
return {
anlsSn: Number(item["NO"]),
mhlMngNo: String(item["FTR_IDN"]), // (임시값이라 추후 수정 필요)
rcptrNo: String(item["수용가"]),
};
}).filter((el: any) => el !== undefined);
return MatchingResultData;
};
// 시간패턴
export const timePatternZod = z.object({
hrId: z.number(), // 시간
rt: z.number().nullish(), // 비율
});
export type timePatternZodType = z.infer<typeof timePatternZod>;
export const excelToTimePatternData = (excelData: any) => {
const timePatternData: timePatternZodType[] = excelData.map((item: any) => {
return {
hrId: Number(item["시간"]),
rt: Number(item["비율"]),
};
}).filter((el: any) => el !== undefined);
return timePatternData;
};
// 유량자료
export const flowRateZod = z.object({
pplMngNo: z.string(), // 관로번호
mtrngDt: z.string(), // 시간
oilq: z.number().nullish(), // 유량(㎥/h)
});
export type flowRateZodType = z.infer<typeof flowRateZod>;
export const excelToFlowRateData = (excelData: any) => {
const flowRateData: flowRateZodType[] = excelData.map((item: any) => {
const dateTimeStr = String(item["시간"]); // "M/D/YY H:MM" 형식 (예: "2/23/25 0:10")
const formattedDateTime = parseAndFormatDateTimeToISOCompatibleString(dateTimeStr, null, 'M/D/YY_H:MM');
if (!formattedDateTime) return undefined;
return {
pplMngNo: String(item["관로번호"]),
mtrngDt: formattedDateTime,
oilq: Number(item["유량(㎥/h)"]),
};
}).filter((el: any) => el !== undefined);
return flowRateData;
};
// 수용가 물사용량
export const rcptrInfoZod = z.object({
rcptrNo: z.string(), // 수용가번호
useYm: z.string(), // 사용연월
usqty: z.number().nullish(), // 사용량(㎥)
});
export type rcptrInfoZodType = z.infer<typeof rcptrInfoZod>;
export const excelToRcptrInfoData = (excelData: any) => {
const flowRateData: rcptrInfoZodType[] = excelData.map((item: any) => {
return {
rcptrNo: String(item["수용가번호"]),
useYm: String(item["사용연월"]),
usqty: Number(item["사용량(㎥)"]),
};
}).filter((el: any) => el !== undefined);
return flowRateData;
};
// 강우자료 (수정 필요)
export const rainZod = z.object({
mtrngDt: z.string(), // 날짜 + 시간
amrf: z.number().nullish(), // 강우량(mm)
});
export type rainZodType = z.infer<typeof rainZod>;
export const excelToRainData = (excelData: any) => {
const rainData: rainZodType[] = excelData.map((item: any) => {
const datePartStr = String(item["날짜"]); // "M/D/YYYY" 형식 (예: "5/9/2021")
const timePartStr = String(item["시간"]); // "H:MM" 형식 (예: "8:00")
const formattedForRainZod = parseAndFormatDateTimeToISOCompatibleString(datePartStr, timePartStr, 'M/D/YYYY_H:MM');
if (!formattedForRainZod) return undefined;
return {
mtrngDt: formattedForRainZod,
amrf: Number(item["강우량(mm)"]),
};
}).filter((el: any) => el !== undefined);
return rainData;
};