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()
|
||||
|
||||
Reference in New Issue
Block a user