319 lines
9.7 KiB
Python
319 lines
9.7 KiB
Python
#!/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()
|
|
|