Spaces:
Sleeping
Sleeping
| """ | |
| generate_board_exam_question.py | |
| ------------------------------- | |
| Tool for generating comprehensive board exam style questions with detailed explanations. | |
| This tool creates long vignette-style multiple choice questions similar to those found in medical | |
| board exams, complete with detailed explanations for each answer choice that teach key concepts. | |
| Key Features: | |
| - Generates realistic clinical vignettes using OpenAI | |
| - Creates 4-5 multiple choice options | |
| - Provides detailed explanations for why each wrong answer is incorrect | |
| - Explains why the correct answer is right with educational content | |
| - Focuses on high-yield board exam topics | |
| - Uses AI-powered quality review for educational excellence | |
| """ | |
| import asyncio | |
| import json | |
| from typing import Any, Dict, List, Union, Optional | |
| from tools.base import Tool | |
| from tools.utils import ToolExecutionError, logger, load_prompt | |
| class GenerateBoardExamQuestionTool(Tool): | |
| """ | |
| Tool for generating comprehensive board exam style questions with detailed explanations. | |
| This tool creates realistic clinical vignettes with multiple choice questions and | |
| provides educational explanations for each answer choice. | |
| """ | |
| def __init__(self) -> None: | |
| """Initialize the GenerateBoardExamQuestionTool.""" | |
| super().__init__() | |
| self.name = "generate_board_exam_question" | |
| self.description = "Generate comprehensive board exam style questions with detailed explanations for medical education." | |
| self.args_schema = { | |
| "type": "object", | |
| "properties": { | |
| "topic": { | |
| "type": "string", | |
| "description": "The medical topic or condition to create a board exam question about (e.g., 'pneumonia', 'heart failure', 'diabetes management')" | |
| }, | |
| "difficulty_level": { | |
| "type": "string", | |
| "description": "The difficulty level of the question", | |
| "enum": ["medical_student", "resident", "board_exam", "advanced"], | |
| "default": "board_exam" | |
| }, | |
| "question_type": { | |
| "type": "string", | |
| "description": "The type of question to generate", | |
| "enum": ["diagnosis", "management", "pathophysiology", "pharmacology", "complications"], | |
| "default": "diagnosis" | |
| } | |
| }, | |
| "required": ["topic"] | |
| } | |
| def openai_spec(self, legacy=False): | |
| """Return OpenAI function specification.""" | |
| return { | |
| "name": self.name, | |
| "description": self.description, | |
| "parameters": self.args_schema | |
| } | |
| async def run( | |
| self, | |
| topic: str, | |
| difficulty_level: str = "board_exam", | |
| question_type: str = "diagnosis" | |
| ) -> Dict[str, Any]: | |
| """ | |
| Generate a comprehensive board exam question using a streamlined 2-step AI pipeline. | |
| Step 1: Generate question blueprint (differential, clues, reasoning strategy) | |
| Step 2: Draft initial question + critique + enhance in one combined step | |
| Args: | |
| topic (str): The medical topic to create a question about | |
| difficulty_level (str): The difficulty level (medical_student, resident, board_exam, advanced) | |
| question_type (str): The type of question (diagnosis, management, pathophysiology, pharmacology, complications) | |
| Returns: | |
| Dict[str, Any]: Complete board exam question with vignette, options, and explanations | |
| """ | |
| try: | |
| logger.info(f"Generating streamlined 2-step board exam question for topic: {topic}") | |
| # Step 1: Generate Question Blueprint (differential strategy and clues) | |
| blueprint = await self._generate_question_blueprint(topic, difficulty_level, question_type) | |
| # Step 2: Draft + Critique + Enhance in one combined step | |
| final_result = await self._draft_critique_enhance(blueprint, topic, difficulty_level, question_type) | |
| return final_result | |
| # Format the complete question | |
| complete_question = { | |
| "topic": topic, | |
| "difficulty_level": difficulty_level, | |
| "question_type": question_type, | |
| "blueprint": blueprint, | |
| "vignette": final_result["vignette"], | |
| "question_stem": final_result["question_stem"], | |
| "answer_choices": final_result["answer_choices"], | |
| "correct_answer": "A", # First choice is always correct in our format | |
| "explanations": final_result["explanations"], | |
| "enhancement_notes": final_result.get("enhancement_notes", ""), | |
| "clinical_reasoning_notes": "Streamlined 2-step AI generation with blueprint + critique", | |
| "question_level": "ID Fellowship Board-Level Difficulty", | |
| "generation_method": "2-step OpenAI pipeline: Blueprint + Draft/Critique/Enhance" | |
| } | |
| logger.info(f"Successfully generated 2-step board exam question for {topic}") | |
| return complete_question | |
| except Exception as e: | |
| logger.error(f"AI-powered GenerateBoardExamQuestionTool failed: {e}", exc_info=True) | |
| raise ToolExecutionError(f"Failed to generate AI board exam question: {e}") | |
| async def _generate_question_blueprint(self, topic: str, difficulty_level: str, question_type: str) -> Dict[str, Any]: | |
| """ | |
| Step 1: Generate the strategic blueprint for the question. | |
| Creates the differential diagnosis strategy, clues, and reasoning approach. | |
| """ | |
| try: | |
| logger.info(f"Generating question blueprint for {topic}") | |
| from core.utils.llm_connector import call_llm | |
| # Load the blueprint prompt template | |
| prompt = load_prompt("generate_question_blueprint.j2", | |
| topic=topic, | |
| difficulty_level=difficulty_level, | |
| question_type=question_type | |
| ) | |
| # Call OpenAI to generate the blueprint | |
| try: | |
| response = await asyncio.wait_for( | |
| call_llm(prompt), | |
| timeout=30.0 | |
| ) | |
| logger.info(f"Blueprint generated: {len(response) if response else 0} characters") | |
| except asyncio.TimeoutError: | |
| logger.warning("Blueprint generation timed out") | |
| return self._fallback_blueprint(topic) | |
| # Parse JSON response | |
| if not response or response.strip() == "": | |
| logger.warning("Empty blueprint response") | |
| return self._fallback_blueprint(topic) | |
| # Clean and parse response | |
| response = response.strip() | |
| if response.startswith("```json"): | |
| response = response[7:] | |
| if response.endswith("```"): | |
| response = response[:-3] | |
| blueprint = json.loads(response.strip()) | |
| return blueprint | |
| except Exception as e: | |
| logger.error(f"Error generating question blueprint: {str(e)}") | |
| return self._fallback_blueprint(topic) | |
| async def _draft_critique_enhance(self, blueprint: Dict[str, Any], topic: str, difficulty_level: str, question_type: str) -> Dict[str, Any]: | |
| """ | |
| Step 2: Draft the initial question, critique it, and enhance in one combined step. | |
| """ | |
| try: | |
| logger.info(f"Drafting, critiquing, and enhancing question for {topic}") | |
| from core.utils.llm_connector import call_llm | |
| # Load the combined draft+critique+enhance prompt template | |
| prompt = load_prompt("draft_critique_enhance_board_exam.j2", | |
| blueprint=blueprint, | |
| topic=topic, | |
| difficulty_level=difficulty_level, | |
| question_type=question_type | |
| ) | |
| # Call OpenAI for the combined draft+critique+enhance step | |
| try: | |
| response = await asyncio.wait_for( | |
| call_llm(prompt), | |
| timeout=45.0 # Longer timeout for combined step | |
| ) | |
| logger.info(f"Draft+Critique+Enhance completed: {len(response) if response else 0} characters") | |
| except asyncio.TimeoutError: | |
| logger.warning("Draft+Critique+Enhance timed out") | |
| return await self._fallback_question_generation(topic, difficulty_level, question_type) | |
| # Parse JSON response | |
| if not response or response.strip() == "": | |
| logger.warning("Empty draft+critique+enhance response") | |
| return await self._fallback_question_generation(topic, difficulty_level, question_type) | |
| # Clean and parse response | |
| response = response.strip() | |
| if response.startswith("```json"): | |
| response = response[7:] | |
| if response.endswith("```"): | |
| response = response[:-3] | |
| final_result = json.loads(response.strip()) | |
| return final_result | |
| except Exception as e: | |
| logger.error(f"Error in draft+critique+enhance: {str(e)}") | |
| return await self._fallback_question_generation(topic, difficulty_level, question_type) | |
| def _fallback_blueprint(self, topic: str) -> Dict[str, Any]: | |
| """Fallback blueprint if AI generation fails""" | |
| return { | |
| "scenario_description": f"Clinical presentation involving {topic}", | |
| "primary_diagnosis": topic, | |
| "differential_diagnoses": [ | |
| "Primary target condition", | |
| "Common alternative 1", | |
| "Common alternative 2", | |
| "Less likely option 1", | |
| "Less likely option 2" | |
| ], | |
| "diagnostic_clues": { | |
| "supporting_primary": ["Clinical finding 1", "Clinical finding 2", "Lab finding"], | |
| "misleading_clues": ["Distracting finding 1", "Distracting finding 2"] | |
| }, | |
| "reasoning_strategy": f"Question will test differential diagnosis skills for {topic}" | |
| } | |
| async def _fallback_question_generation(self, topic: str, difficulty_level: str, question_type: str) -> Dict[str, Any]: | |
| """Fallback question generation if AI steps fail""" | |
| return { | |
| "topic": topic, | |
| "vignette": f"A patient presents with clinical findings consistent with {topic}. Further evaluation is needed to determine the most appropriate diagnosis and management.", | |
| "question_stem": "What is the most likely diagnosis?", | |
| "answer_choices": [ | |
| topic, | |
| "Alternative diagnosis 1", | |
| "Alternative diagnosis 2", | |
| "Alternative diagnosis 3", | |
| "Alternative diagnosis 4" | |
| ], | |
| "explanations": { | |
| "correct": f"The clinical presentation is most consistent with {topic}.", | |
| "incorrect": "The other options are less likely given the clinical presentation." | |
| }, | |
| "enhancement_notes": "Fallback question generated due to AI processing error" | |
| } | |
| async def _search_medical_guidelines(self, topic: str, question_type: str) -> Dict[str, Any]: | |
| """ | |
| NEW Step 0: Search medical guidelines and evidence-based sources for the topic. | |
| This provides rich, current medical knowledge to inform question generation. | |
| """ | |
| try: | |
| logger.info(f"Searching medical guidelines for topic: {topic}") | |
| # Use internet search tool to find current medical guidelines | |
| from tools.internet_search import InternetSearchTool | |
| search_tool = InternetSearchTool() | |
| # Create focused search queries for medical guidelines | |
| guideline_queries = [ | |
| f"{topic} clinical practice guidelines 2024 2023", | |
| f"{topic} diagnosis guidelines AHA ACC ESC", | |
| f"{topic} management protocol evidence based medicine", | |
| f"{topic} differential diagnosis clinical criteria" | |
| ] | |
| guideline_findings = { | |
| "references": [], | |
| "summary": "", | |
| "key_findings": [], | |
| "diagnostic_criteria": [], | |
| "differential_points": [] | |
| } | |
| # Search for guidelines and evidence | |
| for query in guideline_queries[:2]: # Limit to 2 searches to avoid timeout | |
| try: | |
| logger.info(f"Searching: {query}") | |
| search_results = await search_tool.run(q=query, max_results=3) | |
| if search_results and isinstance(search_results, dict) and 'results' in search_results: | |
| for result in search_results['results'][:2]: # Top 2 results per query | |
| if any(source in result.get('href', '').lower() for source in | |
| ['guidelines', 'aha.org', 'acc.org', 'esc.org', 'uptodate', 'nejm', 'cochrane']): | |
| guideline_findings["references"].append({ | |
| "title": result.get('title', ''), | |
| "url": result.get('href', ''), | |
| "snippet": result.get('snippet', '') | |
| }) | |
| # Extract key findings from snippet | |
| snippet = result.get('snippet', '') | |
| if snippet: | |
| guideline_findings["key_findings"].append(snippet) | |
| except Exception as e: | |
| logger.warning(f"Search query failed: {query} - {e}") | |
| continue | |
| # Summarize findings | |
| if guideline_findings["key_findings"]: | |
| guideline_findings["summary"] = f"Found {len(guideline_findings['references'])} guideline sources for {topic}" | |
| logger.info(f"Successfully found {len(guideline_findings['references'])} guideline references") | |
| else: | |
| guideline_findings["summary"] = f"No specific guidelines found, using general medical knowledge for {topic}" | |
| logger.warning("No guideline sources found, will use general medical knowledge") | |
| return guideline_findings | |
| except Exception as e: | |
| logger.warning(f"Guideline search failed: {e}") | |
| # Return empty findings - the system will work with general knowledge | |
| return { | |
| "references": [], | |
| "summary": f"Guideline search unavailable, using general medical knowledge for {topic}", | |
| "key_findings": [], | |
| "diagnostic_criteria": [], | |
| "differential_points": [] | |
| } | |
| async def _generate_ai_comparison_table(self, topic: str, difficulty_level: str, question_type: str, guideline_findings: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: | |
| """ | |
| Step 1: Use OpenAI to generate a comprehensive comparison differential table. | |
| Now enhanced with evidence-based guideline findings. | |
| """ | |
| try: | |
| from core.utils.llm_connector import call_llm | |
| import asyncio | |
| logger.info(f"Loading prompt template for comparison table...") | |
| # Prepare guideline context for the prompt | |
| guideline_context = "" | |
| if guideline_findings and guideline_findings.get("key_findings"): | |
| guideline_context = f""" | |
| EVIDENCE-BASED CONTEXT from Medical Guidelines: | |
| {chr(10).join(guideline_findings["key_findings"][:3])} | |
| GUIDELINE SOURCES: | |
| {chr(10).join([f"- {ref.get('title', 'Unknown')}" for ref in guideline_findings.get("references", [])[:3]])} | |
| """ | |
| else: | |
| guideline_context = "No specific guidelines found. Use standard medical knowledge." | |
| prompt = load_prompt( | |
| "generate_comparison_table.j2", | |
| topic=topic, | |
| difficulty_level=difficulty_level, | |
| question_type=question_type, | |
| guideline_context=guideline_context | |
| ) | |
| logger.info(f"Prompt loaded successfully, making OpenAI API call...") | |
| # Call OpenAI with timeout to generate the comparison table | |
| try: | |
| response = await asyncio.wait_for( | |
| call_llm(prompt), | |
| timeout=30.0 # 30 second timeout | |
| ) | |
| logger.info(f"OpenAI response received: {len(response) if response else 0} characters") | |
| except asyncio.TimeoutError: | |
| logger.warning("OpenAI API call timed out after 30 seconds") | |
| return await self._fallback_comparison_table(topic, difficulty_level, question_type, guideline_findings) | |
| # Validate and parse the JSON response | |
| if not response or response.strip() == "": | |
| logger.warning("OpenAI returned empty response") | |
| return await self._fallback_comparison_table(topic, difficulty_level, question_type, guideline_findings) | |
| # Clean the response - remove markdown code blocks if present | |
| cleaned_response = response.strip() | |
| if cleaned_response.startswith("```json"): | |
| cleaned_response = cleaned_response[7:] # Remove ```json | |
| if cleaned_response.endswith("```"): | |
| cleaned_response = cleaned_response[:-3] # Remove ``` | |
| cleaned_response = cleaned_response.strip() | |
| try: | |
| comparison_data = json.loads(cleaned_response) | |
| logger.info("Successfully parsed OpenAI JSON response") | |
| return { | |
| "table": comparison_data, | |
| "generation_method": "AI-powered differential reasoning with evidence-based guidelines", | |
| "model_used": "OpenAI", | |
| "guideline_sources": guideline_findings.get("references", []) if guideline_findings else [], | |
| "evidence_summary": guideline_findings.get("summary", "") if guideline_findings else "" | |
| } | |
| except json.JSONDecodeError as e: | |
| logger.error(f"Failed to parse comparison table JSON: {e}") | |
| logger.error(f"Raw response: {response[:200]}...") | |
| logger.error(f"Cleaned response: {cleaned_response[:200]}...") | |
| # Fallback to deterministic method if JSON parsing fails | |
| return await self._fallback_comparison_table(topic, difficulty_level, question_type, guideline_findings) | |
| except Exception as e: | |
| logger.error(f"AI comparison table generation failed: {e}") | |
| # Fallback to deterministic method | |
| return await self._fallback_comparison_table(topic, difficulty_level, question_type, guideline_findings) | |
| async def _generate_ai_vignette_and_question(self, topic: str, difficulty_level: str, question_type: str, comparison_table: Dict[str, Any]) -> Dict[str, Any]: | |
| """ | |
| Step 2: Use OpenAI to generate complete vignette, question stem, answer choices, and explanations. | |
| Replaces the old deterministic steps 4-6 with AI-powered clinical scenario creation. | |
| """ | |
| try: | |
| from core.utils.llm_connector import call_llm | |
| import asyncio | |
| # Convert comparison table to JSON string for the prompt | |
| table_json = json.dumps(comparison_table.get("table", {}), indent=2) | |
| logger.info(f"Loading vignette prompt template...") | |
| prompt = load_prompt( | |
| "generate_board_exam_vignette.j2", | |
| topic=topic, | |
| difficulty_level=difficulty_level, | |
| question_type=question_type, | |
| comparison_table=table_json | |
| ) | |
| logger.info(f"Making OpenAI API call for vignette generation...") | |
| # Call OpenAI with timeout to generate the complete question | |
| try: | |
| response = await asyncio.wait_for( | |
| call_llm(prompt), | |
| timeout=45.0 # 45 second timeout for more complex generation | |
| ) | |
| logger.info(f"OpenAI vignette response received: {len(response) if response else 0} characters") | |
| except asyncio.TimeoutError: | |
| logger.warning("OpenAI vignette API call timed out after 45 seconds") | |
| return await self._fallback_vignette_generation(topic, comparison_table) | |
| # Validate and parse the JSON response | |
| if not response or response.strip() == "": | |
| logger.warning("OpenAI returned empty vignette response") | |
| return await self._fallback_vignette_generation(topic, comparison_table) | |
| # Clean the response - remove markdown code blocks if present | |
| cleaned_response = response.strip() | |
| if cleaned_response.startswith("```json"): | |
| cleaned_response = cleaned_response[7:] # Remove ```json | |
| if cleaned_response.endswith("```"): | |
| cleaned_response = cleaned_response[:-3] # Remove ``` | |
| cleaned_response = cleaned_response.strip() | |
| try: | |
| vignette_data = json.loads(cleaned_response) | |
| logger.info("Successfully parsed OpenAI vignette JSON response") | |
| return { | |
| **vignette_data, | |
| "generation_method": "AI-powered clinical scenario creation", | |
| "model_used": "OpenAI" | |
| } | |
| except json.JSONDecodeError as e: | |
| logger.error(f"Failed to parse vignette JSON: {e}") | |
| logger.error(f"Raw response: {response[:200]}...") | |
| logger.error(f"Cleaned response: {cleaned_response[:200]}...") | |
| # Fallback to deterministic method if JSON parsing fails | |
| return await self._fallback_vignette_generation(topic, comparison_table) | |
| except Exception as e: | |
| logger.error(f"AI vignette generation failed: {e}") | |
| # Fallback to deterministic method | |
| return await self._fallback_vignette_generation(topic, comparison_table) | |
| async def _ai_quality_review(self, topic: str, vignette_data: Dict[str, Any]) -> Dict[str, Any]: | |
| """ | |
| Step 3: Use OpenAI to perform comprehensive quality review of the generated question. | |
| Provides AI-powered assessment of clinical accuracy, educational value, and appropriateness. | |
| """ | |
| try: | |
| from core.utils.llm_connector import call_llm | |
| import asyncio | |
| logger.info(f"Loading quality review prompt template...") | |
| prompt = load_prompt( | |
| "quality_review_board_exam.j2", | |
| topic=topic, | |
| vignette=vignette_data.get("vignette", ""), | |
| question_stem=vignette_data.get("question_stem", ""), | |
| answer_choices=json.dumps(vignette_data.get("answer_choices", [])), | |
| explanations=json.dumps(vignette_data.get("explanations", {})) | |
| ) | |
| logger.info(f"Making OpenAI API call for quality review...") | |
| # Call OpenAI for quality assessment with timeout | |
| try: | |
| response = await asyncio.wait_for( | |
| call_llm(prompt), | |
| timeout=30.0 # 30 second timeout | |
| ) | |
| logger.info(f"OpenAI quality review response received: {len(response) if response else 0} characters") | |
| except asyncio.TimeoutError: | |
| logger.warning("OpenAI quality review API call timed out after 30 seconds") | |
| return self._fallback_quality_review(vignette_data) | |
| # Validate and parse the JSON response | |
| if not response or response.strip() == "": | |
| logger.warning("OpenAI returned empty quality review response") | |
| return self._fallback_quality_review(vignette_data) | |
| # Clean the response - remove markdown code blocks if present | |
| cleaned_response = response.strip() | |
| if cleaned_response.startswith("```json"): | |
| cleaned_response = cleaned_response[7:] # Remove ```json | |
| if cleaned_response.endswith("```"): | |
| cleaned_response = cleaned_response[:-3] # Remove ``` | |
| cleaned_response = cleaned_response.strip() | |
| try: | |
| quality_data = json.loads(cleaned_response) | |
| logger.info("Successfully parsed OpenAI quality review JSON response") | |
| return { | |
| **quality_data, | |
| "review_method": "AI-powered quality assessment", | |
| "model_used": "OpenAI" | |
| } | |
| except json.JSONDecodeError as e: | |
| logger.error(f"Failed to parse quality review JSON: {e}") | |
| logger.error(f"Raw response: {response[:200]}...") | |
| logger.error(f"Cleaned response: {cleaned_response[:200]}...") | |
| # Fallback to deterministic quality scoring | |
| return self._fallback_quality_review(vignette_data) | |
| except Exception as e: | |
| logger.error(f"AI quality review failed: {e}") | |
| # Fallback to deterministic quality scoring | |
| return self._fallback_quality_review(vignette_data) | |
| async def _ai_final_enhancement(self, topic: str, vignette_data: Dict[str, Any], quality_review: Dict[str, Any]) -> Dict[str, Any]: | |
| """ | |
| NEW Step 4: Final AI enhancement - Apply critique and ensure board-level ID fellowship difficulty. | |
| This step takes the quality review feedback and enhances the question to ensure: | |
| 1. Board-level difficulty appropriate for ID fellowship | |
| 2. Correct answer is not given away in the vignette | |
| 3. All critique points from quality review are addressed | |
| """ | |
| try: | |
| from core.utils.llm_connector import call_llm | |
| import asyncio | |
| logger.info(f"Loading final enhancement prompt template...") | |
| # Prepare quality review context | |
| quality_feedback = json.dumps(quality_review, indent=2) | |
| current_vignette = vignette_data.get("vignette", "") | |
| current_question = vignette_data.get("question_stem", "") | |
| current_choices = json.dumps(vignette_data.get("answer_choices", [])) | |
| current_explanations = json.dumps(vignette_data.get("explanations", {})) | |
| prompt = load_prompt( | |
| "final_enhancement_board_exam.j2", | |
| topic=topic, | |
| current_vignette=current_vignette, | |
| current_question=current_question, | |
| current_choices=current_choices, | |
| current_explanations=current_explanations, | |
| quality_feedback=quality_feedback | |
| ) | |
| logger.info(f"Making OpenAI API call for final enhancement...") | |
| # Call OpenAI for final enhancement with timeout | |
| try: | |
| response = await asyncio.wait_for( | |
| call_llm(prompt), | |
| timeout=45.0 # 45 second timeout for complex enhancement | |
| ) | |
| logger.info(f"OpenAI final enhancement response received: {len(response) if response else 0} characters") | |
| except asyncio.TimeoutError: | |
| logger.warning("OpenAI final enhancement API call timed out after 45 seconds") | |
| return self._fallback_final_enhancement(vignette_data, quality_review) | |
| # Validate and parse the JSON response | |
| if not response or response.strip() == "": | |
| logger.warning("OpenAI returned empty final enhancement response") | |
| return self._fallback_final_enhancement(vignette_data, quality_review) | |
| # Clean the response - remove markdown code blocks if present | |
| cleaned_response = response.strip() | |
| if cleaned_response.startswith("```json"): | |
| cleaned_response = cleaned_response[7:] # Remove ```json | |
| if cleaned_response.endswith("```"): | |
| cleaned_response = cleaned_response[:-3] # Remove ``` | |
| cleaned_response = cleaned_response.strip() | |
| try: | |
| enhanced_data = json.loads(cleaned_response) | |
| logger.info("Successfully parsed OpenAI final enhancement JSON response") | |
| return { | |
| **enhanced_data, | |
| "enhancement_method": "AI-powered final enhancement for ID board difficulty", | |
| "model_used": "OpenAI" | |
| } | |
| except json.JSONDecodeError as e: | |
| logger.error(f"Failed to parse final enhancement JSON: {e}") | |
| logger.error(f"Raw response: {response[:200]}...") | |
| logger.error(f"Cleaned response: {cleaned_response[:200]}...") | |
| # Fallback to original vignette if JSON parsing fails | |
| return self._fallback_final_enhancement(vignette_data, quality_review) | |
| except Exception as e: | |
| logger.error(f"AI final enhancement failed: {e}") | |
| # Fallback to original vignette | |
| return self._fallback_final_enhancement(vignette_data, quality_review) | |
| def _fallback_final_enhancement(self, vignette_data: Dict[str, Any], quality_review: Dict[str, Any]) -> Dict[str, Any]: | |
| """Fallback method for final enhancement when AI fails.""" | |
| logger.warning("Using fallback for final enhancement - returning original question with minor adjustments") | |
| # Return original with minor enhancement notes | |
| enhanced_data = { | |
| "vignette": vignette_data.get("vignette", ""), | |
| "question_stem": vignette_data.get("question_stem", ""), | |
| "answer_choices": vignette_data.get("answer_choices", []), | |
| "explanations": vignette_data.get("explanations", {}), | |
| "enhancement_notes": "Fallback enhancement: Original question maintained due to API limitations", | |
| "enhancement_method": "Fallback - no enhancement applied", | |
| "model_used": "Deterministic" | |
| } | |
| return enhanced_data | |
| async def _fallback_comparison_table(self, topic: str, difficulty_level: str, question_type: str, guideline_findings: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: | |
| """Fallback method for comparison table generation when AI fails.""" | |
| logger.warning("Using fallback deterministic comparison table generation") | |
| # Simple fallback - create a basic comparison table | |
| fallback_table = { | |
| "correct_diagnosis": topic, | |
| "differential_diagnoses": ["Alternative diagnosis 1", "Alternative diagnosis 2", "Alternative diagnosis 3", "Alternative diagnosis 4"], | |
| "comparison_criteria": ["Clinical presentation", "Laboratory findings", "Imaging findings", "Treatment response"] | |
| } | |
| # Include guideline references if available | |
| references = [] | |
| if guideline_findings and guideline_findings.get("references"): | |
| references = guideline_findings["references"] | |
| return { | |
| "table": fallback_table, | |
| "generation_method": "Fallback deterministic method", | |
| "model_used": "Deterministic", | |
| "guideline_sources": references, | |
| "evidence_summary": guideline_findings.get("summary", "") if guideline_findings else "" | |
| } | |
| async def _fallback_vignette_generation(self, topic: str, comparison_table: Dict[str, Any]) -> Dict[str, Any]: | |
| """Fallback method for vignette generation when AI fails.""" | |
| logger.warning("Using fallback deterministic vignette generation") | |
| # Simple fallback vignette | |
| fallback_vignette = { | |
| "vignette": f"A patient presents with symptoms consistent with {topic}. Clinical evaluation reveals relevant findings.", | |
| "question_stem": f"What is the most likely diagnosis?", | |
| "answer_choices": [ | |
| topic, # Correct answer | |
| "Alternative diagnosis 1", | |
| "Alternative diagnosis 2", | |
| "Alternative diagnosis 3", | |
| "Alternative diagnosis 4" | |
| ], | |
| "explanations": { | |
| "correct": f"The clinical presentation is most consistent with {topic}.", | |
| "incorrect": "The other options are less likely given the clinical presentation." | |
| } | |
| } | |
| return { | |
| **fallback_vignette, | |
| "generation_method": "Fallback deterministic method", | |
| "model_used": "Deterministic" | |
| } | |
| def _fallback_quality_review(self, vignette_data: Dict[str, Any]) -> Dict[str, Any]: | |
| """Fallback method for quality review when AI fails.""" | |
| logger.warning("Using fallback deterministic quality review") | |
| # Simple quality scoring | |
| return { | |
| "percentage_score": 75, | |
| "clinical_accuracy": "Adequate", | |
| "educational_value": "Moderate", | |
| "improvements_needed": ["Enhance clinical specificity", "Add more detailed explanations"], | |
| "review_method": "Fallback deterministic scoring", | |
| "model_used": "Deterministic" | |
| } | |
| def _identify_discriminating_features(self, topic: str, question_type: str) -> Dict[str, List[str]]: | |
| """ | |
| Identify 3 key discriminating features that establish the diagnosis. | |
| This ensures the question tests true clinical reasoning rather than pattern recognition. | |
| """ | |
| discriminating_features = { | |
| "diagnostic_clues": [], | |
| "differentials_ruled_out": [], | |
| "reasoning_pathway": [] | |
| } | |
| if "histoplasmosis" in topic.lower() or ("dimorphic" in topic.lower() and "histoplasma" in topic.lower()): | |
| discriminating_features = { | |
| "diagnostic_clues": [ | |
| "Geographic exposure to Ohio River Valley with cave/soil activities", | |
| "Bilateral hilar lymphadenopathy with multiple small pulmonary nodules", | |
| "Lymphopenia (characteristic vs. neutrophilia in bacterial infections)" | |
| ], | |
| "differentials_ruled_out": [ | |
| "Coccidioidomycosis (wrong geographic region, different antigen)", | |
| "Blastomycosis (typically unilateral, skin lesions, different morphology)", | |
| "Sarcoidosis (no geographic exposure, different antigen pattern)" | |
| ], | |
| "reasoning_pathway": [ | |
| "Endemic mycosis in appropriate geographic region", | |
| "Characteristic imaging pattern and immune response", | |
| "Specific laboratory confirmation with urine antigen" | |
| ] | |
| } | |
| elif "coccidioidomycosis" in topic.lower(): | |
| discriminating_features = { | |
| "diagnostic_clues": [ | |
| "Geographic exposure to southwestern US (Arizona, California)", | |
| "Erythema nodosum with arthralgias (Valley fever syndrome)", | |
| "Peripheral eosinophilia and elevated ESR" | |
| ], | |
| "differentials_ruled_out": [ | |
| "Histoplasmosis (wrong geographic region, no cave exposure)", | |
| "Community-acquired pneumonia (no eosinophilia, different imaging)", | |
| "Sarcoidosis (geographic exposure history, different serology)" | |
| ], | |
| "reasoning_pathway": [ | |
| "Desert southwest exposure with dust inhalation", | |
| "Characteristic immune-mediated manifestations", | |
| "Specific serologic and antigen testing" | |
| ] | |
| } | |
| elif "blastomycosis" in topic.lower(): | |
| discriminating_features = { | |
| "diagnostic_clues": [ | |
| "Geographic exposure to Great Lakes region or southeastern US", | |
| "Verrucous skin lesions with central ulceration", | |
| "Broad-based budding yeast on histology" | |
| ], | |
| "differentials_ruled_out": [ | |
| "Histoplasmosis (skin lesions rare, different morphology)", | |
| "Sporotrichosis (different lesion pattern, occupational exposure)", | |
| "Squamous cell carcinoma (histology shows organisms)" | |
| ], | |
| "reasoning_pathway": [ | |
| "Endemic region with outdoor recreational activities", | |
| "Characteristic cutaneous manifestations", | |
| "Distinctive microscopic morphology" | |
| ] | |
| } | |
| elif "candida auris" in topic.lower(): | |
| discriminating_features = { | |
| "diagnostic_clues": [ | |
| "Healthcare exposure with invasive devices (central lines, ventilators)", | |
| "Multi-drug resistance to fluconazole, amphotericin B, and echinocandins", | |
| "Rapid transmission in healthcare settings with environmental persistence" | |
| ], | |
| "differentials_ruled_out": [ | |
| "Candida albicans (typically fluconazole-sensitive, different MALDI-TOF pattern)", | |
| "Candida parapsilosis (usually echinocandin-sensitive, lower MIC patterns)", | |
| "Candida glabrata (different resistance profile, distinct molecular identification)" | |
| ], | |
| "reasoning_pathway": [ | |
| "High-risk healthcare environment with device exposure", | |
| "Distinctive antifungal resistance pattern requiring molecular ID", | |
| "Infection control implications requiring immediate isolation" | |
| ] | |
| } | |
| elif "pneumonia" in topic.lower(): | |
| discriminating_features = { | |
| "diagnostic_clues": [ | |
| "Positive urinary pneumococcal antigen with lobar consolidation", | |
| "Procalcitonin >2.0 ng/mL indicating bacterial etiology", | |
| "CURB-65 score elements for severity assessment" | |
| ], | |
| "differentials_ruled_out": [ | |
| "Atypical pneumonia (different imaging, lower procalcitonin)", | |
| "Viral pneumonia (no positive bacterial antigen)", | |
| "Pulmonary embolism (no consolidation, different biomarkers)" | |
| ], | |
| "reasoning_pathway": [ | |
| "Bacterial vs. atypical vs. viral etiology determination", | |
| "Severity assessment for treatment location", | |
| "Targeted antibiotic selection based on pathogen" | |
| ] | |
| } | |
| else: | |
| # Generic discriminating features | |
| discriminating_features = { | |
| "diagnostic_clues": [ | |
| f"Specific laboratory finding unique to {topic}", | |
| f"Characteristic imaging pattern for {topic}", | |
| f"Epidemiologic factor supporting {topic}" | |
| ], | |
| "differentials_ruled_out": [ | |
| f"Common differential #1 with different laboratory pattern", | |
| f"Common differential #2 with different imaging findings", | |
| f"Common differential #3 with different epidemiology" | |
| ], | |
| "reasoning_pathway": [ | |
| f"Recognition of {topic} pattern", | |
| f"Systematic differential diagnosis approach", | |
| f"Integration of clinical and laboratory data" | |
| ] | |
| } | |
| return discriminating_features | |
| def _analyze_clinical_context(self, topic: str, question_type: str, difficulty_level: str) -> Dict[str, Any]: | |
| """ | |
| Step 1: Medical Knowledge Reasoning | |
| Analyze the topic to understand its clinical context, pathophysiology, and key characteristics. | |
| This provides the foundation for creating clinically accurate content. | |
| """ | |
| clinical_context = { | |
| "condition_category": "", | |
| "pathophysiology": "", | |
| "key_clinical_features": [], | |
| "diagnostic_approach": "", | |
| "treatment_principles": "", | |
| "complications": [], | |
| "epidemiology": "", | |
| "board_exam_focus": "" | |
| } | |
| # Analyze specific conditions with medical reasoning | |
| if "aspergillus" in topic.lower(): | |
| if "niger" in topic.lower(): | |
| clinical_context = { | |
| "condition_category": "Invasive fungal infection - Aspergillosis", | |
| "pathophysiology": "Aspergillus niger causes invasive pulmonary aspergillosis in immunocompromised hosts, with angioinvasion leading to tissue necrosis and hemorrhage", | |
| "key_clinical_features": [ | |
| "Fever unresponsive to antibiotics in neutropenic patients", | |
| "Hemoptysis and pleuritic chest pain", | |
| "Rapid progression with tissue necrosis", | |
| "Elevated galactomannan antigen" | |
| ], | |
| "diagnostic_approach": "CT chest showing nodules with halo sign, galactomannan testing, tissue biopsy with septate hyphae", | |
| "treatment_principles": "Voriconazole or amphotericin B for invasive disease, requires prolonged therapy", | |
| "complications": ["Massive hemoptysis", "Disseminated infection", "CNS invasion"], | |
| "epidemiology": "Immunocompromised patients, particularly neutropenic patients and stem cell transplant recipients", | |
| "board_exam_focus": "Recognition in immunocompromised host, differentiation from other molds, antifungal selection" | |
| } | |
| else: | |
| # Generic Aspergillus | |
| clinical_context = { | |
| "condition_category": "Invasive fungal infection - Aspergillosis", | |
| "pathophysiology": "Aspergillus species cause spectrum from allergic reactions to invasive disease with angioinvasion", | |
| "key_clinical_features": [ | |
| "Varies by host: ABPA in asthmatics, aspergilloma in cavitary disease, invasive in immunocompromised", | |
| "Hemoptysis, cough, dyspnea", | |
| "Fever and systemic symptoms in invasive disease" | |
| ], | |
| "diagnostic_approach": "Imaging, galactomannan antigen, tissue diagnosis", | |
| "treatment_principles": "Voriconazole first-line for invasive, surgical resection for aspergilloma", | |
| "complications": ["Massive bleeding", "Respiratory failure", "Dissemination"], | |
| "epidemiology": "Ubiquitous environmental mold, opportunistic pathogen", | |
| "board_exam_focus": "Clinical syndrome recognition, diagnostic approach, treatment selection" | |
| } | |
| elif "candida auris" in topic.lower(): | |
| clinical_context = { | |
| "condition_category": "Emerging multidrug-resistant fungal pathogen", | |
| "pathophysiology": "C. auris causes invasive candidiasis with unique resistance mechanisms and environmental persistence", | |
| "key_clinical_features": [ | |
| "Healthcare-associated bloodstream infection", | |
| "Fever, hypotension, organ dysfunction", | |
| "Multi-drug resistance pattern", | |
| "Environmental contamination and transmission" | |
| ], | |
| "diagnostic_approach": "Blood cultures, molecular identification (MALDI-TOF often misidentifies), susceptibility testing", | |
| "treatment_principles": "Echinocandin first-line, infection control isolation mandatory", | |
| "complications": ["Endocarditis", "Endophthalmitis", "Healthcare outbreaks"], | |
| "epidemiology": "Healthcare settings, ICU patients with devices, international spread", | |
| "board_exam_focus": "Recognition of resistance pattern, infection control implications, treatment challenges" | |
| } | |
| elif "histoplasmosis" in topic.lower(): | |
| clinical_context = { | |
| "condition_category": "Endemic dimorphic fungal infection", | |
| "pathophysiology": "Histoplasma capsulatum causes pulmonary infection via inhalation, can disseminate in immunocompromised", | |
| "key_clinical_features": [ | |
| "Geographic exposure to Ohio/Mississippi River Valley", | |
| "Cave or soil exposure with bird/bat droppings", | |
| "Acute: fever, cough, weight loss, bilateral hilar lymphadenopathy", | |
| "Chronic: progressive pulmonary disease" | |
| ], | |
| "diagnostic_approach": "Urine histoplasma antigen, complement fixation, tissue diagnosis", | |
| "treatment_principles": "Itraconazole for moderate disease, amphotericin B for severe", | |
| "complications": ["Disseminated histoplasmosis", "Chronic pulmonary disease", "Mediastinal fibrosis"], | |
| "epidemiology": "Endemic to central US, spelunkers, construction workers", | |
| "board_exam_focus": "Geographic correlation, diagnostic testing, treatment duration" | |
| } | |
| elif "pneumonia" in topic.lower(): | |
| clinical_context = { | |
| "condition_category": "Community-acquired bacterial pneumonia", | |
| "pathophysiology": "Bacterial invasion of alveolar space causing inflammatory response and consolidation", | |
| "key_clinical_features": [ | |
| "Acute onset fever, productive cough, pleuritic pain", | |
| "Lobar consolidation on imaging", | |
| "Elevated inflammatory markers" | |
| ], | |
| "diagnostic_approach": "Clinical presentation, chest imaging, urinary antigens, cultures", | |
| "treatment_principles": "Empirical antibiotics based on severity and risk factors", | |
| "complications": ["Respiratory failure", "Sepsis", "Pleural effusion"], | |
| "epidemiology": "Common community infection, seasonal variation", | |
| "board_exam_focus": "Severity assessment, pathogen prediction, antibiotic selection" | |
| } | |
| else: | |
| # Generic analysis for unknown conditions | |
| clinical_context = { | |
| "condition_category": f"Medical condition: {topic}", | |
| "pathophysiology": f"Underlying disease mechanisms of {topic}", | |
| "key_clinical_features": [f"Primary manifestations of {topic}"], | |
| "diagnostic_approach": f"Diagnostic workup for {topic}", | |
| "treatment_principles": f"Evidence-based management of {topic}", | |
| "complications": [f"Potential complications of {topic}"], | |
| "epidemiology": f"Population and risk factors for {topic}", | |
| "board_exam_focus": f"Key clinical decision points for {topic}" | |
| } | |
| return clinical_context | |
| def _reason_differential_diagnoses(self, clinical_context: Dict[str, Any]) -> Dict[str, Any]: | |
| """ | |
| Step 2: Differential Diagnosis Reasoning | |
| Systematically identify the most clinically relevant differential diagnoses | |
| based on the condition category and clinical features. | |
| """ | |
| condition_category = clinical_context["condition_category"] | |
| key_features = clinical_context["key_clinical_features"] | |
| # Reasoning-based differential selection | |
| if "aspergillosis" in condition_category.lower(): | |
| differential_analysis = { | |
| "primary_differentials": [ | |
| { | |
| "condition": "Invasive pulmonary aspergillosis", | |
| "discriminating_features": ["Galactomannan positive", "Halo sign on CT", "Septate hyphae on biopsy"], | |
| "reasoning": "Most likely in neutropenic patient with characteristic imaging" | |
| }, | |
| { | |
| "condition": "Mucormycosis (Rhizopus)", | |
| "discriminating_features": ["Non-septate hyphae", "Tissue necrosis", "Diabetic ketoacidosis risk"], | |
| "reasoning": "Key differential in immunocompromised with angioinvasive mold infection" | |
| }, | |
| { | |
| "condition": "Bacterial pneumonia with lung abscess", | |
| "discriminating_features": ["Positive bacterial cultures", "Response to antibiotics", "Different imaging pattern"], | |
| "reasoning": "Must exclude bacterial cause before antifungal therapy" | |
| }, | |
| { | |
| "condition": "Pulmonary tuberculosis", | |
| "discriminating_features": ["AFB positive", "Cavitary lesions", "Different epidemiology"], | |
| "reasoning": "Important differential in endemic areas with cavitary disease" | |
| }, | |
| { | |
| "condition": "Lung cancer with secondary infection", | |
| "discriminating_features": ["Mass lesion", "Malignant cells on biopsy", "Progressive disease"], | |
| "reasoning": "Must consider malignancy in differential of pulmonary nodules" | |
| } | |
| ], | |
| "reasoning_framework": "Focus on immunocompromised host with angioinvasive infection patterns" | |
| } | |
| elif "candida auris" in condition_category.lower(): | |
| differential_analysis = { | |
| "primary_differentials": [ | |
| { | |
| "condition": "Candida auris candidemia", | |
| "discriminating_features": ["Multi-drug resistance", "MALDI-TOF misidentification", "Healthcare transmission"], | |
| "reasoning": "Emerging pathogen with unique resistance and transmission characteristics" | |
| }, | |
| { | |
| "condition": "Candida albicans candidemia", | |
| "discriminating_features": ["Fluconazole sensitivity", "Correct MALDI-TOF ID", "Standard resistance pattern"], | |
| "reasoning": "Most common Candida species, typically more susceptible" | |
| }, | |
| { | |
| "condition": "Candida glabrata candidemia", | |
| "discriminating_features": ["Fluconazole resistance", "Echinocandin sensitivity", "Predictable pattern"], | |
| "reasoning": "Known for azole resistance but different from C. auris" | |
| }, | |
| { | |
| "condition": "Bacterial sepsis (MRSA)", | |
| "discriminating_features": ["Gram-positive cocci", "Different antibiotic resistance", "No environmental persistence"], | |
| "reasoning": "Similar clinical presentation but different pathogen class" | |
| }, | |
| { | |
| "condition": "Central line-associated bloodstream infection", | |
| "discriminating_features": ["Line-related organism", "Responds to line removal", "Less organ dysfunction"], | |
| "reasoning": "Device-related infection with different management approach" | |
| } | |
| ], | |
| "reasoning_framework": "Focus on healthcare-associated resistant organisms with infection control implications" | |
| } | |
| elif "histoplasmosis" in condition_category.lower(): | |
| differential_analysis = { | |
| "primary_differentials": [ | |
| { | |
| "condition": "Acute pulmonary histoplasmosis", | |
| "discriminating_features": ["Ohio River Valley exposure", "Positive urine antigen", "Bilateral hilar LAD"], | |
| "reasoning": "Geographic and exposure history key to diagnosis" | |
| }, | |
| { | |
| "condition": "Coccidioidomycosis", | |
| "discriminating_features": ["Southwest US exposure", "Eosinophilia", "Spherules on histology"], | |
| "reasoning": "Different endemic mycosis with distinct geography" | |
| }, | |
| { | |
| "condition": "Blastomycosis", | |
| "discriminating_features": ["Great Lakes region", "Skin lesions", "Broad-based budding"], | |
| "reasoning": "Endemic mycosis with characteristic morphology and geography" | |
| }, | |
| { | |
| "condition": "Sarcoidosis", | |
| "discriminating_features": ["No geographic exposure", "Elevated ACE", "Non-caseating granulomas"], | |
| "reasoning": "Similar imaging pattern but different pathophysiology" | |
| }, | |
| { | |
| "condition": "Tuberculosis", | |
| "discriminating_features": ["AFB positive", "Different epidemiology", "Caseating granulomas"], | |
| "reasoning": "Important granulomatous disease differential" | |
| } | |
| ], | |
| "reasoning_framework": "Focus on endemic mycoses with geographic discrimination" | |
| } | |
| elif "pneumonia" in condition_category.lower(): | |
| differential_analysis = { | |
| "primary_differentials": [ | |
| { | |
| "condition": "Community-acquired pneumonia (S. pneumoniae)", | |
| "discriminating_features": ["Lobar consolidation", "Positive urinary antigen", "High procalcitonin"], | |
| "reasoning": "Most common CAP pathogen with characteristic presentation" | |
| }, | |
| { | |
| "condition": "Atypical pneumonia (Legionella)", | |
| "discriminating_features": ["Patchy infiltrates", "Hyponatremia", "Travel exposure"], | |
| "reasoning": "Different clinical syndrome and epidemiology" | |
| }, | |
| { | |
| "condition": "Viral pneumonia", | |
| "discriminating_features": ["Bilateral infiltrates", "Low procalcitonin", "Viral PCR positive"], | |
| "reasoning": "Non-bacterial etiology with different treatment" | |
| }, | |
| { | |
| "condition": "Healthcare-associated pneumonia", | |
| "discriminating_features": ["Resistant organisms", "Healthcare exposure", "Multilobar disease"], | |
| "reasoning": "Different risk factor profile and pathogen spectrum" | |
| }, | |
| { | |
| "condition": "Pulmonary embolism", | |
| "discriminating_features": ["No consolidation", "D-dimer elevated", "Travel/immobilization"], | |
| "reasoning": "Non-infectious cause of acute dyspnea and chest pain" | |
| } | |
| ], | |
| "reasoning_framework": "Focus on bacterial vs atypical vs viral etiologies with severity assessment" | |
| } | |
| else: | |
| # Generic differential reasoning | |
| differential_analysis = { | |
| "primary_differentials": [ | |
| {"condition": f"Primary diagnosis: {clinical_context['condition_category']}", "discriminating_features": ["Specific diagnostic features"], "reasoning": "Primary condition based on clinical context"}, | |
| {"condition": "Common differential #1", "discriminating_features": ["Alternative features"], "reasoning": "Important alternative diagnosis"}, | |
| {"condition": "Common differential #2", "discriminating_features": ["Different pattern"], "reasoning": "Must-consider differential"}, | |
| {"condition": "Common differential #3", "discriminating_features": ["Distinct characteristics"], "reasoning": "Key distinguishing diagnosis"}, | |
| {"condition": "Common differential #4", "discriminating_features": ["Alternative findings"], "reasoning": "Additional consideration"} | |
| ], | |
| "reasoning_framework": f"Systematic differential approach for {clinical_context['condition_category']}" | |
| } | |
| return differential_analysis | |
| def _generate_reasoned_comparison_table(self, clinical_context: Dict[str, Any], differential_analysis: Dict[str, Any]) -> Dict[str, Any]: | |
| """ | |
| Step 3: Generate a clinically sophisticated comparison table based on medical reasoning. | |
| Uses the clinical context and differential analysis to create specific, accurate comparisons. | |
| """ | |
| # Extract the primary differentials from reasoning | |
| differentials = differential_analysis["primary_differentials"] | |
| # Create detailed comparison table with clinical specificity | |
| comparison_table = {} | |
| for i, differential in enumerate(differentials): | |
| key = "correct_answer" if i == 0 else f"distractor_{i}" | |
| condition = differential["condition"] | |
| discriminating_features = differential["discriminating_features"] | |
| reasoning = differential["reasoning"] | |
| # Generate detailed clinical characteristics based on condition type | |
| if "aspergillus" in condition.lower(): | |
| if "invasive" in condition.lower(): | |
| comparison_table[key] = { | |
| "condition": condition, | |
| "clinical_presentation": "Fever unresponsive to antibiotics, hemoptysis, pleuritic chest pain in neutropenic patient", | |
| "epidemiology_risk_factors": "Neutropenia, hematologic malignancy, stem cell transplant, prolonged corticosteroids", | |
| "laboratory_findings": "Neutrophil count <500/μL, elevated galactomannan (>0.5), negative bacterial cultures", | |
| "imaging_characteristics": "Pulmonary nodules with halo sign on CT, cavitation in later stages", | |
| "diagnostic_tests": "Serum galactomannan positive, tissue biopsy shows septate hyphae with dichotomous branching", | |
| "treatment": "Voriconazole 6mg/kg IV q12h x2 then 4mg/kg q12h, or amphotericin B 5mg/kg/day", | |
| "reasoning": reasoning | |
| } | |
| else: | |
| comparison_table[key] = { | |
| "condition": condition, | |
| "clinical_presentation": "Variable based on syndrome - allergic, chronic, or invasive manifestations", | |
| "epidemiology_risk_factors": "Depends on host status and environmental exposure", | |
| "laboratory_findings": "May have eosinophilia in ABPA, galactomannan variable", | |
| "imaging_characteristics": "Aspergilloma shows 'air crescent sign', invasive shows nodules", | |
| "diagnostic_tests": "Specific testing depends on clinical syndrome", | |
| "treatment": "Syndrome-specific approach", | |
| "reasoning": reasoning | |
| } | |
| elif "mucormycosis" in condition.lower(): | |
| comparison_table[key] = { | |
| "condition": condition, | |
| "clinical_presentation": "Rapid onset fever, facial pain, black eschar, altered mental status", | |
| "epidemiology_risk_factors": "Diabetic ketoacidosis, neutropenia, iron overload, corticosteroids", | |
| "laboratory_findings": "Elevated glucose, acidosis, negative galactomannan", | |
| "imaging_characteristics": "Rapid progression, tissue necrosis, 'black turbinate sign'", | |
| "diagnostic_tests": "Tissue biopsy shows broad, non-septate hyphae with right-angle branching", | |
| "treatment": "High-dose amphotericin B 10mg/kg/day, urgent surgical debridement", | |
| "reasoning": reasoning | |
| } | |
| elif "candida auris" in condition.lower(): | |
| comparison_table[key] = { | |
| "condition": condition, | |
| "clinical_presentation": "Healthcare-associated fever, hypotension, multi-organ dysfunction", | |
| "epidemiology_risk_factors": "ICU stay >14 days, central venous catheter, broad-spectrum antibiotics, mechanical ventilation", | |
| "laboratory_findings": "Positive blood cultures, elevated lactate, multi-drug resistance pattern", | |
| "imaging_characteristics": "May show endophthalmitis, endocarditis vegetations, hepatosplenic candidiasis", | |
| "diagnostic_tests": "Molecular identification required (MALDI-TOF misidentifies), MIC testing shows resistance", | |
| "treatment": "Empirical echinocandin (micafungin 100mg/day), contact isolation protocols", | |
| "reasoning": reasoning | |
| } | |
| elif "candida albicans" in condition.lower(): | |
| comparison_table[key] = { | |
| "condition": condition, | |
| "clinical_presentation": "Fever, hypotension, but typically less severe organ dysfunction", | |
| "epidemiology_risk_factors": "Healthcare or community exposure, shorter duration of risk factors", | |
| "laboratory_findings": "Positive blood cultures, typically fluconazole-sensitive (MIC <2 μg/mL)", | |
| "imaging_characteristics": "Similar complications but lower frequency of metastatic seeding", | |
| "diagnostic_tests": "MALDI-TOF correctly identifies, standard antifungal susceptibility pattern", | |
| "treatment": "Fluconazole 800mg loading then 400mg daily, or echinocandin for severe disease", | |
| "reasoning": reasoning | |
| } | |
| elif "histoplasmosis" in condition.lower(): | |
| comparison_table[key] = { | |
| "condition": condition, | |
| "clinical_presentation": "Gradual onset fever, nonproductive cough, weight loss, fatigue", | |
| "epidemiology_risk_factors": "Ohio/Mississippi River Valley exposure, cave exploration, soil disturbance activities", | |
| "laboratory_findings": "Lymphopenia, elevated LDH, urine histoplasma antigen >10 ng/mL", | |
| "imaging_characteristics": "Bilateral hilar lymphadenopathy with multiple small pulmonary nodules", | |
| "diagnostic_tests": "Urine histoplasma antigen, complement fixation titers >1:32, tissue shows oval yeasts", | |
| "treatment": "Itraconazole 200mg BID for 6-12 weeks for moderate disease", | |
| "reasoning": reasoning | |
| } | |
| elif "coccidioidomycosis" in condition.lower(): | |
| comparison_table[key] = { | |
| "condition": condition, | |
| "clinical_presentation": "Fever, cough, pleuritic pain, arthralgias, erythema nodosum", | |
| "epidemiology_risk_factors": "Southwestern US exposure (Arizona, California), dust storm exposure, construction work", | |
| "laboratory_findings": "Eosinophilia >4%, elevated ESR, negative urine histoplasma antigen", | |
| "imaging_characteristics": "Often unilateral infiltrate, thin-walled cavities, hilar adenopathy", | |
| "diagnostic_tests": "Coccidioides IgM/IgG serology, spherules on tissue examination", | |
| "treatment": "Fluconazole 400mg daily for mild-moderate disease, amphotericin B for severe", | |
| "reasoning": reasoning | |
| } | |
| elif "pneumonia" in condition.lower() and "community" in condition.lower(): | |
| comparison_table[key] = { | |
| "condition": condition, | |
| "clinical_presentation": "Acute onset fever, productive cough with rust-colored sputum, pleuritic chest pain", | |
| "epidemiology_risk_factors": "Age >65, COPD, diabetes, recent travel (cruise ship), smoking history", | |
| "laboratory_findings": "Leukocytosis >12,000/μL with left shift, procalcitonin >2.0 ng/mL, positive urinary antigen", | |
| "imaging_characteristics": "Lobar consolidation with air bronchograms, typically unilateral", | |
| "diagnostic_tests": "Pneumococcal urinary antigen positive, blood cultures may grow S. pneumoniae", | |
| "treatment": "Ceftriaxone 2g IV daily plus azithromycin 500mg IV daily for hospitalized patients", | |
| "reasoning": reasoning | |
| } | |
| else: | |
| # Generic entry for unknown conditions | |
| comparison_table[key] = { | |
| "condition": condition, | |
| "clinical_presentation": f"Clinical features characteristic of {condition}", | |
| "epidemiology_risk_factors": f"Risk factors specific to {condition}", | |
| "laboratory_findings": f"Laboratory pattern for {condition}", | |
| "imaging_characteristics": f"Imaging findings in {condition}", | |
| "diagnostic_tests": f"Diagnostic approach for {condition}", | |
| "treatment": f"Treatment approach for {condition}", | |
| "reasoning": reasoning | |
| } | |
| return { | |
| "table": comparison_table, | |
| "reasoning_framework": differential_analysis["reasoning_framework"], | |
| "clinical_context": clinical_context["condition_category"], | |
| "correct_condition": comparison_table["correct_answer"]["condition"] | |
| } | |
| def _develop_vignette_strategy(self, comparison_table: Dict[str, Any], clinical_context: Dict[str, Any]) -> Dict[str, Any]: | |
| """ | |
| Step 4: Vignette Construction Reasoning | |
| Develop a strategic approach for creating a clinically complex vignette that has one clear correct answer | |
| while including enough complexity to challenge clinical reasoning. | |
| """ | |
| correct_answer = comparison_table["table"]["correct_answer"] | |
| distractors = [comparison_table["table"][f"distractor_{i}"] for i in range(1, 5)] | |
| # Strategy: Include 3-4 strong features from correct answer + 1-2 confounding features | |
| vignette_strategy = { | |
| "primary_discriminating_features": [], | |
| "confounding_features": [], | |
| "clinical_complexity_elements": [], | |
| "diagnostic_breadcrumbs": [], | |
| "reasoning_challenges": [] | |
| } | |
| # Select the 3-4 strongest discriminating features from correct answer | |
| if "epidemiology_risk_factors" in correct_answer: | |
| vignette_strategy["primary_discriminating_features"].append({ | |
| "category": "epidemiology", | |
| "feature": correct_answer["epidemiology_risk_factors"], | |
| "strength": "strong", | |
| "reasoning": "Key epidemiologic clue that points to correct diagnosis" | |
| }) | |
| if "laboratory_findings" in correct_answer: | |
| vignette_strategy["primary_discriminating_features"].append({ | |
| "category": "laboratory", | |
| "feature": correct_answer["laboratory_findings"], | |
| "strength": "strong", | |
| "reasoning": "Laboratory pattern specific to correct condition" | |
| }) | |
| if "imaging_characteristics" in correct_answer: | |
| vignette_strategy["primary_discriminating_features"].append({ | |
| "category": "imaging", | |
| "feature": correct_answer["imaging_characteristics"], | |
| "strength": "strong", | |
| "reasoning": "Imaging findings that distinguish correct diagnosis" | |
| }) | |
| if "diagnostic_tests" in correct_answer: | |
| vignette_strategy["primary_discriminating_features"].append({ | |
| "category": "diagnostic", | |
| "feature": correct_answer["diagnostic_tests"], | |
| "strength": "strong", | |
| "reasoning": "Specific diagnostic test results confirming diagnosis" | |
| }) | |
| # Add 1-2 confounding features from distractors to maintain difficulty | |
| for i, distractor in enumerate(distractors[:2]): | |
| if "clinical_presentation" in distractor: | |
| # Extract overlapping but non-specific symptoms | |
| presentation = distractor["clinical_presentation"] | |
| if "fever" in presentation.lower(): | |
| vignette_strategy["confounding_features"].append({ | |
| "feature": "fever", | |
| "source": distractor["condition"], | |
| "reasoning": "Non-specific symptom present in multiple conditions" | |
| }) | |
| if "cough" in presentation.lower() and len(vignette_strategy["confounding_features"]) < 2: | |
| vignette_strategy["confounding_features"].append({ | |
| "feature": "cough", | |
| "source": distractor["condition"], | |
| "reasoning": "Common respiratory symptom in multiple conditions" | |
| }) | |
| # Add clinical complexity elements based on condition type | |
| condition_category = clinical_context["condition_category"] | |
| if "fungal" in condition_category.lower() or "aspergillosis" in condition_category.lower(): | |
| vignette_strategy["clinical_complexity_elements"] = [ | |
| "Immunocompromised host status", | |
| "Timeline of symptom progression", | |
| "Response to initial antibiotics", | |
| "Specific risk factor exposure" | |
| ] | |
| elif "candida" in condition_category.lower(): | |
| vignette_strategy["clinical_complexity_elements"] = [ | |
| "Healthcare exposure duration", | |
| "Device-related factors", | |
| "Resistance pattern complexity", | |
| "Infection control implications" | |
| ] | |
| elif "pneumonia" in condition_category.lower(): | |
| vignette_strategy["clinical_complexity_elements"] = [ | |
| "Severity assessment criteria", | |
| "Pathogen probability factors", | |
| "Comorbidity influences", | |
| "Treatment response indicators" | |
| ] | |
| # Diagnostic breadcrumbs - subtle clues that guide toward correct answer | |
| vignette_strategy["diagnostic_breadcrumbs"] = [ | |
| "Specific temporal relationships", | |
| "Characteristic physical exam findings", | |
| "Laboratory value patterns", | |
| "Imaging evolution over time" | |
| ] | |
| # Reasoning challenges - elements that test clinical thinking | |
| vignette_strategy["reasoning_challenges"] = [ | |
| "Must integrate multiple data points", | |
| "Requires prioritization of findings", | |
| "Tests knowledge of disease mechanisms", | |
| "Challenges recognition of patterns" | |
| ] | |
| return vignette_strategy | |
| def _generate_reasoned_vignette(self, vignette_strategy: Dict[str, Any]) -> str: | |
| """ | |
| Step 5: Generate a clinically sophisticated vignette based on the strategic reasoning. | |
| Creates a complex but focused clinical scenario with one clear correct answer. | |
| """ | |
| # Extract key elements from strategy | |
| primary_features = vignette_strategy["primary_discriminating_features"] | |
| confounding_features = vignette_strategy["confounding_features"] | |
| complexity_elements = vignette_strategy["clinical_complexity_elements"] | |
| # Identify the condition type from primary features | |
| condition_indicators = [] | |
| for feature in primary_features: | |
| condition_indicators.append(feature["feature"].lower()) | |
| combined_indicators = " ".join(condition_indicators) | |
| # Generate condition-specific vignette | |
| if "neutropenia" in combined_indicators or "galactomannan" in combined_indicators: | |
| # Aspergillosis vignette | |
| vignette = ( | |
| "A 34-year-old man with acute myeloid leukemia is admitted for induction chemotherapy. " | |
| "On day 12 of hospitalization, he develops fever to 102.8°F (39.3°C) despite broad-spectrum " | |
| "antibiotics (piperacillin-tazobactam and vancomycin) for 48 hours. He reports new onset " | |
| "of right-sided pleuritic chest pain and has had two episodes of small-volume hemoptysis. " | |
| "Physical examination reveals an ill-appearing man with temperature 102.8°F, heart rate 115 bpm, " | |
| "blood pressure 110/65 mmHg, respiratory rate 24/min, and oxygen saturation 94% on 2L nasal cannula. " | |
| "Lung examination shows decreased breath sounds at the right base with dullness to percussion. " | |
| "Laboratory studies show: WBC 400/μL (normal 4,500-11,000) with 85% neutrophils, absolute neutrophil " | |
| "count 340/μL, platelet count 45,000/μL, and creatinine 1.2 mg/dL. Chest CT demonstrates a 2.5-cm " | |
| "right lower lobe nodule with surrounding ground-glass opacity ('halo sign') and a smaller left " | |
| "upper lobe nodule. Serum galactomannan index is 2.8 (normal <0.5). Blood cultures remain negative " | |
| "after 72 hours. The patient has no known drug allergies and has been receiving prophylactic " | |
| "fluconazole 400mg daily since admission." | |
| ) | |
| elif "icu" in combined_indicators and "resistance" in combined_indicators: | |
| # Candida auris vignette | |
| vignette = ( | |
| "A 67-year-old woman with end-stage renal disease on hemodialysis is admitted to the ICU " | |
| "following complications from abdominal surgery 18 days ago. She has required multiple " | |
| "invasive procedures including central venous catheter placement, mechanical ventilation, " | |
| "and broad-spectrum antibiotic therapy with vancomycin, meropenem, and fluconazole. " | |
| "On hospital day 20, she develops new fever to 101.6°F (38.7°C) and hypotension requiring " | |
| "vasopressor support. Physical examination reveals temperature 101.6°F, heart rate 125 bpm, " | |
| "blood pressure 85/45 mmHg on norepinephrine, and clear lungs. The central line insertion " | |
| "site appears clean without erythema. Laboratory studies show: WBC 14,200/μL with 78% neutrophils " | |
| "and 15% bands, lactate 3.8 mmol/L (normal <2.0), and procalcitonin 1.2 ng/mL. Blood cultures " | |
| "drawn from both central and peripheral sites grow yeast after 16 hours. The isolate demonstrates " | |
| "resistance to fluconazole (MIC >64 μg/mL) and intermediate resistance to amphotericin B (MIC 2 μg/mL). " | |
| "MALDI-TOF mass spectrometry reports 'Candida haemulonii' with low confidence score (1.6). " | |
| "The microbiology laboratory requests molecular identification due to the unusual resistance pattern." | |
| ) | |
| elif "ohio" in combined_indicators or "cave" in combined_indicators: | |
| # Histoplasmosis vignette | |
| vignette = ( | |
| "A 42-year-old construction worker from Cincinnati, Ohio, presents to the emergency department " | |
| "with a 4-week history of persistent nonproductive cough, low-grade fever, and 15-pound " | |
| "unintentional weight loss. He reports recent recreational spelunking activities at Mammoth Cave " | |
| "in Kentucky approximately 7 weeks ago with several friends. Initial symptoms began gradually " | |
| "2 weeks after the cave trip with fatigue and intermittent fever, progressing to persistent cough " | |
| "and night sweats. He denies chest pain initially but now reports mild bilateral chest discomfort. " | |
| "Physical examination reveals an afebrile man (temperature 99.8°F) with scattered tender erythematous " | |
| "nodules on both anterior shins and mild bilateral ankle swelling. Vital signs show heart rate 88 bpm, " | |
| "blood pressure 135/82 mmHg, respiratory rate 18/min, and oxygen saturation 96% on room air. " | |
| "Laboratory studies reveal: WBC 3,400/μL (normal 4,500-11,000) with 68% lymphocytes, ESR 82 mm/hr, " | |
| "and LDH 445 U/L (normal <250). Chest CT demonstrates bilateral hilar lymphadenopathy with multiple " | |
| "small (<1 cm) pulmonary nodules scattered throughout both lung fields. Urine Histoplasma antigen " | |
| "is 18.5 ng/mL (normal <0.6 ng/mL)." | |
| ) | |
| elif "cruise" in combined_indicators and "consolidation" in combined_indicators: | |
| # Pneumonia vignette | |
| vignette = ( | |
| "A 71-year-old man with COPD (FEV1 42% predicted) and well-controlled type 2 diabetes mellitus " | |
| "presents to the emergency department with a 36-hour history of acute onset productive cough " | |
| "with rust-colored sputum, right-sided pleuritic chest pain, and fever. He recently returned " | |
| "from a 10-day cruise to the Caribbean 4 days ago and felt well during the entire trip. " | |
| "Symptoms began abruptly yesterday evening with rigors and high fever, followed by productive " | |
| "cough and sharp chest pain that worsens with deep inspiration. He denies recent antibiotic use " | |
| "or hospitalization. Physical examination reveals an ill-appearing man with temperature 103.1°F " | |
| "(39.5°C), heart rate 118 bmp, blood pressure 125/78 mmHg, respiratory rate 28/min, and oxygen " | |
| "saturation 88% on room air improving to 95% on 3L nasal cannula. Lung examination shows dullness " | |
| "to percussion and bronchial breath sounds over the right lower lobe posteriorly. Laboratory studies " | |
| "reveal: WBC 17,200/μL with 86% neutrophils and 10% bands, procalcitonin 4.2 ng/mL (normal <0.1), " | |
| "lactate 1.9 mmol/L, and creatinine 1.1 mg/dL. Chest X-ray demonstrates right lower lobe consolidation " | |
| "with air bronchograms. Pneumococcal urinary antigen is positive. His calculated CURB-65 score is 2 " | |
| "(age >65, respiratory rate >30)." | |
| ) | |
| else: | |
| # Generic sophisticated vignette | |
| vignette = ( | |
| f"A patient with relevant clinical risk factors presents with characteristic symptoms and signs. " | |
| f"The presentation includes key discriminating findings from the primary features: " | |
| f"{', '.join([f['feature'] for f in primary_features[:2]])}. " | |
| f"Physical examination and diagnostic studies reveal significant findings that help establish " | |
| f"the diagnosis through systematic clinical reasoning. The case includes some overlapping features " | |
| f"that could suggest alternative diagnoses, requiring careful analysis of all discriminating elements." | |
| ) | |
| return vignette | |
| def _create_reasoned_question_stem(self, vignette: str, clinical_context: Dict[str, Any]) -> str: | |
| """ | |
| Step 6: Create a focused question stem that tests the specific clinical reasoning objective. | |
| """ | |
| condition_category = clinical_context["condition_category"] | |
| # Generate condition-appropriate question stems | |
| if "antifungal" in condition_category.lower() or "aspergillosis" in condition_category.lower(): | |
| question_stem = ( | |
| f"{vignette}\n\n" | |
| "Which of the following is the most appropriate next step in management?" | |
| ) | |
| elif "identification" in condition_category.lower() or "candida" in condition_category.lower(): | |
| question_stem = ( | |
| f"{vignette}\n\n" | |
| "Which of the following is the most likely causative organism?" | |
| ) | |
| elif "treatment" in condition_category.lower(): | |
| question_stem = ( | |
| f"{vignette}\n\n" | |
| "Which of the following is the most appropriate initial treatment?" | |
| ) | |
| elif "diagnosis" in condition_category.lower(): | |
| question_stem = ( | |
| f"{vignette}\n\n" | |
| "Which of the following is the most likely diagnosis?" | |
| ) | |
| else: | |
| question_stem = ( | |
| f"{vignette}\n\n" | |
| "Based on the clinical presentation and findings, which of the following is most appropriate?" | |
| ) | |
| return question_stem | |
| def _generate_reasoned_answer_choices(self, comparison_table: Dict[str, Any], clinical_context: Dict[str, Any]) -> List[str]: | |
| """ | |
| Step 7: Generate answer choices based on the comparison table with clinical reasoning. | |
| Each choice should represent a clinically plausible option with varying degrees of appropriateness. | |
| """ | |
| table = comparison_table["table"] | |
| condition_category = clinical_context["condition_category"] | |
| answer_choices = [] | |
| # Correct answer - format based on condition category | |
| correct_answer = table["correct_answer"] | |
| if "antifungal" in condition_category.lower(): | |
| if "aspergillosis" in correct_answer["condition"].lower(): | |
| answer_choices.append("Initiate voriconazole therapy") | |
| elif "candida" in correct_answer["condition"].lower(): | |
| answer_choices.append("Start micafungin therapy") | |
| else: | |
| answer_choices.append("Begin targeted antifungal therapy") | |
| elif "identification" in condition_category.lower(): | |
| answer_choices.append(correct_answer["condition"]) | |
| elif "treatment" in condition_category.lower(): | |
| treatment = correct_answer.get("treatment", "Appropriate targeted therapy") | |
| answer_choices.append(treatment) | |
| else: | |
| answer_choices.append(correct_answer["condition"]) | |
| # Generate distractor choices from comparison table | |
| for i in range(1, 5): | |
| distractor_key = f"distractor_{i}" | |
| if distractor_key in table: | |
| distractor = table[distractor_key] | |
| if "antifungal" in condition_category.lower(): | |
| if "bacterial" in distractor["condition"].lower(): | |
| answer_choices.append("Continue current antibiotic therapy") | |
| elif "viral" in distractor["condition"].lower(): | |
| answer_choices.append("Start antiviral therapy") | |
| elif "tuberculosis" in distractor["condition"].lower(): | |
| answer_choices.append("Initiate anti-tuberculosis therapy") | |
| else: | |
| answer_choices.append(f"Begin therapy for {distractor['condition']}") | |
| elif "identification" in condition_category.lower(): | |
| answer_choices.append(distractor["condition"]) | |
| elif "treatment" in condition_category.lower(): | |
| treatment = distractor.get("treatment", f"Treatment for {distractor['condition']}") | |
| answer_choices.append(treatment) | |
| else: | |
| answer_choices.append(distractor["condition"]) | |
| # Ensure we have exactly 5 choices | |
| while len(answer_choices) < 5: | |
| answer_choices.append("Alternative management approach") | |
| return answer_choices[:5] | |
| def _generate_reasoned_explanations(self, comparison_table: Dict[str, Any], clinical_context: Dict[str, Any]) -> Dict[str, str]: | |
| """ | |
| Step 8: Generate detailed explanations that demonstrate clinical reasoning for each answer choice. | |
| """ | |
| table = comparison_table["table"] | |
| correct_answer = table["correct_answer"] | |
| explanations = { | |
| "correct_explanation": "", | |
| "distractor_explanations": [] | |
| } | |
| # Correct answer explanation | |
| condition = correct_answer["condition"] | |
| key_features = [] | |
| if "epidemiology_risk_factors" in correct_answer: | |
| key_features.append(f"epidemiologic risk factors ({correct_answer['epidemiology_risk_factors']})") | |
| if "laboratory_findings" in correct_answer: | |
| key_features.append(f"laboratory findings ({correct_answer['laboratory_findings']})") | |
| if "diagnostic_tests" in correct_answer: | |
| key_features.append(f"diagnostic test results ({correct_answer['diagnostic_tests']})") | |
| if "imaging_characteristics" in correct_answer: | |
| key_features.append(f"imaging characteristics ({correct_answer['imaging_characteristics']})") | |
| explanations["correct_explanation"] = ( | |
| f"Correct. The clinical presentation is most consistent with {condition}. " | |
| f"Key supporting features include: {', '.join(key_features)}. " | |
| f"These findings in combination provide strong evidence for this diagnosis and guide appropriate management." | |
| ) | |
| # Distractor explanations | |
| for i in range(1, 5): | |
| distractor_key = f"distractor_{i}" | |
| if distractor_key in table: | |
| distractor = table[distractor_key] | |
| # Identify why this distractor is incorrect | |
| differences = [] | |
| if "epidemiology_risk_factors" in distractor and "epidemiology_risk_factors" in correct_answer: | |
| if distractor["epidemiology_risk_factors"] != correct_answer["epidemiology_risk_factors"]: | |
| differences.append(f"epidemiologic factors differ ({distractor['epidemiology_risk_factors']} vs expected {correct_answer['epidemiology_risk_factors']})") | |
| if "laboratory_findings" in distractor and "laboratory_findings" in correct_answer: | |
| if distractor["laboratory_findings"] != correct_answer["laboratory_findings"]: | |
| differences.append(f"laboratory pattern inconsistent ({distractor['laboratory_findings']} not consistent with findings)") | |
| if "diagnostic_tests" in distractor and "diagnostic_tests" in correct_answer: | |
| if distractor["diagnostic_tests"] != correct_answer["diagnostic_tests"]: | |
| differences.append(f"diagnostic test results do not support this diagnosis") | |
| explanation = ( | |
| f"Incorrect. While {distractor['condition']} could present with some similar features, " | |
| f"the clinical presentation is not consistent with this diagnosis. " | |
| f"Key differences include: {', '.join(differences[:2]) if differences else 'clinical pattern and diagnostic findings do not align with this condition'}." | |
| ) | |
| explanations["distractor_explanations"].append(explanation) | |
| return explanations | |
| def _clinical_quality_review(self, question_data: Dict[str, Any]) -> Dict[str, Any]: | |
| """ | |
| Step 9: Final clinical quality review and refinement. | |
| Reviews the complete question for clinical accuracy, educational value, and appropriate difficulty. | |
| """ | |
| review_results = { | |
| "clinical_accuracy_score": 0, | |
| "educational_value_score": 0, | |
| "difficulty_appropriateness": 0, | |
| "improvements_needed": [], | |
| "quality_passed": False | |
| } | |
| # Clinical accuracy assessment | |
| vignette = question_data.get("question_stem", "") | |
| answer_choices = question_data.get("answer_choices", []) | |
| clinical_accuracy_points = 0 | |
| # Check for specific clinical details (each worth 1 point, max 5) | |
| clinical_details = [ | |
| "specific lab values", | |
| "imaging findings", | |
| "epidemiologic factors", | |
| "temporal relationships", | |
| "physical exam findings" | |
| ] | |
| for detail in clinical_details: | |
| if any(keyword in vignette.lower() for keyword in detail.split()): | |
| clinical_accuracy_points += 1 | |
| review_results["clinical_accuracy_score"] = min(clinical_accuracy_points, 5) | |
| # Educational value assessment | |
| educational_points = 0 | |
| if len(answer_choices) == 5: | |
| educational_points += 1 | |
| if any("most appropriate" in choice.lower() for choice in [question_data.get("question_stem", "")]): | |
| educational_points += 1 | |
| if len(vignette) > 300: # Substantial clinical detail | |
| educational_points += 1 | |
| if "reasoning" in str(question_data.get("explanations", {})).lower(): | |
| educational_points += 1 | |
| review_results["educational_value_score"] = educational_points | |
| # Difficulty appropriateness (board exam level) | |
| difficulty_points = 0 | |
| if len(vignette) > 200: # Complex vignette | |
| difficulty_points += 1 | |
| if any(term in vignette.lower() for term in ["icu", "immunocompromised", "resistance", "multiple"]): | |
| difficulty_points += 1 | |
| if review_results["clinical_accuracy_score"] >= 3: | |
| difficulty_points += 1 | |
| review_results["difficulty_appropriateness"] = difficulty_points | |
| # Quality assessment | |
| total_score = ( | |
| review_results["clinical_accuracy_score"] + | |
| review_results["educational_value_score"] + | |
| review_results["difficulty_appropriateness"] | |
| ) | |
| if total_score >= 8: | |
| review_results["quality_passed"] = True | |
| else: | |
| if review_results["clinical_accuracy_score"] < 3: | |
| review_results["improvements_needed"].append("Increase clinical specificity and accuracy") | |
| if review_results["educational_value_score"] < 3: | |
| review_results["improvements_needed"].append("Enhance educational value and learning objectives") | |
| if review_results["difficulty_appropriateness"] < 2: | |
| review_results["improvements_needed"].append("Adjust difficulty to board exam level") | |
| return review_results | |
| def _generate_comparison_table(self, topic: str, question_type: str, difficulty_level: str) -> Dict[str, Any]: | |
| """ | |
| Generate a comprehensive comparison table with the correct answer and 4 plausible distractors. | |
| This systematic approach creates discriminating features for each option to enable | |
| sophisticated question generation that tests true clinical reasoning. | |
| Returns a table with 5 conditions and their distinguishing characteristics. | |
| """ | |
| # Define categories for comparison based on question type | |
| if question_type == "diagnosis": | |
| categories = [ | |
| "clinical_presentation", | |
| "epidemiology_risk_factors", | |
| "laboratory_findings", | |
| "imaging_characteristics", | |
| "diagnostic_tests" | |
| ] | |
| elif question_type == "management": | |
| categories = [ | |
| "first_line_treatment", | |
| "dosing_regimen", | |
| "duration_therapy", | |
| "monitoring_requirements", | |
| "contraindications" | |
| ] | |
| elif question_type == "pharmacology": | |
| categories = [ | |
| "mechanism_action", | |
| "absorption_distribution", | |
| "drug_interactions", | |
| "adverse_effects", | |
| "dosing_adjustments" | |
| ] | |
| else: | |
| categories = [ | |
| "pathophysiology", | |
| "clinical_features", | |
| "diagnostic_approach", | |
| "treatment_principles", | |
| "prognosis_complications" | |
| ] | |
| # Generate condition-specific comparison table | |
| if "candida auris" in topic.lower(): | |
| comparison_table = { | |
| "correct_answer": { | |
| "condition": "Candida auris invasive candidiasis", | |
| "clinical_presentation": "ICU patient with central line, fever, hypotension, multi-organ dysfunction", | |
| "epidemiology_risk_factors": "Healthcare exposure, invasive devices, broad-spectrum antibiotics, ICU stay >14 days", | |
| "laboratory_findings": "Blood cultures positive for yeast, elevated lactate, multi-drug resistance pattern", | |
| "imaging_characteristics": "May show endophthalmitis, endocarditis vegetations, or hepatosplenic lesions", | |
| "diagnostic_tests": "MALDI-TOF often misidentifies; requires molecular identification (PCR/sequencing)" | |
| }, | |
| "distractor_1": { | |
| "condition": "Candida albicans candidemia", | |
| "clinical_presentation": "Similar fever and hypotension but usually less severe organ dysfunction", | |
| "epidemiology_risk_factors": "Healthcare exposure but can occur in community settings, shorter ICU stays", | |
| "laboratory_findings": "Blood cultures positive, typically fluconazole-sensitive (MIC <2 μg/mL)", | |
| "imaging_characteristics": "Similar complications but lower frequency of metastatic seeding", | |
| "diagnostic_tests": "MALDI-TOF correctly identifies as C. albicans, standard susceptibility testing" | |
| }, | |
| "distractor_2": { | |
| "condition": "Candida glabrata candidemia", | |
| "clinical_presentation": "Often more indolent course, less acute organ dysfunction", | |
| "epidemiology_risk_factors": "Diabetes, advanced age, previous azole exposure", | |
| "laboratory_findings": "Fluconazole-resistant but echinocandin-sensitive, normal lactate often", | |
| "imaging_characteristics": "Lower rate of metastatic complications compared to C. auris", | |
| "diagnostic_tests": "MALDI-TOF correctly identifies, predictable resistance pattern" | |
| }, | |
| "distractor_3": { | |
| "condition": "Bacterial sepsis (MRSA)", | |
| "clinical_presentation": "Similar fever, hypotension, but may have skin/soft tissue source", | |
| "epidemiology_risk_factors": "Healthcare exposure, invasive devices, prior MRSA colonization", | |
| "laboratory_findings": "Blood cultures positive for gram-positive cocci, procalcitonin markedly elevated", | |
| "imaging_characteristics": "May show pneumonia, skin infections, or endocarditis", | |
| "diagnostic_tests": "Gram stain shows gram-positive cocci in clusters, rapid PCR available" | |
| }, | |
| "distractor_4": { | |
| "condition": "Candidemia due to central line infection", | |
| "clinical_presentation": "Fever temporally related to line access, may lack organ dysfunction", | |
| "epidemiology_risk_factors": "Recent line placement, total parenteral nutrition, immunosuppression", | |
| "laboratory_findings": "Blood cultures positive from line and peripheral sites", | |
| "imaging_characteristics": "May show line-associated thrombus or vegetation", | |
| "diagnostic_tests": "Standard Candida species identification, usually antifungal-sensitive" | |
| } | |
| } | |
| elif "histoplasmosis" in topic.lower() or ("dimorphic" in topic.lower() and "histoplasma" in topic.lower()): | |
| comparison_table = { | |
| "correct_answer": { | |
| "condition": "Acute pulmonary histoplasmosis", | |
| "clinical_presentation": "Fever, cough, weight loss after cave/soil exposure", | |
| "epidemiology_risk_factors": "Ohio/Mississippi River Valley, cave exploration, soil disturbance", | |
| "laboratory_findings": "Lymphopenia, elevated LDH, positive urine antigen", | |
| "imaging_characteristics": "Bilateral hilar lymphadenopathy with multiple small pulmonary nodules", | |
| "diagnostic_tests": "Urine Histoplasma antigen >10 ng/mL, complement fixation titers >1:32" | |
| }, | |
| "distractor_1": { | |
| "condition": "Coccidioidomycosis (Valley Fever)", | |
| "clinical_presentation": "Similar respiratory symptoms but with arthritis, erythema nodosum", | |
| "epidemiology_risk_factors": "Southwestern US (Arizona, California), dust exposure", | |
| "laboratory_findings": "Eosinophilia (>4%), elevated ESR, negative urine histoplasma antigen", | |
| "imaging_characteristics": "Often unilateral infiltrate, thin-walled cavities", | |
| "diagnostic_tests": "Coccidioides IgM/IgG, spherules on histology" | |
| }, | |
| "distractor_2": { | |
| "condition": "Blastomycosis", | |
| "clinical_presentation": "Pulmonary symptoms with characteristic skin lesions", | |
| "epidemiology_risk_factors": "Great Lakes region, outdoor activities near water", | |
| "laboratory_findings": "Normal lymphocyte count, negative urine histoplasma antigen", | |
| "imaging_characteristics": "Mass-like consolidation, often unilateral", | |
| "diagnostic_tests": "Broad-based budding yeasts on histology, Blastomyces antigen" | |
| }, | |
| "distractor_3": { | |
| "condition": "Sarcoidosis with Löfgren syndrome", | |
| "clinical_presentation": "Similar bilateral hilar lymphadenopathy with erythema nodosum", | |
| "epidemiology_risk_factors": "No specific geographic exposure, autoimmune predisposition", | |
| "laboratory_findings": "Normal lymphocytes, elevated ACE level, negative fungal antigens", | |
| "imaging_characteristics": "Bilateral hilar lymphadenopathy but without pulmonary nodules", | |
| "diagnostic_tests": "Elevated serum ACE, non-caseating granulomas on biopsy" | |
| }, | |
| "distractor_4": { | |
| "condition": "Hypersensitivity pneumonitis", | |
| "clinical_presentation": "Cough and dyspnea after organic dust exposure", | |
| "epidemiology_risk_factors": "Occupational/environmental organic dust exposure", | |
| "laboratory_findings": "Normal lymphocytes, negative fungal antigens", | |
| "imaging_characteristics": "Ground-glass opacities, upper lobe predominance", | |
| "diagnostic_tests": "Specific precipitating antibodies, lymphocytosis on BAL" | |
| } | |
| } | |
| elif "pneumonia" in topic.lower(): | |
| comparison_table = { | |
| "correct_answer": { | |
| "condition": "Community-acquired pneumonia (S. pneumoniae)", | |
| "clinical_presentation": "Acute onset fever, productive cough with rust-colored sputum, pleuritic pain", | |
| "epidemiology_risk_factors": "Age >65, COPD, diabetes, recent travel (cruise)", | |
| "laboratory_findings": "Leukocytosis with left shift, procalcitonin >2.0 ng/mL, positive urinary antigen", | |
| "imaging_characteristics": "Lobar consolidation with air bronchograms", | |
| "diagnostic_tests": "Positive pneumococcal urinary antigen, blood cultures may be positive" | |
| }, | |
| "distractor_1": { | |
| "condition": "Atypical pneumonia (Legionella)", | |
| "clinical_presentation": "Gradual onset, dry cough, GI symptoms, confusion", | |
| "epidemiology_risk_factors": "Recent travel, hotel/cruise exposure, older age", | |
| "laboratory_findings": "Hyponatremia, elevated LDH, lower procalcitonin (<1.0 ng/mL)", | |
| "imaging_characteristics": "Patchy infiltrates, often multilobar", | |
| "diagnostic_tests": "Legionella urinary antigen (type 1 only), respiratory PCR" | |
| }, | |
| "distractor_2": { | |
| "condition": "Viral pneumonia (Influenza)", | |
| "clinical_presentation": "Fever, myalgias, dry cough, gradual onset", | |
| "epidemiology_risk_factors": "Winter season, crowded conditions, unvaccinated", | |
| "laboratory_findings": "Normal or low WBC, low procalcitonin (<0.5 ng/mL)", | |
| "imaging_characteristics": "Bilateral interstitial infiltrates", | |
| "diagnostic_tests": "Respiratory viral PCR positive for influenza" | |
| }, | |
| "distractor_3": { | |
| "condition": "Healthcare-associated pneumonia (P. aeruginosa)", | |
| "clinical_presentation": "Similar symptoms but in hospitalized/recent healthcare exposure", | |
| "epidemiology_risk_factors": "Recent hospitalization, ventilator, broad-spectrum antibiotics", | |
| "laboratory_findings": "Leukocytosis, elevated procalcitonin, resistant organism pattern", | |
| "imaging_characteristics": "Often multilobar, necrotizing changes", | |
| "diagnostic_tests": "Sputum culture shows resistant gram-negative rods" | |
| }, | |
| "distractor_4": { | |
| "condition": "Pulmonary embolism", | |
| "clinical_presentation": "Acute dyspnea, pleuritic pain, but no productive cough", | |
| "epidemiology_risk_factors": "Recent travel, immobilization, hypercoagulable state", | |
| "laboratory_findings": "Elevated D-dimer, normal procalcitonin, no leukocytosis", | |
| "imaging_characteristics": "No consolidation, may show pleural effusion", | |
| "diagnostic_tests": "CT pulmonary angiogram shows filling defects" | |
| } | |
| } | |
| else: | |
| # Generic comparison table for any condition | |
| comparison_table = { | |
| "correct_answer": { | |
| "condition": f"Primary diagnosis: {topic}", | |
| "clinical_presentation": f"Classic presentation of {topic} with characteristic symptoms", | |
| "epidemiology_risk_factors": f"Typical risk factors and demographics for {topic}", | |
| "laboratory_findings": f"Laboratory pattern diagnostic for {topic}", | |
| "imaging_characteristics": f"Imaging findings pathognomonic for {topic}", | |
| "diagnostic_tests": f"Gold standard diagnostic test for {topic}" | |
| }, | |
| "distractor_1": { | |
| "condition": f"Common differential diagnosis #1", | |
| "clinical_presentation": f"Similar symptoms but key distinguishing features", | |
| "epidemiology_risk_factors": f"Different risk factor profile", | |
| "laboratory_findings": f"Laboratory pattern that rules out {topic}", | |
| "imaging_characteristics": f"Different imaging pattern", | |
| "diagnostic_tests": f"Different diagnostic test results" | |
| }, | |
| "distractor_2": { | |
| "condition": f"Common differential diagnosis #2", | |
| "clinical_presentation": f"Overlapping symptoms with subtle differences", | |
| "epidemiology_risk_factors": f"Alternative epidemiologic pattern", | |
| "laboratory_findings": f"Distinct laboratory abnormalities", | |
| "imaging_characteristics": f"Alternative imaging findings", | |
| "diagnostic_tests": f"Specific tests that distinguish from {topic}" | |
| }, | |
| "distractor_3": { | |
| "condition": f"Common differential diagnosis #3", | |
| "clinical_presentation": f"Similar initial presentation", | |
| "epidemiology_risk_factors": f"Different patient population", | |
| "laboratory_findings": f"Laboratory findings inconsistent with {topic}", | |
| "imaging_characteristics": f"Imaging that excludes {topic}", | |
| "diagnostic_tests": f"Testing that rules out {topic}" | |
| }, | |
| "distractor_4": { | |
| "condition": f"Common differential diagnosis #4", | |
| "clinical_presentation": f"Mimics {topic} but with key differences", | |
| "epidemiology_risk_factors": f"Distinct epidemiologic factors", | |
| "laboratory_findings": f"Different laboratory profile", | |
| "imaging_characteristics": f"Characteristic imaging for alternative diagnosis", | |
| "diagnostic_tests": f"Confirmatory tests for alternative condition" | |
| } | |
| } | |
| return { | |
| "table": comparison_table, | |
| "categories": categories, | |
| "correct_condition": comparison_table["correct_answer"]["condition"], | |
| "difficulty_level": difficulty_level | |
| } | |
| def _generate_sophisticated_vignette(self, topic: str, question_type: str, comparison_table: Dict[str, Any]) -> str: | |
| """ | |
| Generate a sophisticated clinical vignette using the comparison table. | |
| Strategy: | |
| 1. Use 3-4 key features from the correct answer | |
| 2. Include 1-2 features that could suggest distractors to maintain difficulty | |
| 3. Avoid giving away the answer while providing sufficient discriminating information | |
| """ | |
| correct_features = comparison_table["table"]["correct_answer"] | |
| distractors = [comparison_table["table"][f"distractor_{i}"] for i in range(1, 5)] | |
| # Select 3 strong discriminating features from correct answer | |
| key_correct_features = [] | |
| if "clinical_presentation" in correct_features: | |
| key_correct_features.append(correct_features["clinical_presentation"]) | |
| if "epidemiology_risk_factors" in correct_features: | |
| key_correct_features.append(correct_features["epidemiology_risk_factors"]) | |
| if "laboratory_findings" in correct_features: | |
| key_correct_features.append(correct_features["laboratory_findings"]) | |
| # Select 1-2 features that might suggest distractors (to maintain difficulty) | |
| confounding_features = [] | |
| for distractor in distractors[:2]: # Use first 2 distractors | |
| if "clinical_presentation" in distractor: | |
| # Extract a non-specific symptom that could apply to multiple conditions | |
| presentation = distractor["clinical_presentation"] | |
| if "fever" in presentation.lower() and "fever" not in key_correct_features[0].lower(): | |
| confounding_features.append("fever") | |
| elif "cough" in presentation.lower() and len(confounding_features) < 2: | |
| confounding_features.append("cough") | |
| # Construct vignette based on specific topic | |
| if "candida auris" in topic.lower(): | |
| vignette = ( | |
| f"A 72-year-old man with end-stage renal disease on hemodialysis develops fever and hypotension " | |
| f"48 hours after central venous catheter placement in the ICU. He has been hospitalized for " | |
| f"3 weeks following complicated abdominal surgery with multiple invasive procedures and " | |
| f"broad-spectrum antibiotic exposure. Physical examination reveals temperature 101.8°F (38.8°C), " | |
| f"heart rate 125 bpm, blood pressure 85/45 mmHg despite fluid resuscitation. The central line " | |
| f"insertion site appears clean without erythema. Laboratory studies show WBC 12,500/μL with " | |
| f"left shift, lactate 3.2 mmol/L, and creatinine 4.8 mg/dL (baseline 4.2 mg/dL). Blood cultures " | |
| f"drawn from both the central line and peripheral sites grow yeast after 18 hours. The organism " | |
| f"demonstrates resistance to fluconazole (MIC >64 μg/mL) and intermediate resistance to " | |
| f"amphotericin B (MIC 2 μg/mL). MALDI-TOF mass spectrometry initially reports the organism as " | |
| f"'C. haemulonii' with low confidence score. The microbiology laboratory requests molecular " | |
| f"identification due to the unusual resistance pattern and concern for an emerging pathogen." | |
| ) | |
| elif "histoplasmosis" in topic.lower(): | |
| vignette = ( | |
| f"A 45-year-old construction worker from Louisville, Kentucky, presents with a 3-week history of " | |
| f"nonproductive cough, low-grade fever, and 12-pound unintentional weight loss. He reports " | |
| f"recent recreational cave exploration activities in Mammoth Cave 6 weeks prior to symptom onset. " | |
| f"Initial symptoms began gradually with fatigue and low-grade fever, progressing to persistent " | |
| f"cough and night sweats. Physical examination reveals temperature 100.8°F (38.2°C), scattered " | |
| f"tender erythematous nodules on both shins, and mild bilateral ankle swelling. Vital signs " | |
| f"are otherwise stable except for mild tachypnea. Laboratory studies show WBC 3,100/μL " | |
| f"(normal 4,500-11,000) with 65% lymphocytes, ESR 75 mm/hr, and LDH 420 U/L (normal <250 U/L). " | |
| f"Chest CT demonstrates bilateral hilar lymphadenopathy with multiple small (<1 cm) pulmonary " | |
| f"nodules scattered throughout both lung fields. Urine Histoplasma antigen is 15.2 ng/mL " | |
| f"(normal <0.6 ng/mL). The patient denies immunocompromising conditions and has no significant " | |
| f"past medical history aside from seasonal allergies." | |
| ) | |
| elif "pneumonia" in topic.lower(): | |
| vignette = ( | |
| f"A 68-year-old man with COPD (FEV1 45% predicted) and well-controlled diabetes mellitus " | |
| f"presents to the emergency department with 48 hours of productive cough with rust-colored " | |
| f"sputum, right-sided pleuritic chest pain, and fever. He recently returned from a 7-day " | |
| f"cruise 5 days ago and reports feeling well during travel. Symptoms began abruptly with " | |
| f"rigors and high fever, followed by productive cough and sharp chest pain that worsens " | |
| f"with inspiration. Physical examination reveals an ill-appearing man with dullness to " | |
| f"percussion and bronchial breath sounds over the right lower lobe. Temperature 102.8°F " | |
| f"(39.3°C), heart rate 110 bpm, blood pressure 118/72 mmHg, respiratory rate 26/min, " | |
| f"oxygen saturation 89% on room air improving to 94% on 2L nasal cannula. Laboratory " | |
| f"studies show WBC 15,800/μL with 82% neutrophils and 8% bands, procalcitonin 3.8 ng/mL " | |
| f"(normal <0.1 ng/mL), and lactate 1.8 mmol/L. Chest X-ray shows right lower lobe " | |
| f"consolidation with air bronchograms. Pneumococcal urinary antigen is positive. " | |
| f"His CURB-65 score is calculated as 2 points (age >65, urea normal, RR >30, BP normal, confusion absent)." | |
| ) | |
| else: | |
| # Generic sophisticated vignette using comparison table features | |
| vignette = ( | |
| f"A patient with {correct_features.get('epidemiology_risk_factors', 'relevant risk factors')} " | |
| f"presents with {correct_features.get('clinical_presentation', 'characteristic symptoms')}. " | |
| f"Physical examination and initial studies reveal {correct_features.get('laboratory_findings', 'significant findings')}. " | |
| f"Imaging demonstrates {correct_features.get('imaging_characteristics', 'relevant abnormalities')}. " | |
| f"The clinical presentation includes some features that could suggest alternative diagnoses, " | |
| f"requiring careful analysis of the discriminating features to establish the correct diagnosis." | |
| ) | |
| return vignette | |
| def _create_advanced_question_stem_from_table(self, topic: str, question_type: str, comparison_table: Dict[str, Any]) -> str: | |
| """Create an advanced question stem based on the comparison table analysis.""" | |
| if question_type == "diagnosis": | |
| # Transform to higher-order management question | |
| if "candida auris" in topic.lower(): | |
| return "Given the antifungal resistance pattern and institutional infection control concerns, what is the most appropriate initial management approach?" | |
| elif "histoplasmosis" in topic.lower(): | |
| return "Based on the clinical presentation, epidemiologic factors, and diagnostic findings, what is the most appropriate treatment approach?" | |
| elif "pneumonia" in topic.lower(): | |
| return "Given the clinical severity assessment and microbiological findings, what is the most appropriate antibiotic management?" | |
| else: | |
| return "Based on the discriminating clinical features, what is the most appropriate next step in management?" | |
| elif question_type == "management": | |
| # Transform to monitoring/complications question | |
| if "candida auris" in topic.lower(): | |
| return "What is the most critical monitoring parameter during treatment of this multi-drug resistant organism?" | |
| elif "histoplasmosis" in topic.lower(): | |
| return "Which parameter should be monitored most closely during antifungal therapy for this condition?" | |
| elif "pneumonia" in topic.lower(): | |
| return "What is the most important clinical parameter to assess treatment response in this patient?" | |
| else: | |
| return "Which monitoring approach is most critical for optimizing treatment outcomes?" | |
| elif question_type == "pharmacology": | |
| # Transform to drug selection/interaction question | |
| return "Which pharmacologic consideration is most important when selecting therapy for this patient?" | |
| elif question_type == "pathophysiology": | |
| return "Based on the underlying pathophysiologic mechanism, why is the recommended approach most appropriate?" | |
| else: | |
| return "What is the most appropriate clinical decision based on the comparative analysis of this presentation?" | |
| def _generate_answer_choices_from_table(self, comparison_table: Dict[str, Any]) -> List[Dict[str, str]]: | |
| """Generate answer choices based on the comparison table.""" | |
| table = comparison_table["table"] | |
| choices = [] | |
| # Correct answer from the comparison table | |
| correct_condition = table["correct_answer"]["condition"] | |
| # Generate management-focused choices if this was originally a diagnosis question | |
| if "candida auris" in correct_condition.lower(): | |
| choice_texts = [ | |
| "Immediate empirical echinocandin therapy with infection control isolation", | |
| "Fluconazole therapy pending final susceptibility results", | |
| "Amphotericin B monotherapy for broad-spectrum coverage", | |
| "Combination antifungal therapy with fluconazole plus echinocandin", | |
| "Observation with repeat cultures in 48 hours" | |
| ] | |
| elif "histoplasmosis" in correct_condition.lower(): | |
| choice_texts = [ | |
| "Itraconazole 200 mg twice daily for 6-12 weeks", | |
| "Amphotericin B 0.7 mg/kg/day for 4-6 weeks", | |
| "Fluconazole 400 mg daily for 12 weeks", | |
| "Observation with repeat imaging in 4 weeks", | |
| "Empirical antibacterial therapy pending culture results" | |
| ] | |
| elif "pneumonia" in correct_condition.lower(): | |
| choice_texts = [ | |
| "Ceftriaxone 2g IV daily plus azithromycin 500mg IV daily", | |
| "Levofloxacin 750mg IV daily monotherapy", | |
| "Piperacillin-tazobactam 4.5g IV every 6 hours plus vancomycin", | |
| "Doxycycline 100mg twice daily", | |
| "Oseltamivir 75mg twice daily" | |
| ] | |
| else: | |
| # Generic choices based on the conditions in the table | |
| choice_texts = [ | |
| f"Treatment appropriate for {table['correct_answer']['condition']}", | |
| f"Treatment more suitable for {table['distractor_1']['condition']}", | |
| f"Treatment indicated for {table['distractor_2']['condition']}", | |
| f"Treatment appropriate for {table['distractor_3']['condition']}", | |
| f"Treatment suitable for {table['distractor_4']['condition']}" | |
| ] | |
| # Format as lettered choices | |
| letters = ["A", "B", "C", "D", "E"] | |
| for i, text in enumerate(choice_texts): | |
| choices.append({ | |
| "letter": letters[i], | |
| "text": text, | |
| "is_correct": i == 0 # First choice is always correct | |
| }) | |
| return choices | |
| def _generate_explanations_from_table(self, topic: str, question_type: str, comparison_table: Dict[str, Any], answer_choices: List[Dict]) -> Dict[str, str]: | |
| """Generate detailed explanations using the comparison table analysis.""" | |
| explanations = {} | |
| table = comparison_table["table"] | |
| for choice in answer_choices: | |
| letter = choice["letter"] | |
| text = choice["text"] | |
| is_correct = choice["is_correct"] | |
| if is_correct: | |
| correct_features = table["correct_answer"] | |
| if "candida auris" in topic.lower(): | |
| explanations[letter] = ( | |
| f"**CORRECT**: {text} is the appropriate approach. The clinical presentation suggests " | |
| f"C. auris invasive candidiasis based on: (1) {correct_features['epidemiology_risk_factors']}, " | |
| f"(2) {correct_features['laboratory_findings']}, and (3) {correct_features['diagnostic_tests']}. " | |
| f"C. auris requires immediate echinocandin therapy due to intrinsic fluconazole resistance and " | |
| f"frequent amphotericin B resistance. Infection control isolation is critical due to environmental " | |
| f"persistence and healthcare transmission risk. The resistance pattern and MALDI-TOF misidentification " | |
| f"are characteristic of this emerging pathogen." | |
| ) | |
| elif "histoplasmosis" in topic.lower(): | |
| explanations[letter] = ( | |
| f"**CORRECT**: {text} is the appropriate treatment. The clinical presentation confirms " | |
| f"acute pulmonary histoplasmosis based on: (1) {correct_features['epidemiology_risk_factors']}, " | |
| f"(2) {correct_features['imaging_characteristics']}, and (3) {correct_features['diagnostic_tests']}. " | |
| f"For moderate pulmonary disease with symptoms >4 weeks and weight loss, itraconazole is the " | |
| f"first-line oral therapy per IDSA guidelines. The combination of geographic exposure, bilateral " | |
| f"hilar lymphadenopathy, and positive urine antigen distinguishes this from other endemic mycoses." | |
| ) | |
| elif "pneumonia" in topic.lower(): | |
| explanations[letter] = ( | |
| f"**CORRECT**: {text} is the appropriate antibiotic regimen. The clinical presentation confirms " | |
| f"community-acquired pneumonia with: (1) {correct_features['clinical_presentation']}, " | |
| f"(2) {correct_features['laboratory_findings']}, and (3) {correct_features['diagnostic_tests']}. " | |
| f"The CURB-65 score of 2 indicates moderate severity requiring hospitalization. The combination " | |
| f"provides coverage for typical pathogens (ceftriaxone) and atypical pathogens (azithromycin) " | |
| f"as recommended by IDSA/ATS guidelines." | |
| ) | |
| else: | |
| explanations[letter] = ( | |
| f"**CORRECT**: {text} is appropriate based on the discriminating features: " | |
| f"{correct_features.get('clinical_presentation', '')} with " | |
| f"{correct_features.get('laboratory_findings', '')} and " | |
| f"{correct_features.get('diagnostic_tests', '')}." | |
| ) | |
| else: | |
| # Generate specific explanations for incorrect choices using comparison table | |
| choice_index = ord(letter) - ord('A') | |
| if choice_index <= 4: | |
| distractor_key = f"distractor_{choice_index}" if choice_index > 0 else "correct_answer" | |
| if distractor_key in table and choice_index > 0: | |
| distractor_features = table[distractor_key] | |
| explanations[letter] = ( | |
| f"**INCORRECT**: {text} would be more appropriate for " | |
| f"{distractor_features['condition']}. This condition differs because it typically presents with " | |
| f"{distractor_features.get('clinical_presentation', 'different clinical features')} and " | |
| f"{distractor_features.get('laboratory_findings', 'different laboratory findings')}. " | |
| f"The epidemiology ({distractor_features.get('epidemiology_risk_factors', 'different risk factors')}) " | |
| f"and diagnostic testing ({distractor_features.get('diagnostic_tests', 'different test results')}) " | |
| f"help distinguish this from the correct diagnosis." | |
| ) | |
| else: | |
| explanations[letter] = ( | |
| f"**INCORRECT**: {text} is not appropriate for this clinical scenario based on the " | |
| f"discriminating features presented in the vignette." | |
| ) | |
| else: | |
| explanations[letter] = f"**INCORRECT**: {text} is not indicated for this clinical presentation." | |
| return explanations | |
| def _generate_clinical_vignette_advanced(self, topic: str, difficulty_level: str, question_type: str, discriminating_features: Dict[str, List[str]]) -> str: | |
| """Generate a clinical vignette that includes discriminating features but doesn't give away the answer.""" | |
| if "histoplasmosis" in topic.lower() or ("dimorphic" in topic.lower() and "histoplasma" in topic.lower()): | |
| vignette = ( | |
| "A 45-year-old construction worker from Louisville, Kentucky, presents with a 3-week history of " | |
| "nonproductive cough, low-grade fever (100.8°F), and 12-pound unintentional weight loss. He reports " | |
| "recent recreational cave exploration activities 6 weeks prior to symptom onset. Physical examination " | |
| "reveals scattered tender erythematous nodules on both shins. Vital signs are stable except for mild " | |
| "tachypnea. Laboratory studies show WBC 3,100/μL (normal 4,500-11,000) with 65% lymphocytes. " | |
| "Chest CT demonstrates bilateral hilar lymphadenopathy with multiple small (<1 cm) pulmonary nodules " | |
| "scattered throughout both lung fields. The patient denies any known immunocompromising conditions " | |
| "and has no significant past medical history." | |
| ) | |
| elif "coccidioidomycosis" in topic.lower(): | |
| vignette = ( | |
| "A 28-year-old graduate student from Phoenix, Arizona, presents with a 2-week history of dry cough, " | |
| "pleuritic chest pain, and arthralgias affecting knees and ankles. She reports recent field research " | |
| "in the Sonoran Desert involving soil sampling. Physical examination reveals tender erythematous " | |
| "nodules on the anterior shins and mild ankle swelling. Temperature is 101.4°F with otherwise normal " | |
| "vital signs. Laboratory studies show WBC 8,200/μL with 12% eosinophils (normal <4%) and ESR 78 mm/hr. " | |
| "Chest X-ray shows a right lower lobe infiltrate with hilar prominence. The patient is otherwise " | |
| "healthy with no known drug allergies or significant medical history." | |
| ) | |
| elif "blastomycosis" in topic.lower(): | |
| vignette = ( | |
| "A 52-year-old avid fisherman from northern Wisconsin presents with a 6-week history of productive " | |
| "cough, low-grade fever, and a progressively enlarging skin lesion on his right forearm. He frequently " | |
| "camps near lake shores and clears fallen timber. Physical examination reveals a 3-cm verrucous plaque " | |
| "with central ulceration and rolled borders on the forearm. Pulmonary examination shows decreased " | |
| "breath sounds at the right base. Chest CT demonstrates a right lower lobe mass-like consolidation. " | |
| "Tissue biopsy of the skin lesion shows numerous thick-walled yeast forms with broad-based budding. " | |
| "The patient has diabetes mellitus type 2 but is otherwise healthy." | |
| ) | |
| elif "candida auris" in topic.lower(): | |
| vignette = ( | |
| "A 72-year-old man with end-stage renal disease on hemodialysis develops fever and hypotension 48 hours " | |
| "after central venous catheter placement in the ICU. He has been hospitalized for 3 weeks following " | |
| "complicated abdominal surgery with multiple invasive procedures. Blood cultures drawn from both the " | |
| "central line and peripheral sites grow yeast identified as Candida species. The organism demonstrates " | |
| "resistance to fluconazole (MIC >64 μg/mL) and intermediate resistance to amphotericin B (MIC 2 μg/mL). " | |
| "MALDI-TOF mass spectrometry initially misidentifies the organism as C. haemulonii. The microbiology " | |
| "laboratory requests molecular identification and antifungal susceptibility testing. The infection " | |
| "control team is notified due to concerns about a multi-drug resistant Candida species." | |
| ) | |
| elif "pneumonia" in topic.lower(): | |
| vignette = ( | |
| "A 68-year-old man with COPD (FEV1 45% predicted) and well-controlled diabetes presents with 48 hours " | |
| "of productive cough with rust-colored sputum, right-sided pleuritic chest pain, and fever. He recently " | |
| "returned from a 7-day cruise 5 days ago. Physical examination reveals dullness to percussion and " | |
| "bronchial breath sounds over the right lower lobe. Temperature 102.8°F, heart rate 110 bpm, blood " | |
| "pressure 118/72 mmHg, respiratory rate 26/min, oxygen saturation 89% on room air. Laboratory studies " | |
| "show WBC 15,800/μL with 82% neutrophils, procalcitonin 3.8 ng/mL. Chest X-ray shows right lower lobe " | |
| "consolidation with air bronchograms. His CURB-65 score is calculated as 2 points." | |
| ) | |
| else: | |
| # Generic advanced vignette | |
| vignette = ( | |
| f"A patient with appropriate risk factors for {topic} presents with characteristic clinical features. " | |
| f"The presentation includes key discriminating findings that help establish the diagnosis through " | |
| f"systematic clinical reasoning. Physical examination and initial diagnostic studies reveal findings " | |
| f"consistent with the suspected condition. Additional specialized testing is being considered to " | |
| f"confirm the diagnosis and guide management decisions." | |
| ) | |
| return vignette | |
| def _create_advanced_question_stem(self, topic: str, question_type: str, discriminating_features: Dict[str, List[str]]) -> str: | |
| """Create a higher-order question stem that tests clinical reasoning.""" | |
| if question_type == "diagnosis": | |
| # Upgrade to management question | |
| if "histoplasmosis" in topic.lower() or "dimorphic" in topic.lower(): | |
| return "Based on the clinical presentation and epidemiologic factors, what is the most appropriate initial treatment approach?" | |
| elif "pneumonia" in topic.lower(): | |
| return "Given the severity assessment and likely pathogen, what is the most appropriate antibiotic regimen?" | |
| else: | |
| return f"What is the most appropriate next step in management for this patient?" | |
| elif question_type == "management": | |
| # Upgrade to monitoring/complications question | |
| if "histoplasmosis" in topic.lower() or "dimorphic" in topic.lower(): | |
| return "After initiating the appropriate antifungal therapy, which laboratory parameter should be monitored most closely?" | |
| elif "pneumonia" in topic.lower(): | |
| return "What is the most important parameter to monitor for treatment response in this patient?" | |
| else: | |
| return f"Which monitoring parameter is most critical during treatment of this condition?" | |
| elif question_type == "pharmacology": | |
| # Upgrade to drug interactions/side effects question | |
| if "histoplasmosis" in topic.lower() or "dimorphic" in topic.lower(): | |
| return "What is the most important drug interaction to consider with the first-line oral antifungal therapy?" | |
| elif "pneumonia" in topic.lower(): | |
| return "Which adverse effect requires monitoring with the recommended β-lactam antibiotic?" | |
| else: | |
| return f"What is the most significant drug interaction concern with first-line therapy?" | |
| elif question_type == "pathophysiology": | |
| # Upgrade to mechanism-based treatment question | |
| return f"Based on the underlying pathophysiology, why is the recommended treatment approach most appropriate?" | |
| elif question_type == "complications": | |
| # Upgrade to prognostic factors question | |
| return f"Which factor most significantly influences the prognosis in this condition?" | |
| else: | |
| return f"What is the most appropriate clinical decision-making approach for this patient?" | |
| def _generate_advanced_answer_choices(self, topic: str, question_type: str, difficulty_level: str, discriminating_features: Dict[str, List[str]]) -> List[Dict[str, str]]: | |
| """Generate answer choices for higher-order questions.""" | |
| if "histoplasmosis" in topic.lower() or "dimorphic" in topic.lower(): | |
| if question_type == "diagnosis": # Now management question | |
| choices = [ | |
| "Itraconazole 200 mg twice daily for 6-12 weeks", | |
| "Amphotericin B 0.7 mg/kg/day for 4-6 weeks", | |
| "Fluconazole 400 mg daily for 12 weeks", | |
| "Observation with repeat imaging in 4 weeks", | |
| "Empirical antibacterial therapy with ceftriaxone and azithromycin" | |
| ] | |
| elif question_type == "management": # Now monitoring question | |
| choices = [ | |
| "Itraconazole serum levels after 2 weeks of therapy", | |
| "Complete blood count every 2 weeks", | |
| "Serum creatinine weekly", | |
| "Liver function tests monthly", | |
| "Chest imaging every 4 weeks" | |
| ] | |
| elif question_type == "pharmacology": # Drug interactions | |
| choices = [ | |
| "Proton pump inhibitors reducing itraconazole absorption", | |
| "Warfarin enhancing anticoagulation effects", | |
| "Statins increasing rhabdomyolysis risk", | |
| "Calcium channel blockers causing hypotension", | |
| "ACE inhibitors potentiating hyperkalemia" | |
| ] | |
| elif "pneumonia" in topic.lower(): | |
| if question_type == "diagnosis": # Now management question | |
| choices = [ | |
| "Ceftriaxone 2g IV daily plus azithromycin 500mg IV daily", | |
| "Levofloxacin 750mg IV daily monotherapy", | |
| "Piperacillin-tazobactam 4.5g IV every 6 hours", | |
| "Vancomycin plus cefepime", | |
| "Oseltamivir 75mg twice daily" | |
| ] | |
| elif question_type == "management": # Now monitoring question | |
| choices = [ | |
| "Clinical improvement within 48-72 hours", | |
| "White blood cell count normalization", | |
| "Chest X-ray clearing within 24 hours", | |
| "Procalcitonin reduction by 50% daily", | |
| "Oxygen saturation improvement within 6 hours" | |
| ] | |
| elif question_type == "pharmacology": # Drug interactions | |
| choices = [ | |
| "QTc prolongation with azithromycin", | |
| "Nephrotoxicity with ceftriaxone", | |
| "Hepatotoxicity with beta-lactams", | |
| "Ototoxicity with macrolides", | |
| "Photosensitivity with cephalosporins" | |
| ] | |
| else: | |
| # Generic advanced choices | |
| choices = [ | |
| f"Evidence-based first-line approach for {topic}", | |
| f"Alternative therapy with higher risk profile", | |
| f"Inappropriate therapy for this clinical scenario", | |
| f"Treatment with contraindication in this patient", | |
| f"Suboptimal therapy with inadequate coverage" | |
| ] | |
| # Format as lettered choices | |
| letters = ["A", "B", "C", "D", "E"] | |
| formatted_choices = [] | |
| for i, choice in enumerate(choices): | |
| formatted_choices.append({ | |
| "letter": letters[i], | |
| "text": choice, | |
| "is_correct": i == 0 # First choice is always correct | |
| }) | |
| return formatted_choices | |
| def _generate_advanced_explanations(self, topic: str, answer_choices: List[Dict], question_type: str, discriminating_features: Dict[str, List[str]]) -> Dict[str, str]: | |
| """Generate explanations that emphasize clinical reasoning.""" | |
| explanations = {} | |
| for choice in answer_choices: | |
| letter = choice["letter"] | |
| text = choice["text"] | |
| is_correct = choice["is_correct"] | |
| if is_correct: | |
| if "itraconazole" in text.lower() and "histoplasmosis" in topic.lower(): | |
| explanations[letter] = ( | |
| f"**CORRECT**: {text} is the appropriate treatment. The clinical presentation suggests acute " | |
| f"pulmonary histoplasmosis based on: (1) Geographic exposure to Ohio River Valley with cave activities, " | |
| f"(2) Bilateral hilar lymphadenopathy with multiple small nodules characteristic of histoplasmosis, and " | |
| f"(3) Lymphopenia rather than neutrophilia, which helps distinguish from bacterial infections. " | |
| f"For moderate pulmonary disease with symptoms lasting >4 weeks, itraconazole is the first-line " | |
| f"oral therapy per IDSA guidelines. Amphotericin B is reserved for severe or disseminated disease." | |
| ) | |
| elif "ceftriaxone" in text.lower() and "azithromycin" in text.lower(): | |
| explanations[letter] = ( | |
| f"**CORRECT**: {text} is the appropriate antibiotic regimen. The clinical presentation suggests " | |
| f"community-acquired pneumonia with: (1) Classic symptoms and consolidation pattern, " | |
| f"(2) CURB-65 score of 2 indicating moderate severity requiring hospitalization, and " | |
| f"(3) High procalcitonin (3.8 ng/mL) suggesting bacterial etiology. The combination provides " | |
| f"coverage for typical pathogens (ceftriaxone) and atypical pathogens (azithromycin) as " | |
| f"recommended by IDSA/ATS guidelines for hospitalized CAP patients." | |
| ) | |
| elif "itraconazole serum levels" in text.lower(): | |
| explanations[letter] = ( | |
| f"**CORRECT**: {text} is the most important monitoring parameter. Itraconazole has significant " | |
| f"pharmacokinetic variability and drug interactions that can affect absorption and metabolism. " | |
| f"Therapeutic levels (>1.0 mcg/mL) should be checked after 2 weeks of therapy to ensure adequate " | |
| f"exposure. Subtherapeutic levels are associated with treatment failure, while supratherapeutic " | |
| f"levels increase toxicity risk. Other monitoring is important but less critical for treatment success." | |
| ) | |
| elif "clinical improvement" in text.lower(): | |
| explanations[letter] = ( | |
| f"**CORRECT**: {text} is the most important parameter to monitor. Clinical response should occur " | |
| f"within 48-72 hours of appropriate antibiotic therapy, including improved vital signs, decreased " | |
| f"oxygen requirements, and reduced cough/sputum production. Lack of improvement suggests treatment " | |
| f"failure, resistant organism, or complications requiring reassessment. Laboratory parameters and " | |
| f"imaging may lag behind clinical improvement." | |
| ) | |
| else: | |
| explanations[letter] = ( | |
| f"**CORRECT**: {text} is the appropriate clinical decision based on the discriminating features " | |
| f"presented in this case and current evidence-based guidelines." | |
| ) | |
| else: | |
| # Generate specific incorrect explanations | |
| if "amphotericin" in text.lower() and "histoplasmosis" in topic.lower(): | |
| explanations[letter] = ( | |
| f"**INCORRECT**: {text} is inappropriate for this patient. Amphotericin B is reserved for " | |
| f"severe pulmonary histoplasmosis or disseminated disease. This patient has moderate disease " | |
| f"without signs of respiratory failure or dissemination. Starting with amphotericin exposes " | |
| f"the patient to unnecessary nephrotoxicity and infusion-related reactions when oral itraconazole " | |
| f"is equally effective for this severity." | |
| ) | |
| elif "observation" in text.lower(): | |
| explanations[letter] = ( | |
| f"**INCORRECT**: {text} is inappropriate. The patient has symptomatic disease lasting >4 weeks " | |
| f"with significant weight loss, indicating moderate severity that requires antifungal treatment. " | |
| f"Observation is only appropriate for asymptomatic patients or those with very mild, self-limited " | |
| f"disease. Untreated moderate disease can progress to chronic or disseminated forms." | |
| ) | |
| elif "levofloxacin" in text.lower() and "pneumonia" in topic.lower(): | |
| explanations[letter] = ( | |
| f"**INCORRECT**: {text} monotherapy is suboptimal. While levofloxacin covers both typical and " | |
| f"atypical pathogens, current IDSA/ATS guidelines recommend combination therapy (β-lactam plus " | |
| f"macrolide) for hospitalized CAP patients to improve outcomes. Monotherapy is reserved for " | |
| f"outpatients or specific clinical scenarios with contraindications to combination therapy." | |
| ) | |
| else: | |
| explanations[letter] = ( | |
| f"**INCORRECT**: {text} is not appropriate based on the clinical presentation and discriminating " | |
| f"features that establish the diagnosis and guide optimal management decisions." | |
| ) | |
| return explanations | |
| def _generate_clinical_vignette(self, topic: str, difficulty_level: str, question_type: str) -> str: | |
| """Generate a realistic clinical vignette with at least 3 diagnostic hints.""" | |
| # Enhanced vignette templates with specific diagnostic hints | |
| vignette_templates = { | |
| "pneumonia": { | |
| "patient": "A 65-year-old man with a history of COPD and diabetes mellitus", | |
| "presentation": "presents to the emergency department with a 3-day history of productive cough with purulent sputum, fever to 102.5°F (39.2°C), and shortness of breath", | |
| "exam": "On examination, he appears ill and has decreased breath sounds and crackles in the right lower lobe", | |
| "vitals": "Temperature 102.5°F (39.2°C), heart rate 110 bpm, blood pressure 140/85 mmHg, respiratory rate 24/min, oxygen saturation 88% on room air", | |
| "diagnostic_hints": [ | |
| "Chest X-ray shows a right lower lobe consolidation", | |
| "White blood cell count is 18,000/μL with 85% neutrophils and left shift", | |
| "Procalcitonin level is elevated at 2.5 ng/mL (normal <0.1 ng/mL)" | |
| ] | |
| }, | |
| "heart failure": { | |
| "patient": "A 72-year-old woman with a history of hypertension and coronary artery disease", | |
| "presentation": "presents with a 2-week history of progressive dyspnea on exertion, orthopnea, and bilateral lower extremity edema", | |
| "exam": "Physical examination reveals jugular venous distension, bilateral pulmonary rales, and 2+ pitting edema to the knees", | |
| "vitals": "Temperature 98.6°F (37°C), heart rate 95 bpm, blood pressure 160/95 mmHg, respiratory rate 20/min, oxygen saturation 92% on room air", | |
| "diagnostic_hints": [ | |
| "Echocardiogram shows ejection fraction of 35% with global hypokinesis", | |
| "B-type natriuretic peptide (BNP) is elevated at 1,200 pg/mL (normal <100 pg/mL)", | |
| "Chest X-ray demonstrates cardiomegaly with bilateral pulmonary vascular congestion" | |
| ] | |
| }, | |
| "diabetes": { | |
| "patient": "A 58-year-old obese man with a 10-year history of type 2 diabetes mellitus", | |
| "presentation": "presents for routine follow-up. He reports good adherence to metformin but continues to have elevated blood glucose readings", | |
| "exam": "Physical examination is notable for mild diabetic retinopathy on fundoscopy and diminished vibration sensation in both feet", | |
| "vitals": "Temperature 98.4°F (36.9°C), heart rate 78 bpm, blood pressure 145/90 mmHg, BMI 32 kg/m²", | |
| "diagnostic_hints": [ | |
| "Hemoglobin A1c is 9.2% (target <7%)", | |
| "Fasting glucose levels average 180-220 mg/dL over the past month", | |
| "Microalbumin-to-creatinine ratio is 45 mg/g (normal <30 mg/g), indicating early diabetic nephropathy" | |
| ] | |
| }, | |
| "sepsis": { | |
| "patient": "A 45-year-old woman with no significant medical history", | |
| "presentation": "presents to the emergency department with a 12-hour history of fever, chills, and altered mental status", | |
| "exam": "She appears acutely ill with warm, flushed skin and is confused to time and place", | |
| "vitals": "Temperature 101.8°F (38.8°C), heart rate 120 bpm, blood pressure 85/50 mmHg, respiratory rate 28/min, oxygen saturation 94% on room air", | |
| "diagnostic_hints": [ | |
| "Lactate level is 4.2 mmol/L (normal <2.0 mmol/L)", | |
| "White blood cell count is 18,500/μL with 20% immature forms (bands)", | |
| "Procalcitonin is markedly elevated at 8.5 ng/mL, and blood cultures are pending" | |
| ] | |
| }, | |
| "myocardial infarction": { | |
| "patient": "A 62-year-old man with a history of hypertension and smoking", | |
| "presentation": "presents with 2 hours of severe substernal chest pain radiating to his left arm, associated with diaphoresis and nausea", | |
| "exam": "He appears diaphoretic and anxious, with normal heart sounds and clear lung fields", | |
| "vitals": "Temperature 98.2°F (36.8°C), heart rate 95 bpm, blood pressure 150/90 mmHg, respiratory rate 18/min, oxygen saturation 98% on room air", | |
| "diagnostic_hints": [ | |
| "ECG shows ST-segment elevation in leads II, III, and aVF", | |
| "Troponin I is elevated at 8.5 ng/mL (normal <0.04 ng/mL)", | |
| "Echocardiogram reveals new wall motion abnormality in the inferior wall" | |
| ] | |
| } | |
| } | |
| # Get template or create enhanced generic one | |
| if topic.lower() in vignette_templates: | |
| template = vignette_templates[topic.lower()] | |
| else: | |
| template = self._create_enhanced_generic_template(topic, question_type) | |
| # Add question-type specific hints | |
| additional_hints = self._get_question_type_hints(topic, question_type) | |
| all_hints = template["diagnostic_hints"] + additional_hints | |
| # Ensure we have at least 3 hints | |
| if len(all_hints) < 3: | |
| all_hints.extend(self._generate_additional_hints(topic, question_type, 3 - len(all_hints))) | |
| # Add laboratory/imaging based on question type | |
| additional_info = "" | |
| if question_type == "diagnosis": | |
| additional_info = " Additional diagnostic studies are obtained to confirm the diagnosis." | |
| elif question_type == "management": | |
| additional_info = " The diagnosis has been established and treatment options are being considered." | |
| elif question_type == "pharmacology": | |
| additional_info = " Medication management is being optimized based on the clinical findings." | |
| # Combine into complete vignette with diagnostic hints | |
| vignette = f"{template['patient']} {template['presentation']}. {template['exam']}. Vital signs: {template['vitals']}. " | |
| # Add diagnostic hints | |
| for i, hint in enumerate(all_hints[:3], 1): # Use top 3 hints | |
| vignette += f"Laboratory/imaging finding {i}: {hint}. " | |
| vignette += additional_info | |
| return vignette | |
| def _create_enhanced_generic_template(self, topic: str, question_type: str) -> Dict[str, Any]: | |
| """Create an enhanced generic template with diagnostic hints.""" | |
| return { | |
| "patient": f"A 55-year-old patient with relevant medical history for {topic}", | |
| "presentation": f"presents with classic symptoms and signs consistent with {topic}", | |
| "exam": f"Physical examination reveals key findings suggestive of {topic}", | |
| "vitals": "Vital signs show relevant abnormalities for the condition", | |
| "diagnostic_hints": [ | |
| f"Key diagnostic test result #1 specific to {topic}", | |
| f"Key diagnostic test result #2 that confirms {topic}", | |
| f"Key diagnostic test result #3 that rules out alternatives to {topic}" | |
| ] | |
| } | |
| def _get_question_type_hints(self, topic: str, question_type: str) -> List[str]: | |
| """Get additional hints specific to the question type.""" | |
| hints = [] | |
| if question_type == "diagnosis": | |
| hints.extend([ | |
| "Imaging findings are consistent with the suspected diagnosis", | |
| "Biomarker levels support the clinical impression" | |
| ]) | |
| elif question_type == "management": | |
| hints.extend([ | |
| "Current vital signs indicate urgency of intervention", | |
| "Comorbidities influence treatment selection" | |
| ]) | |
| elif question_type == "pharmacology": | |
| hints.extend([ | |
| "Renal function affects medication dosing", | |
| "Drug interactions must be considered" | |
| ]) | |
| elif question_type == "pathophysiology": | |
| hints.extend([ | |
| "Underlying mechanism explains the clinical presentation", | |
| "Pathophysiologic process accounts for laboratory abnormalities" | |
| ]) | |
| return hints | |
| def _generate_additional_hints(self, topic: str, question_type: str, num_needed: int) -> List[str]: | |
| """Generate additional diagnostic hints when needed.""" | |
| generic_hints = [ | |
| f"Clinical course is typical for {topic}", | |
| f"Response to initial interventions supports {topic} diagnosis", | |
| f"Differential diagnosis considerations favor {topic}", | |
| f"Risk factors align with {topic} presentation", | |
| f"Timeline of symptoms is characteristic of {topic}" | |
| ] | |
| return generic_hints[:num_needed] | |
| def _create_question_stem(self, topic: str, question_type: str) -> str: | |
| """Create the question stem based on type.""" | |
| question_stems = { | |
| "diagnosis": f"What is the most likely diagnosis?", | |
| "management": f"What is the most appropriate next step in management?", | |
| "pathophysiology": f"What is the most likely underlying pathophysiology?", | |
| "pharmacology": f"What is the most appropriate medication for this patient?", | |
| "complications": f"What is the most likely complication to monitor for?" | |
| } | |
| return question_stems.get(question_type, f"What is the most appropriate approach for this patient with {topic}?") | |
| def _generate_answer_choices(self, topic: str, question_type: str, difficulty_level: str) -> List[Dict[str, str]]: | |
| """Generate 5 answer choices with the correct answer first, designed to have only one correct option based on vignette hints.""" | |
| # Enhanced topic-specific answer choices with clear distinctions | |
| choices_map = { | |
| "pneumonia": { | |
| "diagnosis": [ | |
| "Community-acquired pneumonia", | |
| "Pulmonary embolism", # Would not have consolidation, elevated WBC, or procalcitonin | |
| "Congestive heart failure", # Would not have fever, purulent sputum, or elevated procalcitonin | |
| "Chronic obstructive pulmonary disease exacerbation", # Would not have consolidation or elevated procalcitonin | |
| "Lung cancer" # Would not have acute fever, elevated WBC, or procalcitonin | |
| ], | |
| "management": [ | |
| "Start empirical antibiotic therapy and obtain cultures", | |
| "Prescribe bronchodilators only", # Inadequate for bacterial pneumonia with elevated procalcitonin | |
| "Immediate intubation", # Not indicated with current O2 sat and mental status | |
| "High-dose corticosteroids", # Not first-line for bacterial pneumonia | |
| "Observe without treatment" # Inappropriate with clear signs of bacterial infection | |
| ] | |
| }, | |
| "heart failure": { | |
| "diagnosis": [ | |
| "Acute decompensated heart failure", | |
| "Pneumonia", # Would not have elevated BNP, cardiomegaly, or reduced EF | |
| "Pulmonary embolism", # Would not have elevated BNP, cardiomegaly, or bilateral edema | |
| "Chronic kidney disease", # Would not have reduced EF or pulmonary congestion | |
| "Liver cirrhosis" # Would not have elevated BNP, reduced EF, or pulmonary congestion | |
| ], | |
| "management": [ | |
| "Diuretics and ACE inhibitor optimization", | |
| "Immediate cardiac catheterization", # Not indicated for chronic heart failure exacerbation | |
| "High-dose beta-blockers", # Inappropriate in acute decompensated state | |
| "Fluid resuscitation", # Contraindicated with volume overload | |
| "Antibiotics" # Not indicated without signs of infection | |
| ] | |
| }, | |
| "sepsis": { | |
| "diagnosis": [ | |
| "Sepsis with organ dysfunction", | |
| "Pneumonia without systemic involvement", # Would not have hypotension, altered mental status, or elevated lactate | |
| "Hypovolemic shock", # Would not have warm, flushed skin or elevated WBC with left shift | |
| "Cardiogenic shock", # Would not have warm skin, elevated WBC, or elevated procalcitonin | |
| "Anaphylactic shock" # Would not have fever, elevated WBC, or elevated procalcitonin | |
| ], | |
| "management": [ | |
| "Immediate IV antibiotics and fluid resuscitation", | |
| "Wait for culture results before antibiotics", # Inappropriate delay in sepsis | |
| "Vasopressors as first-line therapy", # Should try fluid resuscitation first | |
| "High-dose corticosteroids", # Not first-line for septic shock | |
| "Observation only" # Inappropriate with clear signs of sepsis | |
| ] | |
| }, | |
| "myocardial infarction": { | |
| "diagnosis": [ | |
| "ST-elevation myocardial infarction (STEMI)", | |
| "Unstable angina", # Would not have ST elevation or markedly elevated troponin | |
| "Pericarditis", # Would not have focal ST elevation or wall motion abnormality | |
| "Aortic dissection", # Would not have ST elevation or elevated troponin | |
| "Pulmonary embolism" # Would not have ST elevation in inferior leads or wall motion abnormality | |
| ], | |
| "management": [ | |
| "Immediate cardiac catheterization for primary PCI", | |
| "Medical management with anticoagulation only", # Inadequate for STEMI | |
| "Thrombolytic therapy", # PCI preferred when available within time window | |
| "Observation with serial troponins", # Inappropriate delay for STEMI | |
| "High-dose aspirin only" # Inadequate monotherapy for STEMI | |
| ] | |
| }, | |
| "diabetes": { | |
| "diagnosis": [ | |
| "Poorly controlled type 2 diabetes mellitus", | |
| "Type 1 diabetes mellitus", # Would typically present at younger age, different history | |
| "Gestational diabetes", # Not applicable to 58-year-old male | |
| "Secondary diabetes due to pancreatitis", # Would need history of pancreatitis | |
| "Prediabetes" # HbA1c 9.2% exceeds diagnostic threshold | |
| ], | |
| "management": [ | |
| "Intensify diabetes management with additional oral agent or insulin", | |
| "Continue metformin alone", # Inadequate given HbA1c 9.2% | |
| "Dietary modification only", # Insufficient for HbA1c 9.2% | |
| "Discontinue all medications", # Inappropriate and dangerous | |
| "Refer to endocrinology without changes" # Should initiate intensification while arranging referral | |
| ] | |
| } | |
| } | |
| # Get choices or create enhanced generic ones | |
| if topic.lower() in choices_map and question_type in choices_map[topic.lower()]: | |
| choice_list = choices_map[topic.lower()][question_type] | |
| else: | |
| choice_list = self._generate_enhanced_generic_choices(topic, question_type) | |
| # Format as lettered choices | |
| letters = ["A", "B", "C", "D", "E"] | |
| formatted_choices = [] | |
| for i, choice in enumerate(choice_list): | |
| formatted_choices.append({ | |
| "letter": letters[i], | |
| "text": choice, | |
| "is_correct": i == 0 # First choice is always correct | |
| }) | |
| return formatted_choices | |
| def _generate_enhanced_generic_choices(self, topic: str, question_type: str) -> List[str]: | |
| """Generate enhanced generic choices that are clearly distinguishable.""" | |
| if question_type == "diagnosis": | |
| return [ | |
| f"Correct diagnosis: {topic}", | |
| f"Common differential that lacks key diagnostic features", | |
| f"Alternative condition with different laboratory pattern", | |
| f"Condition with different imaging findings", | |
| f"Condition with different clinical course" | |
| ] | |
| elif question_type == "management": | |
| return [ | |
| f"Evidence-based first-line treatment for {topic}", | |
| f"Treatment appropriate for different condition", | |
| f"Treatment with contraindication in this case", | |
| f"Treatment that is premature or excessive", | |
| f"Treatment that is inadequate for severity" | |
| ] | |
| elif question_type == "pharmacology": | |
| return [ | |
| f"Appropriate medication choice for {topic}", | |
| f"Medication with contraindication in this patient", | |
| f"Medication inappropriate for this condition", | |
| f"Medication with significant drug interaction", | |
| f"Medication with inappropriate dosing" | |
| ] | |
| else: | |
| return [ | |
| f"Correct answer for {topic} {question_type}", | |
| f"Common misconception about {topic}", | |
| f"Less likely but possible option", | |
| f"Incorrect but plausible choice", | |
| f"Clearly incorrect option" | |
| ] | |
| def _generate_explanations(self, topic: str, answer_choices: List[Dict], question_type: str) -> Dict[str, str]: | |
| """Generate detailed explanations for each answer choice.""" | |
| explanations = {} | |
| for choice in answer_choices: | |
| letter = choice["letter"] | |
| text = choice["text"] | |
| is_correct = choice["is_correct"] | |
| if is_correct: | |
| explanations[letter] = f"**CORRECT**: {text} is the correct answer. This diagnosis fits the clinical presentation because [detailed explanation of why this is correct, including pathophysiology, clinical features, and supporting evidence]. Key teaching points include [relevant educational content about the condition, management, and important clinical pearls]." | |
| else: | |
| explanations[letter] = f"**INCORRECT**: {text} is incorrect because [detailed explanation of why this option is wrong, including how it differs from the correct diagnosis]. However, this is an important differential to consider because [educational content about this condition, when it should be considered, and key distinguishing features]." | |
| return explanations | |
| def _enhance_question_difficulty( | |
| self, | |
| topic: str, | |
| difficulty_level: str, | |
| question_type: str, | |
| vignette: str, | |
| question_stem: str, | |
| answer_choices: List[Dict[str, str]], | |
| explanations: Dict[str, str] | |
| ) -> Dict[str, Any]: | |
| """ | |
| Quality control method to enhance question difficulty and clinical accuracy. | |
| This method reviews the initial question and makes it more challenging while | |
| maintaining clinical accuracy, especially for infectious disease specialists. | |
| """ | |
| enhancement_notes = [] | |
| # Enhanced vignette with more specific clinical details | |
| enhanced_vignette = self._enhance_vignette_specificity(topic, vignette, difficulty_level) | |
| if enhanced_vignette != vignette: | |
| enhancement_notes.append("Enhanced vignette with more specific clinical details") | |
| # Enhanced answer choices with more nuanced distractors | |
| enhanced_choices = self._enhance_answer_choices_specificity(topic, answer_choices, question_type) | |
| if enhanced_choices != answer_choices: | |
| enhancement_notes.append("Enhanced answer choices with more specific distractors") | |
| # Enhanced explanations with more detailed clinical reasoning | |
| enhanced_explanations = self._enhance_explanations_depth(topic, enhanced_choices, question_type) | |
| return { | |
| "vignette": enhanced_vignette, | |
| "question_stem": question_stem, | |
| "answer_choices": enhanced_choices, | |
| "explanations": enhanced_explanations, | |
| "enhancement_notes": "; ".join(enhancement_notes) if enhancement_notes else "No enhancements needed" | |
| } | |
| def _enhance_vignette_specificity(self, topic: str, vignette: str, difficulty_level: str) -> str: | |
| """Enhance vignette with more specific clinical details for higher difficulty.""" | |
| # Topic-specific enhancements for infectious disease focus | |
| if "dimorphic fungi" in topic.lower() or "fungal" in topic.lower(): | |
| # Make it more specific for ID specialists | |
| enhanced_vignette = ( | |
| "A 45-year-old construction worker from the Ohio River Valley presents with a 3-week history of " | |
| "fever, nonproductive cough, and 15-pound weight loss. He reports recent spelunking activities " | |
| "in Kentucky caves 6 weeks ago. Physical examination reveals scattered erythematous nodules on " | |
| "the shins consistent with erythema nodosum, and bilateral hilar lymphadenopathy on chest imaging. " | |
| "Temperature 101.2°F (38.4°C), heart rate 88 bpm, blood pressure 135/80 mmHg, respiratory rate 20/min, " | |
| "oxygen saturation 94% on room air. Laboratory findings: WBC 3,200/μL with lymphopenia, " | |
| "ESR 85 mm/hr, and LDH 420 U/L. Chest CT shows bilateral hilar lymphadenopathy with multiple " | |
| "small pulmonary nodules. Histoplasma urine antigen is positive at 15.2 ng/mL (normal <0.6 ng/mL). " | |
| "Serum complement fixation titers for Histoplasma are elevated at 1:32 for both mycelial and yeast forms." | |
| ) | |
| return enhanced_vignette | |
| elif "pneumonia" in topic.lower(): | |
| # Make pneumonia more challenging for ID specialists | |
| enhanced_vignette = ( | |
| "A 68-year-old man with COPD (FEV1 45% predicted) and diabetes mellitus presents with acute onset " | |
| "of productive cough with rust-colored sputum, pleuritic chest pain, and fever for 48 hours. " | |
| "He recently returned from a cruise ship 5 days ago. Physical examination reveals dullness to " | |
| "percussion and bronchial breath sounds over the right lower lobe. Vital signs: temperature 102.8°F " | |
| "(39.3°C), heart rate 115 bpm, blood pressure 110/70 mmHg, respiratory rate 28/min, oxygen saturation " | |
| "89% on room air. Laboratory findings: WBC 16,800/μL with 85% neutrophils and 12% bands, procalcitonin " | |
| "4.2 ng/mL, lactate 2.8 mmol/L. Chest X-ray shows right lower lobe consolidation with air bronchograms. " | |
| "Urinary pneumococcal antigen is positive, and blood cultures are pending. His CURB-65 score is 2." | |
| ) | |
| return enhanced_vignette | |
| # If no specific enhancement available, return original | |
| return vignette | |
| def _enhance_answer_choices_specificity(self, topic: str, answer_choices: List[Dict[str, str]], question_type: str) -> List[Dict[str, str]]: | |
| """Enhance answer choices with more specific, challenging distractors.""" | |
| if "dimorphic fungi" in topic.lower() or "fungal" in topic.lower(): | |
| if question_type == "diagnosis": | |
| enhanced_choices = [ | |
| {"letter": "A", "text": "Acute pulmonary histoplasmosis", "is_correct": True}, | |
| {"letter": "B", "text": "Chronic pulmonary coccidioidomycosis", "is_correct": False}, | |
| {"letter": "C", "text": "Pulmonary blastomycosis", "is_correct": False}, | |
| {"letter": "D", "text": "Sarcoidosis with Löfgren syndrome", "is_correct": False}, | |
| {"letter": "E", "text": "Hypersensitivity pneumonitis", "is_correct": False} | |
| ] | |
| return enhanced_choices | |
| elif question_type == "management": | |
| enhanced_choices = [ | |
| {"letter": "A", "text": "Itraconazole 200 mg twice daily for 6-12 weeks", "is_correct": True}, | |
| {"letter": "B", "text": "Amphotericin B 0.7 mg/kg/day for 2 weeks", "is_correct": False}, | |
| {"letter": "C", "text": "Fluconazole 400 mg daily for 12 weeks", "is_correct": False}, | |
| {"letter": "D", "text": "Voriconazole 200 mg twice daily for 8 weeks", "is_correct": False}, | |
| {"letter": "E", "text": "Observation without antifungal therapy", "is_correct": False} | |
| ] | |
| return enhanced_choices | |
| elif "pneumonia" in topic.lower(): | |
| if question_type == "diagnosis": | |
| enhanced_choices = [ | |
| {"letter": "A", "text": "Community-acquired pneumonia (CAP) with Streptococcus pneumoniae", "is_correct": True}, | |
| {"letter": "B", "text": "Healthcare-associated pneumonia (HCAP) with Pseudomonas aeruginosa", "is_correct": False}, | |
| {"letter": "C", "text": "Atypical pneumonia with Legionella pneumophila", "is_correct": False}, | |
| {"letter": "D", "text": "Viral pneumonia with influenza A", "is_correct": False}, | |
| {"letter": "E", "text": "Aspiration pneumonia with anaerobic bacteria", "is_correct": False} | |
| ] | |
| return enhanced_choices | |
| elif question_type == "management": | |
| enhanced_choices = [ | |
| {"letter": "A", "text": "Ceftriaxone 2g IV daily plus azithromycin 500mg IV daily", "is_correct": True}, | |
| {"letter": "B", "text": "Piperacillin-tazobactam 4.5g IV every 6 hours", "is_correct": False}, | |
| {"letter": "C", "text": "Levofloxacin 750mg IV daily monotherapy", "is_correct": False}, | |
| {"letter": "D", "text": "Vancomycin 15mg/kg IV every 12 hours plus cefepime 2g IV every 8 hours", "is_correct": False}, | |
| {"letter": "E", "text": "Doxycycline 100mg PO twice daily", "is_correct": False} | |
| ] | |
| return enhanced_choices | |
| # Return original if no specific enhancement | |
| return answer_choices | |
| def _enhance_explanations_depth(self, topic: str, answer_choices: List[Dict[str, str]], question_type: str) -> Dict[str, str]: | |
| """Generate more detailed, clinically sophisticated explanations.""" | |
| explanations = {} | |
| for choice in answer_choices: | |
| letter = choice["letter"] | |
| text = choice["text"] | |
| is_correct = choice["is_correct"] | |
| if is_correct: | |
| if "histoplasmosis" in text.lower(): | |
| explanations[letter] = ( | |
| f"**CORRECT**: {text} is the correct diagnosis. This patient's presentation is classic for " | |
| f"acute pulmonary histoplasmosis following cave exposure in an endemic area (Ohio River Valley). " | |
| f"Key diagnostic features include: (1) Geographic risk factor with recent spelunking in Kentucky, " | |
| f"(2) Constellation of fever, weight loss, and erythema nodosum, (3) Bilateral hilar lymphadenopathy " | |
| f"with pulmonary nodules on CT, (4) Positive urine antigen (highly sensitive and specific), and " | |
| f"(5) Elevated complement fixation titers for both mycelial and yeast forms. The lymphopenia is " | |
| f"characteristic of histoplasmosis and helps distinguish it from bacterial infections. Treatment " | |
| f"with itraconazole is indicated for moderate symptoms lasting >4 weeks." | |
| ) | |
| elif "pneumonia" in text.lower() and "streptococcus" in text.lower(): | |
| explanations[letter] = ( | |
| f"**CORRECT**: {text} is the correct diagnosis. This patient presents with classic features " | |
| f"of pneumococcal pneumonia: (1) Acute onset with rust-colored sputum and pleuritic chest pain, " | |
| f"(2) Lobar consolidation on chest imaging, (3) Positive urinary pneumococcal antigen, and " | |
| f"(4) High procalcitonin suggesting bacterial etiology. The recent cruise ship exposure increases " | |
| f"risk for pneumococcal disease. CURB-65 score of 2 indicates moderate severity requiring " | |
| f"hospitalization. The combination of β-lactam plus macrolide is recommended for hospitalized " | |
| f"CAP patients to cover both typical and atypical pathogens." | |
| ) | |
| else: | |
| explanations[letter] = ( | |
| f"**CORRECT**: {text} is the correct answer based on the clinical presentation, " | |
| f"diagnostic findings, and epidemiologic factors presented in this case." | |
| ) | |
| else: | |
| # Enhanced incorrect explanations | |
| if "coccidioidomycosis" in text.lower(): | |
| explanations[letter] = ( | |
| f"**INCORRECT**: {text} is incorrect. While coccidioidomycosis can present with similar " | |
| f"pulmonary symptoms and erythema nodosum, this patient's geographic exposure (Ohio River Valley " | |
| f"caves) and positive Histoplasma urine antigen are not consistent with coccidioidomycosis. " | |
| f"Coccidioidomycosis is endemic to the southwestern United States, particularly Arizona and " | |
| f"California. The urine antigen test is specific for Histoplasma and would not be positive " | |
| f"in coccidioidomycosis." | |
| ) | |
| elif "sarcoidosis" in text.lower(): | |
| explanations[letter] = ( | |
| f"**INCORRECT**: {text} is incorrect. While sarcoidosis with Löfgren syndrome can present with " | |
| f"bilateral hilar lymphadenopathy and erythema nodosum, several features argue against this " | |
| f"diagnosis: (1) Recent cave exposure in endemic area, (2) Positive Histoplasma urine antigen, " | |
| f"(3) Elevated complement fixation titers, and (4) Significant weight loss and fever. Löfgren " | |
| f"syndrome typically has a more acute onset and better prognosis than chronic sarcoidosis." | |
| ) | |
| elif "legionella" in text.lower(): | |
| explanations[letter] = ( | |
| f"**INCORRECT**: {text} is incorrect. While Legionella pneumonia can occur in cruise ship " | |
| f"outbreaks, the positive urinary pneumococcal antigen specifically identifies S. pneumoniae. " | |
| f"Legionella would typically present with hyponatremia, elevated LDH, and diarrhea, and would " | |
| f"require urinary Legionella antigen testing for diagnosis. The clinical picture and positive " | |
| f"pneumococcal antigen confirm pneumococcal etiology." | |
| ) | |
| else: | |
| explanations[letter] = ( | |
| f"**INCORRECT**: {text} is incorrect based on the clinical presentation and diagnostic findings. " | |
| f"This option can be ruled out by the specific laboratory and imaging findings presented." | |
| ) | |
| return explanations | |
| async def _convert_generic_to_clinical_vignette(self, generic_vignette: str, clinical_context: Dict[str, Any], comparison_table: Dict[str, Any]) -> str: | |
| """ | |
| Step 10: Convert generic placeholder vignette into realistic clinical scenario | |
| This addresses the issue of generic vignettes with placeholder text that gives away answers. | |
| """ | |
| condition_category = clinical_context["condition_category"] | |
| correct_answer = comparison_table["table"]["correct_answer"] | |
| # Create realistic clinical vignettes based on condition type | |
| if "aspergillosis" in condition_category.lower() or "aspergillus" in condition_category.lower(): | |
| realistic_vignette = ( | |
| "A 34-year-old man with acute myeloid leukemia is admitted for induction chemotherapy. " | |
| "On day 12 of hospitalization, he develops fever to 102.8°F (39.3°C) despite broad-spectrum " | |
| "antibiotics (piperacillin-tazobactam and vancomycin) for 48 hours. He reports new onset " | |
| "of right-sided pleuritic chest pain and has had two episodes of small-volume hemoptysis. " | |
| "Physical examination reveals an ill-appearing man with temperature 102.8°F, heart rate 115 bpm, " | |
| "blood pressure 110/65 mmHg, respiratory rate 24/min, and oxygen saturation 94% on 2L nasal cannula. " | |
| "Lung examination shows decreased breath sounds at the right base with dullness to percussion. " | |
| "Laboratory studies show: WBC 400/μL (normal 4,500-11,000) with 85% neutrophils, absolute neutrophil " | |
| "count 340/μL, platelet count 45,000/μL, and creatinine 1.2 mg/dL. Chest CT demonstrates a 2.5-cm " | |
| "right lower lobe nodule with surrounding ground-glass opacity ('halo sign') and a smaller left " | |
| "upper lobe nodule. Serum galactomannan index is 2.8 (normal <0.5). Blood cultures remain negative " | |
| "after 72 hours. The patient has no known drug allergies and has been receiving prophylactic " | |
| "fluconazole 400mg daily since admission." | |
| ) | |
| elif "candida" in condition_category.lower() and "auris" in condition_category.lower(): | |
| realistic_vignette = ( | |
| "A 67-year-old woman with end-stage renal disease on hemodialysis is admitted to the ICU " | |
| "following complications from abdominal surgery 18 days ago. She has required multiple " | |
| "invasive procedures including central venous catheter placement, mechanical ventilation, " | |
| "and broad-spectrum antibiotic therapy with vancomycin, meropenem, and fluconazole. " | |
| "On hospital day 20, she develops new fever to 101.6°F (38.7°C) and hypotension requiring " | |
| "vasopressor support. Physical examination reveals temperature 101.6°F, heart rate 125 bpm, " | |
| "blood pressure 85/45 mmHg on norepinephrine, and clear lungs. The central line insertion " | |
| "site appears clean without erythema. Laboratory studies show: WBC 14,200/μL with 78% neutrophils " | |
| "and 15% bands, lactate 3.8 mmol/L (normal <2.0), and procalcitonin 1.2 ng/mL. Blood cultures " | |
| "drawn from both central and peripheral sites grow yeast after 16 hours. The isolate demonstrates " | |
| "resistance to fluconazole (MIC >64 μg/mL) and intermediate resistance to amphotericin B (MIC 2 μg/mL). " | |
| "MALDI-TOF mass spectrometry reports 'Candida haemulonii' with low confidence score (1.6). " | |
| "The microbiology laboratory requests molecular identification due to the unusual resistance pattern." | |
| ) | |
| elif "histoplasmosis" in condition_category.lower() or "cave" in condition_category.lower(): | |
| realistic_vignette = ( | |
| "A 42-year-old construction worker from Cincinnati, Ohio, presents to the emergency department " | |
| "with a 4-week history of persistent nonproductive cough, low-grade fever, and 15-pound " | |
| "unintentional weight loss. He reports recent recreational spelunking activities at Mammoth Cave " | |
| "in Kentucky approximately 7 weeks ago with several friends. Initial symptoms began gradually " | |
| "2 weeks after the cave trip with fatigue and intermittent fever, progressing to persistent cough " | |
| "and night sweats. He denies chest pain initially but now reports mild bilateral chest discomfort. " | |
| "Physical examination reveals an afebrile man (temperature 99.8°F) with scattered tender erythematous " | |
| "nodules on both anterior shins and mild bilateral ankle swelling. Vital signs show heart rate 88 bpm, " | |
| "blood pressure 135/82 mmHg, respiratory rate 18/min, and oxygen saturation 96% on room air. " | |
| "Laboratory studies reveal: WBC 3,400/μL (normal 4,500-11,000) with 68% lymphocytes, ESR 82 mm/hr, " | |
| "and LDH 445 U/L (normal <250). Chest CT demonstrates bilateral hilar lymphadenopathy with multiple " | |
| "small (<1 cm) pulmonary nodules scattered throughout both lung fields. Urine Histoplasma antigen " | |
| "is 18.5 ng/mL (normal <0.6 ng/mL)." | |
| ) | |
| elif "pneumonia" in condition_category.lower() and "pneumococcal" in condition_category.lower(): | |
| realistic_vignette = ( | |
| "A 71-year-old man with COPD (FEV1 42% predicted) and well-controlled type 2 diabetes mellitus " | |
| "presents to the emergency department with a 36-hour history of acute onset productive cough " | |
| "with rust-colored sputum, right-sided pleuritic chest pain, and fever. He recently returned " | |
| "from a 10-day cruise to the Caribbean 4 days ago and felt well during the entire trip. " | |
| "Symptoms began abruptly yesterday evening with rigors and high fever, followed by productive " | |
| "cough and sharp chest pain that worsens with deep inspiration. He denies recent antibiotic use " | |
| "or hospitalization. Physical examination reveals an ill-appearing man with temperature 103.1°F " | |
| "(39.5°C), heart rate 118 bmp, blood pressure 125/78 mmHg, respiratory rate 28/min, and oxygen " | |
| "saturation 88% on room air improving to 95% on 3L nasal cannula. Lung examination shows dullness " | |
| "to percussion and bronchial breath sounds over the right lower lobe posteriorly. Laboratory studies " | |
| "reveal: WBC 17,200/μL with 86% neutrophils and 10% bands, procalcitonin 4.2 ng/mL (normal <0.1), " | |
| "lactate 1.9 mmol/L, and creatinine 1.1 mg/dL. Chest X-ray demonstrates right lower lobe consolidation " | |
| "with air bronchograms. Pneumococcal urinary antigen is positive. His calculated CURB-65 score is 2 " | |
| "(age >65, respiratory rate >30)." | |
| ) | |
| elif "bacterial" in condition_category.lower() and "pneumonia" in condition_category.lower(): | |
| realistic_vignette = ( | |
| "A 45-year-old homeless man with a history of alcohol use disorder presents to the emergency " | |
| "department with a 5-day history of fever, productive cough with foul-smelling sputum, and " | |
| "progressive dyspnea. He reports poor dental hygiene and recalls choking on food 2 weeks ago " | |
| "followed by several days of coughing. He has been living in a shelter and denies recent " | |
| "hospitalization or antibiotic use. Physical examination reveals a cachectic man with temperature " | |
| "101.8°F (38.8°C), heart rate 105 bpm, blood pressure 95/60 mmHg, respiratory rate 26/min, " | |
| "and oxygen saturation 91% on room air. Oral examination shows poor dentition with multiple " | |
| "carious teeth. Lung examination reveals dullness and decreased breath sounds over the right " | |
| "lower lobe with coarse crackles. Laboratory studies show: WBC 18,500/μL with 82% neutrophils " | |
| "and 12% bands, hemoglobin 9.8 g/dL, and albumin 2.4 g/dL. Chest CT demonstrates a thick-walled " | |
| "cavity in the right lower lobe with an air-fluid level and surrounding consolidation." | |
| ) | |
| else: | |
| # Generic fallback that's still better than placeholder text | |
| realistic_vignette = ( | |
| f"A patient presents to the hospital with a complex clinical syndrome. The presentation " | |
| f"includes multiple clinical findings that require systematic analysis to establish the " | |
| f"correct diagnosis. Laboratory studies, imaging findings, and epidemiologic factors " | |
| f"provide important diagnostic clues. The case challenges clinical reasoning skills " | |
| f"and requires integration of multiple data points to arrive at the most likely diagnosis." | |
| ) | |
| return realistic_vignette | |
| def _generate_learning_objectives(self, topic: str, question_type: str) -> List[str]: | |
| """Generate learning objectives for the question.""" | |
| objectives = [ | |
| f"Recognize the clinical presentation of {topic}", | |
| f"Differentiate {topic} from other common conditions", | |
| f"Understand the pathophysiology underlying {topic}", | |
| f"Apply appropriate diagnostic and management strategies" | |
| ] | |
| if question_type == "pharmacology": | |
| objectives.append("Understand medication mechanisms and contraindications") | |
| elif question_type == "complications": | |
| objectives.append("Identify and manage potential complications") | |
| return objectives | |
| def _generate_references(self, topic: str, guideline_sources: Optional[List[Dict[str, str]]] = None) -> List[str]: | |
| """Generate reference suggestions for further reading, including discovered guidelines.""" | |
| references = [] | |
| # Add discovered guideline sources first | |
| if guideline_sources: | |
| for source in guideline_sources[:3]: # Limit to top 3 | |
| title = source.get("title", "") | |
| url = source.get("url", "") | |
| if title and url: | |
| references.append(f"{title} - {url}") | |
| # Add standard references | |
| standard_refs = [ | |
| f"Harrison's Principles of Internal Medicine - {topic.title()} chapter", | |
| f"UpToDate - {topic.title()} clinical topic", | |
| f"Relevant clinical practice guidelines for {topic}", | |
| f"NEJM Clinical Practice articles on {topic}" | |
| ] | |
| # Add standard references if we don't have many guideline sources | |
| references.extend(standard_refs[:max(1, 4 - len(references))]) | |
| return references | |