Files
2025-11-30 08:30:18 +08:00

368 lines
12 KiB
Python

#!/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()