Initial commit
This commit is contained in:
318
skills/treatment-plans/scripts/check_completeness.py
Normal file
318
skills/treatment-plans/scripts/check_completeness.py
Normal file
@@ -0,0 +1,318 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Check Treatment Plan Completeness
|
||||
Validates that all required sections are present in a treatment plan.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import re
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple
|
||||
|
||||
# Required sections for all treatment plans
|
||||
REQUIRED_SECTIONS = [
|
||||
r'\\section\*\{.*Patient Information',
|
||||
r'\\section\*\{.*Diagnosis.*Assessment',
|
||||
r'\\section\*\{.*Goals',
|
||||
r'\\section\*\{.*Interventions',
|
||||
r'\\section\*\{.*Timeline.*Schedule',
|
||||
r'\\section\*\{.*Monitoring',
|
||||
r'\\section\*\{.*Outcomes',
|
||||
r'\\section\*\{.*Follow[- ]?up',
|
||||
r'\\section\*\{.*Education',
|
||||
r'\\section\*\{.*Risk.*Safety',
|
||||
]
|
||||
|
||||
# Section descriptions for user-friendly output
|
||||
SECTION_DESCRIPTIONS = {
|
||||
0: 'Patient Information (de-identified)',
|
||||
1: 'Diagnosis and Assessment',
|
||||
2: 'Treatment Goals (SMART format)',
|
||||
3: 'Interventions (pharmacological, non-pharmacological, procedural)',
|
||||
4: 'Timeline and Schedule',
|
||||
5: 'Monitoring Parameters',
|
||||
6: 'Expected Outcomes',
|
||||
7: 'Follow-up Plan',
|
||||
8: 'Patient Education',
|
||||
9: 'Risk Mitigation and Safety'
|
||||
}
|
||||
|
||||
|
||||
def read_file(filepath: Path) -> str:
|
||||
"""Read and return file contents."""
|
||||
try:
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
return f.read()
|
||||
except FileNotFoundError:
|
||||
print(f"Error: File not found: {filepath}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Error reading file: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def check_sections(content: str) -> Tuple[List[bool], List[str]]:
|
||||
"""
|
||||
Check which required sections are present.
|
||||
Returns tuple of (checklist, missing_sections).
|
||||
"""
|
||||
checklist = []
|
||||
missing = []
|
||||
|
||||
for i, pattern in enumerate(REQUIRED_SECTIONS):
|
||||
if re.search(pattern, content, re.IGNORECASE):
|
||||
checklist.append(True)
|
||||
else:
|
||||
checklist.append(False)
|
||||
missing.append(SECTION_DESCRIPTIONS[i])
|
||||
|
||||
return checklist, missing
|
||||
|
||||
|
||||
def check_smart_goals(content: str) -> Tuple[bool, List[str]]:
|
||||
"""
|
||||
Check if SMART goal criteria are mentioned.
|
||||
Returns (has_smart, missing_criteria).
|
||||
"""
|
||||
smart_criteria = {
|
||||
'Specific': r'\bspecific\b',
|
||||
'Measurable': r'\bmeasurable\b',
|
||||
'Achievable': r'\bachievable\b',
|
||||
'Relevant': r'\brelevant\b',
|
||||
'Time-bound': r'\btime[- ]?bound\b'
|
||||
}
|
||||
|
||||
missing = []
|
||||
for criterion, pattern in smart_criteria.items():
|
||||
if not re.search(pattern, content, re.IGNORECASE):
|
||||
missing.append(criterion)
|
||||
|
||||
has_smart = len(missing) == 0
|
||||
return has_smart, missing
|
||||
|
||||
|
||||
def check_hipaa_notice(content: str) -> bool:
|
||||
"""Check if HIPAA de-identification notice is present."""
|
||||
pattern = r'HIPAA|de-identif|protected health information|PHI'
|
||||
return bool(re.search(pattern, content, re.IGNORECASE))
|
||||
|
||||
|
||||
def check_provider_signature(content: str) -> bool:
|
||||
"""Check if provider signature section is present."""
|
||||
pattern = r'\\section\*\{.*Signature|Provider Signature|Signature'
|
||||
return bool(re.search(pattern, content, re.IGNORECASE))
|
||||
|
||||
|
||||
def check_placeholders_remaining(content: str) -> Tuple[int, List[str]]:
|
||||
"""
|
||||
Check for uncustomized placeholders [like this].
|
||||
Returns (count, sample_placeholders).
|
||||
"""
|
||||
placeholders = re.findall(r'\[([^\]]+)\]', content)
|
||||
|
||||
# Filter out LaTeX commands and references
|
||||
filtered = []
|
||||
for p in placeholders:
|
||||
# Skip if it's a LaTeX command, number, or citation
|
||||
if not (p.startswith('\\') or p.isdigit() or 'cite' in p.lower() or 'ref' in p.lower()):
|
||||
filtered.append(p)
|
||||
|
||||
count = len(filtered)
|
||||
samples = filtered[:5] # Return up to 5 examples
|
||||
|
||||
return count, samples
|
||||
|
||||
|
||||
def display_results(filepath: Path, checklist: List[bool], missing: List[str],
|
||||
smart_complete: bool, smart_missing: List[str],
|
||||
has_hipaa: bool, has_signature: bool,
|
||||
placeholder_count: int, placeholder_samples: List[str]):
|
||||
"""Display completeness check results."""
|
||||
|
||||
total_sections = len(REQUIRED_SECTIONS)
|
||||
present_count = sum(checklist)
|
||||
completeness_pct = (present_count / total_sections) * 100
|
||||
|
||||
print("\n" + "="*70)
|
||||
print("TREATMENT PLAN COMPLETENESS CHECK")
|
||||
print("="*70)
|
||||
print(f"\nFile: {filepath}")
|
||||
print(f"File size: {filepath.stat().st_size:,} bytes")
|
||||
|
||||
# Overall completeness
|
||||
print("\n" + "-"*70)
|
||||
print("OVERALL COMPLETENESS")
|
||||
print("-"*70)
|
||||
print(f"Required sections present: {present_count}/{total_sections} ({completeness_pct:.0f}%)")
|
||||
|
||||
if completeness_pct == 100:
|
||||
print("✓ All required sections present")
|
||||
else:
|
||||
print(f"✗ {len(missing)} section(s) missing")
|
||||
|
||||
# Section details
|
||||
print("\n" + "-"*70)
|
||||
print("SECTION CHECKLIST")
|
||||
print("-"*70)
|
||||
|
||||
for i, (present, desc) in enumerate(zip(checklist, SECTION_DESCRIPTIONS.values())):
|
||||
status = "✓" if present else "✗"
|
||||
print(f"{status} {desc}")
|
||||
|
||||
# Missing sections
|
||||
if missing:
|
||||
print("\n" + "-"*70)
|
||||
print("MISSING SECTIONS")
|
||||
print("-"*70)
|
||||
for section in missing:
|
||||
print(f" • {section}")
|
||||
|
||||
# SMART goals
|
||||
print("\n" + "-"*70)
|
||||
print("SMART GOALS CHECK")
|
||||
print("-"*70)
|
||||
|
||||
if smart_complete:
|
||||
print("✓ All SMART criteria mentioned in document")
|
||||
else:
|
||||
print(f"✗ {len(smart_missing)} SMART criterion/criteria not found:")
|
||||
for criterion in smart_missing:
|
||||
print(f" • {criterion}")
|
||||
print("\nNote: Goals should be Specific, Measurable, Achievable, Relevant, Time-bound")
|
||||
|
||||
# HIPAA notice
|
||||
print("\n" + "-"*70)
|
||||
print("PRIVACY AND COMPLIANCE")
|
||||
print("-"*70)
|
||||
|
||||
if has_hipaa:
|
||||
print("✓ HIPAA/de-identification notice present")
|
||||
else:
|
||||
print("✗ HIPAA de-identification notice not found")
|
||||
print(" Recommendation: Include HIPAA Safe Harbor de-identification guidance")
|
||||
|
||||
if has_signature:
|
||||
print("✓ Provider signature section present")
|
||||
else:
|
||||
print("✗ Provider signature section not found")
|
||||
|
||||
# Placeholders
|
||||
print("\n" + "-"*70)
|
||||
print("CUSTOMIZATION STATUS")
|
||||
print("-"*70)
|
||||
|
||||
if placeholder_count == 0:
|
||||
print("✓ No uncustomized placeholders detected")
|
||||
else:
|
||||
print(f"⚠ {placeholder_count} placeholder(s) may need customization")
|
||||
print("\nExamples:")
|
||||
for sample in placeholder_samples:
|
||||
print(f" • [{sample}]")
|
||||
print("\nRecommendation: Replace all [bracketed placeholders] with patient-specific information")
|
||||
|
||||
# Summary
|
||||
print("\n" + "="*70)
|
||||
print("SUMMARY")
|
||||
print("="*70)
|
||||
|
||||
# Calculate overall score
|
||||
score_components = [
|
||||
completeness_pct / 100, # Section completeness (0-1)
|
||||
1.0 if smart_complete else 0.6, # SMART goals (full or partial credit)
|
||||
1.0 if has_hipaa else 0.0, # HIPAA notice (binary)
|
||||
1.0 if has_signature else 0.0, # Signature (binary)
|
||||
1.0 if placeholder_count == 0 else 0.5 # Customization (full or partial)
|
||||
]
|
||||
|
||||
overall_score = (sum(score_components) / len(score_components)) * 100
|
||||
|
||||
print(f"\nOverall completeness score: {overall_score:.0f}%")
|
||||
|
||||
if overall_score >= 90:
|
||||
print("Status: ✓ EXCELLENT - Treatment plan is comprehensive")
|
||||
elif overall_score >= 75:
|
||||
print("Status: ✓ GOOD - Minor improvements needed")
|
||||
elif overall_score >= 60:
|
||||
print("Status: ⚠ FAIR - Several sections need attention")
|
||||
else:
|
||||
print("Status: ✗ INCOMPLETE - Significant work needed")
|
||||
|
||||
print("\n" + "="*70)
|
||||
|
||||
# Return exit code based on completeness
|
||||
return 0 if completeness_pct >= 80 else 1
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Check treatment plan completeness',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Check a treatment plan file
|
||||
python check_completeness.py my_treatment_plan.tex
|
||||
|
||||
# Check and exit with error code if incomplete (for CI/CD)
|
||||
python check_completeness.py plan.tex && echo "Complete"
|
||||
|
||||
This script checks for:
|
||||
- All required sections (10 core sections)
|
||||
- SMART goal criteria
|
||||
- HIPAA de-identification notice
|
||||
- Provider signature section
|
||||
- Uncustomized placeholders
|
||||
|
||||
Exit codes:
|
||||
0 - All required sections present (≥80% complete)
|
||||
1 - Missing required sections (<80% complete)
|
||||
2 - File error or invalid arguments
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'file',
|
||||
type=Path,
|
||||
help='Treatment plan file to check (.tex format)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'-v', '--verbose',
|
||||
action='store_true',
|
||||
help='Show detailed output'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check file exists and is .tex
|
||||
if not args.file.exists():
|
||||
print(f"Error: File not found: {args.file}", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
if args.file.suffix.lower() not in ['.tex', '.txt']:
|
||||
print(f"Warning: Expected .tex file, got {args.file.suffix}", file=sys.stderr)
|
||||
|
||||
# Read file
|
||||
content = read_file(args.file)
|
||||
|
||||
# Perform checks
|
||||
checklist, missing = check_sections(content)
|
||||
smart_complete, smart_missing = check_smart_goals(content)
|
||||
has_hipaa = check_hipaa_notice(content)
|
||||
has_signature = check_provider_signature(content)
|
||||
placeholder_count, placeholder_samples = check_placeholders_remaining(content)
|
||||
|
||||
# Display results
|
||||
exit_code = display_results(
|
||||
args.file, checklist, missing,
|
||||
smart_complete, smart_missing,
|
||||
has_hipaa, has_signature,
|
||||
placeholder_count, placeholder_samples
|
||||
)
|
||||
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
244
skills/treatment-plans/scripts/generate_template.py
Normal file
244
skills/treatment-plans/scripts/generate_template.py
Normal file
@@ -0,0 +1,244 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate Treatment Plan Template
|
||||
Interactive script to select and generate treatment plan templates.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
# Template types and descriptions
|
||||
TEMPLATES = {
|
||||
'general_medical': {
|
||||
'name': 'General Medical Treatment Plan',
|
||||
'file': 'general_medical_treatment_plan.tex',
|
||||
'description': 'For primary care and chronic disease management (diabetes, hypertension, etc.)'
|
||||
},
|
||||
'rehabilitation': {
|
||||
'name': 'Rehabilitation Treatment Plan',
|
||||
'file': 'rehabilitation_treatment_plan.tex',
|
||||
'description': 'For physical therapy, occupational therapy, and rehabilitation services'
|
||||
},
|
||||
'mental_health': {
|
||||
'name': 'Mental Health Treatment Plan',
|
||||
'file': 'mental_health_treatment_plan.tex',
|
||||
'description': 'For psychiatric and behavioral health treatment'
|
||||
},
|
||||
'chronic_disease': {
|
||||
'name': 'Chronic Disease Management Plan',
|
||||
'file': 'chronic_disease_management_plan.tex',
|
||||
'description': 'For complex multimorbidity and long-term care coordination'
|
||||
},
|
||||
'perioperative': {
|
||||
'name': 'Perioperative Care Plan',
|
||||
'file': 'perioperative_care_plan.tex',
|
||||
'description': 'For surgical and procedural patient management'
|
||||
},
|
||||
'pain_management': {
|
||||
'name': 'Pain Management Plan',
|
||||
'file': 'pain_management_plan.tex',
|
||||
'description': 'For acute and chronic pain treatment (multimodal approach)'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def get_templates_dir():
|
||||
"""Get the path to the templates directory."""
|
||||
# Assume script is in .claude/skills/treatment-plans/scripts/
|
||||
script_dir = Path(__file__).parent
|
||||
templates_dir = script_dir.parent / 'assets'
|
||||
return templates_dir
|
||||
|
||||
|
||||
def list_templates():
|
||||
"""Display available templates."""
|
||||
print("\n" + "="*70)
|
||||
print("AVAILABLE TREATMENT PLAN TEMPLATES")
|
||||
print("="*70)
|
||||
|
||||
for i, (key, info) in enumerate(TEMPLATES.items(), 1):
|
||||
print(f"\n{i}. {info['name']}")
|
||||
print(f" Type: {key}")
|
||||
print(f" File: {info['file']}")
|
||||
print(f" Description: {info['description']}")
|
||||
|
||||
print("\n" + "="*70)
|
||||
|
||||
|
||||
def interactive_selection():
|
||||
"""Interactive template selection."""
|
||||
list_templates()
|
||||
|
||||
while True:
|
||||
try:
|
||||
choice = input("\nSelect template number (1-6) or 'q' to quit: ").strip().lower()
|
||||
|
||||
if choice == 'q':
|
||||
print("Exiting...")
|
||||
sys.exit(0)
|
||||
|
||||
choice_num = int(choice)
|
||||
|
||||
if 1 <= choice_num <= len(TEMPLATES):
|
||||
template_key = list(TEMPLATES.keys())[choice_num - 1]
|
||||
return template_key
|
||||
else:
|
||||
print(f"Please enter a number between 1 and {len(TEMPLATES)}.")
|
||||
except ValueError:
|
||||
print("Invalid input. Please enter a number or 'q' to quit.")
|
||||
|
||||
|
||||
def get_output_filename(template_key, custom_name=None):
|
||||
"""Generate output filename."""
|
||||
if custom_name:
|
||||
# Ensure .tex extension
|
||||
if not custom_name.endswith('.tex'):
|
||||
custom_name += '.tex'
|
||||
return custom_name
|
||||
|
||||
# Default: template_key_YYYYMMDD.tex
|
||||
timestamp = datetime.now().strftime('%Y%m%d')
|
||||
return f"{template_key}_plan_{timestamp}.tex"
|
||||
|
||||
|
||||
def copy_template(template_key, output_path):
|
||||
"""Copy template to output location."""
|
||||
templates_dir = get_templates_dir()
|
||||
template_file = TEMPLATES[template_key]['file']
|
||||
source_path = templates_dir / template_file
|
||||
|
||||
if not source_path.exists():
|
||||
raise FileNotFoundError(f"Template not found: {source_path}")
|
||||
|
||||
# Create output directory if it doesn't exist
|
||||
output_path = Path(output_path)
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Copy template
|
||||
shutil.copy2(source_path, output_path)
|
||||
|
||||
return output_path
|
||||
|
||||
|
||||
def display_success(output_path, template_key):
|
||||
"""Display success message with next steps."""
|
||||
template_info = TEMPLATES[template_key]
|
||||
|
||||
print("\n" + "="*70)
|
||||
print("✓ TEMPLATE GENERATED SUCCESSFULLY")
|
||||
print("="*70)
|
||||
print(f"\nTemplate: {template_info['name']}")
|
||||
print(f"Output file: {output_path}")
|
||||
print(f"File size: {os.path.getsize(output_path):,} bytes")
|
||||
|
||||
print("\n" + "-"*70)
|
||||
print("NEXT STEPS:")
|
||||
print("-"*70)
|
||||
|
||||
print("\n1. CUSTOMIZE THE TEMPLATE:")
|
||||
print(" - Open the .tex file in your LaTeX editor")
|
||||
print(" - Replace all [bracketed placeholders] with patient-specific information")
|
||||
print(" - Remove or modify sections as appropriate for your patient")
|
||||
|
||||
print("\n2. COMPILE TO PDF:")
|
||||
print(f" $ pdflatex {output_path.name}")
|
||||
|
||||
print("\n3. VALIDATE (optional):")
|
||||
print(f" $ python check_completeness.py {output_path.name}")
|
||||
print(f" $ python validate_treatment_plan.py {output_path.name}")
|
||||
|
||||
print("\n4. DE-IDENTIFY BEFORE SHARING:")
|
||||
print(" - Remove all HIPAA identifiers (18 identifiers)")
|
||||
print(" - See regulatory_compliance.md reference for details")
|
||||
|
||||
print("\n" + "="*70)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Generate treatment plan template',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Interactive mode (recommended for first-time users)
|
||||
python generate_template.py
|
||||
|
||||
# Direct generation with type specification
|
||||
python generate_template.py --type general_medical --output diabetes_plan.tex
|
||||
|
||||
# Generate with default filename
|
||||
python generate_template.py --type mental_health
|
||||
|
||||
# List available templates
|
||||
python generate_template.py --list
|
||||
|
||||
Available template types:
|
||||
general_medical, rehabilitation, mental_health, chronic_disease,
|
||||
perioperative, pain_management
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--type',
|
||||
choices=list(TEMPLATES.keys()),
|
||||
help='Template type to generate'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--output',
|
||||
help='Output filename (default: auto-generated with timestamp)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--list',
|
||||
action='store_true',
|
||||
help='List available templates and exit'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# List templates and exit
|
||||
if args.list:
|
||||
list_templates()
|
||||
return
|
||||
|
||||
# Determine template type
|
||||
if args.type:
|
||||
template_key = args.type
|
||||
print(f"\nGenerating template: {TEMPLATES[template_key]['name']}")
|
||||
else:
|
||||
# Interactive mode
|
||||
template_key = interactive_selection()
|
||||
|
||||
# Determine output filename
|
||||
if args.output:
|
||||
output_filename = args.output
|
||||
else:
|
||||
output_filename = get_output_filename(template_key)
|
||||
|
||||
# Default output to current directory
|
||||
output_path = Path.cwd() / output_filename
|
||||
|
||||
# Confirm overwrite if file exists
|
||||
if output_path.exists():
|
||||
response = input(f"\nFile {output_filename} already exists. Overwrite? (y/n): ").strip().lower()
|
||||
if response != 'y':
|
||||
print("Cancelled.")
|
||||
return
|
||||
|
||||
# Copy template
|
||||
try:
|
||||
output_path = copy_template(template_key, output_path)
|
||||
display_success(output_path, template_key)
|
||||
except Exception as e:
|
||||
print(f"\n✗ ERROR: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
369
skills/treatment-plans/scripts/timeline_generator.py
Normal file
369
skills/treatment-plans/scripts/timeline_generator.py
Normal file
@@ -0,0 +1,369 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Treatment Timeline Generator
|
||||
Generates visual treatment timelines from treatment plan files.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import re
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Dict, Tuple
|
||||
|
||||
# Try to import matplotlib, but make it optional
|
||||
try:
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.dates as mdates
|
||||
from matplotlib.patches import Rectangle
|
||||
HAS_MATPLOTLIB = True
|
||||
except ImportError:
|
||||
HAS_MATPLOTLIB = False
|
||||
|
||||
|
||||
def extract_timeline_info(content: str) -> Dict[str, List[Tuple[str, str]]]:
|
||||
"""
|
||||
Extract timeline and schedule information from treatment plan.
|
||||
Returns dict with phases, appointments, milestones.
|
||||
"""
|
||||
timeline_data = {
|
||||
'phases': [],
|
||||
'appointments': [],
|
||||
'milestones': []
|
||||
}
|
||||
|
||||
# Extract treatment phases
|
||||
# Look for patterns like "Week 1-4: Description" or "Months 1-3: Description"
|
||||
phase_patterns = [
|
||||
r'(Week[s]?\s*\d+[-–]\d+|Month[s]?\s*\d+[-–]\d+)[:\s]+([^\n]+)',
|
||||
r'(POD\s*\d+[-–]\d+)[:\s]+([^\n]+)',
|
||||
r'(\d+[-–]\d+\s*week[s]?)[:\s]+([^\n]+)'
|
||||
]
|
||||
|
||||
for pattern in phase_patterns:
|
||||
matches = re.findall(pattern, content, re.IGNORECASE)
|
||||
for timeframe, description in matches:
|
||||
timeline_data['phases'].append((timeframe.strip(), description.strip()))
|
||||
|
||||
# Extract appointments
|
||||
# Look for patterns like "Week 2: Visit" or "Month 3: Follow-up"
|
||||
apt_patterns = [
|
||||
r'(Week\s*\d+|Month\s*\d+|POD\s*\d+)[:\s]+(Visit|Appointment|Follow-up|Check-up|Consultation)([^\n]*)',
|
||||
r'(Every\s+\d+\s+\w+)[:\s]+(Visit|Appointment|therapy|session)([^\n]*)'
|
||||
]
|
||||
|
||||
for pattern in apt_patterns:
|
||||
matches = re.findall(pattern, content, re.IGNORECASE)
|
||||
for timeframe, visit_type, details in matches:
|
||||
timeline_data['appointments'].append((timeframe.strip(), f"{visit_type}{details}".strip()))
|
||||
|
||||
# Extract milestones/assessments
|
||||
# Look for "reassessment", "goal evaluation", "milestone" mentions
|
||||
milestone_patterns = [
|
||||
r'(Week\s*\d+|Month\s*\d+)[:\s]+(reassess|evaluation|assessment|milestone)([^\n]*)',
|
||||
r'(\w+\s*\d+)[:\s]+(HbA1c|labs?|imaging|test)([^\n]*)'
|
||||
]
|
||||
|
||||
for pattern in milestone_patterns:
|
||||
matches = re.findall(pattern, content, re.IGNORECASE)
|
||||
for timeframe, event_type, details in matches:
|
||||
timeline_data['milestones'].append((timeframe.strip(), f"{event_type}{details}".strip()))
|
||||
|
||||
return timeline_data
|
||||
|
||||
|
||||
def parse_timeframe_to_days(timeframe: str) -> Tuple[int, int]:
|
||||
"""
|
||||
Parse timeframe string to start and end days.
|
||||
Examples: "Week 1-4" -> (0, 28), "Month 3" -> (60, 90)
|
||||
"""
|
||||
timeframe = timeframe.lower()
|
||||
|
||||
# Week patterns
|
||||
if 'week' in timeframe:
|
||||
weeks = re.findall(r'\d+', timeframe)
|
||||
if len(weeks) == 2:
|
||||
start_week = int(weeks[0])
|
||||
end_week = int(weeks[1])
|
||||
return ((start_week - 1) * 7, end_week * 7)
|
||||
elif len(weeks) == 1:
|
||||
week = int(weeks[0])
|
||||
return ((week - 1) * 7, week * 7)
|
||||
|
||||
# Month patterns
|
||||
if 'month' in timeframe:
|
||||
months = re.findall(r'\d+', timeframe)
|
||||
if len(months) == 2:
|
||||
start_month = int(months[0])
|
||||
end_month = int(months[1])
|
||||
return ((start_month - 1) * 30, end_month * 30)
|
||||
elif len(months) == 1:
|
||||
month = int(months[0])
|
||||
return ((month - 1) * 30, month * 30)
|
||||
|
||||
# POD (post-operative day) patterns
|
||||
if 'pod' in timeframe:
|
||||
days = re.findall(r'\d+', timeframe)
|
||||
if len(days) == 2:
|
||||
return (int(days[0]), int(days[1]))
|
||||
elif len(days) == 1:
|
||||
day = int(days[0])
|
||||
return (day, day + 1)
|
||||
|
||||
# Default fallback
|
||||
return (0, 7)
|
||||
|
||||
|
||||
def create_text_timeline(timeline_data: Dict, output_file: Path = None):
|
||||
"""Create a text-based timeline representation."""
|
||||
|
||||
lines = []
|
||||
lines.append("="*70)
|
||||
lines.append("TREATMENT TIMELINE")
|
||||
lines.append("="*70)
|
||||
|
||||
# Treatment phases
|
||||
if timeline_data['phases']:
|
||||
lines.append("\nTREATMENT PHASES:")
|
||||
lines.append("-"*70)
|
||||
for timeframe, description in timeline_data['phases']:
|
||||
lines.append(f"{timeframe:20s} | {description}")
|
||||
|
||||
# Appointments
|
||||
if timeline_data['appointments']:
|
||||
lines.append("\nSCHEDULED APPOINTMENTS:")
|
||||
lines.append("-"*70)
|
||||
for timeframe, details in timeline_data['appointments']:
|
||||
lines.append(f"{timeframe:20s} | {details}")
|
||||
|
||||
# Milestones
|
||||
if timeline_data['milestones']:
|
||||
lines.append("\nMILESTONES & ASSESSMENTS:")
|
||||
lines.append("-"*70)
|
||||
for timeframe, event in timeline_data['milestones']:
|
||||
lines.append(f"{timeframe:20s} | {event}")
|
||||
|
||||
lines.append("\n" + "="*70)
|
||||
|
||||
# Output
|
||||
output_text = "\n".join(lines)
|
||||
|
||||
if output_file:
|
||||
with open(output_file, 'w') as f:
|
||||
f.write(output_text)
|
||||
print(f"\nText timeline saved to: {output_file}")
|
||||
else:
|
||||
print(output_text)
|
||||
|
||||
return output_text
|
||||
|
||||
|
||||
def create_visual_timeline(timeline_data: Dict, output_file: Path, start_date: str = None):
|
||||
"""Create a visual Gantt-chart style timeline (requires matplotlib)."""
|
||||
|
||||
if not HAS_MATPLOTLIB:
|
||||
print("Error: matplotlib not installed. Install with: pip install matplotlib", file=sys.stderr)
|
||||
print("Generating text timeline instead...", file=sys.stderr)
|
||||
text_output = output_file.with_suffix('.txt')
|
||||
create_text_timeline(timeline_data, text_output)
|
||||
return
|
||||
|
||||
# Parse start date
|
||||
if start_date:
|
||||
try:
|
||||
start = datetime.strptime(start_date, '%Y-%m-%d')
|
||||
except ValueError:
|
||||
print(f"Invalid date format: {start_date}. Using today.", file=sys.stderr)
|
||||
start = datetime.now()
|
||||
else:
|
||||
start = datetime.now()
|
||||
|
||||
# Prepare data for plotting
|
||||
phases = []
|
||||
for timeframe, description in timeline_data['phases']:
|
||||
start_day, end_day = parse_timeframe_to_days(timeframe)
|
||||
phases.append({
|
||||
'name': f"{timeframe}: {description[:40]}",
|
||||
'start': start + timedelta(days=start_day),
|
||||
'end': start + timedelta(days=end_day),
|
||||
'type': 'phase'
|
||||
})
|
||||
|
||||
# Add appointments as events
|
||||
events = []
|
||||
for timeframe, details in timeline_data['appointments']:
|
||||
start_day, _ = parse_timeframe_to_days(timeframe)
|
||||
events.append({
|
||||
'name': f"{timeframe}: {details[:40]}",
|
||||
'date': start + timedelta(days=start_day),
|
||||
'type': 'appointment'
|
||||
})
|
||||
|
||||
# Add milestones
|
||||
for timeframe, event in timeline_data['milestones']:
|
||||
start_day, _ = parse_timeframe_to_days(timeframe)
|
||||
events.append({
|
||||
'name': f"{timeframe}: {event[:40]}",
|
||||
'date': start + timedelta(days=start_day),
|
||||
'type': 'milestone'
|
||||
})
|
||||
|
||||
# Create figure
|
||||
fig, ax = plt.subplots(figsize=(12, 8))
|
||||
|
||||
# Plot phases as horizontal bars
|
||||
y_position = len(phases) + len(events)
|
||||
|
||||
for i, phase in enumerate(phases):
|
||||
duration = (phase['end'] - phase['start']).days
|
||||
ax.barh(y_position - i, duration, left=mdates.date2num(phase['start']),
|
||||
height=0.6, color='steelblue', alpha=0.7, edgecolor='black')
|
||||
ax.text(mdates.date2num(phase['start']) + duration/2, y_position - i,
|
||||
phase['name'], va='center', ha='center', fontsize=9, color='white', weight='bold')
|
||||
|
||||
# Plot events as markers
|
||||
event_y = y_position - len(phases) - 1
|
||||
|
||||
for i, event in enumerate(events):
|
||||
marker = 'o' if event['type'] == 'appointment' else 's'
|
||||
color = 'green' if event['type'] == 'appointment' else 'orange'
|
||||
ax.plot(mdates.date2num(event['date']), event_y - i, marker=marker,
|
||||
markersize=10, color=color, markeredgecolor='black')
|
||||
ax.text(mdates.date2num(event['date']) + 2, event_y - i, event['name'],
|
||||
va='center', ha='left', fontsize=8)
|
||||
|
||||
# Format x-axis as dates
|
||||
ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %Y'))
|
||||
ax.xaxis.set_major_locator(mdates.MonthLocator())
|
||||
plt.xticks(rotation=45, ha='right')
|
||||
|
||||
# Labels and title
|
||||
ax.set_xlabel('Date', fontsize=12, weight='bold')
|
||||
ax.set_title('Treatment Plan Timeline', fontsize=14, weight='bold', pad=20)
|
||||
ax.set_yticks([])
|
||||
ax.grid(axis='x', alpha=0.3, linestyle='--')
|
||||
|
||||
# Legend
|
||||
from matplotlib.lines import Line2D
|
||||
legend_elements = [
|
||||
Rectangle((0, 0), 1, 1, fc='steelblue', alpha=0.7, edgecolor='black', label='Treatment Phase'),
|
||||
Line2D([0], [0], marker='o', color='w', markerfacecolor='green', markersize=10,
|
||||
markeredgecolor='black', label='Appointment'),
|
||||
Line2D([0], [0], marker='s', color='w', markerfacecolor='orange', markersize=10,
|
||||
markeredgecolor='black', label='Milestone/Assessment')
|
||||
]
|
||||
ax.legend(handles=legend_elements, loc='upper right', framealpha=0.9)
|
||||
|
||||
plt.tight_layout()
|
||||
|
||||
# Save
|
||||
plt.savefig(output_file, dpi=300, bbox_inches='tight')
|
||||
print(f"\nVisual timeline saved to: {output_file}")
|
||||
|
||||
# Close plot
|
||||
plt.close()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Generate treatment timeline visualization',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Generate text timeline
|
||||
python timeline_generator.py --plan my_plan.tex
|
||||
|
||||
# Generate visual timeline (requires matplotlib)
|
||||
python timeline_generator.py --plan my_plan.tex --output timeline.png --visual
|
||||
|
||||
# Specify start date for visual timeline
|
||||
python timeline_generator.py --plan my_plan.tex --output timeline.pdf --visual --start 2025-02-01
|
||||
|
||||
Output formats:
|
||||
Text: .txt
|
||||
Visual: .png, .pdf, .svg (requires matplotlib)
|
||||
|
||||
Note: Visual timeline generation requires matplotlib.
|
||||
Install with: pip install matplotlib
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--plan',
|
||||
type=Path,
|
||||
required=True,
|
||||
help='Treatment plan file to analyze (.tex format)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--output',
|
||||
type=Path,
|
||||
help='Output file (default: timeline.txt or timeline.png if --visual)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--visual',
|
||||
action='store_true',
|
||||
help='Generate visual timeline (requires matplotlib)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--start',
|
||||
help='Start date for timeline (YYYY-MM-DD format, default: today)'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check plan file exists
|
||||
if not args.plan.exists():
|
||||
print(f"Error: File not found: {args.plan}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Read plan
|
||||
try:
|
||||
with open(args.plan, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
except Exception as e:
|
||||
print(f"Error reading file: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Extract timeline information
|
||||
print("Extracting timeline information from treatment plan...")
|
||||
timeline_data = extract_timeline_info(content)
|
||||
|
||||
# Check if any timeline info found
|
||||
total_items = (len(timeline_data['phases']) +
|
||||
len(timeline_data['appointments']) +
|
||||
len(timeline_data['milestones']))
|
||||
|
||||
if total_items == 0:
|
||||
print("\nWarning: No timeline information detected in treatment plan.", file=sys.stderr)
|
||||
print("The plan may not contain structured timeline/schedule sections.", file=sys.stderr)
|
||||
print("\nTip: Include sections with timeframes like:", file=sys.stderr)
|
||||
print(" - Week 1-4: Initial phase", file=sys.stderr)
|
||||
print(" - Month 3: Follow-up visit", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Found {len(timeline_data['phases'])} phase(s), "
|
||||
f"{len(timeline_data['appointments'])} appointment(s), "
|
||||
f"{len(timeline_data['milestones'])} milestone(s)")
|
||||
|
||||
# Determine output file
|
||||
if not args.output:
|
||||
if args.visual:
|
||||
args.output = Path('timeline.png')
|
||||
else:
|
||||
args.output = Path('timeline.txt')
|
||||
|
||||
# Generate timeline
|
||||
if args.visual:
|
||||
create_visual_timeline(timeline_data, args.output, args.start)
|
||||
else:
|
||||
create_text_timeline(timeline_data, args.output)
|
||||
|
||||
print(f"\nTimeline generation complete!")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
367
skills/treatment-plans/scripts/validate_treatment_plan.py
Normal file
367
skills/treatment-plans/scripts/validate_treatment_plan.py
Normal file
@@ -0,0 +1,367 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Validate Treatment Plan Quality
|
||||
Comprehensive validation of treatment plan content quality and compliance.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import re
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
# Validation criteria and patterns
|
||||
VALIDATION_CHECKS = {
|
||||
'smart_goals': {
|
||||
'name': 'SMART Goals Criteria',
|
||||
'patterns': [
|
||||
(r'\bspecific\b', 'Specific criterion'),
|
||||
(r'\bmeasurable\b', 'Measurable criterion'),
|
||||
(r'\bachievable\b', 'Achievable criterion'),
|
||||
(r'\brelevant\b', 'Relevant criterion'),
|
||||
(r'\btime[- ]?bound\b', 'Time-bound criterion')
|
||||
]
|
||||
},
|
||||
'evidence_based': {
|
||||
'name': 'Evidence-Based Practice',
|
||||
'patterns': [
|
||||
(r'guideline|evidence|study|trial|research', 'Evidence/guideline references'),
|
||||
(r'\\cite\{|\\bibitem\{|\\bibliography\{', 'Citations present')
|
||||
]
|
||||
},
|
||||
'patient_centered': {
|
||||
'name': 'Patient-Centered Care',
|
||||
'patterns': [
|
||||
(r'patient.*preference|shared decision|patient.*value|patient.*priority', 'Patient preferences'),
|
||||
(r'quality of life|functional.*goal|patient.*goal', 'Functional/QoL goals')
|
||||
]
|
||||
},
|
||||
'safety': {
|
||||
'name': 'Safety and Risk Mitigation',
|
||||
'patterns': [
|
||||
(r'adverse.*effect|side effect|risk|complication', 'Adverse effects mentioned'),
|
||||
(r'monitoring|warning sign|emergency|when to call', 'Safety monitoring plan')
|
||||
]
|
||||
},
|
||||
'medication': {
|
||||
'name': 'Medication Documentation',
|
||||
'patterns': [
|
||||
(r'\\d+\s*mg|\\d+\s*mcg|dose|dosage', 'Specific doses'),
|
||||
(r'daily|BID|TID|QID|once|twice', 'Frequency specified'),
|
||||
(r'rationale|indication|because|for', 'Rationale provided')
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def read_file(filepath: Path) -> str:
|
||||
"""Read and return file contents."""
|
||||
try:
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
return f.read()
|
||||
except Exception as e:
|
||||
print(f"Error reading file: {e}", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
def validate_content(content: str) -> Dict[str, Tuple[int, int, List[str]]]:
|
||||
"""
|
||||
Validate content against criteria.
|
||||
Returns dict with results: {category: (passed, total, missing_items)}
|
||||
"""
|
||||
results = {}
|
||||
|
||||
for category, checks in VALIDATION_CHECKS.items():
|
||||
patterns = checks['patterns']
|
||||
passed = 0
|
||||
missing = []
|
||||
|
||||
for pattern, description in patterns:
|
||||
if re.search(pattern, content, re.IGNORECASE):
|
||||
passed += 1
|
||||
else:
|
||||
missing.append(description)
|
||||
|
||||
total = len(patterns)
|
||||
results[category] = (passed, total, missing)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def check_icd10_codes(content: str) -> Tuple[bool, int]:
|
||||
"""Check for ICD-10 code presence."""
|
||||
# ICD-10 format: Letter followed by 2 digits, optionally more digits/letters
|
||||
pattern = r'\b[A-TV-Z]\d{2}\.?[\dA-TV-Z]*\b'
|
||||
matches = re.findall(pattern, content)
|
||||
|
||||
has_codes = len(matches) > 0
|
||||
count = len(matches)
|
||||
|
||||
return has_codes, count
|
||||
|
||||
|
||||
def check_timeframes(content: str) -> Tuple[bool, List[str]]:
|
||||
"""Check for specific timeframes in goals."""
|
||||
timeframe_patterns = [
|
||||
r'\d+\s*week',
|
||||
r'\d+\s*month',
|
||||
r'\d+\s*day',
|
||||
r'within\s+\d+',
|
||||
r'by\s+\w+\s+\d+'
|
||||
]
|
||||
|
||||
found_timeframes = []
|
||||
for pattern in timeframe_patterns:
|
||||
matches = re.findall(pattern, content, re.IGNORECASE)
|
||||
found_timeframes.extend(matches[:3]) # Limit to avoid too many
|
||||
|
||||
has_timeframes = len(found_timeframes) > 0
|
||||
|
||||
return has_timeframes, found_timeframes[:5]
|
||||
|
||||
|
||||
def check_quantitative_goals(content: str) -> Tuple[bool, List[str]]:
|
||||
"""Check for quantitative/measurable goals."""
|
||||
# Look for numbers with units in goal context
|
||||
patterns = [
|
||||
r'\d+\s*%', # Percentages (HbA1c 7%)
|
||||
r'\d+/\d+', # Ratios (BP 130/80)
|
||||
r'\d+\s*mg/dL', # Lab values
|
||||
r'\d+\s*mmHg', # Blood pressure
|
||||
r'\d+\s*feet|meters', # Distance
|
||||
r'\d+\s*pounds|lbs|kg', # Weight
|
||||
r'\d+/10', # Pain scales
|
||||
r'\d+\s*minutes|hours' # Time
|
||||
]
|
||||
|
||||
found_metrics = []
|
||||
for pattern in patterns:
|
||||
matches = re.findall(pattern, content, re.IGNORECASE)
|
||||
found_metrics.extend(matches[:2])
|
||||
|
||||
has_metrics = len(found_metrics) > 0
|
||||
|
||||
return has_metrics, found_metrics[:5]
|
||||
|
||||
|
||||
def assess_readability(content: str) -> str:
|
||||
"""Basic readability assessment (very simplified)."""
|
||||
# Remove LaTeX commands for word count
|
||||
text_content = re.sub(r'\\[a-zA-Z]+(\{[^}]*\})?', '', content)
|
||||
text_content = re.sub(r'[{}%\\]', '', text_content)
|
||||
|
||||
words = text_content.split()
|
||||
word_count = len(words)
|
||||
|
||||
# Very rough sentences (periods followed by space/newline)
|
||||
sentences = re.split(r'[.!?]+\s+', text_content)
|
||||
sentence_count = len([s for s in sentences if s.strip()])
|
||||
|
||||
if sentence_count > 0:
|
||||
avg_words_per_sentence = word_count / sentence_count
|
||||
|
||||
if avg_words_per_sentence < 15:
|
||||
return "Simple (good for patient materials)"
|
||||
elif avg_words_per_sentence < 25:
|
||||
return "Moderate (appropriate for professional documentation)"
|
||||
else:
|
||||
return "Complex (may be difficult for some readers)"
|
||||
|
||||
return "Unable to assess"
|
||||
|
||||
|
||||
def display_validation_results(filepath: Path, results: Dict,
|
||||
has_icd10: bool, icd10_count: int,
|
||||
has_timeframes: bool, timeframe_examples: List[str],
|
||||
has_metrics: bool, metric_examples: List[str],
|
||||
readability: str):
|
||||
"""Display comprehensive validation results."""
|
||||
|
||||
print("\n" + "="*70)
|
||||
print("TREATMENT PLAN QUALITY VALIDATION")
|
||||
print("="*70)
|
||||
print(f"\nFile: {filepath}")
|
||||
print(f"File size: {filepath.stat().st_size:,} bytes")
|
||||
|
||||
# Overall quality score
|
||||
total_passed = sum(r[0] for r in results.values())
|
||||
total_checks = sum(r[1] for r in results.values())
|
||||
quality_pct = (total_passed / total_checks) * 100 if total_checks > 0 else 0
|
||||
|
||||
print("\n" + "-"*70)
|
||||
print("OVERALL QUALITY SCORE")
|
||||
print("-"*70)
|
||||
print(f"Validation checks passed: {total_passed}/{total_checks} ({quality_pct:.0f}%)")
|
||||
|
||||
# Detailed category results
|
||||
print("\n" + "-"*70)
|
||||
print("QUALITY CRITERIA ASSESSMENT")
|
||||
print("-"*70)
|
||||
|
||||
for category, (passed, total, missing) in results.items():
|
||||
category_name = VALIDATION_CHECKS[category]['name']
|
||||
pct = (passed / total) * 100 if total > 0 else 0
|
||||
status = "✓" if passed == total else "⚠" if passed > 0 else "✗"
|
||||
|
||||
print(f"\n{status} {category_name}: {passed}/{total} ({pct:.0f}%)")
|
||||
|
||||
if missing:
|
||||
print(" Missing:")
|
||||
for item in missing:
|
||||
print(f" • {item}")
|
||||
|
||||
# Specific checks
|
||||
print("\n" + "-"*70)
|
||||
print("SPECIFIC VALIDATION CHECKS")
|
||||
print("-"*70)
|
||||
|
||||
# ICD-10 codes
|
||||
if has_icd10:
|
||||
print(f"✓ ICD-10 diagnosis codes present ({icd10_count} found)")
|
||||
else:
|
||||
print("✗ No ICD-10 diagnosis codes detected")
|
||||
print(" Recommendation: Include ICD-10 codes for all diagnoses")
|
||||
|
||||
# Timeframes
|
||||
if has_timeframes:
|
||||
print(f"✓ Time-bound goals present")
|
||||
if timeframe_examples:
|
||||
print(" Examples:", ", ".join(timeframe_examples[:3]))
|
||||
else:
|
||||
print("✗ No specific timeframes found in goals")
|
||||
print(" Recommendation: Add specific timeframes (e.g., 'within 3 months', '8 weeks')")
|
||||
|
||||
# Measurable metrics
|
||||
if has_metrics:
|
||||
print(f"✓ Quantitative/measurable goals present")
|
||||
if metric_examples:
|
||||
print(" Examples:", ", ".join(metric_examples[:3]))
|
||||
else:
|
||||
print("⚠ Limited quantitative metrics found")
|
||||
print(" Recommendation: Include specific measurable targets (HbA1c <7%, BP <130/80)")
|
||||
|
||||
# Readability
|
||||
print(f"\nReadability assessment: {readability}")
|
||||
|
||||
# Summary and recommendations
|
||||
print("\n" + "="*70)
|
||||
print("SUMMARY AND RECOMMENDATIONS")
|
||||
print("="*70)
|
||||
|
||||
if quality_pct >= 90:
|
||||
print("\n✓ EXCELLENT quality - Treatment plan meets high standards")
|
||||
elif quality_pct >= 75:
|
||||
print("\n✓ GOOD quality - Treatment plan is well-developed with minor areas for improvement")
|
||||
elif quality_pct >= 60:
|
||||
print("\n⚠ FAIR quality - Several important elements need strengthening")
|
||||
else:
|
||||
print("\n✗ NEEDS IMPROVEMENT - Significant quality issues to address")
|
||||
|
||||
# Specific recommendations
|
||||
print("\nKey Recommendations:")
|
||||
|
||||
recommendations = []
|
||||
|
||||
# SMART goals
|
||||
if results['smart_goals'][0] < results['smart_goals'][1]:
|
||||
recommendations.append("Ensure all goals meet SMART criteria (Specific, Measurable, Achievable, Relevant, Time-bound)")
|
||||
|
||||
# Evidence-based
|
||||
if results['evidence_based'][0] == 0:
|
||||
recommendations.append("Add evidence-based rationale and cite clinical practice guidelines")
|
||||
|
||||
# Patient-centered
|
||||
if results['patient_centered'][0] < results['patient_centered'][1]:
|
||||
recommendations.append("Incorporate patient preferences and functional quality-of-life goals")
|
||||
|
||||
# Safety
|
||||
if results['safety'][0] < results['safety'][1]:
|
||||
recommendations.append("Include comprehensive safety monitoring and risk mitigation strategies")
|
||||
|
||||
# Medication documentation
|
||||
if results['medication'][0] < results['medication'][1]:
|
||||
recommendations.append("Document medications with specific doses, frequencies, and rationales")
|
||||
|
||||
if not has_icd10:
|
||||
recommendations.append("Add ICD-10 diagnosis codes for billing and documentation support")
|
||||
|
||||
if not has_timeframes:
|
||||
recommendations.append("Add specific timeframes to all treatment goals")
|
||||
|
||||
if recommendations:
|
||||
for i, rec in enumerate(recommendations, 1):
|
||||
print(f"{i}. {rec}")
|
||||
else:
|
||||
print("None - Treatment plan demonstrates excellent quality across all criteria!")
|
||||
|
||||
print("\n" + "="*70)
|
||||
|
||||
# Return exit code
|
||||
return 0 if quality_pct >= 70 else 1
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Validate treatment plan quality and compliance',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Validate a treatment plan
|
||||
python validate_treatment_plan.py my_plan.tex
|
||||
|
||||
# Use in automated workflows (exits with error if quality <70%)
|
||||
python validate_treatment_plan.py plan.tex && echo "Quality check passed"
|
||||
|
||||
Validation Categories:
|
||||
- SMART goals criteria (Specific, Measurable, Achievable, Relevant, Time-bound)
|
||||
- Evidence-based practice (guidelines, citations)
|
||||
- Patient-centered care (preferences, functional goals)
|
||||
- Safety and risk mitigation (adverse effects, monitoring)
|
||||
- Medication documentation (doses, frequencies, rationales)
|
||||
- ICD-10 coding, timeframes, measurable metrics
|
||||
|
||||
Exit Codes:
|
||||
0 - Quality ≥70% (acceptable)
|
||||
1 - Quality <70% (needs improvement)
|
||||
2 - File error or invalid arguments
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'file',
|
||||
type=Path,
|
||||
help='Treatment plan file to validate (.tex format)'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check file exists
|
||||
if not args.file.exists():
|
||||
print(f"Error: File not found: {args.file}", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
# Read and validate
|
||||
content = read_file(args.file)
|
||||
|
||||
# Run validation checks
|
||||
results = validate_content(content)
|
||||
has_icd10, icd10_count = check_icd10_codes(content)
|
||||
has_timeframes, timeframe_examples = check_timeframes(content)
|
||||
has_metrics, metric_examples = check_quantitative_goals(content)
|
||||
readability = assess_readability(content)
|
||||
|
||||
# Display results
|
||||
exit_code = display_validation_results(
|
||||
args.file, results,
|
||||
has_icd10, icd10_count,
|
||||
has_timeframes, timeframe_examples,
|
||||
has_metrics, metric_examples,
|
||||
readability
|
||||
)
|
||||
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user