alwaysgood commited on
Commit
758679e
·
verified ·
1 Parent(s): af706d0

Delete internal_api.py

Browse files
Files changed (1) hide show
  1. internal_api.py +0 -754
internal_api.py DELETED
@@ -1,754 +0,0 @@
1
- """
2
- Extended Internal API endpoints for automation and demo control
3
- GitHub Actions 및 시연 제어를 위한 확장된 내부 API
4
- """
5
-
6
- from fastapi import FastAPI, HTTPException, Header, Request, BackgroundTasks
7
- from datetime import datetime, timedelta
8
- import os
9
- import asyncio
10
- import json
11
- import uuid
12
- import numpy as np
13
- from typing import Optional, List, Dict, Any
14
- import logging
15
- from config import INTERNAL_API_KEY
16
-
17
- logger = logging.getLogger(__name__)
18
-
19
- # 환경변수에서 내부 API 키 가져오기
20
- INTERNAL_API_KEY = os.getenv("INTERNAL_API_KEY", "")
21
-
22
- # 전역 상태 관리
23
- demo_session = {
24
- "active": False,
25
- "session_id": None,
26
- "start_time": None,
27
- "simulation_start_time": None,
28
- "active_stations": [],
29
- "current_issues": [],
30
- "performance_history": [],
31
- "total_processed": 0
32
- }
33
-
34
- # 활성 문제 상황들
35
- active_issues = {}
36
-
37
- def verify_internal_api_key(authorization: str = Header(None)):
38
- """내부 API 키 검증"""
39
- if not authorization or authorization != f"Bearer {INTERNAL_API_KEY}":
40
- raise HTTPException(status_code=401, detail="Unauthorized")
41
- return True
42
-
43
- def register_internal_routes(app: FastAPI):
44
- """FastAPI 앱에 확장된 내부 API 라우트 등록"""
45
-
46
- # ============================================================================
47
- # 기존 엔드포인트들 (호환성 유지)
48
- # ============================================================================
49
-
50
- @app.post("/api/internal/collect_data", tags=["Internal"])
51
- async def collect_data_endpoint(
52
- request: Request,
53
- authorization: str = Header(None)
54
- ):
55
- """데이터 수집 및 처리 엔드포인트 (확장된 버전)"""
56
- verify_internal_api_key(authorization)
57
-
58
- try:
59
- # 요청 데이터 파싱
60
- request_data = await request.json()
61
- task_type = request_data.get('task', 'normal_collection')
62
- stations_data = request_data.get('stations_data', [])
63
- simulation_time = request_data.get('simulation_time')
64
-
65
- logger.info(f"Data collection request: {task_type}, {len(stations_data)} records")
66
-
67
- # 데이터 처리 모듈 동적 임포트
68
- try:
69
- from data_processor import DataProcessor
70
- processor = DataProcessor()
71
- except ImportError:
72
- # 간단한 처리 로직 사용
73
- processor = SimpleDataProcessor()
74
-
75
- # 배치 데이터 처리
76
- if task_type == 'batch_data_collection':
77
- processed_count = await process_batch_data(stations_data, simulation_time)
78
-
79
- # 시연 세션 업데이트
80
- if demo_session["active"]:
81
- demo_session["total_processed"] += processed_count
82
-
83
- return {
84
- "success": True,
85
- "timestamp": datetime.now().isoformat(),
86
- "task_type": task_type,
87
- "records_saved": processed_count,
88
- "stations_processed": len(set(d.get('station_id') for d in stations_data)),
89
- "processing_time_ms": 150, # 시뮬레이션
90
- "simulation_time": simulation_time
91
- }
92
-
93
- # 일반 데이터 수집 (기존 로직)
94
- else:
95
- # 실제 외부 API 수집 시뮬레이션
96
- collected_data = await simulate_data_collection()
97
- processed_data = await processor.process_data(collected_data)
98
- saved_count = await processor.save_to_database(processed_data)
99
-
100
- return {
101
- "success": True,
102
- "timestamp": datetime.now().isoformat(),
103
- "stations_collected": len(collected_data),
104
- "records_saved": saved_count,
105
- "message": f"Successfully collected and processed data for {len(collected_data)} stations"
106
- }
107
-
108
- except Exception as e:
109
- logger.error(f"Data collection failed: {str(e)}")
110
- raise HTTPException(status_code=500, detail=str(e))
111
-
112
- @app.post("/api/internal/update_predictions", tags=["Internal"])
113
- async def update_predictions_endpoint(
114
- request: Request,
115
- authorization: str = Header(None)
116
- ):
117
- """예측 업데이트 엔드포인트 (확장된 버전)"""
118
- verify_internal_api_key(authorization)
119
-
120
- try:
121
- request_data = await request.json()
122
- demo_mode = request_data.get('demo_mode', False)
123
- speed_multiplier = request_data.get('speed_multiplier', 1)
124
-
125
- # 예측 업데이트 실행
126
- if demo_mode:
127
- results = await update_demo_predictions(speed_multiplier)
128
- else:
129
- # 기존 예측 업데이트 로직
130
- try:
131
- from prediction_updater import PredictionUpdater
132
- updater = PredictionUpdater()
133
- results = await updater.update_all_predictions()
134
- except ImportError:
135
- results = await simulate_prediction_update()
136
-
137
- return {
138
- "success": True,
139
- "timestamp": datetime.now().isoformat(),
140
- "predictions_updated": results["updated_count"],
141
- "stations": results["stations"],
142
- "prediction_horizon": "72 points (6 hours)",
143
- "demo_mode": demo_mode,
144
- "speed_multiplier": speed_multiplier,
145
- "message": f"Successfully updated predictions for {results['updated_count']} stations"
146
- }
147
-
148
- except Exception as e:
149
- logger.error(f"Prediction update failed: {str(e)}")
150
- raise HTTPException(status_code=500, detail=str(e))
151
-
152
- # ============================================================================
153
- # 새로운 시연 제어 엔드포인트들
154
- # ============================================================================
155
-
156
- @app.post("/api/internal/demo_control", tags=["Demo Control"])
157
- async def demo_control_endpoint(
158
- request: Request,
159
- authorization: str = Header(None)
160
- ):
161
- """시연 제어 (시작/중지)"""
162
- verify_internal_api_key(authorization)
163
-
164
- try:
165
- request_data = await request.json()
166
- action = request_data.get('action')
167
-
168
- if action == 'start_demo':
169
- return await start_demo_session(request_data)
170
- elif action == 'stop_demo':
171
- return await stop_demo_session(request_data)
172
- else:
173
- raise HTTPException(status_code=400, detail=f"Unknown action: {action}")
174
-
175
- except Exception as e:
176
- logger.error(f"Demo control failed: {str(e)}")
177
- raise HTTPException(status_code=500, detail=str(e))
178
-
179
- @app.get("/api/internal/demo_status", tags=["Demo Control"])
180
- async def demo_status_endpoint(authorization: str = Header(None)):
181
- """시연 상태 조회"""
182
- verify_internal_api_key(authorization)
183
-
184
- try:
185
- current_time = datetime.now()
186
-
187
- if demo_session["active"]:
188
- running_minutes = (current_time - demo_session["start_time"]).total_seconds() / 60
189
-
190
- return {
191
- "demo_active": True,
192
- "session_id": demo_session["session_id"],
193
- "start_time": demo_session["start_time"].isoformat(),
194
- "running_minutes": round(running_minutes, 1),
195
- "simulation_start_time": demo_session["simulation_start_time"],
196
- "current_sim_time": calculate_current_simulation_time(),
197
- "active_stations": len(demo_session["active_stations"]),
198
- "stations_list": demo_session["active_stations"],
199
- "total_processed": demo_session["total_processed"],
200
- "active_issues": len(active_issues),
201
- "current_rmse": get_current_rmse()
202
- }
203
- else:
204
- return {
205
- "demo_active": False,
206
- "last_session_end": demo_session.get("end_time"),
207
- "total_sessions_today": 1 # 간단한 카운터
208
- }
209
-
210
- except Exception as e:
211
- logger.error(f"Demo status check failed: {str(e)}")
212
- raise HTTPException(status_code=500, detail=str(e))
213
-
214
- @app.get("/api/internal/simulation_status", tags=["Demo Control"])
215
- async def simulation_status_endpoint(authorization: str = Header(None)):
216
- """시뮬레이션 시간 상태"""
217
- verify_internal_api_key(authorization)
218
-
219
- try:
220
- current_sim_time = calculate_current_simulation_time()
221
-
222
- return {
223
- "demo_active": demo_session["active"],
224
- "current_simulation_time": current_sim_time,
225
- "simulation_start_time": demo_session["simulation_start_time"],
226
- "real_start_time": demo_session["start_time"].isoformat() if demo_session["start_time"] else None,
227
- "elapsed_real_minutes": calculate_elapsed_real_minutes(),
228
- "simulation_speed": 1 # 현재는 1:1 비율
229
- }
230
-
231
- except Exception as e:
232
- logger.error(f"Simulation status check failed: {str(e)}")
233
- raise HTTPException(status_code=500, detail=str(e))
234
-
235
- @app.post("/api/internal/trigger_demo", tags=["Demo Control"])
236
- async def trigger_demo_issue(
237
- request: Request,
238
- authorization: str = Header(None)
239
- ):
240
- """시연용 문제 상황 발생"""
241
- verify_internal_api_key(authorization)
242
-
243
- try:
244
- request_data = await request.json()
245
- issue_type = request_data.get('issue_type')
246
- issue_config = request_data.get('config', {})
247
- station_id = request_data.get('station_id')
248
-
249
- logger.info(f"Triggering demo issue: {issue_type}")
250
-
251
- # 문제 상황 활성화
252
- issue_id = str(uuid.uuid4())[:8]
253
-
254
- issue_record = {
255
- "id": issue_id,
256
- "type": issue_type,
257
- "config": issue_config,
258
- "station_id": station_id,
259
- "start_time": datetime.now(),
260
- "active": True,
261
- "demo_mode": True
262
- }
263
-
264
- active_issues[issue_id] = issue_record
265
- demo_session["current_issues"].append(issue_id)
266
-
267
- # 문제 유형별 처리
268
- response_data = {
269
- "success": True,
270
- "issue_id": issue_id,
271
- "issue_type": issue_type,
272
- "timestamp": datetime.now().isoformat(),
273
- "target": station_id or "system-wide",
274
- "auto_recovery": issue_config.get('auto_recovery', True)
275
- }
276
-
277
- if issue_type == 'network_failure':
278
- duration = issue_config.get('duration_seconds', 60)
279
- response_data.update({
280
- "expected_duration": f"{duration} seconds",
281
- "expected_impact": "API timeouts, data collection delays"
282
- })
283
-
284
- elif issue_type == 'extreme_weather':
285
- magnitude = issue_config.get('magnitude', 'moderate')
286
- duration = issue_config.get('duration_minutes', 90)
287
- response_data.update({
288
- "expected_duration": f"{duration} minutes",
289
- "expected_impact": f"{magnitude} tide surge, RMSE increase"
290
- })
291
-
292
- elif issue_type == 'sensor_malfunction':
293
- target = issue_config.get('target_station', station_id)
294
- sensors = issue_config.get('affected_sensors', ['tide_level'])
295
- response_data.update({
296
- "target": target,
297
- "expected_duration": "30-90 minutes",
298
- "expected_impact": f"Missing data for {', '.join(sensors)}"
299
- })
300
-
301
- elif issue_type == 'data_corruption':
302
- rate = issue_config.get('corruption_rate', 0.2)
303
- response_data.update({
304
- "expected_duration": "15-60 minutes",
305
- "expected_impact": f"{rate:.1%} data corruption rate"
306
- })
307
-
308
- # 자동 복구 스케줄링
309
- if issue_config.get('auto_recovery', True):
310
- asyncio.create_task(schedule_auto_recovery(issue_id, issue_config))
311
-
312
- return response_data
313
-
314
- except Exception as e:
315
- logger.error(f"Issue trigger failed: {str(e)}")
316
- raise HTTPException(status_code=500, detail=str(e))
317
-
318
- @app.post("/api/internal/system_recovery", tags=["System Recovery"])
319
- async def system_recovery_endpoint(
320
- request: Request,
321
- authorization: str = Header(None)
322
- ):
323
- """시스템 복구"""
324
- verify_internal_api_key(authorization)
325
-
326
- try:
327
- request_data = await request.json()
328
- action = request_data.get('action')
329
-
330
- if action == 'clear_all_issues':
331
- return await clear_all_issues()
332
- elif action == 'reset_simulation':
333
- return await reset_simulation_state(request_data)
334
- else:
335
- raise HTTPException(status_code=400, detail=f"Unknown recovery action: {action}")
336
-
337
- except Exception as e:
338
- logger.error(f"System recovery failed: {str(e)}")
339
- raise HTTPException(status_code=500, detail=str(e))
340
-
341
- @app.post("/api/internal/collection_control", tags=["System Recovery"])
342
- async def collection_control_endpoint(
343
- request: Request,
344
- authorization: str = Header(None)
345
- ):
346
- """데이터 수집 제어"""
347
- verify_internal_api_key(authorization)
348
-
349
- try:
350
- request_data = await request.json()
351
- action = request_data.get('action')
352
-
353
- if action == 'start_collection':
354
- stations = request_data.get('stations', demo_session["active_stations"])
355
- return {
356
- "success": True,
357
- "action": "start_collection",
358
- "active_stations": stations,
359
- "timestamp": datetime.now().isoformat()
360
- }
361
- elif action == 'stop_collection':
362
- return {
363
- "success": True,
364
- "action": "stop_collection",
365
- "timestamp": datetime.now().isoformat()
366
- }
367
- else:
368
- raise HTTPException(status_code=400, detail=f"Unknown collection action: {action}")
369
-
370
- except Exception as e:
371
- logger.error(f"Collection control failed: {str(e)}")
372
- raise HTTPException(status_code=500, detail=str(e))
373
-
374
- # ============================================================================
375
- # 기존 엔드포인트들 (호환성 유지)
376
- # ============================================================================
377
-
378
- @app.get("/api/internal/data_freshness", tags=["Internal"])
379
- async def check_data_freshness(authorization: str = Header(None)):
380
- """데이터 신선도 체크 (확장된 버전)"""
381
- verify_internal_api_key(authorization)
382
-
383
- try:
384
- from supabase_utils import get_supabase_client
385
- client = get_supabase_client()
386
-
387
- # 시연용 관측소들
388
- demo_stations = demo_session["active_stations"] if demo_session["active"] else [
389
- "DT_0001", "DT_0002", "DT_0003", "DT_0008", "DT_0017"
390
- ]
391
-
392
- freshness_report = {}
393
-
394
- for station_id in demo_stations:
395
- try:
396
- response = client.table("tide_observations_processed").select("observed_at").eq(
397
- "station_id", station_id
398
- ).order("observed_at", desc=True).limit(1).execute()
399
-
400
- if response.data:
401
- last_update = datetime.fromisoformat(response.data[0]["observed_at"])
402
- minutes_old = (datetime.now() - last_update).total_seconds() / 60
403
- freshness_report[station_id] = {
404
- "last_update": last_update.isoformat(),
405
- "minutes_old": round(minutes_old, 2),
406
- "status": "fresh" if minutes_old < 10 else "stale"
407
- }
408
- else:
409
- freshness_report[station_id] = {
410
- "last_update": None,
411
- "minutes_old": None,
412
- "status": "no_data"
413
- }
414
- except Exception as e:
415
- freshness_report[station_id] = {
416
- "last_update": None,
417
- "minutes_old": None,
418
- "status": "error",
419
- "error": str(e)
420
- }
421
-
422
- # 가장 오래된 데이터 계산
423
- valid_ages = [v["minutes_old"] for v in freshness_report.values()
424
- if v["minutes_old"] is not None]
425
- oldest_minutes = max(valid_ages) if valid_ages else 0
426
-
427
- return {
428
- "timestamp": datetime.now().isoformat(),
429
- "oldest_data_minutes": round(oldest_minutes, 2),
430
- "stations": freshness_report,
431
- "overall_status": "healthy" if oldest_minutes < 15 else "warning",
432
- "demo_active": demo_session["active"]
433
- }
434
-
435
- except Exception as e:
436
- logger.error(f"Freshness check failed: {str(e)}")
437
- raise HTTPException(status_code=500, detail=str(e))
438
-
439
- @app.post("/api/internal/manual_trigger", tags=["Internal"])
440
- async def manual_trigger(
441
- task: str,
442
- authorization: str = Header(None)
443
- ):
444
- """수동 작업 트리거 (확장된 버전)"""
445
- verify_internal_api_key(authorization)
446
-
447
- if task == "collect_now":
448
- result = await collect_data_endpoint(None, authorization)
449
- return result
450
- elif task == "predict_now":
451
- result = await update_predictions_endpoint(None, authorization)
452
- return result
453
- elif task == "recovery_mode":
454
- return await emergency_recovery()
455
- else:
456
- raise HTTPException(status_code=400, detail=f"Unknown task: {task}")
457
-
458
- logger.info("Extended Internal API routes registered successfully")
459
-
460
- # ============================================================================
461
- # 헬퍼 함수들
462
- # ============================================================================
463
-
464
- async def start_demo_session(request_data: dict):
465
- """시연 세션 시작"""
466
- global demo_session
467
-
468
- session_id = str(uuid.uuid4())[:8]
469
- current_time = datetime.now()
470
- sim_start_time = request_data.get('simulation_settings', {}).get('start_time', '2025-07-01T00:00:00')
471
- stations = request_data.get('stations', ['DT_0001', 'DT_0002', 'DT_0003', 'DT_0008', 'DT_0017'])
472
-
473
- demo_session.update({
474
- "active": True,
475
- "session_id": session_id,
476
- "start_time": current_time,
477
- "simulation_start_time": sim_start_time,
478
- "active_stations": stations,
479
- "current_issues": [],
480
- "performance_history": [],
481
- "total_processed": 0
482
- })
483
-
484
- logger.info(f"Demo session started: {session_id}")
485
-
486
- return {
487
- "success": True,
488
- "session_id": session_id,
489
- "demo_mode": "interactive",
490
- "simulation_start_time": sim_start_time,
491
- "stations": stations,
492
- "timestamp": current_time.isoformat()
493
- }
494
-
495
- async def stop_demo_session(request_data: dict):
496
- """시연 세션 중지"""
497
- global demo_session
498
-
499
- if not demo_session["active"]:
500
- raise HTTPException(status_code=400, detail="No active demo session")
501
-
502
- end_time = datetime.now()
503
- duration_minutes = (end_time - demo_session["start_time"]).total_seconds() / 60
504
-
505
- # 최종 성능 계산
506
- final_rmse = get_current_rmse()
507
- final_accuracy = calculate_final_accuracy()
508
-
509
- session_summary = {
510
- "session_id": demo_session["session_id"],
511
- "duration_minutes": round(duration_minutes, 1),
512
- "total_processed": demo_session["total_processed"],
513
- "final_rmse": final_rmse,
514
- "final_accuracy": final_accuracy,
515
- "issues_encountered": len(demo_session["current_issues"]),
516
- "end_time": end_time.isoformat()
517
- }
518
-
519
- # 세션 초기화
520
- demo_session.update({
521
- "active": False,
522
- "session_id": None,
523
- "start_time": None,
524
- "end_time": end_time.isoformat()
525
- })
526
-
527
- # 활성 문제들 정리
528
- global active_issues
529
- active_issues.clear()
530
-
531
- logger.info(f"Demo session ended: {session_summary['session_id']}")
532
-
533
- return {
534
- "success": True,
535
- **session_summary
536
- }
537
-
538
- async def clear_all_issues():
539
- """모든 활성 문제 해제"""
540
- global active_issues
541
-
542
- cleared_issues = []
543
- for issue_id, issue in active_issues.items():
544
- cleared_issues.append({
545
- "id": issue_id,
546
- "type": issue["type"],
547
- "description": f"{issue['type']} affecting {issue.get('station_id', 'system')}"
548
- })
549
-
550
- active_issues.clear()
551
- demo_session["current_issues"].clear()
552
-
553
- return {
554
- "success": True,
555
- "cleared_issues": cleared_issues,
556
- "timestamp": datetime.now().isoformat()
557
- }
558
-
559
- async def reset_simulation_state(request_data: dict):
560
- """시뮬레이션 상태 리셋"""
561
- preserve_data = request_data.get('preserve_data', True)
562
-
563
- # 성능 히스토리 리셋
564
- demo_session["performance_history"].clear()
565
-
566
- # 카운터 리셋
567
- if request_data.get('reset_counters', True):
568
- demo_session["total_processed"] = 0
569
-
570
- return {
571
- "success": True,
572
- "preserved_records": demo_session["total_processed"] if preserve_data else 0,
573
- "cache_cleared": True,
574
- "counters_reset": True,
575
- "timestamp": datetime.now().isoformat()
576
- }
577
-
578
- async def schedule_auto_recovery(issue_id: str, issue_config: dict):
579
- """자동 복구 스케줄링"""
580
- recovery_delay = issue_config.get('duration_seconds', 60)
581
-
582
- # 지정된 시간 후 문제 해제
583
- await asyncio.sleep(recovery_delay)
584
-
585
- if issue_id in active_issues:
586
- del active_issues[issue_id]
587
- if issue_id in demo_session["current_issues"]:
588
- demo_session["current_issues"].remove(issue_id)
589
-
590
- logger.info(f"Auto-recovered issue: {issue_id}")
591
-
592
- def calculate_current_simulation_time():
593
- """현재 시뮬레이션 시간 계산"""
594
- if not demo_session["active"] or not demo_session["start_time"]:
595
- return demo_session.get("simulation_start_time", "2025-07-01T00:00:00")
596
-
597
- # 실제 경과 시간 (분)
598
- elapsed_real = (datetime.now() - demo_session["start_time"]).total_seconds() / 60
599
-
600
- # 시뮬레이션 시작 시간에 경과 시간 추가
601
- sim_start = datetime.fromisoformat(demo_session["simulation_start_time"])
602
- current_sim = sim_start + timedelta(minutes=elapsed_real)
603
-
604
- return current_sim.isoformat()
605
-
606
- def calculate_elapsed_real_minutes():
607
- """실제 경과 시간 (분)"""
608
- if not demo_session["active"] or not demo_session["start_time"]:
609
- return 0
610
-
611
- return (datetime.now() - demo_session["start_time"]).total_seconds() / 60
612
-
613
- def get_current_rmse():
614
- """현재 RMSE 값 (시뮬레이션)"""
615
- base_rmse = 18.5 # 기본 RMSE
616
-
617
- # 활성 문제들의 영향
618
- for issue in active_issues.values():
619
- if issue["type"] == "extreme_weather":
620
- base_rmse *= 1.8 # 극값 상황에서 RMSE 증가
621
- elif issue["type"] == "sensor_malfunction":
622
- base_rmse *= 1.3 # 센서 오류시 증가
623
- elif issue["type"] == "data_corruption":
624
- base_rmse *= 1.5 # 데이터 손상시 증가
625
-
626
- # 랜덤 변동
627
- variation = np.random.normal(0, 2)
628
- return round(max(10, base_rmse + variation), 1)
629
-
630
- def calculate_final_accuracy():
631
- """최종 정확도 계산 (시뮬레이션)"""
632
- base_accuracy = 89.2
633
-
634
- # 문제 상황들의 영향
635
- for issue in active_issues.values():
636
- if issue["type"] == "extreme_weather":
637
- base_accuracy -= 15
638
- elif issue["type"] == "sensor_malfunction":
639
- base_accuracy -= 8
640
- elif issue["type"] == "data_corruption":
641
- base_accuracy -= 12
642
-
643
- return round(max(50, base_accuracy), 1)
644
-
645
- # ============================================================================
646
- # 간단한 데이터 처리기 (모듈이 없는 경우 대체)
647
- # ============================================================================
648
-
649
- class SimpleDataProcessor:
650
- """간단한 데이터 처리기"""
651
-
652
- async def process_data(self, raw_data):
653
- """데이터 처리 시뮬레이션"""
654
- return raw_data
655
-
656
- async def save_to_database(self, processed_data):
657
- """데이터베이스 저장 시뮬레이션"""
658
- return len(processed_data)
659
-
660
- async def process_batch_data(stations_data: List[Dict], simulation_time: str):
661
- """배치 데이터 처리"""
662
- processed_count = 0
663
-
664
- for data_point in stations_data:
665
- # 기본 검증
666
- required_fields = ['station_id', 'date', 'air_pres', 'wind_dir', 'wind_speed', 'air_temp', 'tide_level']
667
-
668
- if all(field in data_point for field in required_fields):
669
- # 천문조 계산 및 잔차 추가 (시뮬레이션)
670
- astronomical_tide = calculate_astronomical_tide(
671
- data_point['station_id'],
672
- data_point['date']
673
- )
674
- data_point['astronomical_tide'] = astronomical_tide
675
- data_point['residual'] = data_point['tide_level'] - astronomical_tide
676
-
677
- processed_count += 1
678
-
679
- return processed_count
680
-
681
- def calculate_astronomical_tide(station_id: str, timestamp: str):
682
- """천문조 계산 (시뮬레이션)"""
683
- from datetime import datetime
684
-
685
- dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00') if timestamp.endswith('Z') else timestamp)
686
- hours = dt.hour + dt.minute / 60
687
-
688
- # 간단한 조석 계산
689
- M2_period = 12.42
690
- tide = 300 + 200 * np.sin(2 * np.pi * hours / M2_period)
691
-
692
- return round(tide, 1)
693
-
694
- async def simulate_data_collection():
695
- """데이터 수집 시뮬레이션"""
696
- stations = demo_session["active_stations"] if demo_session["active"] else ["DT_0001", "DT_0002"]
697
-
698
- collected_data = []
699
- for station_id in stations:
700
- data_point = {
701
- "station_id": station_id,
702
- "observed_at": datetime.now().isoformat(),
703
- "tide_level": 300 + np.random.normal(0, 50),
704
- "air_temp": 25 + np.random.normal(0, 3),
705
- "air_pres": 1013 + np.random.normal(0, 5),
706
- "wind_speed": max(0, np.random.normal(3, 2)),
707
- "wind_dir": np.random.uniform(0, 360)
708
- }
709
- collected_data.append(data_point)
710
-
711
- return collected_data
712
-
713
- async def simulate_prediction_update():
714
- """예측 업데이트 시뮬레이션"""
715
- stations = demo_session["active_stations"] if demo_session["active"] else ["DT_0001", "DT_0002"]
716
-
717
- return {
718
- "updated_count": len(stations),
719
- "stations": stations
720
- }
721
-
722
- async def update_demo_predictions(speed_multiplier: int):
723
- """시연용 예측 업데이트"""
724
- stations = demo_session["active_stations"]
725
-
726
- # 성능 히스토리 업데이트
727
- current_rmse = get_current_rmse()
728
- demo_session["performance_history"].append({
729
- "timestamp": datetime.now().isoformat(),
730
- "rmse": current_rmse,
731
- "accuracy": calculate_final_accuracy()
732
- })
733
-
734
- return {
735
- "updated_count": len(stations),
736
- "stations": stations,
737
- "current_rmse": current_rmse
738
- }
739
-
740
- async def emergency_recovery():
741
- """응급 복구"""
742
- global active_issues
743
-
744
- # 모든 문제 해제
745
- cleared_count = len(active_issues)
746
- active_issues.clear()
747
- demo_session["current_issues"].clear()
748
-
749
- return {
750
- "success": True,
751
- "action": "emergency_recovery",
752
- "cleared_issues": cleared_count,
753
- "timestamp": datetime.now().isoformat()
754
- }