import os import json import requests import streamlit as st from datetime import datetime # ✅ Streamlit 기본 설정 st.set_page_config(page_title="학사일정 캘린더", layout="centered") st.title("📅 학사일정 캘린더 + AI 요약") st.markdown("NEIS API에서 학사일정을 불러오고 FullCalendar로 시각화합니다.") # ✅ 학교 정보 가져오기 def get_school_info(region_code, school_name, api_key): url = f"https://open.neis.go.kr/hub/schoolInfo?KEY={api_key}&Type=json&pIndex=1&pSize=1&SCHUL_NM={school_name}&ATPT_OFCDC_SC_CODE={region_code}" res = requests.get(url) data = res.json() school = data.get("schoolInfo", [{}])[1].get("row", [{}])[0] return school.get("SD_SCHUL_CODE"), school.get("ATPT_OFCDC_SC_CODE") # ✅ 학사일정 가져오기 def get_schedule(region_code, school_code, year, month, api_key): from_ymd = f"{year}{month:02}01" to_ymd = f"{year}{month:02}31" url = f"https://open.neis.go.kr/hub/SchoolSchedule?KEY={api_key}&Type=json&pIndex=1&pSize=100&ATPT_OFCDC_SC_CODE={region_code}&SD_SCHUL_CODE={school_code}&AA_FROM_YMD={from_ymd}&AA_TO_YMD={to_ymd}" res = requests.get(url) data = res.json() rows = data.get("SchoolSchedule", [{}])[1].get("row", []) return rows # ✅ 요약 생성 (vLLM API 호출) def summarize_schedule(rows, school_name, year): if not rows: return "일정이 없어 요약할 수 없습니다." lines = [] for row in rows: date = row["AA_YMD"] dt = datetime.strptime(date, "%Y%m%d").strftime("%-m월 %-d일") event = row["EVENT_NM"] lines.append(f"{dt}: {event}") text = "\n".join(lines) prompt = f"{school_name}가 {year}년도에 가지는 학사일정은 다음과 같습니다:\n{text}\n주요 일정을 요약해주세요." payload = { "model": "skt/A.X-4.0-Light", "messages": [ {"role": "system", "content": "당신은 학사일정을 요약해주는 AI입니다."}, {"role": "user", "content": prompt} ], "max_tokens": 256, "temperature": 0.0 } try: res = requests.post("http://localhost:8000/v1/chat/completions", json=payload) res.raise_for_status() return res.json()["choices"][0]["message"]["content"].strip() except Exception as e: return f"❌ 요약 실패: {e}" # ✅ 지역/학교/년도/월 선택 UI region_options = { "B10": "서울", "C10": "부산", "D10": "대구", "E10": "인천", "F10": "광주", "G10": "대전", "H10": "울산", "I10": "세종", "J10": "경기", "K10": "강원", "M10": "충북", "N10": "충남", "P10": "전북", "Q10": "전남", "R10": "경북", "S10": "경남", "T10": "제주" } with st.form("query_form"): region = st.selectbox("지역 교육청", options=list(region_options.keys()), format_func=lambda x: f"{region_options[x]} ({x})") school_name = st.text_input("학교명", placeholder="예: 상리초등학교") year = st.selectbox("년도", options=[2022, 2023, 2024, 2025], index=2) month = st.selectbox("월", options=list(range(1, 13)), index=6) submitted = st.form_submit_button("📅 학사일정 불러오기") # ✅ 제출 처리 if submitted: with st.spinner("일정 불러오는 중..."): api_key = os.environ.get("NEIS_API_KEY", "a69e08342c8947b4a52cd72789a5ecaf") school_code, region_code = get_school_info(region, school_name, api_key) if not school_code: st.error("학교 정보를 찾을 수 없습니다.") else: schedule_rows = get_schedule(region_code, school_code, year, month, api_key) if not schedule_rows: st.info("해당 조건의 학사일정이 없습니다.") else: events = [ { "title": row["EVENT_NM"], "start": datetime.strptime(row["AA_YMD"], "%Y%m%d").strftime("%Y-%m-%d") } for row in schedule_rows if "AA_YMD" in row and "EVENT_NM" in row ] event_json = json.dumps(events, ensure_ascii=False) st.components.v1.html(f"""
""", height=650) with st.expander("✨ 1년치 요약 보기", expanded=False): if st.button("🤖 요약 생성하기"): with st.spinner("모델이 요약 중..."): summary = summarize_schedule(schedule_rows, school_name, year) st.success("요약 완료!") st.markdown(f"**{school_name} {year}년 {month}월 일정 요약:**\n\n{summary}")