alwaysgood commited on
Commit
4fce22c
·
verified ·
1 Parent(s): 0b432ca

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +365 -0
app.py CHANGED
@@ -646,6 +646,257 @@ def api_get_extremes(station_id, hours=24):
646
  "count": len(extremes),
647
  "extremes": extremes
648
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
649
 
650
  # --- 5. Gradio 인터페이스 ---
651
  with gr.Blocks(title="통합 조위 예측 시스템", theme=gr.themes.Soft()) as demo:
@@ -787,6 +1038,120 @@ with gr.Blocks(title="통합 조위 예측 시스템", theme=gr.themes.Soft()) a
787
  outputs=api_extreme_output,
788
  api_name="extremes"
789
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
790
 
791
  if __name__ == "__main__":
792
  demo.launch()
 
646
  "count": len(extremes),
647
  "extremes": extremes
648
  }
649
+ def api_get_historical_tide(station_id, date_str, hours=24):
650
+ """과거 특정 날짜의 조위 데이터 조회"""
651
+ supabase = get_supabase_client()
652
+ if not supabase:
653
+ return {"error": "Database connection failed"}
654
+
655
+ try:
656
+ # 날짜 파싱 (YYYY-MM-DD 형식)
657
+ start_date = datetime.strptime(date_str, "%Y-%m-%d")
658
+ end_date = start_date + timedelta(hours=hours)
659
+
660
+ # 과거 예측 데이터 조회
661
+ result = supabase.table('tide_predictions')\
662
+ .select('predicted_at, final_tide_level, harmonic_level, predicted_residual')\
663
+ .eq('station_id', station_id)\
664
+ .gte('predicted_at', start_date.isoformat())\
665
+ .lte('predicted_at', end_date.isoformat())\
666
+ .order('predicted_at')\
667
+ .execute()
668
+
669
+ if result.data:
670
+ # 통계 계산
671
+ levels = [d['final_tide_level'] for d in result.data]
672
+
673
+ return {
674
+ "station_id": station_id,
675
+ "date": date_str,
676
+ "hours": hours,
677
+ "count": len(result.data),
678
+ "statistics": {
679
+ "max": max(levels),
680
+ "min": min(levels),
681
+ "avg": sum(levels) / len(levels)
682
+ },
683
+ "data": result.data[:100] # 최대 100개만 반환
684
+ }
685
+
686
+ # 예측 데이터가 없으면 관측 데이터 확인
687
+ result = supabase.table('tide_observations')\
688
+ .select('observed_at, residual, air_pres, wind_speed, air_temp')\
689
+ .eq('station_id', station_id)\
690
+ .gte('observed_at', start_date.isoformat())\
691
+ .lte('observed_at', end_date.isoformat())\
692
+ .order('observed_at')\
693
+ .execute()
694
+
695
+ if result.data:
696
+ return {
697
+ "station_id": station_id,
698
+ "date": date_str,
699
+ "type": "observation", # 관측 데이터임을 표시
700
+ "count": len(result.data),
701
+ "data": result.data[:100]
702
+ }
703
+
704
+ return {"error": "No historical data found for this date"}
705
+
706
+ except Exception as e:
707
+ return {"error": f"Date parsing error: {str(e)}"}
708
+
709
+ def api_get_historical_extremes(station_id, date_str):
710
+ """과거 특정 날짜의 만조/간조 정보"""
711
+ supabase = get_supabase_client()
712
+ if not supabase:
713
+ return {"error": "Database connection failed"}
714
+
715
+ try:
716
+ # 하루 전체 데이터
717
+ start_date = datetime.strptime(date_str, "%Y-%m-%d")
718
+ end_date = start_date + timedelta(days=1)
719
+
720
+ result = supabase.table('tide_predictions')\
721
+ .select('predicted_at, final_tide_level')\
722
+ .eq('station_id', station_id)\
723
+ .gte('predicted_at', start_date.isoformat())\
724
+ .lt('predicted_at', end_date.isoformat())\
725
+ .order('predicted_at')\
726
+ .execute()
727
+
728
+ if not result.data or len(result.data) < 3:
729
+ return {"error": "Insufficient data for this date"}
730
+
731
+ # 극값 찾기
732
+ extremes = []
733
+ data = result.data
734
+
735
+ for i in range(1, len(data) - 1):
736
+ prev_level = data[i-1]['final_tide_level']
737
+ curr_level = data[i]['final_tide_level']
738
+ next_level = data[i+1]['final_tide_level']
739
+
740
+ if curr_level > prev_level and curr_level > next_level:
741
+ extremes.append({
742
+ 'type': 'high',
743
+ 'time': data[i]['predicted_at'],
744
+ 'level': curr_level
745
+ })
746
+ elif curr_level < prev_level and curr_level < next_level:
747
+ extremes.append({
748
+ 'type': 'low',
749
+ 'time': data[i]['predicted_at'],
750
+ 'level': curr_level
751
+ })
752
+
753
+ # 최고/최저 찾기
754
+ all_levels = [d['final_tide_level'] for d in data]
755
+ daily_max = max(all_levels)
756
+ daily_min = min(all_levels)
757
+
758
+ return {
759
+ "station_id": station_id,
760
+ "date": date_str,
761
+ "daily_max": daily_max,
762
+ "daily_min": daily_min,
763
+ "daily_range": daily_max - daily_min,
764
+ "extremes": extremes,
765
+ "high_tide_count": len([e for e in extremes if e['type'] == 'high']),
766
+ "low_tide_count": len([e for e in extremes if e['type'] == 'low'])
767
+ }
768
+
769
+ except Exception as e:
770
+ return {"error": f"Error: {str(e)}"}
771
+
772
+ def api_compare_dates(station_id, date1, date2):
773
+ """두 날짜의 조위 패턴 비교"""
774
+ supabase = get_supabase_client()
775
+ if not supabase:
776
+ return {"error": "Database connection failed"}
777
+
778
+ try:
779
+ results = {}
780
+
781
+ for date_str in [date1, date2]:
782
+ start_date = datetime.strptime(date_str, "%Y-%m-%d")
783
+ end_date = start_date + timedelta(days=1)
784
+
785
+ result = supabase.table('tide_predictions')\
786
+ .select('predicted_at, final_tide_level')\
787
+ .eq('station_id', station_id)\
788
+ .gte('predicted_at', start_date.isoformat())\
789
+ .lt('predicted_at', end_date.isoformat())\
790
+ .order('predicted_at')\
791
+ .execute()
792
+
793
+ if result.data:
794
+ levels = [d['final_tide_level'] for d in result.data]
795
+ results[date_str] = {
796
+ "max": max(levels),
797
+ "min": min(levels),
798
+ "avg": sum(levels) / len(levels),
799
+ "range": max(levels) - min(levels)
800
+ }
801
+
802
+ if len(results) == 2:
803
+ # 차이 계산
804
+ diff = {
805
+ "max_diff": results[date1]["max"] - results[date2]["max"],
806
+ "min_diff": results[date1]["min"] - results[date2]["min"],
807
+ "avg_diff": results[date1]["avg"] - results[date2]["avg"],
808
+ "range_diff": results[date1]["range"] - results[date2]["range"]
809
+ }
810
+
811
+ return {
812
+ "station_id": station_id,
813
+ "date1": {**{"date": date1}, **results[date1]},
814
+ "date2": {**{"date": date2}, **results[date2]},
815
+ "difference": diff
816
+ }
817
+
818
+ return {"error": "Data not available for both dates"}
819
+
820
+ except Exception as e:
821
+ return {"error": f"Error: {str(e)}"}
822
+
823
+ def api_get_monthly_summary(station_id, year, month):
824
+ """월간 조위 요약 통계"""
825
+ supabase = get_supabase_client()
826
+ if not supabase:
827
+ return {"error": "Database connection failed"}
828
+
829
+ try:
830
+ # 월 시작/종료 날짜
831
+ start_date = datetime(year, month, 1)
832
+ if month == 12:
833
+ end_date = datetime(year + 1, 1, 1)
834
+ else:
835
+ end_date = datetime(year, month + 1, 1)
836
+
837
+ result = supabase.table('tide_predictions')\
838
+ .select('predicted_at, final_tide_level')\
839
+ .eq('station_id', station_id)\
840
+ .gte('predicted_at', start_date.isoformat())\
841
+ .lt('predicted_at', end_date.isoformat())\
842
+ .execute()
843
+
844
+ if not result.data:
845
+ return {"error": "No data for this month"}
846
+
847
+ # 일별 통계 계산
848
+ daily_stats = {}
849
+ for item in result.data:
850
+ date = item['predicted_at'][:10] # YYYY-MM-DD
851
+ if date not in daily_stats:
852
+ daily_stats[date] = []
853
+ daily_stats[date].append(item['final_tide_level'])
854
+
855
+ # 월간 통계
856
+ all_levels = [d['final_tide_level'] for d in result.data]
857
+ monthly_max = max(all_levels)
858
+ monthly_min = min(all_levels)
859
+
860
+ # 가장 높았던 날과 낮았던 날 찾기
861
+ highest_day = None
862
+ lowest_day = None
863
+ highest_value = 0
864
+ lowest_value = 9999
865
+
866
+ for date, levels in daily_stats.items():
867
+ day_max = max(levels)
868
+ day_min = min(levels)
869
+
870
+ if day_max > highest_value:
871
+ highest_value = day_max
872
+ highest_day = date
873
+
874
+ if day_min < lowest_value:
875
+ lowest_value = day_min
876
+ lowest_day = date
877
+
878
+ return {
879
+ "station_id": station_id,
880
+ "year": year,
881
+ "month": month,
882
+ "statistics": {
883
+ "monthly_max": monthly_max,
884
+ "monthly_min": monthly_min,
885
+ "monthly_avg": sum(all_levels) / len(all_levels),
886
+ "monthly_range": monthly_max - monthly_min,
887
+ "total_observations": len(result.data),
888
+ "days_with_data": len(daily_stats)
889
+ },
890
+ "extreme_days": {
891
+ "highest_tide_day": highest_day,
892
+ "highest_tide_value": highest_value,
893
+ "lowest_tide_day": lowest_day,
894
+ "lowest_tide_value": lowest_value
895
+ }
896
+ }
897
+
898
+ except Exception as e:
899
+ return {"error": f"Error: {str(e)}"}
900
 
