Skip to content

Commit 7fcc082

Browse files
committed
feat(ingest): add landslide forecast source
1 parent 79d995d commit 7fcc082

File tree

5 files changed

+388
-1
lines changed

5 files changed

+388
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
- [x] 기상 특보
2525
- [x] 대기질 (PM, O3)
2626
- [x] 교통 돌발정보
27-
- [ ] 산사태경보
27+
- [x] 산사태 예보
2828
- [x] 사이버위기경보
2929
- [x] 테러경보
3030
- [x] 산불경보

src/modules/events/domain/event.enums.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,5 @@ export enum EventSources {
6969
KpxPowerSupply = 13,
7070
FloodAlert = 14,
7171
ForestFireWarning = 15,
72+
LandslideForecast = 16,
7273
}

src/modules/ingest/app/source.list.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { KmaMicroEarthquakeSource } from './sources/kma-micro-earthquake.source'
1111
import { KmaPewsEarthquakeSource } from './sources/kma-pews-earthquake.source';
1212
import { KmaWeatherWarningSource } from './sources/kma-weather-warning.source';
1313
import { KpxPowerSupplySource } from './sources/kpx-power-supply.source';
14+
import { LandslideForecastSource } from './sources/landslide-forecast.source';
1415
import { NcscCyberCrisisSource } from './sources/ncsc-cyber-crisis.source';
1516
import { NctcTerrorAlertSource } from './sources/nctc-terror-alert.source';
1617
import { NfdsFireDispatchSource } from './sources/nfds-fire-dispatch.source';
@@ -30,6 +31,7 @@ export function buildSourceList(
3031
new NfdsFireDispatchSource(regionRepository),
3132
new ForestFireInfoSource(),
3233
new ForestFireWarningSource(),
34+
new LandslideForecastSource(),
3335
new UticTrafficIncidentSource(),
3436
new YnaNewsSource(llmLabelClassifier),
3537
new AirkoreaPmWarningSource(regionRepository),
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { afterEach, describe, expect, it, vi } from 'vitest';
2+
import { EventLevels } from '@/modules/events/domain/event.enums';
3+
import { LandslideForecastSource } from './landslide-forecast.source';
4+
5+
describe('LandslideForecastSource', () => {
6+
afterEach(() => {
7+
vi.useRealTimers();
8+
vi.unstubAllGlobals();
9+
});
10+
11+
it('should parse forecast table and emit issued alerts only', async () => {
12+
vi.useFakeTimers();
13+
vi.setSystemTime(new Date('2025-10-25T06:30:00.000Z'));
14+
15+
const html = `
16+
<table id="fctTbl">
17+
<tbody>
18+
<tr>
19+
<td>
20+
<a href="javascript:fn_moveAlertView('47940', '', '12310');">경상북도 울릉군</a>
21+
</td>
22+
<td>
23+
<a href="javascript:fn_moveAlertView('47940','','12310');" title="주의보">주의보</a>
24+
</td>
25+
<td>
26+
<a href="javascript:fn_moveAlertView('47940','','12310');" title="2025-10-25 13:19">2025-10-25<br>13:19</a>
27+
</td>
28+
<td>
29+
<a href="javascript:fn_moveAlertView('47940','','12310');" title="2025-10-25 14:10">2025-10-25<br>14:10</a>
30+
</td>
31+
<td>
32+
<a href="javascript:fn_moveAlertView('47940','','12310');" title="-">-</a>
33+
</td>
34+
<td>
35+
<a href="javascript:fn_moveAlertView('47940','','12310');" title="작성자">작성자</a>
36+
</td>
37+
<td>발령</td>
38+
</tr>
39+
<tr>
40+
<td>
41+
<a href="javascript:fn_moveAlertView('11110', '', '99999');">서울특별시 종로구</a>
42+
</td>
43+
<td>
44+
<a href="javascript:fn_moveAlertView('11110','','99999');" title="경보">경보</a>
45+
</td>
46+
<td>
47+
<a href="javascript:fn_moveAlertView('11110','','99999');" title="2025-10-25 11:00">2025-10-25<br>11:00</a>
48+
</td>
49+
<td>
50+
<a href="javascript:fn_moveAlertView('11110','','99999');" title="2025-10-25 11:10">2025-10-25<br>11:10</a>
51+
</td>
52+
<td>
53+
<a href="javascript:fn_moveAlertView('11110','','99999');" title="2025-10-25 12:00">2025-10-25<br>12:00</a>
54+
</td>
55+
<td>
56+
<a href="javascript:fn_moveAlertView('11110','','99999');" title="작성자">작성자</a>
57+
</td>
58+
<td>해제</td>
59+
</tr>
60+
</tbody>
61+
</table>
62+
`;
63+
64+
const fetchMock = vi.fn().mockImplementation(() => Promise.resolve(new Response(html, { status: 200 })));
65+
vi.stubGlobal('fetch', fetchMock);
66+
67+
const source = new LandslideForecastSource();
68+
const result = await source.run(null);
69+
70+
expect(result.events).toHaveLength(1);
71+
expect(result.events[0].title).toBe('경상북도 울릉군 산사태 주의보 발령');
72+
expect(result.events[0].level).toBe(EventLevels.Minor);
73+
expect(result.events[0].regionCodes).toEqual(['4794000000']);
74+
expect(result.events[0].occurredAt).toBe('2025-10-25T04:19:00.000Z');
75+
});
76+
});

0 commit comments

Comments
 (0)