""" Module 1: Cross-Cultural Semantic Translator MVP ================================================= A medical AI platform for translating cultural pain metaphors into structured medical ontologies. """ import gradio as gr import json import os from typing import Dict, Tuple, Optional # ============================================================================ # CONFIGURATION # ============================================================================ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "") TRANSCRIPTION_MODE = "api" OPENAI_MODEL = "gpt-5.2" # ============================================================================ # SETUP # ============================================================================ try: from openai import OpenAI client = OpenAI(api_key=OPENAI_API_KEY) if OPENAI_API_KEY else None except ImportError: print("ERROR: OpenAI library not installed.") client = None # ============================================================================ # SYSTEM PROMPT # ============================================================================ MEDICAL_ANTHROPOLOGIST_PROMPT = """You are an expert Medical Anthropologist. Your goal is to translate cultural pain metaphors into structured medical ontologies. Do NOT act as a doctor making a final diagnosis. Analyze the patient's transcript and output a strict JSON object with these exact keys: 'literal_translation', 'metaphor_mapping', 'mcgill_pain_ontology', 'psychological_and_stoicism_flags', 'physician_action_note'. Make sure to include English and original language in metaphor_mapping for reference.""" # ============================================================================ # TRANSCRIPTION # ============================================================================ def transcribe_audio(audio_path: Optional[str]) -> Tuple[str, str]: if audio_path is None: return "", "⚠️ No audio recorded." if client is None: return "", "❌ OpenAI client not initialized." try: with open(audio_path, "rb") as audio_file: transcript = client.audio.transcriptions.create( model="whisper-1", file=audio_file, response_format="text" ) return transcript.strip(), "✓ Transcribed via OpenAI Whisper API" except Exception as e: return "", f"❌ Transcription error: {str(e)}" # ============================================================================ # LLM ANALYSIS # ============================================================================ def analyze_with_llm(transcription: str) -> Tuple[str, str]: if not transcription or not client: return "
❌ Cannot analyze
", "{}" try: response = client.chat.completions.create( model=OPENAI_MODEL, messages=[ {"role": "system", "content": MEDICAL_ANTHROPOLOGIST_PROMPT}, {"role": "user", "content": f"Patient transcript:\n\n{transcription}"} ], response_format={"type": "json_object"}, temperature=0.7 ) json_text = response.choices[0].message.content parsed_json = json.loads(json_text) formatted_output = format_json_for_display(parsed_json) return formatted_output, json_text except Exception as e: import traceback error_html = f"""

❌ Error