901
  # --- 5. Gradio 인터페이스 ---
902
  with gr.Blocks(title="통합 조위 예측 시스템", theme=gr.themes.Soft()) as demo:
 
1038
  outputs=api_extreme_output,
1039
  api_name="extremes"
1040
  )
1041
+ with gr.TabItem("📜 과거 데이터"):
1042
+ gr.Markdown("""
1043
+ ### 과거 조위 데이터 조회
1044
+ 특정 날짜의 조위 정보를 확인할 수 있습니다.
1045
+ """)
1046
+
1047
+ with gr.Row():
1048
+ with gr.Column():
1049
+ hist_station = gr.Dropdown(
1050
+ choices=[(f"{STATION_NAMES[s]} ({s})", s) for s in STATIONS],
1051
+ label="관측소 선택",
1052
+ value=STATIONS[0]
1053
+ )
1054
+ hist_date = gr.Textbox(
1055
+ label="날짜 (YYYY-MM-DD)",
1056
+ value=datetime.now().strftime("%Y-%m-%d")
1057
+ )
1058
+ hist_hours = gr.Number(
1059
+ label="조회 시간 (시간)",
1060
+ value=24,
1061
+ minimum=1,
1062
+ maximum=168
1063
+ )
1064
+
1065
+ hist_btn = gr.Button("조회", variant="primary")
1066
+
1067
+ with gr.Column():
1068
+ hist_output = gr.JSON(label="조회 결과")
1069
+
1070
+ hist_btn.click(
1071
+ fn=api_get_historical_tide,
1072
+ inputs=[hist_station, hist_date, hist_hours],
1073
+ outputs=hist_output,
1074
+ api_name="historical_tide"
1075
+ )
1076
+
1077
+ # 과거 만조/간조
1078
+ with gr.Row():
1079
+ with gr.Column():
1080
+ gr.Markdown("### 과거 만조/간조 정보")
1081
+ ext_station = gr.Dropdown(
1082
+ choices=[(f"{STATION_NAMES[s]} ({s})", s) for s in STATIONS],
1083
+ label="관측소",
1084
+ value=STATIONS[0]
1085
+ )
1086
+ ext_date = gr.Textbox(
1087
+ label="날짜 (YYYY-MM-DD)",
1088
+ value=(datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d")
1089
+ )
1090
+ ext_btn = gr.Button("만조/간조 조회")
1091
+ ext_output = gr.JSON(label="만조/간조 정보")
1092
+
1093
+ with gr.Column():
1094
+ gr.Markdown("### 날짜 비교")
1095
+ comp_station = gr.Dropdown(
1096
+ choices=[(f"{STATION_NAMES[s]} ({s})", s) for s in STATIONS],
1097
+ label="관측소",
1098
+ value=STATIONS[0]
1099
+ )
1100
+ comp_date1 = gr.Textbox(
1101
+ label="날짜 1",
1102
+ value=datetime.now().strftime("%Y-%m-%d")
1103
+ )
1104
+ comp_date2 = gr.Textbox(
1105
+ label="날짜 2",
1106
+ value=(datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d")
1107
+ )
1108
+ comp_btn = gr.Button("비교")
1109
+ comp_output = gr.JSON(label="비교 결과")
1110
+
1111
+ ext_btn.click(
1112
+ fn=api_get_historical_extremes,
1113
+ inputs=[ext_station, ext_date],
1114
+ outputs=ext_output,
1115
+ api_name="historical_extremes"
1116
+ )
1117
+
1118
+ comp_btn.click(
1119
+ fn=api_compare_dates,
1120
+ inputs=[comp_station, comp_date1, comp_date2],
1121
+ outputs=comp_output,
1122
+ api_name="compare_dates"
1123
+ )
1124
+
1125
+ # 월간 요약
1126
+ gr.Markdown("### 월간 요약 통계")
1127
+ with gr.Row():
1128
+ month_station = gr.Dropdown(
1129
+ choices=[(f"{STATION_NAMES[s]} ({s})", s) for s in STATIONS],
1130
+ label="관측소",
1131
+ value=STATIONS[0]
1132
+ )
1133
+ month_year = gr.Number(
1134
+ label="년도",
1135
+ value=datetime.now().year,
1136
+ precision=0
1137
+ )
1138
+ month_month = gr.Number(
1139
+ label="월",
1140
+ value=datetime.now().month,
1141
+ minimum=1,
1142
+ maximum=12,
1143
+ precision=0
1144
+ )
1145
+ month_btn = gr.Button("월간 통계 조회")
1146
+
1147
+ month_output = gr.JSON(label="월간 통계")
1148
+
1149
+ month_btn.click(
1150
+ fn=api_get_monthly_summary,
1151
+ inputs=[month_station, month_year, month_month],
1152
+ outputs=month_output,
1153
+ api_name="monthly_summary"
1154
+ )
1155
 
1156
  if __name__ == "__main__":
1157
  demo.launch()