attune-ai 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- attune/__init__.py +358 -0
- attune/adaptive/__init__.py +13 -0
- attune/adaptive/task_complexity.py +127 -0
- attune/agent_monitoring.py +414 -0
- attune/cache/__init__.py +117 -0
- attune/cache/base.py +166 -0
- attune/cache/dependency_manager.py +256 -0
- attune/cache/hash_only.py +251 -0
- attune/cache/hybrid.py +457 -0
- attune/cache/storage.py +285 -0
- attune/cache_monitor.py +356 -0
- attune/cache_stats.py +298 -0
- attune/cli/__init__.py +152 -0
- attune/cli/__main__.py +12 -0
- attune/cli/commands/__init__.py +1 -0
- attune/cli/commands/batch.py +264 -0
- attune/cli/commands/cache.py +248 -0
- attune/cli/commands/help.py +331 -0
- attune/cli/commands/info.py +140 -0
- attune/cli/commands/inspect.py +436 -0
- attune/cli/commands/inspection.py +57 -0
- attune/cli/commands/memory.py +48 -0
- attune/cli/commands/metrics.py +92 -0
- attune/cli/commands/orchestrate.py +184 -0
- attune/cli/commands/patterns.py +207 -0
- attune/cli/commands/profiling.py +202 -0
- attune/cli/commands/provider.py +98 -0
- attune/cli/commands/routing.py +285 -0
- attune/cli/commands/setup.py +96 -0
- attune/cli/commands/status.py +235 -0
- attune/cli/commands/sync.py +166 -0
- attune/cli/commands/tier.py +121 -0
- attune/cli/commands/utilities.py +114 -0
- attune/cli/commands/workflow.py +579 -0
- attune/cli/core.py +32 -0
- attune/cli/parsers/__init__.py +68 -0
- attune/cli/parsers/batch.py +118 -0
- attune/cli/parsers/cache.py +65 -0
- attune/cli/parsers/help.py +41 -0
- attune/cli/parsers/info.py +26 -0
- attune/cli/parsers/inspect.py +66 -0
- attune/cli/parsers/metrics.py +42 -0
- attune/cli/parsers/orchestrate.py +61 -0
- attune/cli/parsers/patterns.py +54 -0
- attune/cli/parsers/provider.py +40 -0
- attune/cli/parsers/routing.py +110 -0
- attune/cli/parsers/setup.py +42 -0
- attune/cli/parsers/status.py +47 -0
- attune/cli/parsers/sync.py +31 -0
- attune/cli/parsers/tier.py +33 -0
- attune/cli/parsers/workflow.py +77 -0
- attune/cli/utils/__init__.py +1 -0
- attune/cli/utils/data.py +242 -0
- attune/cli/utils/helpers.py +68 -0
- attune/cli_legacy.py +3957 -0
- attune/cli_minimal.py +1159 -0
- attune/cli_router.py +437 -0
- attune/cli_unified.py +814 -0
- attune/config/__init__.py +66 -0
- attune/config/xml_config.py +286 -0
- attune/config.py +545 -0
- attune/coordination.py +870 -0
- attune/core.py +1511 -0
- attune/core_modules/__init__.py +15 -0
- attune/cost_tracker.py +626 -0
- attune/dashboard/__init__.py +41 -0
- attune/dashboard/app.py +512 -0
- attune/dashboard/simple_server.py +435 -0
- attune/dashboard/standalone_server.py +547 -0
- attune/discovery.py +306 -0
- attune/emergence.py +306 -0
- attune/exceptions.py +123 -0
- attune/feedback_loops.py +373 -0
- attune/hot_reload/README.md +473 -0
- attune/hot_reload/__init__.py +62 -0
- attune/hot_reload/config.py +83 -0
- attune/hot_reload/integration.py +229 -0
- attune/hot_reload/reloader.py +298 -0
- attune/hot_reload/watcher.py +183 -0
- attune/hot_reload/websocket.py +177 -0
- attune/levels.py +577 -0
- attune/leverage_points.py +441 -0
- attune/logging_config.py +261 -0
- attune/mcp/__init__.py +10 -0
- attune/mcp/server.py +506 -0
- attune/memory/__init__.py +237 -0
- attune/memory/claude_memory.py +469 -0
- attune/memory/config.py +224 -0
- attune/memory/control_panel.py +1290 -0
- attune/memory/control_panel_support.py +145 -0
- attune/memory/cross_session.py +845 -0
- attune/memory/edges.py +179 -0
- attune/memory/encryption.py +159 -0
- attune/memory/file_session.py +770 -0
- attune/memory/graph.py +570 -0
- attune/memory/long_term.py +913 -0
- attune/memory/long_term_types.py +99 -0
- attune/memory/mixins/__init__.py +25 -0
- attune/memory/mixins/backend_init_mixin.py +249 -0
- attune/memory/mixins/capabilities_mixin.py +208 -0
- attune/memory/mixins/handoff_mixin.py +208 -0
- attune/memory/mixins/lifecycle_mixin.py +49 -0
- attune/memory/mixins/long_term_mixin.py +352 -0
- attune/memory/mixins/promotion_mixin.py +109 -0
- attune/memory/mixins/short_term_mixin.py +182 -0
- attune/memory/nodes.py +179 -0
- attune/memory/redis_bootstrap.py +540 -0
- attune/memory/security/__init__.py +31 -0
- attune/memory/security/audit_logger.py +932 -0
- attune/memory/security/pii_scrubber.py +640 -0
- attune/memory/security/secrets_detector.py +678 -0
- attune/memory/short_term.py +2192 -0
- attune/memory/simple_storage.py +302 -0
- attune/memory/storage/__init__.py +15 -0
- attune/memory/storage_backend.py +167 -0
- attune/memory/summary_index.py +583 -0
- attune/memory/types.py +446 -0
- attune/memory/unified.py +182 -0
- attune/meta_workflows/__init__.py +74 -0
- attune/meta_workflows/agent_creator.py +248 -0
- attune/meta_workflows/builtin_templates.py +567 -0
- attune/meta_workflows/cli_commands/__init__.py +56 -0
- attune/meta_workflows/cli_commands/agent_commands.py +321 -0
- attune/meta_workflows/cli_commands/analytics_commands.py +442 -0
- attune/meta_workflows/cli_commands/config_commands.py +232 -0
- attune/meta_workflows/cli_commands/memory_commands.py +182 -0
- attune/meta_workflows/cli_commands/template_commands.py +354 -0
- attune/meta_workflows/cli_commands/workflow_commands.py +382 -0
- attune/meta_workflows/cli_meta_workflows.py +59 -0
- attune/meta_workflows/form_engine.py +292 -0
- attune/meta_workflows/intent_detector.py +409 -0
- attune/meta_workflows/models.py +569 -0
- attune/meta_workflows/pattern_learner.py +738 -0
- attune/meta_workflows/plan_generator.py +384 -0
- attune/meta_workflows/session_context.py +397 -0
- attune/meta_workflows/template_registry.py +229 -0
- attune/meta_workflows/workflow.py +984 -0
- attune/metrics/__init__.py +12 -0
- attune/metrics/collector.py +31 -0
- attune/metrics/prompt_metrics.py +194 -0
- attune/models/__init__.py +172 -0
- attune/models/__main__.py +13 -0
- attune/models/adaptive_routing.py +437 -0
- attune/models/auth_cli.py +444 -0
- attune/models/auth_strategy.py +450 -0
- attune/models/cli.py +655 -0
- attune/models/empathy_executor.py +354 -0
- attune/models/executor.py +257 -0
- attune/models/fallback.py +762 -0
- attune/models/provider_config.py +282 -0
- attune/models/registry.py +472 -0
- attune/models/tasks.py +359 -0
- attune/models/telemetry/__init__.py +71 -0
- attune/models/telemetry/analytics.py +594 -0
- attune/models/telemetry/backend.py +196 -0
- attune/models/telemetry/data_models.py +431 -0
- attune/models/telemetry/storage.py +489 -0
- attune/models/token_estimator.py +420 -0
- attune/models/validation.py +280 -0
- attune/monitoring/__init__.py +52 -0
- attune/monitoring/alerts.py +946 -0
- attune/monitoring/alerts_cli.py +448 -0
- attune/monitoring/multi_backend.py +271 -0
- attune/monitoring/otel_backend.py +362 -0
- attune/optimization/__init__.py +19 -0
- attune/optimization/context_optimizer.py +272 -0
- attune/orchestration/__init__.py +67 -0
- attune/orchestration/agent_templates.py +707 -0
- attune/orchestration/config_store.py +499 -0
- attune/orchestration/execution_strategies.py +2111 -0
- attune/orchestration/meta_orchestrator.py +1168 -0
- attune/orchestration/pattern_learner.py +696 -0
- attune/orchestration/real_tools.py +931 -0
- attune/pattern_cache.py +187 -0
- attune/pattern_library.py +542 -0
- attune/patterns/debugging/all_patterns.json +81 -0
- attune/patterns/debugging/workflow_20260107_1770825e.json +77 -0
- attune/patterns/refactoring_memory.json +89 -0
- attune/persistence.py +564 -0
- attune/platform_utils.py +265 -0
- attune/plugins/__init__.py +28 -0
- attune/plugins/base.py +361 -0
- attune/plugins/registry.py +268 -0
- attune/project_index/__init__.py +32 -0
- attune/project_index/cli.py +335 -0
- attune/project_index/index.py +667 -0
- attune/project_index/models.py +504 -0
- attune/project_index/reports.py +474 -0
- attune/project_index/scanner.py +777 -0
- attune/project_index/scanner_parallel.py +291 -0
- attune/prompts/__init__.py +61 -0
- attune/prompts/config.py +77 -0
- attune/prompts/context.py +177 -0
- attune/prompts/parser.py +285 -0
- attune/prompts/registry.py +313 -0
- attune/prompts/templates.py +208 -0
- attune/redis_config.py +302 -0
- attune/redis_memory.py +799 -0
- attune/resilience/__init__.py +56 -0
- attune/resilience/circuit_breaker.py +256 -0
- attune/resilience/fallback.py +179 -0
- attune/resilience/health.py +300 -0
- attune/resilience/retry.py +209 -0
- attune/resilience/timeout.py +135 -0
- attune/routing/__init__.py +43 -0
- attune/routing/chain_executor.py +433 -0
- attune/routing/classifier.py +217 -0
- attune/routing/smart_router.py +234 -0
- attune/routing/workflow_registry.py +343 -0
- attune/scaffolding/README.md +589 -0
- attune/scaffolding/__init__.py +35 -0
- attune/scaffolding/__main__.py +14 -0
- attune/scaffolding/cli.py +240 -0
- attune/scaffolding/templates/base_wizard.py.jinja2 +121 -0
- attune/scaffolding/templates/coach_wizard.py.jinja2 +321 -0
- attune/scaffolding/templates/domain_wizard.py.jinja2 +408 -0
- attune/scaffolding/templates/linear_flow_wizard.py.jinja2 +203 -0
- attune/socratic/__init__.py +256 -0
- attune/socratic/ab_testing.py +958 -0
- attune/socratic/blueprint.py +533 -0
- attune/socratic/cli.py +703 -0
- attune/socratic/collaboration.py +1114 -0
- attune/socratic/domain_templates.py +924 -0
- attune/socratic/embeddings.py +738 -0
- attune/socratic/engine.py +794 -0
- attune/socratic/explainer.py +682 -0
- attune/socratic/feedback.py +772 -0
- attune/socratic/forms.py +629 -0
- attune/socratic/generator.py +732 -0
- attune/socratic/llm_analyzer.py +637 -0
- attune/socratic/mcp_server.py +702 -0
- attune/socratic/session.py +312 -0
- attune/socratic/storage.py +667 -0
- attune/socratic/success.py +730 -0
- attune/socratic/visual_editor.py +860 -0
- attune/socratic/web_ui.py +958 -0
- attune/telemetry/__init__.py +39 -0
- attune/telemetry/agent_coordination.py +475 -0
- attune/telemetry/agent_tracking.py +367 -0
- attune/telemetry/approval_gates.py +545 -0
- attune/telemetry/cli.py +1231 -0
- attune/telemetry/commands/__init__.py +14 -0
- attune/telemetry/commands/dashboard_commands.py +696 -0
- attune/telemetry/event_streaming.py +409 -0
- attune/telemetry/feedback_loop.py +567 -0
- attune/telemetry/usage_tracker.py +591 -0
- attune/templates.py +754 -0
- attune/test_generator/__init__.py +38 -0
- attune/test_generator/__main__.py +14 -0
- attune/test_generator/cli.py +234 -0
- attune/test_generator/generator.py +355 -0
- attune/test_generator/risk_analyzer.py +216 -0
- attune/test_generator/templates/unit_test.py.jinja2 +272 -0
- attune/tier_recommender.py +384 -0
- attune/tools.py +183 -0
- attune/trust/__init__.py +28 -0
- attune/trust/circuit_breaker.py +579 -0
- attune/trust_building.py +527 -0
- attune/validation/__init__.py +19 -0
- attune/validation/xml_validator.py +281 -0
- attune/vscode_bridge.py +173 -0
- attune/workflow_commands.py +780 -0
- attune/workflow_patterns/__init__.py +33 -0
- attune/workflow_patterns/behavior.py +249 -0
- attune/workflow_patterns/core.py +76 -0
- attune/workflow_patterns/output.py +99 -0
- attune/workflow_patterns/registry.py +255 -0
- attune/workflow_patterns/structural.py +288 -0
- attune/workflows/__init__.py +539 -0
- attune/workflows/autonomous_test_gen.py +1268 -0
- attune/workflows/base.py +2667 -0
- attune/workflows/batch_processing.py +342 -0
- attune/workflows/bug_predict.py +1084 -0
- attune/workflows/builder.py +273 -0
- attune/workflows/caching.py +253 -0
- attune/workflows/code_review.py +1048 -0
- attune/workflows/code_review_adapters.py +312 -0
- attune/workflows/code_review_pipeline.py +722 -0
- attune/workflows/config.py +645 -0
- attune/workflows/dependency_check.py +644 -0
- attune/workflows/document_gen/__init__.py +25 -0
- attune/workflows/document_gen/config.py +30 -0
- attune/workflows/document_gen/report_formatter.py +162 -0
- attune/workflows/document_gen/workflow.py +1426 -0
- attune/workflows/document_manager.py +216 -0
- attune/workflows/document_manager_README.md +134 -0
- attune/workflows/documentation_orchestrator.py +1205 -0
- attune/workflows/history.py +510 -0
- attune/workflows/keyboard_shortcuts/__init__.py +39 -0
- attune/workflows/keyboard_shortcuts/generators.py +391 -0
- attune/workflows/keyboard_shortcuts/parsers.py +416 -0
- attune/workflows/keyboard_shortcuts/prompts.py +295 -0
- attune/workflows/keyboard_shortcuts/schema.py +193 -0
- attune/workflows/keyboard_shortcuts/workflow.py +509 -0
- attune/workflows/llm_base.py +363 -0
- attune/workflows/manage_docs.py +87 -0
- attune/workflows/manage_docs_README.md +134 -0
- attune/workflows/manage_documentation.py +821 -0
- attune/workflows/new_sample_workflow1.py +149 -0
- attune/workflows/new_sample_workflow1_README.md +150 -0
- attune/workflows/orchestrated_health_check.py +849 -0
- attune/workflows/orchestrated_release_prep.py +600 -0
- attune/workflows/output.py +413 -0
- attune/workflows/perf_audit.py +863 -0
- attune/workflows/pr_review.py +762 -0
- attune/workflows/progress.py +785 -0
- attune/workflows/progress_server.py +322 -0
- attune/workflows/progressive/README 2.md +454 -0
- attune/workflows/progressive/README.md +454 -0
- attune/workflows/progressive/__init__.py +82 -0
- attune/workflows/progressive/cli.py +219 -0
- attune/workflows/progressive/core.py +488 -0
- attune/workflows/progressive/orchestrator.py +723 -0
- attune/workflows/progressive/reports.py +520 -0
- attune/workflows/progressive/telemetry.py +274 -0
- attune/workflows/progressive/test_gen.py +495 -0
- attune/workflows/progressive/workflow.py +589 -0
- attune/workflows/refactor_plan.py +694 -0
- attune/workflows/release_prep.py +895 -0
- attune/workflows/release_prep_crew.py +969 -0
- attune/workflows/research_synthesis.py +404 -0
- attune/workflows/routing.py +168 -0
- attune/workflows/secure_release.py +593 -0
- attune/workflows/security_adapters.py +297 -0
- attune/workflows/security_audit.py +1329 -0
- attune/workflows/security_audit_phase3.py +355 -0
- attune/workflows/seo_optimization.py +633 -0
- attune/workflows/step_config.py +234 -0
- attune/workflows/telemetry_mixin.py +269 -0
- attune/workflows/test5.py +125 -0
- attune/workflows/test5_README.md +158 -0
- attune/workflows/test_coverage_boost_crew.py +849 -0
- attune/workflows/test_gen/__init__.py +52 -0
- attune/workflows/test_gen/ast_analyzer.py +249 -0
- attune/workflows/test_gen/config.py +88 -0
- attune/workflows/test_gen/data_models.py +38 -0
- attune/workflows/test_gen/report_formatter.py +289 -0
- attune/workflows/test_gen/test_templates.py +381 -0
- attune/workflows/test_gen/workflow.py +655 -0
- attune/workflows/test_gen.py +54 -0
- attune/workflows/test_gen_behavioral.py +477 -0
- attune/workflows/test_gen_parallel.py +341 -0
- attune/workflows/test_lifecycle.py +526 -0
- attune/workflows/test_maintenance.py +627 -0
- attune/workflows/test_maintenance_cli.py +590 -0
- attune/workflows/test_maintenance_crew.py +840 -0
- attune/workflows/test_runner.py +622 -0
- attune/workflows/tier_tracking.py +531 -0
- attune/workflows/xml_enhanced_crew.py +285 -0
- attune_ai-2.0.0.dist-info/METADATA +1026 -0
- attune_ai-2.0.0.dist-info/RECORD +457 -0
- attune_ai-2.0.0.dist-info/WHEEL +5 -0
- attune_ai-2.0.0.dist-info/entry_points.txt +26 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE +201 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +101 -0
- attune_ai-2.0.0.dist-info/top_level.txt +5 -0
- attune_healthcare/__init__.py +13 -0
- attune_healthcare/monitors/__init__.py +9 -0
- attune_healthcare/monitors/clinical_protocol_monitor.py +315 -0
- attune_healthcare/monitors/monitoring/__init__.py +44 -0
- attune_healthcare/monitors/monitoring/protocol_checker.py +300 -0
- attune_healthcare/monitors/monitoring/protocol_loader.py +214 -0
- attune_healthcare/monitors/monitoring/sensor_parsers.py +306 -0
- attune_healthcare/monitors/monitoring/trajectory_analyzer.py +389 -0
- attune_llm/README.md +553 -0
- attune_llm/__init__.py +28 -0
- attune_llm/agent_factory/__init__.py +53 -0
- attune_llm/agent_factory/adapters/__init__.py +85 -0
- attune_llm/agent_factory/adapters/autogen_adapter.py +312 -0
- attune_llm/agent_factory/adapters/crewai_adapter.py +483 -0
- attune_llm/agent_factory/adapters/haystack_adapter.py +298 -0
- attune_llm/agent_factory/adapters/langchain_adapter.py +362 -0
- attune_llm/agent_factory/adapters/langgraph_adapter.py +333 -0
- attune_llm/agent_factory/adapters/native.py +228 -0
- attune_llm/agent_factory/adapters/wizard_adapter.py +423 -0
- attune_llm/agent_factory/base.py +305 -0
- attune_llm/agent_factory/crews/__init__.py +67 -0
- attune_llm/agent_factory/crews/code_review.py +1113 -0
- attune_llm/agent_factory/crews/health_check.py +1262 -0
- attune_llm/agent_factory/crews/refactoring.py +1128 -0
- attune_llm/agent_factory/crews/security_audit.py +1018 -0
- attune_llm/agent_factory/decorators.py +287 -0
- attune_llm/agent_factory/factory.py +558 -0
- attune_llm/agent_factory/framework.py +193 -0
- attune_llm/agent_factory/memory_integration.py +328 -0
- attune_llm/agent_factory/resilient.py +320 -0
- attune_llm/agents_md/__init__.py +22 -0
- attune_llm/agents_md/loader.py +218 -0
- attune_llm/agents_md/parser.py +271 -0
- attune_llm/agents_md/registry.py +307 -0
- attune_llm/claude_memory.py +466 -0
- attune_llm/cli/__init__.py +8 -0
- attune_llm/cli/sync_claude.py +487 -0
- attune_llm/code_health.py +1313 -0
- attune_llm/commands/__init__.py +51 -0
- attune_llm/commands/context.py +375 -0
- attune_llm/commands/loader.py +301 -0
- attune_llm/commands/models.py +231 -0
- attune_llm/commands/parser.py +371 -0
- attune_llm/commands/registry.py +429 -0
- attune_llm/config/__init__.py +29 -0
- attune_llm/config/unified.py +291 -0
- attune_llm/context/__init__.py +22 -0
- attune_llm/context/compaction.py +455 -0
- attune_llm/context/manager.py +434 -0
- attune_llm/contextual_patterns.py +361 -0
- attune_llm/core.py +907 -0
- attune_llm/git_pattern_extractor.py +435 -0
- attune_llm/hooks/__init__.py +24 -0
- attune_llm/hooks/config.py +306 -0
- attune_llm/hooks/executor.py +289 -0
- attune_llm/hooks/registry.py +302 -0
- attune_llm/hooks/scripts/__init__.py +39 -0
- attune_llm/hooks/scripts/evaluate_session.py +201 -0
- attune_llm/hooks/scripts/first_time_init.py +285 -0
- attune_llm/hooks/scripts/pre_compact.py +207 -0
- attune_llm/hooks/scripts/session_end.py +183 -0
- attune_llm/hooks/scripts/session_start.py +163 -0
- attune_llm/hooks/scripts/suggest_compact.py +225 -0
- attune_llm/learning/__init__.py +30 -0
- attune_llm/learning/evaluator.py +438 -0
- attune_llm/learning/extractor.py +514 -0
- attune_llm/learning/storage.py +560 -0
- attune_llm/levels.py +227 -0
- attune_llm/pattern_confidence.py +414 -0
- attune_llm/pattern_resolver.py +272 -0
- attune_llm/pattern_summary.py +350 -0
- attune_llm/providers.py +967 -0
- attune_llm/routing/__init__.py +32 -0
- attune_llm/routing/model_router.py +362 -0
- attune_llm/security/IMPLEMENTATION_SUMMARY.md +413 -0
- attune_llm/security/PHASE2_COMPLETE.md +384 -0
- attune_llm/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
- attune_llm/security/QUICK_REFERENCE.md +316 -0
- attune_llm/security/README.md +262 -0
- attune_llm/security/__init__.py +62 -0
- attune_llm/security/audit_logger.py +929 -0
- attune_llm/security/audit_logger_example.py +152 -0
- attune_llm/security/pii_scrubber.py +640 -0
- attune_llm/security/secrets_detector.py +678 -0
- attune_llm/security/secrets_detector_example.py +304 -0
- attune_llm/security/secure_memdocs.py +1192 -0
- attune_llm/security/secure_memdocs_example.py +278 -0
- attune_llm/session_status.py +745 -0
- attune_llm/state.py +246 -0
- attune_llm/utils/__init__.py +5 -0
- attune_llm/utils/tokens.py +349 -0
- attune_software/SOFTWARE_PLUGIN_README.md +57 -0
- attune_software/__init__.py +13 -0
- attune_software/cli/__init__.py +120 -0
- attune_software/cli/inspect.py +362 -0
- attune_software/cli.py +574 -0
- attune_software/plugin.py +188 -0
- workflow_scaffolding/__init__.py +11 -0
- workflow_scaffolding/__main__.py +12 -0
- workflow_scaffolding/cli.py +206 -0
- workflow_scaffolding/generator.py +265 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Clinical Monitoring Components
|
|
2
|
+
|
|
3
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
4
|
+
Licensed under Fair Source 0.9
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .protocol_checker import (
|
|
8
|
+
ComplianceStatus,
|
|
9
|
+
ProtocolChecker,
|
|
10
|
+
ProtocolCheckResult,
|
|
11
|
+
ProtocolDeviation,
|
|
12
|
+
)
|
|
13
|
+
from .protocol_loader import (
|
|
14
|
+
ClinicalProtocol,
|
|
15
|
+
ProtocolCriterion,
|
|
16
|
+
ProtocolIntervention,
|
|
17
|
+
ProtocolLoader,
|
|
18
|
+
load_protocol,
|
|
19
|
+
)
|
|
20
|
+
from .sensor_parsers import VitalSignReading, VitalSignType, normalize_vitals, parse_sensor_data
|
|
21
|
+
from .trajectory_analyzer import TrajectoryAnalyzer, TrajectoryPrediction, VitalTrend
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
# Protocol Loading
|
|
25
|
+
"ClinicalProtocol",
|
|
26
|
+
"ComplianceStatus",
|
|
27
|
+
"ProtocolCheckResult",
|
|
28
|
+
# Protocol Checking
|
|
29
|
+
"ProtocolChecker",
|
|
30
|
+
"ProtocolCriterion",
|
|
31
|
+
"ProtocolDeviation",
|
|
32
|
+
"ProtocolIntervention",
|
|
33
|
+
"ProtocolLoader",
|
|
34
|
+
# Trajectory Analysis
|
|
35
|
+
"TrajectoryAnalyzer",
|
|
36
|
+
"TrajectoryPrediction",
|
|
37
|
+
# Sensor Parsing
|
|
38
|
+
"VitalSignReading",
|
|
39
|
+
"VitalSignType",
|
|
40
|
+
"VitalTrend",
|
|
41
|
+
"load_protocol",
|
|
42
|
+
"normalize_vitals",
|
|
43
|
+
"parse_sensor_data",
|
|
44
|
+
]
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"""Protocol Checker
|
|
2
|
+
|
|
3
|
+
Checks patient sensor data against clinical protocol criteria.
|
|
4
|
+
|
|
5
|
+
This is the "linter" for healthcare - runs the protocol rules against current state.
|
|
6
|
+
|
|
7
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
8
|
+
Licensed under Fair Source 0.9
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from enum import Enum
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from .protocol_loader import ClinicalProtocol, ProtocolCriterion, ProtocolIntervention
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ComplianceStatus(Enum):
|
|
20
|
+
"""Status of protocol compliance"""
|
|
21
|
+
|
|
22
|
+
COMPLIANT = "compliant"
|
|
23
|
+
DEVIATION = "deviation"
|
|
24
|
+
OVERDUE = "overdue"
|
|
25
|
+
PENDING = "pending"
|
|
26
|
+
COMPLETED = "completed"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class CriterionResult:
|
|
31
|
+
"""Result of evaluating a single criterion"""
|
|
32
|
+
|
|
33
|
+
criterion: ProtocolCriterion
|
|
34
|
+
met: bool
|
|
35
|
+
actual_value: Any
|
|
36
|
+
points_awarded: int
|
|
37
|
+
reasoning: str
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class ProtocolDeviation:
|
|
42
|
+
"""A deviation from protocol (like a linting violation)"""
|
|
43
|
+
|
|
44
|
+
intervention: ProtocolIntervention
|
|
45
|
+
status: ComplianceStatus
|
|
46
|
+
time_activated: datetime | None = None
|
|
47
|
+
time_due: datetime | None = None
|
|
48
|
+
time_completed: datetime | None = None
|
|
49
|
+
overdue_by: str | None = None
|
|
50
|
+
reasoning: str = ""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class ProtocolCheckResult:
|
|
55
|
+
"""Result of checking patient against protocol.
|
|
56
|
+
|
|
57
|
+
This is like the output of running a linter.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
protocol_activated: bool
|
|
61
|
+
activation_score: int
|
|
62
|
+
threshold: int
|
|
63
|
+
criteria_results: list[CriterionResult]
|
|
64
|
+
deviations: list[ProtocolDeviation]
|
|
65
|
+
compliant_items: list[str]
|
|
66
|
+
alert_level: str # "NONE", "WARNING", "CRITICAL"
|
|
67
|
+
recommendation: str
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ProtocolChecker:
|
|
71
|
+
"""Checks patient state against clinical protocol.
|
|
72
|
+
|
|
73
|
+
This is the "linter engine" for healthcare.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(self):
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
def check_compliance(
|
|
80
|
+
self,
|
|
81
|
+
protocol: ClinicalProtocol,
|
|
82
|
+
patient_data: dict[str, Any],
|
|
83
|
+
intervention_status: dict[str, Any] | None = None,
|
|
84
|
+
) -> ProtocolCheckResult:
|
|
85
|
+
"""Check if patient data meets protocol criteria.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
protocol: Clinical protocol to check against
|
|
89
|
+
patient_data: Current patient sensor data
|
|
90
|
+
intervention_status: Status of interventions (if protocol active)
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
ProtocolCheckResult with deviations
|
|
94
|
+
|
|
95
|
+
Example:
|
|
96
|
+
>>> patient = {"systolic_bp": 95, "respiratory_rate": 24, "hr": 110}
|
|
97
|
+
>>> result = checker.check_compliance(sepsis_protocol, patient)
|
|
98
|
+
>>> if result.protocol_activated:
|
|
99
|
+
... print(f"ALERT: {result.recommendation}")
|
|
100
|
+
|
|
101
|
+
"""
|
|
102
|
+
# Step 1: Evaluate screening criteria
|
|
103
|
+
criteria_results = []
|
|
104
|
+
total_points = 0
|
|
105
|
+
|
|
106
|
+
for criterion in protocol.screening_criteria:
|
|
107
|
+
result = self._evaluate_criterion(criterion, patient_data)
|
|
108
|
+
criteria_results.append(result)
|
|
109
|
+
if result.met:
|
|
110
|
+
total_points += result.points_awarded
|
|
111
|
+
|
|
112
|
+
# Step 2: Determine if protocol should activate
|
|
113
|
+
protocol_activated = total_points >= protocol.screening_threshold
|
|
114
|
+
|
|
115
|
+
# Step 3: If activated, check intervention compliance
|
|
116
|
+
deviations = []
|
|
117
|
+
compliant_items = []
|
|
118
|
+
|
|
119
|
+
if protocol_activated and intervention_status:
|
|
120
|
+
for intervention in protocol.interventions:
|
|
121
|
+
status = intervention_status.get(intervention.action, {})
|
|
122
|
+
deviation = self._check_intervention_status(intervention, status)
|
|
123
|
+
|
|
124
|
+
if deviation:
|
|
125
|
+
deviations.append(deviation)
|
|
126
|
+
else:
|
|
127
|
+
compliant_items.append(intervention.action)
|
|
128
|
+
|
|
129
|
+
# Step 4: Determine alert level
|
|
130
|
+
alert_level = self._determine_alert_level(
|
|
131
|
+
protocol_activated,
|
|
132
|
+
deviations,
|
|
133
|
+
total_points,
|
|
134
|
+
protocol.screening_threshold,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Step 5: Generate recommendation
|
|
138
|
+
recommendation = self._generate_recommendation(
|
|
139
|
+
protocol,
|
|
140
|
+
protocol_activated,
|
|
141
|
+
deviations,
|
|
142
|
+
criteria_results,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
return ProtocolCheckResult(
|
|
146
|
+
protocol_activated=protocol_activated,
|
|
147
|
+
activation_score=total_points,
|
|
148
|
+
threshold=protocol.screening_threshold,
|
|
149
|
+
criteria_results=criteria_results,
|
|
150
|
+
deviations=deviations,
|
|
151
|
+
compliant_items=compliant_items,
|
|
152
|
+
alert_level=alert_level,
|
|
153
|
+
recommendation=recommendation,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def _evaluate_criterion(
|
|
157
|
+
self,
|
|
158
|
+
criterion: ProtocolCriterion,
|
|
159
|
+
patient_data: dict[str, Any],
|
|
160
|
+
) -> CriterionResult:
|
|
161
|
+
"""Evaluate a single criterion"""
|
|
162
|
+
actual_value = patient_data.get(criterion.parameter)
|
|
163
|
+
|
|
164
|
+
if actual_value is None:
|
|
165
|
+
return CriterionResult(
|
|
166
|
+
criterion=criterion,
|
|
167
|
+
met=False,
|
|
168
|
+
actual_value=None,
|
|
169
|
+
points_awarded=0,
|
|
170
|
+
reasoning=f"{criterion.parameter} not available",
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Evaluate condition
|
|
174
|
+
met = self._evaluate_condition(actual_value, criterion.condition, criterion.value)
|
|
175
|
+
|
|
176
|
+
return CriterionResult(
|
|
177
|
+
criterion=criterion,
|
|
178
|
+
met=met,
|
|
179
|
+
actual_value=actual_value,
|
|
180
|
+
points_awarded=criterion.points if met else 0,
|
|
181
|
+
reasoning=f"{criterion.parameter}={actual_value} {criterion.condition} {criterion.value}",
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def _evaluate_condition(self, actual: Any, condition: str, expected: Any) -> bool:
|
|
185
|
+
"""Evaluate a condition (like <=, >=, ==, etc.)"""
|
|
186
|
+
if condition == "<=":
|
|
187
|
+
return actual <= expected
|
|
188
|
+
if condition == ">=":
|
|
189
|
+
return actual >= expected
|
|
190
|
+
if condition == "==":
|
|
191
|
+
return actual == expected
|
|
192
|
+
if condition == "!=":
|
|
193
|
+
return actual != expected
|
|
194
|
+
if condition == "<":
|
|
195
|
+
return actual < expected
|
|
196
|
+
if condition == ">":
|
|
197
|
+
return actual > expected
|
|
198
|
+
if condition == "altered":
|
|
199
|
+
# Special case for mental status
|
|
200
|
+
return actual != "normal" if isinstance(actual, str) else actual < 15
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
def _check_intervention_status(
|
|
204
|
+
self,
|
|
205
|
+
intervention: ProtocolIntervention,
|
|
206
|
+
status: dict[str, Any],
|
|
207
|
+
) -> ProtocolDeviation | None:
|
|
208
|
+
"""Check if intervention has been completed"""
|
|
209
|
+
completed = status.get("completed", False)
|
|
210
|
+
_time_completed = status.get("time_completed")
|
|
211
|
+
time_due = status.get("time_due")
|
|
212
|
+
|
|
213
|
+
if completed:
|
|
214
|
+
return None # No deviation - intervention done
|
|
215
|
+
|
|
216
|
+
# Check if overdue
|
|
217
|
+
if time_due:
|
|
218
|
+
if isinstance(time_due, datetime) and datetime.now() > time_due:
|
|
219
|
+
return ProtocolDeviation(
|
|
220
|
+
intervention=intervention,
|
|
221
|
+
status=ComplianceStatus.OVERDUE,
|
|
222
|
+
time_due=time_due,
|
|
223
|
+
overdue_by=str(datetime.now() - time_due),
|
|
224
|
+
reasoning=f"{intervention.action} overdue (due: {intervention.timing})",
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Pending but not yet overdue
|
|
228
|
+
return ProtocolDeviation(
|
|
229
|
+
intervention=intervention,
|
|
230
|
+
status=ComplianceStatus.PENDING,
|
|
231
|
+
reasoning=f"{intervention.action} pending (due: {intervention.timing})",
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
def _determine_alert_level(
|
|
235
|
+
self,
|
|
236
|
+
protocol_activated: bool,
|
|
237
|
+
deviations: list[ProtocolDeviation],
|
|
238
|
+
score: int,
|
|
239
|
+
threshold: int,
|
|
240
|
+
) -> str:
|
|
241
|
+
"""Determine alert level"""
|
|
242
|
+
if not protocol_activated:
|
|
243
|
+
return "NONE"
|
|
244
|
+
|
|
245
|
+
# Check for overdue critical interventions
|
|
246
|
+
overdue_count = sum(1 for d in deviations if d.status == ComplianceStatus.OVERDUE)
|
|
247
|
+
|
|
248
|
+
if overdue_count > 0:
|
|
249
|
+
return "CRITICAL"
|
|
250
|
+
|
|
251
|
+
# Check for pending interventions
|
|
252
|
+
pending_count = sum(1 for d in deviations if d.status == ComplianceStatus.PENDING)
|
|
253
|
+
|
|
254
|
+
if pending_count > 0:
|
|
255
|
+
return "WARNING"
|
|
256
|
+
|
|
257
|
+
return "NONE"
|
|
258
|
+
|
|
259
|
+
def _generate_recommendation(
|
|
260
|
+
self,
|
|
261
|
+
protocol: ClinicalProtocol,
|
|
262
|
+
activated: bool,
|
|
263
|
+
deviations: list[ProtocolDeviation],
|
|
264
|
+
criteria_results: list[CriterionResult],
|
|
265
|
+
) -> str:
|
|
266
|
+
"""Generate actionable recommendation"""
|
|
267
|
+
if not activated:
|
|
268
|
+
met_criteria = [c for c in criteria_results if c.met]
|
|
269
|
+
if met_criteria:
|
|
270
|
+
return (
|
|
271
|
+
f"Patient meets {len(met_criteria)} of {protocol.screening_threshold} "
|
|
272
|
+
f"criteria. Continue monitoring."
|
|
273
|
+
)
|
|
274
|
+
return "Patient stable. Continue routine monitoring."
|
|
275
|
+
|
|
276
|
+
# Protocol activated
|
|
277
|
+
if not deviations:
|
|
278
|
+
return (
|
|
279
|
+
f"{protocol.name} protocol active. All interventions complete. "
|
|
280
|
+
f"Continue monitoring per protocol."
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
# Has deviations
|
|
284
|
+
overdue = [d for d in deviations if d.status == ComplianceStatus.OVERDUE]
|
|
285
|
+
pending = [d for d in deviations if d.status == ComplianceStatus.PENDING]
|
|
286
|
+
|
|
287
|
+
if overdue:
|
|
288
|
+
return (
|
|
289
|
+
f"CRITICAL: {len(overdue)} interventions OVERDUE. "
|
|
290
|
+
f"Immediate action required: {', '.join(d.intervention.action for d in overdue[:3])}"
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
if pending:
|
|
294
|
+
return (
|
|
295
|
+
f"WARNING: {protocol.name} activated. "
|
|
296
|
+
f"{len(pending)} interventions pending: "
|
|
297
|
+
f"{', '.join(d.intervention.action for d in pending[:3])}"
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
return "Protocol monitoring active."
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Protocol Loader
|
|
2
|
+
|
|
3
|
+
Loads clinical pathway protocols from JSON files.
|
|
4
|
+
|
|
5
|
+
This is like loading linting configs - protocols define the rules.
|
|
6
|
+
|
|
7
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
8
|
+
Licensed under Fair Source 0.9
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class ProtocolCriterion:
|
|
19
|
+
"""A single criterion in a protocol"""
|
|
20
|
+
|
|
21
|
+
parameter: str
|
|
22
|
+
condition: str # "<=", ">=", "==", "!=", "altered", etc.
|
|
23
|
+
value: Any | None = None
|
|
24
|
+
points: int = 0
|
|
25
|
+
description: str | None = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class ProtocolIntervention:
|
|
30
|
+
"""A required intervention"""
|
|
31
|
+
|
|
32
|
+
order: int
|
|
33
|
+
action: str
|
|
34
|
+
timing: str
|
|
35
|
+
required: bool = True
|
|
36
|
+
parameters: dict[str, Any] | None = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class ClinicalProtocol:
|
|
41
|
+
"""Clinical pathway protocol.
|
|
42
|
+
|
|
43
|
+
This is the "linting config" for healthcare - defines the rules.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
name: str
|
|
47
|
+
version: str
|
|
48
|
+
applies_to: list[str]
|
|
49
|
+
|
|
50
|
+
# Screening criteria (when to activate protocol)
|
|
51
|
+
screening_criteria: list[ProtocolCriterion]
|
|
52
|
+
screening_threshold: int
|
|
53
|
+
|
|
54
|
+
# Required interventions (what to do)
|
|
55
|
+
interventions: list[ProtocolIntervention]
|
|
56
|
+
|
|
57
|
+
# Monitoring requirements
|
|
58
|
+
monitoring_frequency: str
|
|
59
|
+
reassessment_timing: str
|
|
60
|
+
|
|
61
|
+
# Escalation criteria (when to call for help)
|
|
62
|
+
escalation_criteria: list[str] | None = None
|
|
63
|
+
|
|
64
|
+
# Documentation requirements
|
|
65
|
+
documentation_requirements: list[str] | None = None
|
|
66
|
+
|
|
67
|
+
# Raw protocol data
|
|
68
|
+
raw_protocol: dict[str, Any] | None = None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ProtocolLoader:
|
|
72
|
+
"""Loads clinical protocols from JSON files.
|
|
73
|
+
|
|
74
|
+
Similar to loading .eslintrc or pyproject.toml - we're loading
|
|
75
|
+
the protocol configuration.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(self, protocol_directory: str | None = None):
|
|
79
|
+
if protocol_directory:
|
|
80
|
+
self.protocol_dir = Path(protocol_directory)
|
|
81
|
+
else:
|
|
82
|
+
# Default to protocols directory in plugin
|
|
83
|
+
plugin_dir = Path(__file__).parent.parent.parent
|
|
84
|
+
self.protocol_dir = plugin_dir / "protocols"
|
|
85
|
+
|
|
86
|
+
def load_protocol(self, protocol_name: str) -> ClinicalProtocol:
|
|
87
|
+
"""Load protocol by name.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
protocol_name: Name of protocol (e.g., "sepsis", "post_operative")
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
ClinicalProtocol object
|
|
94
|
+
|
|
95
|
+
Example:
|
|
96
|
+
>>> loader = ProtocolLoader()
|
|
97
|
+
>>> protocol = loader.load_protocol("sepsis")
|
|
98
|
+
>>> print(f"Loaded: {protocol.name} v{protocol.version}")
|
|
99
|
+
|
|
100
|
+
"""
|
|
101
|
+
protocol_file = self.protocol_dir / f"{protocol_name}.json"
|
|
102
|
+
|
|
103
|
+
if not protocol_file.exists():
|
|
104
|
+
raise FileNotFoundError(
|
|
105
|
+
f"Protocol not found: {protocol_name}\nLooked in: {self.protocol_dir}",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
with open(protocol_file) as f:
|
|
109
|
+
data = json.load(f)
|
|
110
|
+
|
|
111
|
+
return self._parse_protocol(data)
|
|
112
|
+
|
|
113
|
+
def _parse_protocol(self, data: dict[str, Any]) -> ClinicalProtocol:
|
|
114
|
+
"""Parse protocol JSON into ClinicalProtocol object"""
|
|
115
|
+
# Parse screening criteria
|
|
116
|
+
screening_data = data.get("screening_criteria", {})
|
|
117
|
+
criteria = []
|
|
118
|
+
|
|
119
|
+
for crit in screening_data.get("criteria", []):
|
|
120
|
+
criteria.append(
|
|
121
|
+
ProtocolCriterion(
|
|
122
|
+
parameter=crit["parameter"],
|
|
123
|
+
condition=crit["condition"],
|
|
124
|
+
value=crit.get("value"),
|
|
125
|
+
points=crit.get("points", 0),
|
|
126
|
+
description=crit.get("description"),
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Parse interventions
|
|
131
|
+
interventions = []
|
|
132
|
+
for interv in data.get("interventions", []):
|
|
133
|
+
interventions.append(
|
|
134
|
+
ProtocolIntervention(
|
|
135
|
+
order=interv["order"],
|
|
136
|
+
action=interv["action"],
|
|
137
|
+
timing=interv["timing"],
|
|
138
|
+
required=interv.get("required", True),
|
|
139
|
+
parameters=interv.get("parameters"),
|
|
140
|
+
),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Parse monitoring requirements
|
|
144
|
+
monitoring = data.get("monitoring_requirements", {})
|
|
145
|
+
|
|
146
|
+
return ClinicalProtocol(
|
|
147
|
+
name=data["protocol_name"],
|
|
148
|
+
version=data["protocol_version"],
|
|
149
|
+
applies_to=data.get("applies_to", []),
|
|
150
|
+
screening_criteria=criteria,
|
|
151
|
+
screening_threshold=screening_data.get("threshold", 0),
|
|
152
|
+
interventions=interventions,
|
|
153
|
+
monitoring_frequency=monitoring.get("vitals_frequency", "hourly"),
|
|
154
|
+
reassessment_timing=monitoring.get("reassessment", "hourly"),
|
|
155
|
+
escalation_criteria=data.get("escalation_criteria", {}).get("if", []),
|
|
156
|
+
documentation_requirements=data.get("documentation_requirements", []),
|
|
157
|
+
raw_protocol=data,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
def list_available_protocols(self) -> list[str]:
|
|
161
|
+
"""List all available protocols"""
|
|
162
|
+
if not self.protocol_dir.exists():
|
|
163
|
+
return []
|
|
164
|
+
|
|
165
|
+
protocols = []
|
|
166
|
+
for file in self.protocol_dir.glob("*.json"):
|
|
167
|
+
protocols.append(file.stem)
|
|
168
|
+
|
|
169
|
+
return sorted(protocols)
|
|
170
|
+
|
|
171
|
+
def validate_protocol(self, protocol: ClinicalProtocol) -> list[str]:
|
|
172
|
+
"""Validate protocol structure.
|
|
173
|
+
|
|
174
|
+
Returns list of validation errors (empty if valid)
|
|
175
|
+
"""
|
|
176
|
+
errors = []
|
|
177
|
+
|
|
178
|
+
if not protocol.name:
|
|
179
|
+
errors.append("Protocol must have a name")
|
|
180
|
+
|
|
181
|
+
if not protocol.version:
|
|
182
|
+
errors.append("Protocol must have a version")
|
|
183
|
+
|
|
184
|
+
if not protocol.screening_criteria:
|
|
185
|
+
errors.append("Protocol must have screening criteria")
|
|
186
|
+
|
|
187
|
+
if not protocol.interventions:
|
|
188
|
+
errors.append("Protocol must have interventions")
|
|
189
|
+
|
|
190
|
+
# Check intervention order
|
|
191
|
+
orders = [i.order for i in protocol.interventions]
|
|
192
|
+
if len(orders) != len(set(orders)):
|
|
193
|
+
errors.append("Intervention orders must be unique")
|
|
194
|
+
|
|
195
|
+
return errors
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def load_protocol(protocol_name: str, protocol_dir: str | None = None) -> ClinicalProtocol:
|
|
199
|
+
"""Convenience function to load a protocol.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
protocol_name: Name of protocol
|
|
203
|
+
protocol_dir: Optional custom protocol directory
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
ClinicalProtocol object
|
|
207
|
+
|
|
208
|
+
Example:
|
|
209
|
+
>>> protocol = load_protocol("sepsis")
|
|
210
|
+
>>> print(f"{protocol.name}: {len(protocol.interventions)} interventions")
|
|
211
|
+
|
|
212
|
+
"""
|
|
213
|
+
loader = ProtocolLoader(protocol_dir)
|
|
214
|
+
return loader.load_protocol(protocol_name)
|