From 58cff598a783522dc2e65be8f6c628dde7783634 Mon Sep 17 00:00:00 2001 From: KWON Date: Mon, 22 Dec 2025 17:41:31 +0900 Subject: [PATCH] =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api_server.py | 181 +++++++++++++++++++++++++++++++++++----- app/gnu_autoupload.py | 4 +- build/app/entrypoint.sh | 2 + 3 files changed, 165 insertions(+), 22 deletions(-) diff --git a/app/api_server.py b/app/api_server.py index be3c1b9..07de2e4 100644 --- a/app/api_server.py +++ b/app/api_server.py @@ -2,6 +2,7 @@ import os import requests import json import re +import importlib.util from flask import Flask, request, jsonify, send_from_directory, make_response import sqlite3 from datetime import datetime, timedelta @@ -71,6 +72,126 @@ def get_captured_rainfall(date): logger.error(f"캡처 강수량 조회 실패: {e}") return [], 0.0, None +def generate_weather_data(date): + """ + 날씨 데이터가 없을 때 Selenium으로 데이터 추출하여 SQLite에만 저장 + (캡처 이미지 저장, FTP 업로드, 게시글 등록은 하지 않음) + + Args: + date: 'YYYYMMDD' 형식 + + Returns: + bool: 성공 여부 + """ + try: + logger.info(f"날씨 데이터 추출 시작 ({date})...") + + # weather_capture 모듈에서 필요한 함수들을 동적으로 임포트 + import importlib.util + spec = importlib.util.spec_from_file_location( + "weather_capture", + "/app/weather_capture.py" + ) + weather_capture = importlib.util.module_from_spec(spec) + spec.loader.exec_module(weather_capture) + + # Selenium으로 강우량 데이터 추출 + from selenium_manager import SeleniumManager + + selenium_mgr = SeleniumManager() + driver = selenium_mgr.get_driver() + + try: + driver.get(weather_capture.WEATHER_URL) + rainfall_data = weather_capture.extract_rainfall_from_page(driver) + + if rainfall_data: + # SQLite에만 저장 (이미지 저장, FTP 업로드는 하지 않음) + success = save_rainfall_to_sqlite(rainfall_data, date) + logger.info(f"날씨 데이터 추출 완료 ({date}): {success}") + return success + else: + logger.warning(f"강수량 데이터 추출 실패 ({date})") + return False + finally: + selenium_mgr.close_driver() + + except Exception as e: + logger.error(f"날씨 데이터 추출 중 오류: {type(e).__name__}: {e}") + return False + +def save_rainfall_to_sqlite(rainfall_data, date): + """ + 추출한 강수량 데이터를 SQLite DB에만 저장 + (이미지 저장, FTP 업로드, 게시글 등록 없음) + + Args: + rainfall_data: {시간(int): 강수량(float)} 딕셔너리 + date: 'YYYYMMDD' 형식 + + Returns: + bool: 성공 여부 + """ + if not rainfall_data: + logger.warning("저장할 강수량 데이터가 없음") + return False + + try: + os.makedirs(os.path.dirname(DB_PATH) or '/data', exist_ok=True) + conn = sqlite3.connect(DB_PATH) + curs = conn.cursor() + + # 테이블 생성 + curs.execute(''' + CREATE TABLE IF NOT EXISTS rainfall_capture ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + date TEXT NOT NULL, + hour INTEGER NOT NULL, + rainfall REAL NOT NULL, + UNIQUE(date, hour) + ) + ''') + + curs.execute(''' + CREATE TABLE IF NOT EXISTS rainfall_summary ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + date TEXT NOT NULL UNIQUE, + total_rainfall REAL NOT NULL, + capture_time TEXT NOT NULL + ) + ''') + + # 기존 데이터 삭제 + curs.execute('DELETE FROM rainfall_capture WHERE date = ?', (date,)) + curs.execute('DELETE FROM rainfall_summary WHERE date = ?', (date,)) + + # 시간별 강수량 저장 + total_rainfall = 0.0 + for hour in sorted(rainfall_data.keys()): + rainfall = rainfall_data[hour] + curs.execute( + 'INSERT INTO rainfall_capture (date, hour, rainfall) VALUES (?, ?, ?)', + (date, hour, rainfall) + ) + total_rainfall += rainfall + + # 합계 저장 + capture_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + curs.execute( + 'INSERT INTO rainfall_summary (date, total_rainfall, capture_time) VALUES (?, ?, ?)', + (date, total_rainfall, capture_time) + ) + + conn.commit() + conn.close() + + logger.info(f"SQLite 저장 완료: {date} 총 강수량 {total_rainfall:.1f}mm") + return True + + except Exception as e: + logger.error(f"SQLite 저장 실패: {type(e).__name__}: {e}") + return False + def get_forecast_rainfall(date): """ 기상청 API를 통한 강수량 예보 조회 @@ -96,27 +217,47 @@ def get_forecast_rainfall(date): 'nx': '57', 'ny': '130' } + , + 'data_status': 'ok' | 'generating' | 'unavailable' + } + """ + today = datetime.now().strftime('%Y%m%d') + is_forecast = date_str > today + data_status = 'ok' + + if is_forecast: + # 미래 날짜: API 예보 + hourly, total = get_forecast_rainfall(date_str) + note = "⚠️ 이는 기상청 08:00 발표 예보입니다. 실제 이벤트 적용 기준은 당일 09:00 캡처 데이터입니다." + else: + # 당일 이상: 캡처 데이터 + hourly, total, timestamp = get_captured_rainfall(date_str) - response = requests.get(url, params=params, timeout=10) - data = response.json() - - if data['response']['header']['resultCode'] != '00': - return [], 0.0 - - rainfall_by_hour = {} - for item in data['response']['body']['items']['item']: - if item['category'] == 'RN1': # 1시간 강수량 - hour = int(item['fcstTime'][:2]) - if 10 <= hour <= 21: # 10시~21시(오후 9시) - rainfall_by_hour[hour] = parse_rainfall_value(item['fcstValue']) - - hourly_list = [(h, rainfall_by_hour.get(h, 0.0)) for h in range(10, 22)] - total = sum(rain for _, rain in hourly_list) - - return hourly_list, total - except Exception as e: - logger.error(f"API 강수량 예보 조회 실패: {e}") - return [], 0.0 + # 데이터가 없을 경우 생성 시도 + if not hourly: + logger.warning(f"날씨 캡처 데이터 없음 ({date_str}), 데이터 생성 시도...") + if generate_weather_data(date_str): + # 다시 조회 + hourly, total, timestamp = get_captured_rainfall(date_str) + if hourly: + data_status = 'generating' + note = "💡 방금 날씨 데이터를 생성했습니다." + else: + data_status = 'unavailable' + note = "⚠️ 날씨 데이터를 생성했으나 조회할 수 없습니다. 나중에 다시 시도해주세요." + else: + data_status = 'unavailable' + note = "⚠️ 날씨 데이터를 생성할 수 없습니다. 09:00에 자동으로 생성되며, 나중에 다시 시도해주세요." + else: + note = None + + return { + 'date': date_str, + 'is_forecast': is_forecast, + 'hourly_data': hourly, + 'total': total, + 'note': note, + 'data_status': data_status.0 def get_rainfall_data(date_str): """ diff --git a/app/gnu_autoupload.py b/app/gnu_autoupload.py index 33544fb..50c9a12 100644 --- a/app/gnu_autoupload.py +++ b/app/gnu_autoupload.py @@ -378,7 +378,8 @@ def main(): file_list = [MAIN['file1'], MAIN['file2']] - # 게시글 작성, wr_id = write_board( + # 게시글 작성 + wr_id = write_board( board=MAIN['board'], subject=MAIN['subject'], content=MAIN['content'], @@ -388,7 +389,6 @@ def main(): file_list=file_list, msg_sender=msg_sender, url=MAIN.get('url') - msg_sender=msg_sender ) if success: diff --git a/build/app/entrypoint.sh b/build/app/entrypoint.sh index 2f5961e..caed737 100644 --- a/build/app/entrypoint.sh +++ b/build/app/entrypoint.sh @@ -11,6 +11,8 @@ echo "[$(date '+%Y-%m-%d %H:%M:%S')] ========================================" # Flask 웹서버를 백그라운드에서 시작 echo "[$(date '+%Y-%m-%d %H:%M:%S')] Flask 웹서버 시작 (포트 5000)..." cd /app +export FLASK_APP=api_server.py +export PYTHONUNBUFFERED=1 /usr/bin/python -m flask run --host=0.0.0.0 --port=5000 >> /logs/flask.log 2>&1 & FLASK_PID=$! echo "[$(date '+%Y-%m-%d %H:%M:%S')] Flask PID: $FLASK_PID"