mirror of
https://github.com/K-Dense-AI/claude-scientific-skills.git
synced 2026-01-26 16:58:56 +08:00
Added ISO 13485 certification prep skill
This commit is contained in:
@@ -0,0 +1,440 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
ISO 13485 Gap Analysis Tool
|
||||
|
||||
This script analyzes documentation provided by the user and identifies gaps
|
||||
against ISO 13485:2016 requirements.
|
||||
|
||||
Usage:
|
||||
python gap_analyzer.py --docs-dir <path> [--output <path>]
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Set, Tuple
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
# ISO 13485:2016 Required Documented Procedures
|
||||
REQUIRED_PROCEDURES = {
|
||||
"4.1.5": {
|
||||
"title": "Risk Management",
|
||||
"keywords": ["risk", "risk management", "iso 14971", "risk analysis", "risk control"],
|
||||
"clause": "4.1.5"
|
||||
},
|
||||
"4.1.6": {
|
||||
"title": "Software Validation",
|
||||
"keywords": ["software validation", "computer software", "software application", "validation"],
|
||||
"clause": "4.1.6"
|
||||
},
|
||||
"4.2.4": {
|
||||
"title": "Control of Documents",
|
||||
"keywords": ["document control", "document approval", "document revision", "obsolete document"],
|
||||
"clause": "4.2.4"
|
||||
},
|
||||
"4.2.5": {
|
||||
"title": "Control of Records",
|
||||
"keywords": ["record control", "retention", "record storage", "record retrieval"],
|
||||
"clause": "4.2.5"
|
||||
},
|
||||
"5.5.3": {
|
||||
"title": "Internal Communication",
|
||||
"keywords": ["internal communication", "communication process", "qms communication"],
|
||||
"clause": "5.5.3"
|
||||
},
|
||||
"5.6.1": {
|
||||
"title": "Management Review",
|
||||
"keywords": ["management review", "review meeting", "management meeting"],
|
||||
"clause": "5.6.1"
|
||||
},
|
||||
"6.2": {
|
||||
"title": "Human Resources / Competence",
|
||||
"keywords": ["competence", "training", "human resources", "personnel qualification"],
|
||||
"clause": "6.2"
|
||||
},
|
||||
"7.2.3": {
|
||||
"title": "Customer Communication",
|
||||
"keywords": ["customer communication", "customer feedback", "advisory notice"],
|
||||
"clause": "7.2.3"
|
||||
},
|
||||
"7.3": {
|
||||
"title": "Design and Development",
|
||||
"keywords": ["design", "development", "design input", "design output", "design verification", "design validation"],
|
||||
"clause": "7.3"
|
||||
},
|
||||
"7.4.1": {
|
||||
"title": "Purchasing",
|
||||
"keywords": ["purchasing", "supplier", "procurement", "vendor"],
|
||||
"clause": "7.4.1"
|
||||
},
|
||||
"7.4.3": {
|
||||
"title": "Verification of Purchased Product",
|
||||
"keywords": ["purchased product", "incoming inspection", "verification of purchased"],
|
||||
"clause": "7.4.3"
|
||||
},
|
||||
"7.5.1": {
|
||||
"title": "Production and Service Provision",
|
||||
"keywords": ["production", "manufacturing", "work instruction", "process control"],
|
||||
"clause": "7.5.1"
|
||||
},
|
||||
"7.5.6": {
|
||||
"title": "Process Validation",
|
||||
"keywords": ["process validation", "validation protocol", "validation report"],
|
||||
"clause": "7.5.6"
|
||||
},
|
||||
"7.5.8": {
|
||||
"title": "Product Identification",
|
||||
"keywords": ["product identification", "identification", "labeling", "marking"],
|
||||
"clause": "7.5.8"
|
||||
},
|
||||
"7.5.9": {
|
||||
"title": "Traceability",
|
||||
"keywords": ["traceability", "lot", "serial number", "batch"],
|
||||
"clause": "7.5.9"
|
||||
},
|
||||
"7.5.11": {
|
||||
"title": "Preservation of Product",
|
||||
"keywords": ["preservation", "storage", "packaging", "handling"],
|
||||
"clause": "7.5.11"
|
||||
},
|
||||
"7.6": {
|
||||
"title": "Control of Monitoring and Measuring Equipment",
|
||||
"keywords": ["calibration", "monitoring equipment", "measuring equipment", "measurement"],
|
||||
"clause": "7.6"
|
||||
},
|
||||
"8.2.1": {
|
||||
"title": "Feedback",
|
||||
"keywords": ["feedback", "post-production", "post-market", "early warning"],
|
||||
"clause": "8.2.1"
|
||||
},
|
||||
"8.2.2": {
|
||||
"title": "Complaint Handling",
|
||||
"keywords": ["complaint", "customer complaint", "complaint handling", "complaint investigation"],
|
||||
"clause": "8.2.2"
|
||||
},
|
||||
"8.2.3": {
|
||||
"title": "Reporting to Regulatory Authorities",
|
||||
"keywords": ["regulatory reporting", "adverse event", "mdr report", "reportable event"],
|
||||
"clause": "8.2.3"
|
||||
},
|
||||
"8.2.4": {
|
||||
"title": "Internal Audit",
|
||||
"keywords": ["internal audit", "audit program", "audit plan", "audit checklist"],
|
||||
"clause": "8.2.4"
|
||||
},
|
||||
"8.2.5": {
|
||||
"title": "Monitoring and Measurement of Processes",
|
||||
"keywords": ["process monitoring", "process measurement", "process metrics"],
|
||||
"clause": "8.2.5"
|
||||
},
|
||||
"8.2.6": {
|
||||
"title": "Monitoring and Measurement of Product",
|
||||
"keywords": ["product inspection", "product testing", "acceptance criteria", "release"],
|
||||
"clause": "8.2.6"
|
||||
},
|
||||
"8.3": {
|
||||
"title": "Control of Nonconforming Product",
|
||||
"keywords": ["nonconforming", "ncr", "nonconformance", "reject"],
|
||||
"clause": "8.3"
|
||||
},
|
||||
"8.5.2": {
|
||||
"title": "Corrective Action",
|
||||
"keywords": ["corrective action", "capa", "root cause"],
|
||||
"clause": "8.5.2"
|
||||
},
|
||||
"8.5.3": {
|
||||
"title": "Preventive Action",
|
||||
"keywords": ["preventive action", "capa", "prevention"],
|
||||
"clause": "8.5.3"
|
||||
}
|
||||
}
|
||||
|
||||
# Additional key documents (not procedures but required)
|
||||
KEY_DOCUMENTS = {
|
||||
"Quality Manual": ["quality manual", "qm-", "quality management system"],
|
||||
"Medical Device File": ["medical device file", "mdf", "device master record", "dmr"],
|
||||
"Quality Policy": ["quality policy"],
|
||||
"Quality Objectives": ["quality objective"],
|
||||
}
|
||||
|
||||
|
||||
class GapAnalyzer:
|
||||
"""Analyzes documentation against ISO 13485 requirements."""
|
||||
|
||||
def __init__(self, docs_dir: str):
|
||||
"""Initialize analyzer with document directory."""
|
||||
self.docs_dir = Path(docs_dir)
|
||||
self.found_procedures: Dict[str, List[str]] = {}
|
||||
self.found_documents: Dict[str, List[str]] = {}
|
||||
|
||||
def analyze(self) -> Dict:
|
||||
"""Run gap analysis on provided documentation."""
|
||||
print(f"Analyzing documents in: {self.docs_dir}")
|
||||
|
||||
if not self.docs_dir.exists():
|
||||
print(f"ERROR: Directory not found: {self.docs_dir}")
|
||||
return {}
|
||||
|
||||
# Scan all documents
|
||||
documents = self._scan_documents()
|
||||
print(f"Found {len(documents)} documents to analyze")
|
||||
|
||||
# Search for each required procedure
|
||||
for clause_id, proc_info in REQUIRED_PROCEDURES.items():
|
||||
self._search_for_procedure(documents, clause_id, proc_info)
|
||||
|
||||
# Search for key documents
|
||||
for doc_name, keywords in KEY_DOCUMENTS.items():
|
||||
self._search_for_document(documents, doc_name, keywords)
|
||||
|
||||
# Generate gap analysis report
|
||||
report = self._generate_report()
|
||||
|
||||
return report
|
||||
|
||||
def _scan_documents(self) -> List[Tuple[Path, str]]:
|
||||
"""Scan directory for documents and read content."""
|
||||
documents = []
|
||||
|
||||
# Supported file extensions
|
||||
extensions = ['.txt', '.md', '.doc', '.docx', '.pdf', '.odt']
|
||||
|
||||
for ext in extensions:
|
||||
for file_path in self.docs_dir.rglob(f'*{ext}'):
|
||||
try:
|
||||
# Read file content (simple text reading)
|
||||
if ext in ['.txt', '.md']:
|
||||
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
content = f.read().lower()
|
||||
documents.append((file_path, content))
|
||||
else:
|
||||
# For other formats, just note the filename
|
||||
# (Full text extraction would require additional libraries)
|
||||
filename = file_path.name.lower()
|
||||
documents.append((file_path, filename))
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not read {file_path}: {e}")
|
||||
|
||||
return documents
|
||||
|
||||
def _search_for_procedure(self, documents: List[Tuple[Path, str]],
|
||||
clause_id: str, proc_info: Dict):
|
||||
"""Search documents for a specific procedure."""
|
||||
title = proc_info['title']
|
||||
keywords = proc_info['keywords']
|
||||
|
||||
matches = []
|
||||
for file_path, content in documents:
|
||||
# Check if any keyword appears in the document
|
||||
if any(keyword in content for keyword in keywords):
|
||||
matches.append(str(file_path.relative_to(self.docs_dir)))
|
||||
|
||||
if matches:
|
||||
self.found_procedures[clause_id] = matches
|
||||
|
||||
def _search_for_document(self, documents: List[Tuple[Path, str]],
|
||||
doc_name: str, keywords: List[str]):
|
||||
"""Search for key documents."""
|
||||
matches = []
|
||||
for file_path, content in documents:
|
||||
if any(keyword in content for keyword in keywords):
|
||||
matches.append(str(file_path.relative_to(self.docs_dir)))
|
||||
|
||||
if matches:
|
||||
self.found_documents[doc_name] = matches
|
||||
|
||||
def _generate_report(self) -> Dict:
|
||||
"""Generate comprehensive gap analysis report."""
|
||||
total_procedures = len(REQUIRED_PROCEDURES)
|
||||
found_count = len(self.found_procedures)
|
||||
missing_count = total_procedures - found_count
|
||||
|
||||
missing_procedures = []
|
||||
for clause_id, proc_info in REQUIRED_PROCEDURES.items():
|
||||
if clause_id not in self.found_procedures:
|
||||
missing_procedures.append({
|
||||
"clause": clause_id,
|
||||
"title": proc_info['title'],
|
||||
"keywords": proc_info['keywords']
|
||||
})
|
||||
|
||||
missing_documents = []
|
||||
for doc_name, keywords in KEY_DOCUMENTS.items():
|
||||
if doc_name not in self.found_documents:
|
||||
missing_documents.append({
|
||||
"document": doc_name,
|
||||
"keywords": keywords
|
||||
})
|
||||
|
||||
compliance_percentage = (found_count / total_procedures) * 100
|
||||
|
||||
report = {
|
||||
"analysis_date": datetime.now().isoformat(),
|
||||
"documents_analyzed": str(self.docs_dir),
|
||||
"summary": {
|
||||
"total_required_procedures": total_procedures,
|
||||
"procedures_found": found_count,
|
||||
"procedures_missing": missing_count,
|
||||
"compliance_percentage": round(compliance_percentage, 1)
|
||||
},
|
||||
"found_procedures": self.found_procedures,
|
||||
"missing_procedures": missing_procedures,
|
||||
"found_documents": self.found_documents,
|
||||
"missing_documents": missing_documents,
|
||||
"recommendations": self._generate_recommendations(missing_procedures, missing_documents)
|
||||
}
|
||||
|
||||
return report
|
||||
|
||||
def _generate_recommendations(self, missing_procedures: List[Dict],
|
||||
missing_documents: List[Dict]) -> List[str]:
|
||||
"""Generate recommendations based on gaps."""
|
||||
recommendations = []
|
||||
|
||||
if not self.found_documents.get("Quality Manual"):
|
||||
recommendations.append(
|
||||
"CRITICAL: Create a Quality Manual - this is the foundational document of your QMS"
|
||||
)
|
||||
|
||||
if not self.found_documents.get("Quality Policy"):
|
||||
recommendations.append(
|
||||
"HIGH PRIORITY: Develop and document your Quality Policy statement"
|
||||
)
|
||||
|
||||
if missing_procedures:
|
||||
high_priority_clauses = ["8.2.2", "8.5.2", "8.5.3", "7.4.1", "8.2.4"]
|
||||
high_priority_missing = [p for p in missing_procedures
|
||||
if p['clause'] in high_priority_clauses]
|
||||
|
||||
if high_priority_missing:
|
||||
titles = [p['title'] for p in high_priority_missing]
|
||||
recommendations.append(
|
||||
f"HIGH PRIORITY: Develop the following critical procedures: {', '.join(titles)}"
|
||||
)
|
||||
|
||||
if len(missing_procedures) > 20:
|
||||
recommendations.append(
|
||||
"Consider engaging a consultant or using templates to accelerate QMS development"
|
||||
)
|
||||
|
||||
if len(missing_procedures) < 5:
|
||||
recommendations.append(
|
||||
"You're close to completion! Focus on finalizing remaining procedures and conducting internal audit"
|
||||
)
|
||||
|
||||
return recommendations
|
||||
|
||||
|
||||
def print_report(report: Dict):
|
||||
"""Print formatted gap analysis report."""
|
||||
print("\n" + "="*80)
|
||||
print(" ISO 13485:2016 GAP ANALYSIS REPORT")
|
||||
print("="*80)
|
||||
print(f"\nAnalysis Date: {report['analysis_date']}")
|
||||
print(f"Documents Location: {report['documents_analyzed']}\n")
|
||||
|
||||
# Summary
|
||||
summary = report['summary']
|
||||
print("-" * 80)
|
||||
print(" SUMMARY")
|
||||
print("-" * 80)
|
||||
print(f"Total Required Procedures: {summary['total_required_procedures']}")
|
||||
print(f"Procedures Found: {summary['procedures_found']}")
|
||||
print(f"Procedures Missing: {summary['procedures_missing']}")
|
||||
print(f"Compliance: {summary['compliance_percentage']}%\n")
|
||||
|
||||
# Found Procedures
|
||||
if report['found_procedures']:
|
||||
print("-" * 80)
|
||||
print(" FOUND PROCEDURES")
|
||||
print("-" * 80)
|
||||
for clause_id, files in sorted(report['found_procedures'].items()):
|
||||
proc_info = REQUIRED_PROCEDURES[clause_id]
|
||||
print(f"\n[{clause_id}] {proc_info['title']}")
|
||||
for file in files:
|
||||
print(f" - {file}")
|
||||
|
||||
# Missing Procedures
|
||||
if report['missing_procedures']:
|
||||
print("\n" + "-" * 80)
|
||||
print(" MISSING PROCEDURES")
|
||||
print("-" * 80)
|
||||
for proc in report['missing_procedures']:
|
||||
print(f"\n[{proc['clause']}] {proc['title']}")
|
||||
print(f" Keywords to include: {', '.join(proc['keywords'][:3])}")
|
||||
|
||||
# Found Documents
|
||||
if report['found_documents']:
|
||||
print("\n" + "-" * 80)
|
||||
print(" FOUND KEY DOCUMENTS")
|
||||
print("-" * 80)
|
||||
for doc_name, files in report['found_documents'].items():
|
||||
print(f"\n{doc_name}:")
|
||||
for file in files:
|
||||
print(f" - {file}")
|
||||
|
||||
# Missing Documents
|
||||
if report['missing_documents']:
|
||||
print("\n" + "-" * 80)
|
||||
print(" MISSING KEY DOCUMENTS")
|
||||
print("-" * 80)
|
||||
for doc in report['missing_documents']:
|
||||
print(f" - {doc['document']}")
|
||||
|
||||
# Recommendations
|
||||
if report['recommendations']:
|
||||
print("\n" + "-" * 80)
|
||||
print(" RECOMMENDATIONS")
|
||||
print("-" * 80)
|
||||
for i, rec in enumerate(report['recommendations'], 1):
|
||||
print(f"{i}. {rec}")
|
||||
|
||||
print("\n" + "="*80)
|
||||
print(" END OF REPORT")
|
||||
print("="*80 + "\n")
|
||||
|
||||
|
||||
def save_report(report: Dict, output_path: str):
|
||||
"""Save report to JSON file."""
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(report, f, indent=2)
|
||||
print(f"Report saved to: {output_path}")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='ISO 13485 Gap Analysis Tool',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||
)
|
||||
parser.add_argument(
|
||||
'--docs-dir',
|
||||
required=True,
|
||||
help='Directory containing documentation to analyze'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--output',
|
||||
help='Output file path for JSON report (optional)'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Run analysis
|
||||
analyzer = GapAnalyzer(args.docs_dir)
|
||||
report = analyzer.analyze()
|
||||
|
||||
# Print report
|
||||
print_report(report)
|
||||
|
||||
# Save report if output path specified
|
||||
if args.output:
|
||||
save_report(report, args.output)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user