{traceback.format_exc()}
""" return error_html, "{}" # ============================================================================ # JSON FORMATTING - 完整版本从 semantic_translator_mvp.py 复制 # ============================================================================ def format_json_for_display(data: Dict) -> str: """Format JSON into human-readable medical report""" html_parts = ['''
'''] # Debug section import json raw_json = json.dumps(data, indent=2, ensure_ascii=False) html_parts.append(f'''
🔍 Debug: Raw JSON
{raw_json}
''') # 1. Literal Translation if 'literal_translation' in data: html_parts.append(f'''

📝 Patient's Description

"{data['literal_translation']}"

''') # 2. Metaphor Mapping if 'metaphor_mapping' in data: metaphor = data['metaphor_mapping'] html_parts.append('''

🔗 Cultural Context

''') def render_value(val, indent=0): margin_left = indent * 20 if isinstance(val, dict): items = [] for k, v in val.items(): k_readable = k.replace('_', ' ').title() items.append(f'
{k_readable}:{render_value(v, indent+1)}
') return ''.join(items) elif isinstance(val, list): if not val: return 'None' items_html = '' return items_html else: return f'{str(val)}' html_parts.append(render_value(metaphor)) html_parts.append('
') # 3. McGill Pain Ontology if 'mcgill_pain_ontology' in data: mcgill = data['mcgill_pain_ontology'] html_parts.append('''

🏥 McGill Pain Assessment

''') field_icons = { 'location': '📍', 'temporal_pattern': '⏱️', 'intensity': '📊', 'quality_descriptors': '💭', 'associated_symptoms_to_query': '🔍', 'functional_impact_to_query': '🚶', 'pain_or_sensory_type': '🩺' } def render_mcgill(val, indent=1): margin_left = indent * 20 if isinstance(val, dict): items = [] for k, v in val.items(): k_readable = k.replace('_', ' ').title() items.append(f'
{k_readable}:{render_mcgill(v, indent+1)}
') return ''.join(items) elif isinstance(val, list): if not val: return 'None specified' return '' + ', '.join(str(v) for v in val) + '' else: return f'{str(val)}' if isinstance(mcgill, list): for item in mcgill: if isinstance(item, dict): for key, value in item.items(): key_readable = key.replace('_', ' ').title() icon = field_icons.get(key, '•') html_parts.append(f'
{icon} {key_readable}:{render_mcgill(value)}
') else: html_parts.append(f'

{str(item)}

') elif isinstance(mcgill, dict): for key, value in mcgill.items(): key_readable = key.replace('_', ' ').title() icon = field_icons.get(key, '•') html_parts.append(f'
{icon} {key_readable}:{render_mcgill(value)}
') else: html_parts.append(f'

{str(mcgill)}

') html_parts.append('
') # 4. Psychological Flags if 'psychological_and_stoicism_flags' in data: psych = data['psychological_and_stoicism_flags'] html_parts.append('''

🧠 Psychological Assessment

''') for key, value in psych.items(): key_readable = key.replace('_', ' ').title() if isinstance(value, dict): html_parts.append(f'

{key_readable}:

') for sub_key, sub_value in value.items(): sub_key_readable = sub_key.replace('_', ' ').title() html_parts.append(f'

• {sub_key_readable}: {sub_value}

') else: html_parts.append(f'

{key_readable}: {value}

') html_parts.append('
') # 5. Physician Action Note if 'physician_action_note' in data: html_parts.append(f'''

⚕️ Clinical Recommendations

{data['physician_action_note']}

''') html_parts.append('
') return ''.join(html_parts) # ============================================================================ # MAIN PROCESSING # ============================================================================ def process_patient_audio(audio) -> Tuple[str, str, str]: try: transcription, trans_status = transcribe_audio(audio) if "Error" in trans_status or not transcription.strip(): return trans_status, transcription, "
⚠️ Cannot analyze without transcription.
" formatted_html, json_output = analyze_with_llm(transcription) if "Error" in formatted_html: return "❌ Analysis failed", transcription, formatted_html return "✅ Analysis complete", transcription, formatted_html except Exception as e: import traceback error_html = f"""

❌ Unexpected Error

{traceback.format_exc()}
""" return "❌ Processing error", "Error during processing", error_html # ============================================================================ # GRADIO UI # ============================================================================ def create_ui(): with gr.Blocks(title="Medical AI Semantic Translator", theme=gr.themes.Soft()) as app: gr.Markdown(""" # 🏥 Module 1: Cross-Cultural Semantic Translator ### Translating Cultural Pain Metaphors into Medical Ontologies **Instructions:** Record your audio description, then click Analyze. """) status_output = gr.Textbox(label="Status", interactive=False, lines=1) with gr.Row(): with gr.Column(scale=1): gr.Markdown("### 🎤 Audio Input") audio_input = gr.Audio(sources=["microphone"], type="filepath", label="Record Your Pain Description") submit_btn = gr.Button("🔍 Analyze", variant="primary", size="lg") gr.Markdown("### 📄 Transcription") transcription_output = gr.Textbox(label="Whisper Transcription", interactive=False, lines=8) with gr.Column(scale=1): gr.Markdown("### 🤖 AI Medical Anthropologist Analysis") analysis_output = gr.HTML(value='
Analysis results will appear here...
') gr.Markdown(f""" --- **Configuration:** `API` mode | `{OPENAI_MODEL}` **Deployed on:** [Hugging Face Spaces](https://huggingface.co/spaces/DIrtyCha/Module1demo) """) submit_btn.click(fn=process_patient_audio, inputs=[audio_input], outputs=[status_output, transcription_output, analysis_output]) return app # ============================================================================ # MAIN # ============================================================================ if __name__ == "__main__": print("=" * 70) print("🚀 Medical AI Semantic Translator MVP") print("=" * 70) if not OPENAI_API_KEY: print("⚠️ WARNING: OPENAI_API_KEY not set!") print(" Go to Settings → Repository Secrets") else: print("✅ OpenAI API key loaded") print("=" * 70) app = create_ui() app.launch()