feat: Integrate Supabase for data management, replacing local PostgreSQL. Implement Supabase service for CRUD operations on patients and scenarios, and update application logic to utilize Supabase API. Add Dockerfile for containerization and .dockerignore for build optimization.
3a338e5
| """ | |
| Google Gemini AI νκ° μλΉμ€ | |
| """ | |
| import json | |
| import os | |
| import google.generativeai as genai | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| # Gemini API μ€μ | |
| GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY") | |
| genai.configure(api_key=GOOGLE_API_KEY) | |
| class GeminiEvaluator: | |
| """κ°νΈ μΈμμΈκ³ νκ°λ₯Ό μν Gemini AI μλΉμ€""" | |
| def __init__(self, model_name="gemini-2.0-flash-exp"): | |
| self.model = genai.GenerativeModel(model_name) | |
| def evaluate_handoff( | |
| self, student_sbar: dict, scenario_data: dict, patient_data: dict | |
| ) -> dict: | |
| """ | |
| νμμ SBAR μΈμμΈκ³λ₯Ό νκ°ν©λλ€. | |
| Args: | |
| student_sbar: νμμ΄ μμ±ν SBAR (situation, background, assessment, recommendation) | |
| scenario_data: μλλ¦¬μ€ λ°μ΄ν° (EMR μ 보) | |
| patient_data: νμ κΈ°λ³Έ μ 보 | |
| Returns: | |
| νκ° κ²°κ³Ό λμ λ리 (total_score, category_scores, strengths, improvements, detailed_feedback) | |
| """ | |
| prompt = self._create_evaluation_prompt( | |
| student_sbar, scenario_data, patient_data | |
| ) | |
| try: | |
| response = self.model.generate_content( | |
| prompt, | |
| generation_config={ | |
| "temperature": 0.7, | |
| "top_p": 0.95, | |
| "max_output_tokens": 2048, | |
| }, | |
| ) | |
| # JSON μλ΅ νμ± | |
| result_text = response.text.strip() | |
| # Markdown μ½λ λΈλ‘ μ κ±° (```json ... ```) | |
| if result_text.startswith("```"): | |
| result_text = result_text.split("```")[1] | |
| if result_text.startswith("json"): | |
| result_text = result_text[4:] | |
| result_text = result_text.strip() | |
| evaluation = json.loads(result_text) | |
| return evaluation | |
| except json.JSONDecodeError as e: | |
| print(f"JSON νμ± μ€λ₯: {e}") | |
| print(f"μλ΅ ν μ€νΈ: {response.text}") | |
| return self._create_default_evaluation() | |
| except Exception as e: | |
| print(f"Gemini API μ€λ₯: {e}") | |
| return self._create_default_evaluation() | |
| def _create_evaluation_prompt( | |
| self, student_sbar: dict, scenario_data: dict, patient_data: dict | |
| ) -> str: | |
| """κ³ λνλ νκ° ν둬ννΈ μμ±""" | |
| prompt = f"""λΉμ μ 20λ κ²½λ ₯μ νκ΅ κ°νΈ κ΅μ‘ μ λ¬Έκ°μ΄μ κ°νΈν κ΅μμ λλ€. | |
| κ°νΈ νμμ΄ μμ±ν SBAR νμμ μΈμμΈκ³λ₯Ό νκ΅ κ°νΈμ¬ κ΅κ°μν λ° μμ μ€λ¬΄ νμ€μ λ°λΌ νκ°ν΄μ£ΌμΈμ. | |
| ## π νμ μ 보 (EMR λ°μ΄ν°) | |
| - **νμλͺ **: {patient_data.get('name')} ({patient_data.get('age')}μΈ {patient_data.get('gender')}) | |
| - **μ§λ¨λͺ **: {patient_data.get('diagnosis')} | |
| - **μ μμΌ**: {patient_data.get('admission_date')} (μ¬μ {scenario_data.get('hospital_day')}μΌμ°¨) | |
| - **μλ λ₯΄κΈ°**: {patient_data.get('allergies')} | |
| ## π₯ μΈμμΈκ³ μν© | |
| - **μλ리μ€**: {scenario_data.get('title')} | |
| - **μΈκ³ μν©**: {scenario_data.get('handoff_situation')} | |
| ### π νλ ₯μ§ν (Vital Signs) | |
| {json.dumps(scenario_data.get('vitals', {}), ensure_ascii=False, indent=2)} | |
| ### π§ͺ κ²μ¬ κ²°κ³Ό (Laboratory Results) | |
| {json.dumps(scenario_data.get('labs', {}), ensure_ascii=False, indent=2)} | |
| ### π μμ¬ μ²λ°© (Physician Orders) | |
| {json.dumps(scenario_data.get('orders', []), ensure_ascii=False, indent=2)} | |
| ### π κ°νΈ κΈ°λ‘ (Nursing Notes) | |
| {json.dumps(scenario_data.get('nursing_notes', []), ensure_ascii=False, indent=2)} | |
| --- | |
| ## π νμμ΄ μμ±ν SBAR μΈμμΈκ³ | |
| **S - Situation (μν©)** | |
| {student_sbar.get('situation', '')} | |
| **B - Background (λ°°κ²½)** | |
| {student_sbar.get('background', '')} | |
| **A - Assessment (νκ°)** | |
| {student_sbar.get('assessment', '')} | |
| **R - Recommendation (κΆκ³ μ¬ν)** | |
| {student_sbar.get('recommendation', '')} | |
| --- | |
| ## π νκ° κΈ°μ€ (μ΄ 100μ ) | |
| ### 1οΈβ£ μμ μ± (Completeness, 0-25μ ) | |
| **νμ μ 보 ν¬ν¨ μ¬λΆλ₯Ό νκ°ν©λλ€:** | |
| **Situation (6μ ):** | |
| - νμ μ μ (μ΄λ¦, λμ΄, μ±λ³) [2μ ] | |
| - μ£ΌνΈμ/μ μ μ¬μ [2μ ] | |
| - νμ¬ μν μμ½ [2μ ] | |
| **Background (6μ ):** | |
| - μ§λ¨λͺ [1μ ] | |
| - κ³Όκ±°λ ₯ [1μ ] | |
| - μλ λ₯΄κΈ° [1μ ] | |
| - νμ¬ ν¬μ½ [2μ ] | |
| - μμ λ ₯ (ν΄λΉ μ) [1μ ] | |
| **Assessment (8μ ):** | |
| - νλ ₯μ§ν (BP, HR, RR, BT, SpO2) [3μ ] | |
| - μ£Όμ κ²μ¬ κ²°κ³Ό (WBC, CRP λ±) [2μ ] | |
| - ν΅μ¦ μ μ [1μ ] | |
| - IV/Foley/Drain λ± λΌμΈ μν [1μ ] | |
| - μ€μν λ³ν/μ°λ €μ¬ν [1μ ] | |
| **Recommendation (5μ ):** | |
| - λ€μ λ¨κ³ κ³ν [2μ ] | |
| - μ£Όμμ¬ν/λͺ¨λν°λ§ [2μ ] | |
| - μμ κ²μ¬/μΉλ£ [1μ ] | |
| ### 2οΈβ£ μ νμ± (Accuracy, 0-25μ ) | |
| **μλ£ μ 보μ μ νμ±μ νκ°ν©λλ€:** | |
| - νλ ₯μ§ν μμΉμ μ νμ± [5μ ] | |
| - κ²μ¬ κ²°κ³Ό μμΉμ μ νμ± [5μ ] | |
| - μν μ©μ΄μ μ¬λ°λ₯Έ μ¬μ© [5μ ] | |
| - μκ° μμμ μ νμ± [5μ ] | |
| - μΈκ³Όκ΄κ³μ λ Όλ¦¬μ± [5μ ] | |
| **κ°μ μμ:** | |
| - μμΉ μ€λ₯ (κ° -2μ ) | |
| - μν μ©μ΄ μ€μ© (κ° -2μ ) | |
| - μκ° μμ νΌλ (-3μ ) | |
| - μ€μ μ 보 λλ½ (κ° -3μ ) | |
| ### 3οΈβ£ λͺ λ£μ± (Clarity, 0-25μ ) | |
| **μμ¬μν΅μ λͺ νμ±μ νκ°ν©λλ€:** | |
| - κ°κ²°νκ³ λͺ νν νν [7μ ] | |
| - λ Όλ¦¬μ μΈ νλ¦ [6μ ] | |
| - λΆνμν μ€λ³΅ μμ [6μ ] | |
| - μ λ¬Έμ μ΄λ©΄μ μ΄ν΄νκΈ° μ¬μ΄ νν [6μ ] | |
| **κ°μ μμ:** | |
| - λͺ¨νΈν νν (κ° -2μ ) | |
| - κ³Όλν μ€λ³΅ (κ° -2μ ) | |
| - λΉλ Όλ¦¬μ μ κ° (-3μ ) | |
| ### 4οΈβ£ μ°μ μμ (Priority, 0-25μ ) | |
| **μ€μ μ 보μ μ°μ μ λ¬μ νκ°ν©λλ€:** | |
| - μκΈ/μ£Όμμ¬ν μ°μ μΈκΈ [8μ ] | |
| - μ€μλμ λ°λ₯Έ μ 보 λ°°μΉ [7μ ] | |
| - μ¦κ° μ‘°μΉ νμ μ¬ν κ°μ‘° [5μ ] | |
| - νμ μμ κ΄λ ¨ μ 보 μ°μ [5μ ] | |
| **νΉλ³ κ³ λ €μ¬ν:** | |
| - λΉμ μ νλ ₯μ§ν μ°μ μΈκΈ (+보λμ€) | |
| - μλ λ₯΄κΈ° μ‘°κΈ° μΈκΈ (+보λμ€) | |
| - ν΅μ¦/λΆνΈκ° μ¦μ μΈκΈ (+보λμ€) | |
| --- | |
| ## π― νκ° μ§μΉ¨ | |
| 1. **νκ΅ κ°νΈ μ€λ¬΄ νμ€ μ€μ**: λνκ°νΈνν μΈμμΈκ³ κ°μ΄λλΌμΈ κΈ°μ€ | |
| 2. **νμ μμ μ΅μ°μ **: νμ μμ μ μν₯μ λ―ΈμΉλ μ 보 νκ° κ°ν | |
| 3. **μ€λ¬΄ μ μ©μ±**: μ€μ μμμμ μ¬μ© κ°λ₯ν μμ€ νκ° | |
| 4. **κ΅μ‘μ νΌλλ°±**: 건μ€μ μ΄κ³ ꡬ체μ μΈ κ°μ μ¬ν μ μ | |
| ## π€ μΆλ ₯ νμ | |
| **λ°λμ μλ JSON νμμΌλ‘λ§ μλ΅νμΈμ. λ€λ₯Έ ν μ€νΈλ ν¬ν¨νμ§ λ§μΈμ.** | |
| ```json | |
| {{ | |
| "total_score": 85, | |
| "category_scores": {{ | |
| "completeness": 22, | |
| "accuracy": 23, | |
| "clarity": 20, | |
| "priority": 20 | |
| }}, | |
| "strengths": [ | |
| "νμμ μ£ΌνΈμμ νμ¬ μνλ₯Ό λͺ ννκ² κΈ°μ νμ΅λλ€.", | |
| "λΉμ μ νλ ₯μ§ν(HR 96, BT 37.9β)λ₯Ό μ νν ν¬ν¨νκ³ μ°μ μμλ₯Ό λμμ΅λλ€.", | |
| "μ£Όμ κ²μ¬ κ²°κ³Ό(WBC 14.8, CRP 6.2)λ₯Ό ꡬ체μ μΌλ‘ μΈκΈνμ΅λλ€.", | |
| "μκΈ μμ μμ μκ°κ³Ό μ€λΉ μ¬νμ λͺ νν μ λ¬νμ΅λλ€.", | |
| "NPO μ μ§ λ° νμμ ν¬μ¬ λ± νμ¬ μΉλ£ κ³νμ μ νν κΈ°μ νμ΅λλ€." | |
| ], | |
| "improvements": [ | |
| "ν΅μ¦ μ μ(NRS 8/10)λ₯Ό Assessment μΉμ μλΆλΆμ λͺ μνμ¬ νμμ λΆνΈκ°μ κ°μ‘°νμΈμ.", | |
| "IV λΌμΈ μν(D5NS 80 mL/hr)λ₯Ό Assessmentμ μΆκ°νλ©΄ λ μμ ν©λλ€.", | |
| "Recommendationμμ μμ ν λͺ¨λν°λ§ κ³ν(V/S q4h, ν©λ³μ¦ κ΄μ°°)μ ꡬ체μ μΌλ‘ λͺ μνμΈμ.", | |
| "McBurney point μν΅, λ°λ°ν΅ λ± μ 체 κ²μ§ μ견μ Assessmentμ ν¬ν¨νλ©΄ μ’μ΅λλ€.", | |
| "μ μ κ²½λ‘(μκΈμ€ κ²½μ )μ μ μ μκ°μ Backgroundμ μΆκ°νλ©΄ μμ μ±μ΄ ν₯μλ©λλ€." | |
| ], | |
| "missing_critical_info": [ | |
| "IV λΌμΈ μ’ λ₯ λ° μ μ", | |
| "μ 체 κ²μ§ μ견 (McBurney point μν΅, λ°λ°ν΅)", | |
| "μμ ν λͺ¨λν°λ§ κ³ν" | |
| ], | |
| "safety_concerns": [ | |
| "ν΅μ¦ μ μκ° λμ(NRS 8/10)μλ λΆκ΅¬νκ³ ν΅μ¦ κ΄λ¦¬ μ°μ μμκ° λͺ ννμ§ μμ΅λλ€.", | |
| "λΉμ μ νλ ₯μ§ν(λ°μ΄ 37.9β)μ λν μ§μμ λͺ¨λν°λ§ κ³νμ΄ κ΅¬μ²΄μ μ΄μ§ μμ΅λλ€." | |
| ], | |
| "clinical_reasoning": {{ | |
| "situation_assessment": "νμμ νμμ μ£ΌνΈμμ νμ¬ μνλ₯Ό λͺ νν νμ νκ³ μμΌλ, ν΅μ¦μ μ¬κ°μ±μ μΆ©λΆν κ°μ‘°νμ§ λͺ»νμ΅λλ€.", | |
| "background_completeness": "μ§λ¨λͺ κ³Ό κ²μ¬ κ²°κ³Όλ μ ν¬ν¨νμΌλ, μ μ κ²½λ‘μ μκ°μ λν μ λ³΄κ° λΆμ‘±ν©λλ€.", | |
| "assessment_quality": "νλ ₯μ§νμ μ£Όμ κ²μ¬ κ²°κ³Όλ₯Ό ν¬ν¨νμΌλ, μ 체 κ²μ§ μ견과 IV λΌμΈ μ λ³΄κ° λλ½λμμ΅λλ€.", | |
| "recommendation_specificity": "μμ κ³νμ λͺ ννλ, μμ μ ν ꡬ체μ μΈ κ°νΈ μ€μ¬ λ° λͺ¨λν°λ§ κ³νμ΄ λ―Έν‘ν©λλ€." | |
| }}, | |
| "grade_level": "B+ (μ°μ)", | |
| "pass_fail": "Pass", | |
| "detailed_feedback": "μ λ°μ μΌλ‘ SBAR ꡬ쑰λ₯Ό μ μ΄ν΄νκ³ μμΌλ©°, νμμ ν΅μ¬ λ¬Έμ λ₯Ό νμ νμ¬ μΈμμΈκ³νμ΅λλ€. νΉν λΉμ μ νλ ₯μ§νμ μ£Όμ κ²μ¬ κ²°κ³Όλ₯Ό μ νν ν¬ν¨ν μ μ΄ μ°μν©λλ€.\\n\\n**κ°μ :** Situationμμ νμμ μ£ΌνΈμ(μ°νλ³΅λΆ ν΅μ¦)μ μ΄λ μμμ λͺ νν κΈ°μ νκ³ , Backgroundμμ κΈμ± μΆ©μμΌ μ§λ¨κ³Ό CT μ견μ ꡬ체μ μΌλ‘ μΈκΈνμ΅λλ€. Recommendationμμ μκΈ μμ μκ°μ λͺ μν κ²λ μ’μ΅λλ€.\\n\\n**κ°μ μ¬ν:** Assessmentμμ ν΅μ¦ μ μ(NRS 8/10)λ₯Ό λ μλΆλΆμ κ°μ‘°νκ³ , IV λΌμΈ μνμ μ 체 κ²μ§ μ견(McBurney point μν΅, λ°λ°ν΅)μ μΆκ°νλ©΄ μμ μ±μ΄ ν₯μλ©λλ€. Recommendationμμ μμ ν V/S λͺ¨λν°λ§ μ£ΌκΈ°(q4h)μ ν©λ³μ¦ κ΄μ°° νλͺ©μ ꡬ체μ μΌλ‘ λͺ μνμΈμ.\\n\\n**νμ μμ :** λμ ν΅μ¦ μ μμ λ°μ΄μ λν μ§μμ κ΄μ°°μ΄ νμνλ©°, μ΄λ₯Ό μΈμμΈκ³ μ λ κ°μ‘°ν΄μΌ ν©λλ€. NPO μ μ§μ νμμ ν¬μ¬λ μ μΈκΈνμ΅λλ€.\\n\\n**μ€λ¬΄ μ μ©:** μμμμ μΆ©λΆν μ¬μ© κ°λ₯ν μμ€μ΄λ, μ 체 κ²μ§ μ견과 λΌμΈ μ 보λ₯Ό μΆκ°νλ©΄ λμ± μμ±λ λμ μΈμμΈκ³κ° λ κ²μ λλ€." | |
| }} | |
| ``` | |
| **νκ° μ μ£Όμμ¬ν:** | |
| 1. κΈμ μ μΈ μΈ‘λ©΄μ λ¨Όμ μΈκΈνκ³ κ±΄μ€μ μΈ νΌλλ°± μ 곡 | |
| 2. ꡬ체μ μΈ κ°μ λ°©λ² μ μ (μμ ν¬ν¨) | |
| 3. νμ μμ κ³Ό κ΄λ ¨λ μ€μ μ 보 λλ½ μ λͺ νν μ§μ | |
| 4. μ€μ μμ μ μ© κ°λ₯μ± κ³ λ € | |
| 5. νμμ μμ€μ λ§λ κΈ°λμΉ μ€μ (κ³Όλν λΉν μ§μ) | |
| """ | |
| return prompt | |
| def _create_default_evaluation(self) -> dict: | |
| """API μ€λ₯ μ κΈ°λ³Έ νκ° κ²°κ³Ό λ°ν""" | |
| return { | |
| "total_score": 0, | |
| "category_scores": { | |
| "completeness": 0, | |
| "accuracy": 0, | |
| "clarity": 0, | |
| "priority": 0, | |
| }, | |
| "strengths": ["νκ°λ₯Ό μ§νν μ μμ΅λλ€."], | |
| "improvements": ["API μ€λ₯κ° λ°μνμ΅λλ€. λμ€μ λ€μ μλν΄μ£ΌμΈμ."], | |
| "detailed_feedback": "νκ° μμ€ν μ λ¬Έμ κ° λ°μνμ΅λλ€. κ΄λ¦¬μμκ² λ¬ΈμνμΈμ.", | |
| } | |
| def chat_feedback( | |
| self, | |
| user_message: str, | |
| chat_history: list, | |
| scenario_data: dict, | |
| patient_data: dict, | |
| ) -> str: | |
| """ | |
| λνν SBAR νΌλλ°± μ 곡 | |
| Args: | |
| user_message: νμμ νμ¬ λ©μμ§ | |
| chat_history: μ΄μ λν κΈ°λ‘ [(role, message), ...] | |
| scenario_data: μλλ¦¬μ€ λ°μ΄ν° (EMR μ 보) | |
| patient_data: νμ κΈ°λ³Έ μ 보 | |
| Returns: | |
| AI νΌλλ°± λ©μμ§ | |
| """ | |
| prompt = self._create_chat_feedback_prompt( | |
| user_message, chat_history, scenario_data, patient_data | |
| ) | |
| try: | |
| response = self.model.generate_content( | |
| prompt, | |
| generation_config={ | |
| "temperature": 0.8, | |
| "top_p": 0.95, | |
| "max_output_tokens": 1500, | |
| }, | |
| ) | |
| return response.text.strip() | |
| except Exception as e: | |
| print(f"Gemini API μ€λ₯: {e}") | |
| return "μ£μ‘ν©λλ€. μΌμμ μΈ μ€λ₯κ° λ°μνμ΅λλ€. λ€μ μλν΄μ£ΌμΈμ." | |
| def _create_chat_feedback_prompt( | |
| self, user_message: str, chat_history: list, scenario_data: dict, patient_data: dict | |
| ) -> str: | |
| """λνν νΌλλ°± ν둬ννΈ μμ±""" | |
| # μ΄μ λν κΈ°λ‘ ν¬λ§·ν | |
| history_text = "" | |
| if chat_history: | |
| for role, message in chat_history: | |
| if role == "user": | |
| history_text += f"\nνμ: {message}\n" | |
| else: | |
| history_text += f"\nAI κ΅μ: {message}\n" | |
| prompt = f"""λΉμ μ 20λ κ²½λ ₯μ νκ΅ κ°νΈ κ΅μ‘ μ λ¬Έκ°μ΄μ κ°νΈν κ΅μμ λλ€. | |
| νμμ΄ SBAR νμμ μΈμμΈκ³λ₯Ό μ°μ΅νκ³ μμΌλ©°, λνλ₯Ό ν΅ν΄ νΌλλ°±μ μ 곡νκ³ μμ΅λλ€. | |
| ## π νμ μ 보 (EMR λ°μ΄ν°) | |
| - **νμλͺ **: {patient_data.get('name')} ({patient_data.get('age')}μΈ {patient_data.get('gender')}) | |
| - **μ§λ¨λͺ **: {patient_data.get('diagnosis')} | |
| - **μ μμΌ**: {patient_data.get('admission_date')} | |
| - **μλ λ₯΄κΈ°**: {patient_data.get('allergies')} | |
| ## π₯ μΈμμΈκ³ μν© | |
| - **μλ리μ€**: {scenario_data.get('title')} | |
| - **μΈκ³ μν©**: {scenario_data.get('handoff_situation')} | |
| ### π νλ ₯μ§ν (Vital Signs) | |
| {json.dumps(scenario_data.get('vitals', {}), ensure_ascii=False, indent=2)} | |
| ### π§ͺ κ²μ¬ κ²°κ³Ό (Laboratory Results) | |
| {json.dumps(scenario_data.get('labs', {}), ensure_ascii=False, indent=2)} | |
| ### π μμ¬ μ²λ°© (Physician Orders) | |
| {json.dumps(scenario_data.get('orders', []), ensure_ascii=False, indent=2)} | |
| ### π κ°νΈ κΈ°λ‘ (Nursing Notes) | |
| {json.dumps(scenario_data.get('nursing_notes', []), ensure_ascii=False, indent=2)} | |
| --- | |
| ## π¬ μ΄μ λν κΈ°λ‘ | |
| {history_text if history_text else "(첫 λνμ λλ€)"} | |
| --- | |
| ## π νμμ νμ¬ λ©μμ§ | |
| {user_message} | |
| --- | |
| ## π νΌλλ°± κ°μ΄λλΌμΈ | |
| 1. **μΉμ νκ³ κ²©λ €μ μΈ ν€**: νμμ΄ νΈμνκ² μ§λ¬Ένκ³ κ°μ ν μ μλλ‘ μ§μ | |
| 2. **SBAR ꡬ쑰 μ€μ νμΈ**: νμμ΄ μμ±ν λ΄μ©μ΄ SBAR νμμ λ°λ₯΄λμ§ νμΈ | |
| 3. **ꡬ체μ μΈ κ°μ μ μ**: λ§μ°ν νΌλλ°±μ΄ μλ ꡬ체μ μΈ μμ μ 곡 | |
| 4. **μ€μ μ 보 λλ½ μ§μ **: EMR λ°μ΄ν°μ μλ μ€μν μ λ³΄κ° λΉ μ‘λ€λ©΄ μΈκΈ | |
| 5. **νμ μμ κ°μ‘°**: νμ μμ κ³Ό κ΄λ ¨λ μ 보λ νΉν κ°μ‘° | |
| 6. **λ¨κ³μ κ°μ **: ν λ²μ λͺ¨λ κ²μ κ³ μΉλ € νμ§ λ§κ³ , μ°μ μμκ° λμ κ²λΆν° μ μ | |
| 7. **μ΅μ’ μ μΆ μ **: μ μλ₯Ό λ§€κΈ°μ§ λ§κ³ , κ°μ λ°©ν₯λ§ μ μ | |
| ## μλ΅ νμ | |
| - μμ°μ€λ¬μ΄ λνμ²΄λ‘ μλ΅νμΈμ. | |
| - νμμ΄ μν λΆλΆμ λ¨Όμ μΉμ°¬νμΈμ. | |
| - κ°μ μ΄ νμν λΆλΆμ "~νλ©΄ λ μ’μ κ² κ°μμ", "~λ₯Ό μΆκ°ν΄λ³΄λ 건 μ΄λ¨κΉμ?" κ°μ μ μ νμμΌλ‘ μ μνμΈμ. | |
| - νμμ μμλ₯Ό λ€μ΄ μ€λͺ νμΈμ. | |
| - νμμ΄ "μ΅μ’ μ μΆ"μ μΈκΈνμ§ μλ ν, μ μλ λ±κΈμ λ§€κΈ°μ§ λ§μΈμ. | |
| μλ΅μ μμ±ν΄μ£ΌμΈμ: | |
| """ | |
| return prompt | |
| def evaluate_conversation( | |
| self, | |
| chat_history: list, | |
| scenario_data: dict, | |
| patient_data: dict | |
| ) -> dict: | |
| """ | |
| μ 체 λνλ₯Ό λΆμνμ¬ μ§μ νΌλλ°± μ 곡 (μ μ μμ) | |
| Args: | |
| chat_history: μ 체 λν κΈ°λ‘ [[user_msg, assistant_msg], ...] | |
| scenario_data: μλλ¦¬μ€ λ°μ΄ν° (EMR μ 보) | |
| patient_data: νμ κΈ°λ³Έ μ 보 | |
| Returns: | |
| νκ° κ²°κ³Ό λμ λ리 (μ μ μμ΄ μ§μ νΌλλ°±λ§) | |
| """ | |
| prompt = self._create_conversation_evaluation_prompt( | |
| chat_history, scenario_data, patient_data | |
| ) | |
| try: | |
| response = self.model.generate_content( | |
| prompt, | |
| generation_config={ | |
| "temperature": 0.7, | |
| "top_p": 0.95, | |
| "max_output_tokens": 2048, | |
| }, | |
| ) | |
| # JSON μλ΅ νμ± | |
| result_text = response.text.strip() | |
| # Markdown μ½λ λΈλ‘ μ κ±° | |
| if result_text.startswith("```"): | |
| result_text = result_text.split("```")[1] | |
| if result_text.startswith("json"): | |
| result_text = result_text[4:] | |
| result_text = result_text.strip() | |
| evaluation = json.loads(result_text) | |
| return evaluation | |
| except json.JSONDecodeError as e: | |
| print(f"JSON νμ± μ€λ₯: {e}") | |
| print(f"μλ΅ ν μ€νΈ: {response.text}") | |
| return self._create_default_evaluation_conversation() | |
| except Exception as e: | |
| print(f"Gemini API μ€λ₯: {e}") | |
| return self._create_default_evaluation_conversation() | |
| def _create_conversation_evaluation_prompt( | |
| self, chat_history: list, scenario_data: dict, patient_data: dict | |
| ) -> str: | |
| """μ 체 λν νκ° ν둬ννΈ μμ±""" | |
| # μ 체 λν λ΄μ© ν¬λ§·ν | |
| conversation_text = "" | |
| for user_msg, bot_msg in chat_history: | |
| if user_msg: | |
| conversation_text += f"\n**νμ**: {user_msg}\n" | |
| if bot_msg: | |
| conversation_text += f"\n**AI κ΅μ**: {bot_msg}\n" | |
| prompt = f"""λΉμ μ 20λ κ²½λ ₯μ νκ΅ κ°νΈ κ΅μ‘ μ λ¬Έκ°μ΄μ κ°νΈν κ΅μμ λλ€. | |
| κ°νΈ νμμ΄ AI κ΅μμ λλ λνλ₯Ό λΆμνμ¬ κ±΄μ€μ μΈ μ’ ν© νΌλλ°±μ μ 곡ν΄μ£ΌμΈμ. | |
| **μ€μ**: | |
| - λνμμ "νμ"μ΄ μ ν λΆλΆμ΄ μ€μ νμ΅νλ κ°νΈ νμμ λλ€. | |
| - "νμ"λ EMR μ 보μ λμ¨ νμμ λλ€. | |
| - νμμ΄ νμμ λν μΈμμΈκ³ λ΄μ©μ μμ±νλ κ²μ νκ°ν΄μ£ΌμΈμ. | |
| ## π νμ μ 보 (EMR λ°μ΄ν° - νμμ΄ μΈμμΈκ³ν΄μΌ ν νμ) | |
| - **νμλͺ **: {patient_data.get('name')} ({patient_data.get('age')}μΈ {patient_data.get('gender')}) | |
| - **μ§λ¨λͺ **: {patient_data.get('diagnosis')} | |
| - **μ μμΌ**: {patient_data.get('admission_date')} | |
| - **μλ λ₯΄κΈ°**: {patient_data.get('allergies')} | |
| ## π₯ μΈμμΈκ³ μν© | |
| - **μλ리μ€**: {scenario_data.get('title')} | |
| - **μΈκ³ μν©**: {scenario_data.get('handoff_situation')} | |
| ### π νλ ₯μ§ν (Vital Signs) | |
| {json.dumps(scenario_data.get('vitals', {}), ensure_ascii=False, indent=2)} | |
| ### π§ͺ κ²μ¬ κ²°κ³Ό (Laboratory Results) | |
| {json.dumps(scenario_data.get('labs', {}), ensure_ascii=False, indent=2)} | |
| ### π μμ¬ μ²λ°© (Physician Orders) | |
| {json.dumps(scenario_data.get('orders', []), ensure_ascii=False, indent=2)} | |
| ### π κ°νΈ κΈ°λ‘ (Nursing Notes) | |
| {json.dumps(scenario_data.get('nursing_notes', []), ensure_ascii=False, indent=2)} | |
| --- | |
| ## π¬ νμκ³Ό AI κ΅μ κ° λν λ΄μ© | |
| **νμμ΄ μμ±ν μΈμμΈκ³ λ΄μ©κ³Ό AI κ΅μμ νΌλλ°±μ΄ ν¬ν¨λ μ 체 λν:** | |
| {conversation_text if conversation_text else "(λν μμ)"} | |
| **μ λνμμ νμμ΄ νμ({patient_data.get('name')})μ λν μΈμμΈκ³λ₯Ό μ΄λ»κ² μμ±νλμ§ λΆμνμΈμ.** | |
| --- | |
| ## π νκ° κ°μ΄λ | |
| **μ€μ:** | |
| - β μ μλ₯Ό λ§€κΈ°μ§ λ§μΈμ (total_score, category_scores μ¬μ© κΈμ§) | |
| - β μ§μ νΌλλ°±μ μ§μ€νμΈμ | |
| - β λνμμ λνλ κ°μ μ ꡬ체μ μΌλ‘ μΈκΈνμΈμ | |
| - β κ°μ ν μ μ 건μ€μ μΌλ‘ μ μνμΈμ | |
| - β λλ½λ μ€μ μ 보λ₯Ό νμ νμΈμ | |
| - β νμ μμ κ³Ό κ΄λ ¨λ μ£Όμμ¬νμ κ°μ‘°νμΈμ | |
| - β νμ΅μ κ²©λ €νλ ν€μ μ μ§νμΈμ | |
| **νκ° κΈ°μ€:** | |
| 1. **μμ μ±**: νμ μ 보(νμ μ μ, μ§λ¨, νλ ₯μ§ν, μ²λ°© λ±) ν¬ν¨ μ¬λΆ | |
| 2. **μ νμ±**: μλ£ μ 보μ μ νμ± | |
| 3. **λͺ λ£μ±**: μμ¬μν΅μ λͺ νμ± | |
| 4. **μ°μ μμ**: μ€μν μ 보μ μ°μ μ λ¬ | |
| 5. **νμ μμ **: νμ μμ κ³Ό κ΄λ ¨λ μ 보 κ°μ‘° | |
| --- | |
| ## π€ μΆλ ₯ νμ (JSON) | |
| λ°λμ μλ JSON νμμΌλ‘λ§ μλ΅νμΈμ: | |
| ```json | |
| {{ | |
| "strengths": [ | |
| "ꡬ체μ μΌλ‘ μν μ 1", | |
| "ꡬ체μ μΌλ‘ μν μ 2", | |
| "ꡬ체μ μΌλ‘ μν μ 3" | |
| ], | |
| "improvements": [ | |
| "ꡬ체μ μΌλ‘ κ°μ ν μ 1", | |
| "ꡬ체μ μΌλ‘ κ°μ ν μ 2", | |
| "ꡬ체μ μΌλ‘ κ°μ ν μ 3" | |
| ], | |
| "detailed_feedback": "νμμ μ λ°μ μΈ νμ΅ μνμ λν μ’ ν©μ μΈ μ견. κΈΈκ² μμ±νλ ꡬ체μ μΌλ‘.", | |
| "missing_critical_info": [ | |
| "λλ½λ μ€μ μ 보 1", | |
| "λλ½λ μ€μ μ 보 2" | |
| ], | |
| "safety_concerns": [ | |
| "νμ μμ κ΄λ ¨ μ£Όμμ¬ν 1", | |
| "νμ μμ κ΄λ ¨ μ£Όμμ¬ν 2" | |
| ] | |
| }} | |
| ``` | |
| **μ°Έκ³ :** | |
| - μ μ νλ(total_score, category_scores)λ ν¬ν¨νμ§ λ§μΈμ | |
| - λ°°μ΄ νλͺ©μ μ΅μ 2-3κ° μ΄μ μμ±νμΈμ | |
| - detailed_feedbackμ 200μ μ΄μ μμ±νμΈμ | |
| - νκΈλ‘ μμ±νμΈμ | |
| """ | |
| return prompt | |
| def _create_default_evaluation_conversation(self) -> dict: | |
| """νκ° μ€ν¨ μ κΈ°λ³Έ κ²°κ³Ό λ°ν (μ μ μμ΄)""" | |
| return { | |
| "strengths": ["λν λ΄μ©μ λΆμνμ΅λλ€."], | |
| "improvements": ["νκ° μμ€ν μ€λ₯λ‘ κ΅¬μ²΄μ μΈ νΌλλ°±μ μ 곡ν μ μμ΅λλ€."], | |
| "detailed_feedback": "νκ° μμ€ν μ λ¬Έμ κ° λ°μνμ΅λλ€. λμ€μ λ€μ μλν΄μ£ΌμΈμ.", | |
| "missing_critical_info": [], | |
| "safety_concerns": [] | |
| } | |