feat: 침입수 분석, RDII 분석 API 연동 수정

This commit is contained in:
swcho
2025-05-29 14:48:43 +09:00
parent 8700dea49a
commit 23992f5d37
3 changed files with 62 additions and 53 deletions
+43 -31
View File
@@ -1,11 +1,11 @@
export const TEST_UUID= '2d4b151d-1828-41e0-91d9-4cd28ea1cc7e'
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'
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;
@@ -31,8 +31,8 @@ function parseAndFormatDateTimeToISOCompatibleString(
} 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;
console.warn(`Time input is missing for ${inputType}:`, dateInput);
return null;
}
timePartStr = String(timeInputForMDY); // "H:MM"
@@ -41,10 +41,31 @@ function parseAndFormatDateTimeToISOCompatibleString(
console.warn(`Invalid date format for ${inputType} (datePart):`, datePartStr);
return null;
}
// Note: Order for M/D/YYYY is month, day, year
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;
@@ -64,31 +85,30 @@ function parseAndFormatDateTimeToISOCompatibleString(
return null;
}
// "YYYY-MM-DDTHH:MM:SS" 형식으로 포맷팅 - 공통 로직
// "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');
// 초는 00으로 고정, 'T'를 사용하여 Date 생성자 호환성 확보
return `${formattedYear}-${formattedMonth}-${formattedDay} ${formattedHours}:${formattedMinutes}:00`;
}
// 수용가-맨홀 매칭결과 (수정 필요)
export const MatchingResultZod = z.object({
anls_sn: z.number(), // 분석 일련번호
mhl_mng_no: z.string(), // 맨홀 번호 (임시값이라 추후 수정 필요)
rcptr_no: z.string(), // 수용가 번호
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 {
anls_sn: Number(item["NO"]),
mhl_mng_no: String(item["FTR_IDN"]), // (임시값이라 추후 수정 필요)
rcptr_no: String(item["수용가"]),
anlsSn: Number(item["NO"]),
mhlMngNo: String(item["FTR_IDN"]), // (임시값이라 추후 수정 필요)
rcptrNo: String(item["수용가"]),
};
}).filter((el: any) => el !== undefined);
return MatchingResultData;
@@ -97,7 +117,7 @@ export const excelToMatchingResultData = (excelData: any) => {
// 시간패턴
export const timePatternZod = z.object({
hr_id: z.number(), // 시간
hrId: z.number(), // 시간
rt: z.number().nullish(), // 비율
});
export type timePatternZodType = z.infer<typeof timePatternZod>;
@@ -105,7 +125,7 @@ export const excelToTimePatternData = (excelData: any) => {
const timePatternData: timePatternZodType[] = excelData.map((item: any) => {
return {
hr_id: Number(item["시간"]),
hrId: Number(item["시간"]),
rt: Number(item["비율"]),
};
}).filter((el: any) => el !== undefined);
@@ -115,30 +135,22 @@ export const excelToTimePatternData = (excelData: any) => {
// 유량자료
export const flowRateZod = z.object({
ppl_mng_no: z.string(), // 관로번호
mtrng_dt: z.string(), // 시간
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["시간"]); // "YYYY-MM-DD H:MM" 형식 (예: "2025-02-23 8:50")
const dateTimeStr = String(item["시간"]); // "M/D/YY H:MM" 형식 (예: "2/23/25 0:10")
const formattedDateTime = parseAndFormatDateTimeToISOCompatibleString(dateTimeStr, null, 'YYYY-MM-DD_H:MM');
const formattedDateTime = parseAndFormatDateTimeToISOCompatibleString(dateTimeStr, null, 'M/D/YY_H:MM');
if (!formattedDateTime) return undefined;
const dateObject = new Date(formattedDateTime);
const mtrngDtValue = !isNaN(dateObject.getTime()) ? dateObject.toISOString() : null;
if (!mtrngDtValue) {
console.warn("Failed to create Date object for flowRateData:", formattedDateTime);
return undefined;
}
return {
ppl_mng_no: String(item["관로번호"]),
mtrng_dt: mtrngDtValue,
pplMngNo: String(item["관로번호"]),
mtrngDt: formattedDateTime,
oilq: Number(item["유량(㎥/h)"]),
};
}).filter((el: any) => el !== undefined);
@@ -148,7 +160,7 @@ export const excelToFlowRateData = (excelData: any) => {
// 강우자료 (수정 필요)
export const rainZod = z.object({
mtrng_dt: z.string(), // 날짜 + 시간
mtrngDt: z.string(), // 날짜 + 시간
amrf: z.number().nullish(), // 강우량(mm)
});
export type rainZodType = z.infer<typeof rainZod>;
@@ -162,7 +174,7 @@ export const excelToRainData = (excelData: any) => {
if (!formattedForRainZod) return undefined;
return {
mtrng_dt: formattedForRainZod,
mtrngDt: formattedForRainZod,
amrf: Number(item["강우량(mm)"]),
};
}).filter((el: any) => el !== undefined);
+10 -13
View File
@@ -121,6 +121,7 @@ const IrSetting = () => {
console.log("Submitting combined analysis data to backend:", analysisData);
// 추후 API 엔드포인트에 맞게 URL 수정 필요
const data = await postData('v1/inftwtAnls', analysisData);
console.log("Response from backend:", data);
if (data.ok) {
alert("분석 데이터가 성공적으로 제출되었습니다.");
resetAllForms(); // 성공 시 폼 초기화 함수 호출
@@ -149,22 +150,19 @@ const IrSetting = () => {
return;
}
// filePickers 구성 시 uploadedFilesData의 file 객체 사용
const filePickers = [
// filePicked 유틸리티가 File 객체를 직접 받는지, ArrayBuffer 등을 받는지 확인 필요
// 여기서는 File 객체를 받고, 내부적으로 ArrayBuffer로 변환하여 parser에 전달한다고 가정
{ file: file2, parser: excelToMatchingResultData },
{ file: file3, parser: excelToTimePatternData },
{ file: file4, parser: excelToFlowRateData }
// 오수관망도 파일은 파싱 대상이 아닐 수 있으므로, 필요에 따라 추가/제외
];
try {
const parsedResults = await Promise.all(
filePickers.map(async ({ file, parser }) => {
if (!file || !file.file) return null; // fileInfo 객체 및 file 객체 null 체크
if (!file || !file.file) return null;
// filePicked 함수에 File 객체와 headerNum 전달
const jsonData = await filePicked(file.file, 0);
if (jsonData === undefined) {
@@ -173,12 +171,11 @@ const IrSetting = () => {
fileName: file.file.name,
atflCd: file.atflCd,
atflId: file.atflId,
parsedData: null // 또는 적절한 기본값
parsedData: null
};
}
// jsonData를 각 parser 함수로 처리
// parser 함수가 filePicked가 반환하는 JSON 형식을 직접 처리한다고 가정
const parsedDataBySpecificFunction = parser(jsonData);
return {
@@ -197,12 +194,12 @@ const IrSetting = () => {
// 새로운 JSON 데이터 생성
const combinedAnalysisData = {
enoCd: select1, // 환경청코드
ctpvCd: select2, // 시도코드
sggCd: select3, // 시군구코드
swageTmplntCd: select4, // 하수처리장코드
swtrmtZoneCd: select5, // 하수처리구역코드
aplcnYmd: new Date(selectedDate).toISOString().substring(0, 7).replace("-", ""), // 적용연월
enoCd: "select1", // 환경청코드
ctpvCd: "select2", // 시도코드
sggCd: "select3", // 시군구코드
swageTmplntCd: "select4", // 하수처리장코드
swtrmtZoneCd: "select5", // 하수처리구역코드
aplcnYm: new Date(selectedDate).toISOString().substring(0, 7).replace("-", ""), // 적용연월
dtrwSwtcRt: parseFloat(input1) || null, // 오수전환율 (숫자로 변환)
anlsInfo: input2, // 분석정보
atflCd: file1?.atflCd, // 첨부파일CD (오수관망도)
@@ -235,7 +232,7 @@ const IrSetting = () => {
form={form}
onSubmitEvent={() => { }}
graphBtn={false}
searchBox={{ title: '분석 대장지역 선택' }} >
searchBox={{ title: '분석 대상지역 선택' }} >
<div className="grid gap-4">
<FormSection name='suyeongaPlace' form={form} titleNm='▷ 오수관망도'>
<FileUploadForm
+9 -9
View File
@@ -205,19 +205,19 @@ export default function RTKForm() {
// 새로운 JSON 데이터 생성
const combinedAnalysisData = {
enoCd: "", // 환경청코드
enoCd: "환경청코드", // 환경청코드
aplcnYm: new Date(formData.date).toISOString().substring(0, 7).replace("-", ""), // 적용연월 (YYYYMM 형식)
ctpvCd: "", // 시도코드
sggCd: "", // 시군구코드
swageTmplntCd: "", // 하수처리장코드
swtrmtZoneCd: "", // 하수처리구역코드
ctpvCd: "시도코드", // 시도코드
sggCd: "시군구코드", // 시군구코드
swageTmplntCd: "하수처리장코드", // 하수처리장코드
swtrmtZoneCd: "하수처리구역코드", // 하수처리구역코드
atflCd: file1?.atflCd, // 첨부파일CD (오수관망도)
atflId: file1?.atflId, // 첨부파일ID (오수관망도)
atflInpNm: file1?.file?.name || "", // 오수관망도 파일명
atflMpngNm: file2?.file?.name || "", // 수용가-맨홀 매칭결과 파일명
atflRainNm: file3?.file?.name || "", // 강우자료 파일명
cntbArea: parseFloat("rdiiArea") || null, // RDII 기여면적(ha)
srcNm: "", // 출처이름 (RTK계수 출처)
cntbArea: parseFloat(formData.rdiiArea) || null, // RDII 기여면적(ha)
srcNm: formData.rtkSource, // 출처이름 (RTK계수 출처)
shortPrdR: parseFloat(formData.unitFactors[0]?.R !== undefined ? formData.unitFactors[0]?.R : "0") || null, // 단기간R
shortPrdT: parseFloat(formData.unitFactors[0]?.T !== undefined ? formData.unitFactors[0]?.T : "0") || null, // 단기간T
@@ -244,7 +244,7 @@ export default function RTKForm() {
longTermBgngDpth: parseFloat(formData.initialDepths[2]?.Do !== undefined ? formData.initialDepths[2]?.Do : "0") || null, // 장기간시작깊이
dtrwSwtcRt: parseFloat(formData.rate !== undefined ? formData.rate : "0") || null, // 오수전환율
anlsInfo: "", // 분석정보
anlsInfo: formData.analysisInfo, // 분석정보
tbSnRcptrMhlMpng: matchingResultData, // 수용가-맨홀 매칭결과
tbSnRainData: rainData, // 강우자료
@@ -298,7 +298,7 @@ export default function RTKForm() {
}
return (
<BaseLayout searchBox={{ title: '분석 대장지역 선택' }} titleName={{ mainTitle: 'RDII 분석', firstCardTitle: 'RDII 분석자료 입력', secondCardTitle: '실측자료 등록' }} form={form} onSubmitEvent={() => { }} graphBtn={false} >
<BaseLayout searchBox={{ title: '분석 대상지역 선택' }} titleName={{ mainTitle: 'RDII 분석', firstCardTitle: 'RDII 분석자료 입력', secondCardTitle: '실측자료 등록' }} form={form} onSubmitEvent={() => { }} graphBtn={false} >
{[<div className="space-y-[1.2rem] overflow-x-auto" key="form-section">
<div className="grid grid-cols-2 gap-4">
<FormSection name="rtkSource" form={form} titleNm="○ RTK계수 출처">