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.
Files changed (457) hide show
  1. attune/__init__.py +358 -0
  2. attune/adaptive/__init__.py +13 -0
  3. attune/adaptive/task_complexity.py +127 -0
  4. attune/agent_monitoring.py +414 -0
  5. attune/cache/__init__.py +117 -0
  6. attune/cache/base.py +166 -0
  7. attune/cache/dependency_manager.py +256 -0
  8. attune/cache/hash_only.py +251 -0
  9. attune/cache/hybrid.py +457 -0
  10. attune/cache/storage.py +285 -0
  11. attune/cache_monitor.py +356 -0
  12. attune/cache_stats.py +298 -0
  13. attune/cli/__init__.py +152 -0
  14. attune/cli/__main__.py +12 -0
  15. attune/cli/commands/__init__.py +1 -0
  16. attune/cli/commands/batch.py +264 -0
  17. attune/cli/commands/cache.py +248 -0
  18. attune/cli/commands/help.py +331 -0
  19. attune/cli/commands/info.py +140 -0
  20. attune/cli/commands/inspect.py +436 -0
  21. attune/cli/commands/inspection.py +57 -0
  22. attune/cli/commands/memory.py +48 -0
  23. attune/cli/commands/metrics.py +92 -0
  24. attune/cli/commands/orchestrate.py +184 -0
  25. attune/cli/commands/patterns.py +207 -0
  26. attune/cli/commands/profiling.py +202 -0
  27. attune/cli/commands/provider.py +98 -0
  28. attune/cli/commands/routing.py +285 -0
  29. attune/cli/commands/setup.py +96 -0
  30. attune/cli/commands/status.py +235 -0
  31. attune/cli/commands/sync.py +166 -0
  32. attune/cli/commands/tier.py +121 -0
  33. attune/cli/commands/utilities.py +114 -0
  34. attune/cli/commands/workflow.py +579 -0
  35. attune/cli/core.py +32 -0
  36. attune/cli/parsers/__init__.py +68 -0
  37. attune/cli/parsers/batch.py +118 -0
  38. attune/cli/parsers/cache.py +65 -0
  39. attune/cli/parsers/help.py +41 -0
  40. attune/cli/parsers/info.py +26 -0
  41. attune/cli/parsers/inspect.py +66 -0
  42. attune/cli/parsers/metrics.py +42 -0
  43. attune/cli/parsers/orchestrate.py +61 -0
  44. attune/cli/parsers/patterns.py +54 -0
  45. attune/cli/parsers/provider.py +40 -0
  46. attune/cli/parsers/routing.py +110 -0
  47. attune/cli/parsers/setup.py +42 -0
  48. attune/cli/parsers/status.py +47 -0
  49. attune/cli/parsers/sync.py +31 -0
  50. attune/cli/parsers/tier.py +33 -0
  51. attune/cli/parsers/workflow.py +77 -0
  52. attune/cli/utils/__init__.py +1 -0
  53. attune/cli/utils/data.py +242 -0
  54. attune/cli/utils/helpers.py +68 -0
  55. attune/cli_legacy.py +3957 -0
  56. attune/cli_minimal.py +1159 -0
  57. attune/cli_router.py +437 -0
  58. attune/cli_unified.py +814 -0
  59. attune/config/__init__.py +66 -0
  60. attune/config/xml_config.py +286 -0
  61. attune/config.py +545 -0
  62. attune/coordination.py +870 -0
  63. attune/core.py +1511 -0
  64. attune/core_modules/__init__.py +15 -0
  65. attune/cost_tracker.py +626 -0
  66. attune/dashboard/__init__.py +41 -0
  67. attune/dashboard/app.py +512 -0
  68. attune/dashboard/simple_server.py +435 -0
  69. attune/dashboard/standalone_server.py +547 -0
  70. attune/discovery.py +306 -0
  71. attune/emergence.py +306 -0
  72. attune/exceptions.py +123 -0
  73. attune/feedback_loops.py +373 -0
  74. attune/hot_reload/README.md +473 -0
  75. attune/hot_reload/__init__.py +62 -0
  76. attune/hot_reload/config.py +83 -0
  77. attune/hot_reload/integration.py +229 -0
  78. attune/hot_reload/reloader.py +298 -0
  79. attune/hot_reload/watcher.py +183 -0
  80. attune/hot_reload/websocket.py +177 -0
  81. attune/levels.py +577 -0
  82. attune/leverage_points.py +441 -0
  83. attune/logging_config.py +261 -0
  84. attune/mcp/__init__.py +10 -0
  85. attune/mcp/server.py +506 -0
  86. attune/memory/__init__.py +237 -0
  87. attune/memory/claude_memory.py +469 -0
  88. attune/memory/config.py +224 -0
  89. attune/memory/control_panel.py +1290 -0
  90. attune/memory/control_panel_support.py +145 -0
  91. attune/memory/cross_session.py +845 -0
  92. attune/memory/edges.py +179 -0
  93. attune/memory/encryption.py +159 -0
  94. attune/memory/file_session.py +770 -0
  95. attune/memory/graph.py +570 -0
  96. attune/memory/long_term.py +913 -0
  97. attune/memory/long_term_types.py +99 -0
  98. attune/memory/mixins/__init__.py +25 -0
  99. attune/memory/mixins/backend_init_mixin.py +249 -0
  100. attune/memory/mixins/capabilities_mixin.py +208 -0
  101. attune/memory/mixins/handoff_mixin.py +208 -0
  102. attune/memory/mixins/lifecycle_mixin.py +49 -0
  103. attune/memory/mixins/long_term_mixin.py +352 -0
  104. attune/memory/mixins/promotion_mixin.py +109 -0
  105. attune/memory/mixins/short_term_mixin.py +182 -0
  106. attune/memory/nodes.py +179 -0
  107. attune/memory/redis_bootstrap.py +540 -0
  108. attune/memory/security/__init__.py +31 -0
  109. attune/memory/security/audit_logger.py +932 -0
  110. attune/memory/security/pii_scrubber.py +640 -0
  111. attune/memory/security/secrets_detector.py +678 -0
  112. attune/memory/short_term.py +2192 -0
  113. attune/memory/simple_storage.py +302 -0
  114. attune/memory/storage/__init__.py +15 -0
  115. attune/memory/storage_backend.py +167 -0
  116. attune/memory/summary_index.py +583 -0
  117. attune/memory/types.py +446 -0
  118. attune/memory/unified.py +182 -0
  119. attune/meta_workflows/__init__.py +74 -0
  120. attune/meta_workflows/agent_creator.py +248 -0
  121. attune/meta_workflows/builtin_templates.py +567 -0
  122. attune/meta_workflows/cli_commands/__init__.py +56 -0
  123. attune/meta_workflows/cli_commands/agent_commands.py +321 -0
  124. attune/meta_workflows/cli_commands/analytics_commands.py +442 -0
  125. attune/meta_workflows/cli_commands/config_commands.py +232 -0
  126. attune/meta_workflows/cli_commands/memory_commands.py +182 -0
  127. attune/meta_workflows/cli_commands/template_commands.py +354 -0
  128. attune/meta_workflows/cli_commands/workflow_commands.py +382 -0
  129. attune/meta_workflows/cli_meta_workflows.py +59 -0
  130. attune/meta_workflows/form_engine.py +292 -0
  131. attune/meta_workflows/intent_detector.py +409 -0
  132. attune/meta_workflows/models.py +569 -0
  133. attune/meta_workflows/pattern_learner.py +738 -0
  134. attune/meta_workflows/plan_generator.py +384 -0
  135. attune/meta_workflows/session_context.py +397 -0
  136. attune/meta_workflows/template_registry.py +229 -0
  137. attune/meta_workflows/workflow.py +984 -0
  138. attune/metrics/__init__.py +12 -0
  139. attune/metrics/collector.py +31 -0
  140. attune/metrics/prompt_metrics.py +194 -0
  141. attune/models/__init__.py +172 -0
  142. attune/models/__main__.py +13 -0
  143. attune/models/adaptive_routing.py +437 -0
  144. attune/models/auth_cli.py +444 -0
  145. attune/models/auth_strategy.py +450 -0
  146. attune/models/cli.py +655 -0
  147. attune/models/empathy_executor.py +354 -0
  148. attune/models/executor.py +257 -0
  149. attune/models/fallback.py +762 -0
  150. attune/models/provider_config.py +282 -0
  151. attune/models/registry.py +472 -0
  152. attune/models/tasks.py +359 -0
  153. attune/models/telemetry/__init__.py +71 -0
  154. attune/models/telemetry/analytics.py +594 -0
  155. attune/models/telemetry/backend.py +196 -0
  156. attune/models/telemetry/data_models.py +431 -0
  157. attune/models/telemetry/storage.py +489 -0
  158. attune/models/token_estimator.py +420 -0
  159. attune/models/validation.py +280 -0
  160. attune/monitoring/__init__.py +52 -0
  161. attune/monitoring/alerts.py +946 -0
  162. attune/monitoring/alerts_cli.py +448 -0
  163. attune/monitoring/multi_backend.py +271 -0
  164. attune/monitoring/otel_backend.py +362 -0
  165. attune/optimization/__init__.py +19 -0
  166. attune/optimization/context_optimizer.py +272 -0
  167. attune/orchestration/__init__.py +67 -0
  168. attune/orchestration/agent_templates.py +707 -0
  169. attune/orchestration/config_store.py +499 -0
  170. attune/orchestration/execution_strategies.py +2111 -0
  171. attune/orchestration/meta_orchestrator.py +1168 -0
  172. attune/orchestration/pattern_learner.py +696 -0
  173. attune/orchestration/real_tools.py +931 -0
  174. attune/pattern_cache.py +187 -0
  175. attune/pattern_library.py +542 -0
  176. attune/patterns/debugging/all_patterns.json +81 -0
  177. attune/patterns/debugging/workflow_20260107_1770825e.json +77 -0
  178. attune/patterns/refactoring_memory.json +89 -0
  179. attune/persistence.py +564 -0
  180. attune/platform_utils.py +265 -0
  181. attune/plugins/__init__.py +28 -0
  182. attune/plugins/base.py +361 -0
  183. attune/plugins/registry.py +268 -0
  184. attune/project_index/__init__.py +32 -0
  185. attune/project_index/cli.py +335 -0
  186. attune/project_index/index.py +667 -0
  187. attune/project_index/models.py +504 -0
  188. attune/project_index/reports.py +474 -0
  189. attune/project_index/scanner.py +777 -0
  190. attune/project_index/scanner_parallel.py +291 -0
  191. attune/prompts/__init__.py +61 -0
  192. attune/prompts/config.py +77 -0
  193. attune/prompts/context.py +177 -0
  194. attune/prompts/parser.py +285 -0
  195. attune/prompts/registry.py +313 -0
  196. attune/prompts/templates.py +208 -0
  197. attune/redis_config.py +302 -0
  198. attune/redis_memory.py +799 -0
  199. attune/resilience/__init__.py +56 -0
  200. attune/resilience/circuit_breaker.py +256 -0
  201. attune/resilience/fallback.py +179 -0
  202. attune/resilience/health.py +300 -0
  203. attune/resilience/retry.py +209 -0
  204. attune/resilience/timeout.py +135 -0
  205. attune/routing/__init__.py +43 -0
  206. attune/routing/chain_executor.py +433 -0
  207. attune/routing/classifier.py +217 -0
  208. attune/routing/smart_router.py +234 -0
  209. attune/routing/workflow_registry.py +343 -0
  210. attune/scaffolding/README.md +589 -0
  211. attune/scaffolding/__init__.py +35 -0
  212. attune/scaffolding/__main__.py +14 -0
  213. attune/scaffolding/cli.py +240 -0
  214. attune/scaffolding/templates/base_wizard.py.jinja2 +121 -0
  215. attune/scaffolding/templates/coach_wizard.py.jinja2 +321 -0
  216. attune/scaffolding/templates/domain_wizard.py.jinja2 +408 -0
  217. attune/scaffolding/templates/linear_flow_wizard.py.jinja2 +203 -0
  218. attune/socratic/__init__.py +256 -0
  219. attune/socratic/ab_testing.py +958 -0
  220. attune/socratic/blueprint.py +533 -0
  221. attune/socratic/cli.py +703 -0
  222. attune/socratic/collaboration.py +1114 -0
  223. attune/socratic/domain_templates.py +924 -0
  224. attune/socratic/embeddings.py +738 -0
  225. attune/socratic/engine.py +794 -0
  226. attune/socratic/explainer.py +682 -0
  227. attune/socratic/feedback.py +772 -0
  228. attune/socratic/forms.py +629 -0
  229. attune/socratic/generator.py +732 -0
  230. attune/socratic/llm_analyzer.py +637 -0
  231. attune/socratic/mcp_server.py +702 -0
  232. attune/socratic/session.py +312 -0
  233. attune/socratic/storage.py +667 -0
  234. attune/socratic/success.py +730 -0
  235. attune/socratic/visual_editor.py +860 -0
  236. attune/socratic/web_ui.py +958 -0
  237. attune/telemetry/__init__.py +39 -0
  238. attune/telemetry/agent_coordination.py +475 -0
  239. attune/telemetry/agent_tracking.py +367 -0
  240. attune/telemetry/approval_gates.py +545 -0
  241. attune/telemetry/cli.py +1231 -0
  242. attune/telemetry/commands/__init__.py +14 -0
  243. attune/telemetry/commands/dashboard_commands.py +696 -0
  244. attune/telemetry/event_streaming.py +409 -0
  245. attune/telemetry/feedback_loop.py +567 -0
  246. attune/telemetry/usage_tracker.py +591 -0
  247. attune/templates.py +754 -0
  248. attune/test_generator/__init__.py +38 -0
  249. attune/test_generator/__main__.py +14 -0
  250. attune/test_generator/cli.py +234 -0
  251. attune/test_generator/generator.py +355 -0
  252. attune/test_generator/risk_analyzer.py +216 -0
  253. attune/test_generator/templates/unit_test.py.jinja2 +272 -0
  254. attune/tier_recommender.py +384 -0
  255. attune/tools.py +183 -0
  256. attune/trust/__init__.py +28 -0
  257. attune/trust/circuit_breaker.py +579 -0
  258. attune/trust_building.py +527 -0
  259. attune/validation/__init__.py +19 -0
  260. attune/validation/xml_validator.py +281 -0
  261. attune/vscode_bridge.py +173 -0
  262. attune/workflow_commands.py +780 -0
  263. attune/workflow_patterns/__init__.py +33 -0
  264. attune/workflow_patterns/behavior.py +249 -0
  265. attune/workflow_patterns/core.py +76 -0
  266. attune/workflow_patterns/output.py +99 -0
  267. attune/workflow_patterns/registry.py +255 -0
  268. attune/workflow_patterns/structural.py +288 -0
  269. attune/workflows/__init__.py +539 -0
  270. attune/workflows/autonomous_test_gen.py +1268 -0
  271. attune/workflows/base.py +2667 -0
  272. attune/workflows/batch_processing.py +342 -0
  273. attune/workflows/bug_predict.py +1084 -0
  274. attune/workflows/builder.py +273 -0
  275. attune/workflows/caching.py +253 -0
  276. attune/workflows/code_review.py +1048 -0
  277. attune/workflows/code_review_adapters.py +312 -0
  278. attune/workflows/code_review_pipeline.py +722 -0
  279. attune/workflows/config.py +645 -0
  280. attune/workflows/dependency_check.py +644 -0
  281. attune/workflows/document_gen/__init__.py +25 -0
  282. attune/workflows/document_gen/config.py +30 -0
  283. attune/workflows/document_gen/report_formatter.py +162 -0
  284. attune/workflows/document_gen/workflow.py +1426 -0
  285. attune/workflows/document_manager.py +216 -0
  286. attune/workflows/document_manager_README.md +134 -0
  287. attune/workflows/documentation_orchestrator.py +1205 -0
  288. attune/workflows/history.py +510 -0
  289. attune/workflows/keyboard_shortcuts/__init__.py +39 -0
  290. attune/workflows/keyboard_shortcuts/generators.py +391 -0
  291. attune/workflows/keyboard_shortcuts/parsers.py +416 -0
  292. attune/workflows/keyboard_shortcuts/prompts.py +295 -0
  293. attune/workflows/keyboard_shortcuts/schema.py +193 -0
  294. attune/workflows/keyboard_shortcuts/workflow.py +509 -0
  295. attune/workflows/llm_base.py +363 -0
  296. attune/workflows/manage_docs.py +87 -0
  297. attune/workflows/manage_docs_README.md +134 -0
  298. attune/workflows/manage_documentation.py +821 -0
  299. attune/workflows/new_sample_workflow1.py +149 -0
  300. attune/workflows/new_sample_workflow1_README.md +150 -0
  301. attune/workflows/orchestrated_health_check.py +849 -0
  302. attune/workflows/orchestrated_release_prep.py +600 -0
  303. attune/workflows/output.py +413 -0
  304. attune/workflows/perf_audit.py +863 -0
  305. attune/workflows/pr_review.py +762 -0
  306. attune/workflows/progress.py +785 -0
  307. attune/workflows/progress_server.py +322 -0
  308. attune/workflows/progressive/README 2.md +454 -0
  309. attune/workflows/progressive/README.md +454 -0
  310. attune/workflows/progressive/__init__.py +82 -0
  311. attune/workflows/progressive/cli.py +219 -0
  312. attune/workflows/progressive/core.py +488 -0
  313. attune/workflows/progressive/orchestrator.py +723 -0
  314. attune/workflows/progressive/reports.py +520 -0
  315. attune/workflows/progressive/telemetry.py +274 -0
  316. attune/workflows/progressive/test_gen.py +495 -0
  317. attune/workflows/progressive/workflow.py +589 -0
  318. attune/workflows/refactor_plan.py +694 -0
  319. attune/workflows/release_prep.py +895 -0
  320. attune/workflows/release_prep_crew.py +969 -0
  321. attune/workflows/research_synthesis.py +404 -0
  322. attune/workflows/routing.py +168 -0
  323. attune/workflows/secure_release.py +593 -0
  324. attune/workflows/security_adapters.py +297 -0
  325. attune/workflows/security_audit.py +1329 -0
  326. attune/workflows/security_audit_phase3.py +355 -0
  327. attune/workflows/seo_optimization.py +633 -0
  328. attune/workflows/step_config.py +234 -0
  329. attune/workflows/telemetry_mixin.py +269 -0
  330. attune/workflows/test5.py +125 -0
  331. attune/workflows/test5_README.md +158 -0
  332. attune/workflows/test_coverage_boost_crew.py +849 -0
  333. attune/workflows/test_gen/__init__.py +52 -0
  334. attune/workflows/test_gen/ast_analyzer.py +249 -0
  335. attune/workflows/test_gen/config.py +88 -0
  336. attune/workflows/test_gen/data_models.py +38 -0
  337. attune/workflows/test_gen/report_formatter.py +289 -0
  338. attune/workflows/test_gen/test_templates.py +381 -0
  339. attune/workflows/test_gen/workflow.py +655 -0
  340. attune/workflows/test_gen.py +54 -0
  341. attune/workflows/test_gen_behavioral.py +477 -0
  342. attune/workflows/test_gen_parallel.py +341 -0
  343. attune/workflows/test_lifecycle.py +526 -0
  344. attune/workflows/test_maintenance.py +627 -0
  345. attune/workflows/test_maintenance_cli.py +590 -0
  346. attune/workflows/test_maintenance_crew.py +840 -0
  347. attune/workflows/test_runner.py +622 -0
  348. attune/workflows/tier_tracking.py +531 -0
  349. attune/workflows/xml_enhanced_crew.py +285 -0
  350. attune_ai-2.0.0.dist-info/METADATA +1026 -0
  351. attune_ai-2.0.0.dist-info/RECORD +457 -0
  352. attune_ai-2.0.0.dist-info/WHEEL +5 -0
  353. attune_ai-2.0.0.dist-info/entry_points.txt +26 -0
  354. attune_ai-2.0.0.dist-info/licenses/LICENSE +201 -0
  355. attune_ai-2.0.0.dist-info/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +101 -0
  356. attune_ai-2.0.0.dist-info/top_level.txt +5 -0
  357. attune_healthcare/__init__.py +13 -0
  358. attune_healthcare/monitors/__init__.py +9 -0
  359. attune_healthcare/monitors/clinical_protocol_monitor.py +315 -0
  360. attune_healthcare/monitors/monitoring/__init__.py +44 -0
  361. attune_healthcare/monitors/monitoring/protocol_checker.py +300 -0
  362. attune_healthcare/monitors/monitoring/protocol_loader.py +214 -0
  363. attune_healthcare/monitors/monitoring/sensor_parsers.py +306 -0
  364. attune_healthcare/monitors/monitoring/trajectory_analyzer.py +389 -0
  365. attune_llm/README.md +553 -0
  366. attune_llm/__init__.py +28 -0
  367. attune_llm/agent_factory/__init__.py +53 -0
  368. attune_llm/agent_factory/adapters/__init__.py +85 -0
  369. attune_llm/agent_factory/adapters/autogen_adapter.py +312 -0
  370. attune_llm/agent_factory/adapters/crewai_adapter.py +483 -0
  371. attune_llm/agent_factory/adapters/haystack_adapter.py +298 -0
  372. attune_llm/agent_factory/adapters/langchain_adapter.py +362 -0
  373. attune_llm/agent_factory/adapters/langgraph_adapter.py +333 -0
  374. attune_llm/agent_factory/adapters/native.py +228 -0
  375. attune_llm/agent_factory/adapters/wizard_adapter.py +423 -0
  376. attune_llm/agent_factory/base.py +305 -0
  377. attune_llm/agent_factory/crews/__init__.py +67 -0
  378. attune_llm/agent_factory/crews/code_review.py +1113 -0
  379. attune_llm/agent_factory/crews/health_check.py +1262 -0
  380. attune_llm/agent_factory/crews/refactoring.py +1128 -0
  381. attune_llm/agent_factory/crews/security_audit.py +1018 -0
  382. attune_llm/agent_factory/decorators.py +287 -0
  383. attune_llm/agent_factory/factory.py +558 -0
  384. attune_llm/agent_factory/framework.py +193 -0
  385. attune_llm/agent_factory/memory_integration.py +328 -0
  386. attune_llm/agent_factory/resilient.py +320 -0
  387. attune_llm/agents_md/__init__.py +22 -0
  388. attune_llm/agents_md/loader.py +218 -0
  389. attune_llm/agents_md/parser.py +271 -0
  390. attune_llm/agents_md/registry.py +307 -0
  391. attune_llm/claude_memory.py +466 -0
  392. attune_llm/cli/__init__.py +8 -0
  393. attune_llm/cli/sync_claude.py +487 -0
  394. attune_llm/code_health.py +1313 -0
  395. attune_llm/commands/__init__.py +51 -0
  396. attune_llm/commands/context.py +375 -0
  397. attune_llm/commands/loader.py +301 -0
  398. attune_llm/commands/models.py +231 -0
  399. attune_llm/commands/parser.py +371 -0
  400. attune_llm/commands/registry.py +429 -0
  401. attune_llm/config/__init__.py +29 -0
  402. attune_llm/config/unified.py +291 -0
  403. attune_llm/context/__init__.py +22 -0
  404. attune_llm/context/compaction.py +455 -0
  405. attune_llm/context/manager.py +434 -0
  406. attune_llm/contextual_patterns.py +361 -0
  407. attune_llm/core.py +907 -0
  408. attune_llm/git_pattern_extractor.py +435 -0
  409. attune_llm/hooks/__init__.py +24 -0
  410. attune_llm/hooks/config.py +306 -0
  411. attune_llm/hooks/executor.py +289 -0
  412. attune_llm/hooks/registry.py +302 -0
  413. attune_llm/hooks/scripts/__init__.py +39 -0
  414. attune_llm/hooks/scripts/evaluate_session.py +201 -0
  415. attune_llm/hooks/scripts/first_time_init.py +285 -0
  416. attune_llm/hooks/scripts/pre_compact.py +207 -0
  417. attune_llm/hooks/scripts/session_end.py +183 -0
  418. attune_llm/hooks/scripts/session_start.py +163 -0
  419. attune_llm/hooks/scripts/suggest_compact.py +225 -0
  420. attune_llm/learning/__init__.py +30 -0
  421. attune_llm/learning/evaluator.py +438 -0
  422. attune_llm/learning/extractor.py +514 -0
  423. attune_llm/learning/storage.py +560 -0
  424. attune_llm/levels.py +227 -0
  425. attune_llm/pattern_confidence.py +414 -0
  426. attune_llm/pattern_resolver.py +272 -0
  427. attune_llm/pattern_summary.py +350 -0
  428. attune_llm/providers.py +967 -0
  429. attune_llm/routing/__init__.py +32 -0
  430. attune_llm/routing/model_router.py +362 -0
  431. attune_llm/security/IMPLEMENTATION_SUMMARY.md +413 -0
  432. attune_llm/security/PHASE2_COMPLETE.md +384 -0
  433. attune_llm/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
  434. attune_llm/security/QUICK_REFERENCE.md +316 -0
  435. attune_llm/security/README.md +262 -0
  436. attune_llm/security/__init__.py +62 -0
  437. attune_llm/security/audit_logger.py +929 -0
  438. attune_llm/security/audit_logger_example.py +152 -0
  439. attune_llm/security/pii_scrubber.py +640 -0
  440. attune_llm/security/secrets_detector.py +678 -0
  441. attune_llm/security/secrets_detector_example.py +304 -0
  442. attune_llm/security/secure_memdocs.py +1192 -0
  443. attune_llm/security/secure_memdocs_example.py +278 -0
  444. attune_llm/session_status.py +745 -0
  445. attune_llm/state.py +246 -0
  446. attune_llm/utils/__init__.py +5 -0
  447. attune_llm/utils/tokens.py +349 -0
  448. attune_software/SOFTWARE_PLUGIN_README.md +57 -0
  449. attune_software/__init__.py +13 -0
  450. attune_software/cli/__init__.py +120 -0
  451. attune_software/cli/inspect.py +362 -0
  452. attune_software/cli.py +574 -0
  453. attune_software/plugin.py +188 -0
  454. workflow_scaffolding/__init__.py +11 -0
  455. workflow_scaffolding/__main__.py +12 -0
  456. workflow_scaffolding/cli.py +206 -0
  457. workflow_scaffolding/generator.py +265 -0
@@ -0,0 +1,306 @@
1
+ """Sensor Data Parsers
2
+
3
+ Parses sensor data from various formats (HL7, FHIR, manual entry).
4
+
5
+ This is like parsing linter output - converting various formats to standard structure.
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 datetime import datetime
14
+ from enum import Enum
15
+ from typing import Any
16
+
17
+
18
+ class VitalSignType(Enum):
19
+ """Types of vital signs"""
20
+
21
+ HEART_RATE = "heart_rate"
22
+ BLOOD_PRESSURE = "blood_pressure"
23
+ RESPIRATORY_RATE = "respiratory_rate"
24
+ TEMPERATURE = "temperature"
25
+ OXYGEN_SATURATION = "oxygen_saturation"
26
+ MENTAL_STATUS = "mental_status"
27
+ PAIN_SCORE = "pain_score"
28
+
29
+
30
+ @dataclass
31
+ class VitalSignReading:
32
+ """Standardized vital sign reading.
33
+
34
+ This is the universal format - all parsers convert to this.
35
+ """
36
+
37
+ vital_type: VitalSignType
38
+ value: Any
39
+ unit: str
40
+ timestamp: datetime
41
+ source: str # "bedside_monitor", "manual_entry", "wearable"
42
+ patient_id: str
43
+ quality: str | None = None # "good", "poor", "artifact"
44
+ metadata: dict[str, Any] | None = None
45
+
46
+ def to_dict(self) -> dict[str, Any]:
47
+ """Convert to dictionary"""
48
+ return {
49
+ "vital_type": self.vital_type.value,
50
+ "value": self.value,
51
+ "unit": self.unit,
52
+ "timestamp": self.timestamp.isoformat(),
53
+ "source": self.source,
54
+ "patient_id": self.patient_id,
55
+ "quality": self.quality,
56
+ "metadata": self.metadata or {},
57
+ }
58
+
59
+
60
+ class BaseSensorParser:
61
+ """Base class for sensor data parsers"""
62
+
63
+ def parse(self, data: str) -> list[VitalSignReading]:
64
+ """Parse sensor data into standardized readings"""
65
+ raise NotImplementedError(
66
+ f"{self.__class__.__name__}.parse() must be implemented. "
67
+ "Create a subclass of BaseSensorParser and implement the parse() method. "
68
+ f"See FHIRObservationParser or SimpleJSONParser for examples."
69
+ )
70
+
71
+
72
+ class FHIRObservationParser(BaseSensorParser):
73
+ """Parse FHIR Observation resources.
74
+
75
+ FHIR is standard for healthcare data exchange.
76
+ """
77
+
78
+ # LOINC codes for common vitals
79
+ LOINC_MAPPINGS = {
80
+ "8867-4": VitalSignType.HEART_RATE,
81
+ "8480-6": VitalSignType.BLOOD_PRESSURE, # Systolic
82
+ "8462-4": VitalSignType.BLOOD_PRESSURE, # Diastolic
83
+ "9279-1": VitalSignType.RESPIRATORY_RATE,
84
+ "8310-5": VitalSignType.TEMPERATURE,
85
+ "2708-6": VitalSignType.OXYGEN_SATURATION,
86
+ "38208-5": VitalSignType.PAIN_SCORE,
87
+ }
88
+
89
+ def parse(self, data: str) -> list[VitalSignReading]:
90
+ """Parse FHIR Observation JSON"""
91
+ try:
92
+ observation = json.loads(data)
93
+ except json.JSONDecodeError:
94
+ return []
95
+
96
+ if observation.get("resourceType") != "Observation":
97
+ return []
98
+
99
+ readings = []
100
+
101
+ # Extract LOINC code
102
+ code = observation.get("code", {})
103
+ loinc_code = None
104
+
105
+ for coding in code.get("coding", []):
106
+ if coding.get("system") == "http://loinc.org":
107
+ loinc_code = coding.get("code")
108
+ break
109
+
110
+ if not loinc_code or loinc_code not in self.LOINC_MAPPINGS:
111
+ return []
112
+
113
+ vital_type = self.LOINC_MAPPINGS[loinc_code]
114
+
115
+ # Extract value
116
+ value_qty = observation.get("valueQuantity", {})
117
+ value = value_qty.get("value")
118
+ unit = value_qty.get("unit", "")
119
+
120
+ # Extract timestamp
121
+ timestamp_str = observation.get("effectiveDateTime")
122
+ timestamp = (
123
+ datetime.fromisoformat(timestamp_str.replace("Z", "+00:00"))
124
+ if timestamp_str
125
+ else datetime.now()
126
+ )
127
+
128
+ # Extract patient ID
129
+ subject = observation.get("subject", {})
130
+ patient_id = subject.get("reference", "").split("/")[-1]
131
+
132
+ reading = VitalSignReading(
133
+ vital_type=vital_type,
134
+ value=value,
135
+ unit=unit,
136
+ timestamp=timestamp,
137
+ source="fhir_observation",
138
+ patient_id=patient_id,
139
+ metadata={"loinc_code": loinc_code},
140
+ )
141
+
142
+ readings.append(reading)
143
+
144
+ return readings
145
+
146
+
147
+ class SimpleJSONParser(BaseSensorParser):
148
+ """Parse simple JSON format for manual entry or simulation.
149
+
150
+ Example:
151
+ {
152
+ "patient_id": "12345",
153
+ "timestamp": "2024-01-20T14:30:00Z",
154
+ "vitals": {
155
+ "hr": 110,
156
+ "systolic_bp": 95,
157
+ "diastolic_bp": 60,
158
+ "respiratory_rate": 24,
159
+ "temp_f": 101.5,
160
+ "o2_sat": 94
161
+ }
162
+ }
163
+
164
+ """
165
+
166
+ VITAL_MAPPINGS = {
167
+ "hr": (VitalSignType.HEART_RATE, "bpm"),
168
+ "heart_rate": (VitalSignType.HEART_RATE, "bpm"),
169
+ "systolic_bp": (VitalSignType.BLOOD_PRESSURE, "mmHg"),
170
+ "diastolic_bp": (VitalSignType.BLOOD_PRESSURE, "mmHg"),
171
+ "bp": (VitalSignType.BLOOD_PRESSURE, "mmHg"),
172
+ "respiratory_rate": (VitalSignType.RESPIRATORY_RATE, "/min"),
173
+ "rr": (VitalSignType.RESPIRATORY_RATE, "/min"),
174
+ "temp_f": (VitalSignType.TEMPERATURE, "°F"),
175
+ "temp_c": (VitalSignType.TEMPERATURE, "°C"),
176
+ "temperature": (VitalSignType.TEMPERATURE, "°F"),
177
+ "o2_sat": (VitalSignType.OXYGEN_SATURATION, "%"),
178
+ "spo2": (VitalSignType.OXYGEN_SATURATION, "%"),
179
+ "mental_status": (VitalSignType.MENTAL_STATUS, "text"),
180
+ "pain": (VitalSignType.PAIN_SCORE, "0-10"),
181
+ }
182
+
183
+ def parse(self, data: str) -> list[VitalSignReading]:
184
+ """Parse simple JSON format"""
185
+ try:
186
+ parsed = json.loads(data)
187
+ except json.JSONDecodeError:
188
+ return []
189
+
190
+ patient_id = parsed.get("patient_id", "unknown")
191
+ timestamp_str = parsed.get("timestamp")
192
+ timestamp = (
193
+ datetime.fromisoformat(timestamp_str.replace("Z", "+00:00"))
194
+ if timestamp_str
195
+ else datetime.now()
196
+ )
197
+
198
+ vitals = parsed.get("vitals", {})
199
+
200
+ readings = []
201
+
202
+ for key, value in vitals.items():
203
+ if key in self.VITAL_MAPPINGS:
204
+ vital_type, unit = self.VITAL_MAPPINGS[key]
205
+
206
+ reading = VitalSignReading(
207
+ vital_type=vital_type,
208
+ value=value,
209
+ unit=unit,
210
+ timestamp=timestamp,
211
+ source="manual_entry",
212
+ patient_id=patient_id,
213
+ )
214
+
215
+ readings.append(reading)
216
+
217
+ return readings
218
+
219
+
220
+ class SensorParserFactory:
221
+ """Factory for creating appropriate sensor parser"""
222
+
223
+ _parsers = {"fhir": FHIRObservationParser, "simple_json": SimpleJSONParser}
224
+
225
+ @classmethod
226
+ def create(cls, format_type: str) -> BaseSensorParser:
227
+ """Create parser for specified format"""
228
+ parser_class = cls._parsers.get(format_type)
229
+
230
+ if not parser_class:
231
+ raise ValueError(
232
+ f"Unsupported sensor format: {format_type}. "
233
+ f"Supported: {', '.join(cls._parsers.keys())}",
234
+ )
235
+
236
+ return parser_class()
237
+
238
+
239
+ def parse_sensor_data(data: str, format_type: str = "simple_json") -> list[VitalSignReading]:
240
+ """Convenience function to parse sensor data.
241
+
242
+ Args:
243
+ data: Raw sensor data (JSON string)
244
+ format_type: "fhir" or "simple_json"
245
+
246
+ Returns:
247
+ List of VitalSignReading objects
248
+
249
+ Example:
250
+ >>> data = '{"patient_id": "12345", "vitals": {"hr": 110}}'
251
+ >>> readings = parse_sensor_data(data, "simple_json")
252
+ >>> print(f"HR: {readings[0].value} {readings[0].unit}")
253
+
254
+ """
255
+ parser = SensorParserFactory.create(format_type)
256
+ return parser.parse(data)
257
+
258
+
259
+ def normalize_vitals(readings: list[VitalSignReading]) -> dict[str, Any]:
260
+ """Normalize vital sign readings into protocol-checkable format.
261
+
262
+ Takes list of VitalSignReading and converts to dict for protocol checker.
263
+
264
+ Args:
265
+ readings: List of vital sign readings
266
+
267
+ Returns:
268
+ Dictionary with normalized values for protocol checking
269
+
270
+ Example:
271
+ >>> normalized = normalize_vitals(readings)
272
+ >>> # Returns: {"hr": 110, "systolic_bp": 95, "respiratory_rate": 24}
273
+
274
+ """
275
+ normalized = {}
276
+
277
+ for reading in readings:
278
+ if reading.vital_type == VitalSignType.HEART_RATE:
279
+ normalized["hr"] = reading.value
280
+
281
+ elif reading.vital_type == VitalSignType.BLOOD_PRESSURE:
282
+ # Determine if systolic or diastolic based on value
283
+ if reading.value > 60: # Likely systolic
284
+ normalized["systolic_bp"] = reading.value
285
+ else: # Likely diastolic
286
+ normalized["diastolic_bp"] = reading.value
287
+
288
+ elif reading.vital_type == VitalSignType.RESPIRATORY_RATE:
289
+ normalized["respiratory_rate"] = reading.value
290
+
291
+ elif reading.vital_type == VitalSignType.TEMPERATURE:
292
+ if reading.unit == "°F":
293
+ normalized["temp_f"] = reading.value
294
+ elif reading.unit == "°C":
295
+ normalized["temp_c"] = reading.value
296
+
297
+ elif reading.vital_type == VitalSignType.OXYGEN_SATURATION:
298
+ normalized["o2_sat"] = reading.value
299
+
300
+ elif reading.vital_type == VitalSignType.MENTAL_STATUS:
301
+ normalized["mental_status"] = reading.value
302
+
303
+ elif reading.vital_type == VitalSignType.PAIN_SCORE:
304
+ normalized["pain_score"] = reading.value
305
+
306
+ return normalized
@@ -0,0 +1,389 @@
1
+ """Trajectory Analyzer (Level 4)
2
+
3
+ Analyzes vital sign trends to predict patient deterioration BEFORE critical.
4
+
5
+ This is Level 4 Anticipatory Empathy - alerting before the patient meets full crisis criteria.
6
+
7
+ Copyright 2025 Smart AI Memory, LLC
8
+ Licensed under Fair Source 0.9
9
+ """
10
+
11
+ from dataclasses import dataclass
12
+ from typing import Any
13
+
14
+
15
+ @dataclass
16
+ class VitalTrend:
17
+ """Trend analysis for a single vital sign"""
18
+
19
+ parameter: str
20
+ current_value: float
21
+ previous_value: float
22
+ change: float
23
+ change_percent: float
24
+ direction: str # "increasing", "decreasing", "stable"
25
+ rate_of_change: float # units per hour
26
+ concerning: bool
27
+ reasoning: str
28
+
29
+
30
+ @dataclass
31
+ class TrajectoryPrediction:
32
+ """Prediction of patient trajectory.
33
+
34
+ This is Level 4 - predicting BEFORE criteria met.
35
+ """
36
+
37
+ trajectory_state: str # "stable", "improving", "concerning", "critical"
38
+ estimated_time_to_critical: str | None
39
+ vital_trends: list[VitalTrend]
40
+ overall_assessment: str
41
+ confidence: float
42
+ recommendations: list[str]
43
+
44
+
45
+ class TrajectoryAnalyzer:
46
+ """Analyzes vital sign trajectory to predict deterioration.
47
+
48
+ This implements Level 4 Anticipatory Empathy.
49
+ """
50
+
51
+ def __init__(self):
52
+ # Define normal ranges
53
+ self.normal_ranges = {
54
+ "hr": (60, 100),
55
+ "systolic_bp": (90, 140),
56
+ "diastolic_bp": (60, 90),
57
+ "respiratory_rate": (12, 20),
58
+ "temp_f": (97.0, 99.5),
59
+ "o2_sat": (95, 100),
60
+ }
61
+
62
+ # Define concerning rates of change
63
+ self.concerning_rates = {
64
+ "hr": 15, # bpm increase over 2 hours
65
+ "systolic_bp": 20, # mmHg decrease
66
+ "respiratory_rate": 5, # breaths/min increase
67
+ "temp_f": 2.0, # degrees increase
68
+ }
69
+
70
+ def analyze_trajectory(
71
+ self,
72
+ current_data: dict[str, float],
73
+ historical_data: list[dict[str, Any]],
74
+ ) -> TrajectoryPrediction:
75
+ """Analyze patient trajectory from historical vitals.
76
+
77
+ Args:
78
+ current_data: Current vital signs
79
+ historical_data: List of previous readings (last 6-12 hours)
80
+
81
+ Returns:
82
+ TrajectoryPrediction with assessment
83
+
84
+ Example:
85
+ >>> history = [
86
+ ... {"timestamp": "12:00", "hr": 95, "systolic_bp": 120},
87
+ ... {"timestamp": "13:00", "hr": 105, "systolic_bp": 110},
88
+ ... {"timestamp": "14:00", "hr": 112, "systolic_bp": 95}
89
+ ... ]
90
+ >>> prediction = analyzer.analyze_trajectory(current_vitals, history)
91
+ >>> if prediction.trajectory_state == "concerning":
92
+ ... print(f"ALERT: {prediction.overall_assessment}")
93
+
94
+ """
95
+ if not historical_data:
96
+ return TrajectoryPrediction(
97
+ trajectory_state="stable",
98
+ estimated_time_to_critical=None,
99
+ vital_trends=[],
100
+ overall_assessment="Insufficient historical data for trajectory analysis",
101
+ confidence=0.3,
102
+ recommendations=["Continue monitoring"],
103
+ )
104
+
105
+ # Analyze trends for each vital sign
106
+ vital_trends = []
107
+
108
+ for parameter, current_value in current_data.items():
109
+ if parameter in ["mental_status"]: # Skip non-numeric
110
+ continue
111
+
112
+ trend = self._analyze_parameter_trend(parameter, current_value, historical_data)
113
+
114
+ if trend:
115
+ vital_trends.append(trend)
116
+
117
+ # Determine overall trajectory state
118
+ trajectory_state = self._determine_trajectory_state(vital_trends)
119
+
120
+ # Estimate time to critical (if concerning)
121
+ time_to_critical = None
122
+ if trajectory_state in ["concerning", "critical"]:
123
+ time_to_critical = self._estimate_time_to_critical(vital_trends, current_data)
124
+
125
+ # Generate overall assessment
126
+ assessment = self._generate_assessment(trajectory_state, vital_trends, time_to_critical)
127
+
128
+ # Generate recommendations
129
+ recommendations = self._generate_recommendations(trajectory_state, vital_trends)
130
+
131
+ # Calculate confidence
132
+ confidence = self._calculate_confidence(historical_data, vital_trends)
133
+
134
+ return TrajectoryPrediction(
135
+ trajectory_state=trajectory_state,
136
+ estimated_time_to_critical=time_to_critical,
137
+ vital_trends=vital_trends,
138
+ overall_assessment=assessment,
139
+ confidence=confidence,
140
+ recommendations=recommendations,
141
+ )
142
+
143
+ def _analyze_parameter_trend(
144
+ self,
145
+ parameter: str,
146
+ current_value: float,
147
+ historical_data: list[dict[str, Any]],
148
+ ) -> VitalTrend | None:
149
+ """Analyze trend for single parameter"""
150
+ # Extract historical values
151
+ historical_values = []
152
+ for entry in historical_data:
153
+ if parameter in entry and entry[parameter] is not None:
154
+ historical_values.append(entry[parameter])
155
+
156
+ if not historical_values:
157
+ return None
158
+
159
+ # Calculate change from most recent
160
+ previous_value = historical_values[-1]
161
+ change = current_value - previous_value
162
+ change_percent = (change / previous_value * 100) if previous_value != 0 else 0
163
+
164
+ # Determine direction
165
+ if abs(change_percent) < 5:
166
+ direction = "stable"
167
+ elif change > 0:
168
+ direction = "increasing"
169
+ else:
170
+ direction = "decreasing"
171
+
172
+ # Calculate rate of change (per hour)
173
+ # Assuming historical data spans 2-6 hours
174
+ hours_elapsed = len(historical_values) / 2 # Rough estimate
175
+ rate_of_change = abs(change) / hours_elapsed if hours_elapsed > 0 else 0
176
+
177
+ # Determine if concerning
178
+ concerning, reasoning = self._is_trend_concerning(
179
+ parameter,
180
+ current_value,
181
+ change,
182
+ rate_of_change,
183
+ direction,
184
+ )
185
+
186
+ return VitalTrend(
187
+ parameter=parameter,
188
+ current_value=current_value,
189
+ previous_value=previous_value,
190
+ change=change,
191
+ change_percent=change_percent,
192
+ direction=direction,
193
+ rate_of_change=rate_of_change,
194
+ concerning=concerning,
195
+ reasoning=reasoning,
196
+ )
197
+
198
+ def _is_trend_concerning(
199
+ self,
200
+ parameter: str,
201
+ current_value: float,
202
+ change: float,
203
+ rate_of_change: float,
204
+ direction: str,
205
+ ) -> tuple[bool, str]:
206
+ """Determine if trend is concerning"""
207
+ # Check if currently out of normal range
208
+ if parameter in self.normal_ranges:
209
+ min_val, max_val = self.normal_ranges[parameter]
210
+
211
+ if current_value < min_val:
212
+ return True, f"{parameter} below normal range ({min_val}-{max_val})"
213
+ if current_value > max_val:
214
+ return True, f"{parameter} above normal range ({min_val}-{max_val})"
215
+
216
+ # Check rate of change
217
+ if parameter in self.concerning_rates:
218
+ threshold = self.concerning_rates[parameter]
219
+
220
+ if parameter == "hr" and direction == "increasing" and rate_of_change > threshold:
221
+ return True, f"HR increasing rapidly (+{change:.0f} bpm)"
222
+
223
+ if (
224
+ parameter == "systolic_bp"
225
+ and direction == "decreasing"
226
+ and rate_of_change > threshold
227
+ ):
228
+ return True, f"BP decreasing rapidly (-{abs(change):.0f} mmHg)"
229
+
230
+ if (
231
+ parameter == "respiratory_rate"
232
+ and direction == "increasing"
233
+ and rate_of_change > threshold
234
+ ):
235
+ return True, f"RR increasing rapidly (+{change:.0f} /min)"
236
+
237
+ if parameter == "temp_f" and direction == "increasing" and rate_of_change > threshold:
238
+ return True, f"Temp increasing rapidly (+{change:.1f}°F)"
239
+
240
+ return False, "Within normal trajectory"
241
+
242
+ def _determine_trajectory_state(self, vital_trends: list[VitalTrend]) -> str:
243
+ """Determine overall trajectory state"""
244
+ concerning_trends = [t for t in vital_trends if t.concerning]
245
+
246
+ if not concerning_trends:
247
+ return "stable"
248
+
249
+ # Count concerning trends by severity
250
+ critical_parameters = ["systolic_bp", "o2_sat"]
251
+ critical_concerning = sum(
252
+ 1 for t in concerning_trends if t.parameter in critical_parameters
253
+ )
254
+
255
+ if critical_concerning >= 1:
256
+ return "critical"
257
+
258
+ if len(concerning_trends) >= 2:
259
+ return "concerning"
260
+
261
+ if len(concerning_trends) == 1:
262
+ return "concerning"
263
+
264
+ return "stable"
265
+
266
+ def _estimate_time_to_critical(
267
+ self,
268
+ vital_trends: list[VitalTrend],
269
+ current_data: dict[str, float],
270
+ ) -> str | None:
271
+ """Estimate time until patient meets critical criteria.
272
+
273
+ This is core Level 4 - predicting the future.
274
+ """
275
+ # Example: If BP dropping at 10 mmHg/hour, currently 95, critical is 85
276
+ # Time to critical = (95 - 85) / 10 = 1 hour
277
+
278
+ for trend in vital_trends:
279
+ if not trend.concerning:
280
+ continue
281
+
282
+ if trend.parameter == "systolic_bp" and trend.direction == "decreasing":
283
+ critical_threshold = 90
284
+ current = trend.current_value
285
+ rate = trend.rate_of_change
286
+
287
+ if rate > 0:
288
+ hours_to_critical = (current - critical_threshold) / rate
289
+ if 0 < hours_to_critical < 24:
290
+ return f"~{int(hours_to_critical)} hours"
291
+
292
+ if trend.parameter == "o2_sat" and trend.direction == "decreasing":
293
+ critical_threshold = 90
294
+ current = trend.current_value
295
+ rate = trend.rate_of_change
296
+
297
+ if rate > 0:
298
+ hours_to_critical = (current - critical_threshold) / rate
299
+ if 0 < hours_to_critical < 24:
300
+ return f"~{int(hours_to_critical)} hours"
301
+
302
+ return None
303
+
304
+ def _generate_assessment(
305
+ self,
306
+ trajectory_state: str,
307
+ vital_trends: list[VitalTrend],
308
+ time_to_critical: str | None,
309
+ ) -> str:
310
+ """Generate overall assessment"""
311
+ if trajectory_state == "stable":
312
+ return "Patient vitals stable. Continue routine monitoring."
313
+
314
+ concerning = [t for t in vital_trends if t.concerning]
315
+
316
+ if trajectory_state == "critical":
317
+ trends_desc = ", ".join(f"{t.parameter} {t.direction}" for t in concerning[:3])
318
+ return (
319
+ f"CRITICAL trajectory detected: {trends_desc}. Immediate intervention recommended."
320
+ )
321
+
322
+ if trajectory_state == "concerning":
323
+ trends_desc = ", ".join(f"{t.parameter} {t.direction}" for t in concerning[:3])
324
+
325
+ if time_to_critical:
326
+ return (
327
+ f"Concerning trajectory: {trends_desc}. "
328
+ f"In our experience, this pattern suggests deterioration. "
329
+ f"Estimated time to critical: {time_to_critical}. "
330
+ "Early intervention may prevent escalation."
331
+ )
332
+
333
+ return (
334
+ f"Concerning trajectory: {trends_desc}. "
335
+ "In our experience, this pattern warrants closer monitoring."
336
+ )
337
+
338
+ return "Patient trajectory under assessment."
339
+
340
+ def _generate_recommendations(
341
+ self,
342
+ trajectory_state: str,
343
+ vital_trends: list[VitalTrend],
344
+ ) -> list[str]:
345
+ """Generate actionable recommendations"""
346
+ if trajectory_state == "stable":
347
+ return ["Continue routine monitoring"]
348
+
349
+ recommendations = []
350
+
351
+ if trajectory_state in ["concerning", "critical"]:
352
+ recommendations.append("Notify physician of trajectory")
353
+ recommendations.append("Increase monitoring frequency")
354
+
355
+ concerning = [t for t in vital_trends if t.concerning]
356
+
357
+ for trend in concerning:
358
+ if trend.parameter == "systolic_bp":
359
+ recommendations.append("Assess volume status and perfusion")
360
+ elif trend.parameter == "hr":
361
+ recommendations.append("Assess for infection or pain")
362
+ elif trend.parameter == "respiratory_rate":
363
+ recommendations.append("Assess respiratory status and oxygenation")
364
+ elif trend.parameter == "temp_f":
365
+ recommendations.append("Assess for infection")
366
+
367
+ if trajectory_state == "critical":
368
+ recommendations.append("Consider rapid response team activation")
369
+
370
+ return recommendations
371
+
372
+ def _calculate_confidence(
373
+ self,
374
+ historical_data: list[dict[str, Any]],
375
+ vital_trends: list[VitalTrend],
376
+ ) -> float:
377
+ """Calculate confidence in prediction"""
378
+ # More data = higher confidence
379
+ data_points = len(historical_data)
380
+ data_confidence = min(data_points / 10, 1.0)
381
+
382
+ # More consistent trends = higher confidence
383
+ if vital_trends:
384
+ concerning_count = sum(1 for t in vital_trends if t.concerning)
385
+ trend_confidence = concerning_count / len(vital_trends)
386
+ else:
387
+ trend_confidence = 0.5
388
+
389
+ return (data_confidence + trend_confidence) / 2