mednotes-opencode 0.1.0
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.
- package/.opencode/agents/med-chat-triager.md +204 -0
- package/.opencode/agents/med-flashcard-maker.md +63 -0
- package/.opencode/agents/med-knowledge-architect.md +230 -0
- package/.opencode/agents/med-link-graph-curator.md +177 -0
- package/.opencode/agents/med-publish-guard.md +62 -0
- package/.opencode/commands/flashcards.md +25 -0
- package/.opencode/commands/mednotes/create.md +25 -0
- package/.opencode/commands/mednotes/enrich.md +27 -0
- package/.opencode/commands/mednotes/fix-wiki.md +27 -0
- package/.opencode/commands/mednotes/history.md +22 -0
- package/.opencode/commands/mednotes/link-body.md +25 -0
- package/.opencode/commands/mednotes/link-related.md +27 -0
- package/.opencode/commands/mednotes/link.md +27 -0
- package/.opencode/commands/mednotes/pdf-library.md +27 -0
- package/.opencode/commands/mednotes/process-chats.md +23 -0
- package/.opencode/commands/mednotes/setup.md +21 -0
- package/.opencode/commands/mednotes/status.md +27 -0
- package/.opencode/commands/mednotes/telemetry.md +27 -0
- package/.opencode/commands/report.md +26 -0
- package/.opencode/mednotes/AGENTS.md +57 -0
- package/.opencode/mednotes/agents/med-chat-triager.md +197 -0
- package/.opencode/mednotes/agents/med-flashcard-maker.md +56 -0
- package/.opencode/mednotes/agents/med-knowledge-architect.md +224 -0
- package/.opencode/mednotes/agents/med-link-graph-curator.md +171 -0
- package/.opencode/mednotes/agents/med-publish-guard.md +55 -0
- package/.opencode/mednotes/contracts/.gitkeep +1 -0
- package/.opencode/mednotes/contracts/agents.json +116 -0
- package/.opencode/mednotes/contracts/opencode-plugin.json +70 -0
- package/.opencode/mednotes/docs/agent-prompt-hardening.md +567 -0
- package/.opencode/mednotes/docs/agent-role-contracts.md +94 -0
- package/.opencode/mednotes/docs/anki-mcp-twenty-rules.md +214 -0
- package/.opencode/mednotes/docs/anki-templates/README.md +39 -0
- package/.opencode/mednotes/docs/anki-templates/cloze.back.html +23 -0
- package/.opencode/mednotes/docs/anki-templates/cloze.front.html +14 -0
- package/.opencode/mednotes/docs/anki-templates/qa.back.html +24 -0
- package/.opencode/mednotes/docs/anki-templates/qa.front.html +14 -0
- package/.opencode/mednotes/docs/anki-templates/style.css +182 -0
- package/.opencode/mednotes/docs/atomicity-splitting-policy.md +113 -0
- package/.opencode/mednotes/docs/extension-docs.md +40 -0
- package/.opencode/mednotes/docs/flashcard-ingestion.md +278 -0
- package/.opencode/mednotes/docs/knowledge-architect.md +208 -0
- package/.opencode/mednotes/docs/merge-policy.md +110 -0
- package/.opencode/mednotes/docs/public-vocabulary.md +104 -0
- package/.opencode/mednotes/docs/semantic-linker.md +141 -0
- package/.opencode/mednotes/docs/taxonomy-policy.md +90 -0
- package/.opencode/mednotes/docs/triage-policy.md +187 -0
- package/.opencode/mednotes/docs/vault-version-control.md +758 -0
- package/.opencode/mednotes/docs/vocabulary-db-recovery.md +58 -0
- package/.opencode/mednotes/docs/workflow-output-contract.md +779 -0
- package/.opencode/mednotes/hooks/hooks.json +79 -0
- package/.opencode/mednotes/package-lock.json +6361 -0
- package/.opencode/mednotes/package.json +15 -0
- package/.opencode/mednotes/pyproject.toml +48 -0
- package/.opencode/mednotes/scripts/bootstrap_windows_python_uv.cmd +13 -0
- package/.opencode/mednotes/scripts/bootstrap_windows_python_uv.ps1 +172 -0
- package/.opencode/mednotes/scripts/enrich_notes.py +23 -0
- package/.opencode/mednotes/scripts/full_reset_windows_python_uv.cmd +13 -0
- package/.opencode/mednotes/scripts/hooks/antigravity_hook_status.mjs +212 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/antigravity.mjs +169 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/harness_payload.mjs +103 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/opencode_plugin.mjs +341 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/opencode_user_config_sync.mjs +177 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/anki_preflight.mjs +214 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/cli.mjs +143 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/diagnostics.mjs +11 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/domain/agent_directive_core.mjs +160 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/fsm_directive.mjs +1470 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/hook_errors.mjs +120 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/retention.mjs +114 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/runtime.mjs +174 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/telemetry_capture.mjs +511 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/vault_guard.mjs +624 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook.mjs +5 -0
- package/.opencode/mednotes/scripts/mednotes/_runtime_paths.py +24 -0
- package/.opencode/mednotes/scripts/mednotes/anki_model_validator.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/capture_extension_diff.py +1562 -0
- package/.opencode/mednotes/scripts/mednotes/feedback_report.py +16 -0
- package/.opencode/mednotes/scripts/mednotes/flashcard_index.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/flashcard_pipeline.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/flashcard_report.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/flashcard_sources.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/obsidian/README.md +6 -0
- package/.opencode/mednotes/scripts/mednotes/obsidian_note_utils.py +20 -0
- package/.opencode/mednotes/scripts/mednotes/pdf_library/cli.py +16 -0
- package/.opencode/mednotes/scripts/mednotes/project_fsm.py +229 -0
- package/.opencode/mednotes/scripts/mednotes/setup_telemetry_email.py +404 -0
- package/.opencode/mednotes/scripts/mednotes/sync_anki_twenty_rules.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/sync_opencode_user_config.py +36 -0
- package/.opencode/mednotes/scripts/mednotes/wiki/cli.py +20 -0
- package/.opencode/mednotes/scripts/mednotes/wiki_graph.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/wiki_tree.py +134 -0
- package/.opencode/mednotes/scripts/reset_windows_python_uv.ps1 +625 -0
- package/.opencode/mednotes/scripts/run_python.mjs +109 -0
- package/.opencode/mednotes/scripts/vault/vault_commit.ps1 +19 -0
- package/.opencode/mednotes/scripts/vault/vault_commit.sh +18 -0
- package/.opencode/mednotes/scripts/vault/vault_git.ps1 +19 -0
- package/.opencode/mednotes/scripts/vault/vault_git.py +3107 -0
- package/.opencode/mednotes/scripts/vault/vault_git.sh +18 -0
- package/.opencode/mednotes/scripts/vault/vault_precommit.ps1 +19 -0
- package/.opencode/mednotes/scripts/vault/vault_precommit.sh +18 -0
- package/.opencode/mednotes/skills/THIRD_PARTY_NOTICES.md +45 -0
- package/.opencode/mednotes/skills/create-medical-flashcards/SKILL.md +113 -0
- package/.opencode/mednotes/skills/create-medical-note/SKILL.md +90 -0
- package/.opencode/mednotes/skills/enrich-medical-note/SKILL.md +120 -0
- package/.opencode/mednotes/skills/fix-medical-wiki/SKILL.md +559 -0
- package/.opencode/mednotes/skills/link-medical-wiki/SKILL.md +224 -0
- package/.opencode/mednotes/skills/obsidian-cli/SKILL.md +118 -0
- package/.opencode/mednotes/skills/obsidian-markdown/SKILL.md +207 -0
- package/.opencode/mednotes/skills/obsidian-markdown/references/CALLOUTS.md +58 -0
- package/.opencode/mednotes/skills/obsidian-markdown/references/EMBEDS.md +63 -0
- package/.opencode/mednotes/skills/obsidian-markdown/references/PROPERTIES.md +61 -0
- package/.opencode/mednotes/skills/obsidian-ops/SKILL.md +136 -0
- package/.opencode/mednotes/skills/pdf-library/SKILL.md +45 -0
- package/.opencode/mednotes/skills/process-medical-chats/SKILL.md +246 -0
- package/.opencode/mednotes/skills/workflow-report/SKILL.md +100 -0
- package/.opencode/mednotes/src/mednotes/__init__.py +5 -0
- package/.opencode/mednotes/src/mednotes/domains/__init__.py +5 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/README.md +26 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/__init__.py +2 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/build_demo_apkg.py +177 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/contracts.py +385 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/flashcards_machine.py +522 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/fsm.py +817 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/index.py +630 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/install_models.py +445 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/model.py +359 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/obsidian_links.py +135 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/obsidian_note_utils.py +546 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/pipeline.py +580 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/report.py +510 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/sources.py +682 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/sync_rules.py +184 -0
- package/.opencode/mednotes/src/mednotes/domains/history/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/history/history_fsm.py +852 -0
- package/.opencode/mednotes/src/mednotes/domains/history/history_machine.py +453 -0
- package/.opencode/mednotes/src/mednotes/domains/setup/__init__.py +7 -0
- package/.opencode/mednotes/src/mednotes/domains/setup/setup_fsm.py +808 -0
- package/.opencode/mednotes/src/mednotes/domains/setup/setup_machine.py +973 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/README.md +64 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/api.py +668 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/batch_state.py +102 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/atomicity/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/atomicity/atomicity.py +877 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/body_link/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/body_link/body_linker.py +1562 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/effects/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/effects/effect_adapters.py +949 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/effects/fix_wiki_runtime_adapters.py +433 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/coverage.py +413 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/graph.py +396 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/graph_fixes.py +161 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/hygiene/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/hygiene/hygiene.py +483 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/__init__.py +2 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/anchors.py +185 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/__init__.py +0 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/cache.py +223 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/config.py +131 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/download.py +224 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/frontmatter.py +59 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/insert.py +227 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/local_import.py +54 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/__init__.py +42 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/web_profiles.py +99 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/web_search.py +203 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/wikimedia.py +102 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/markdown_db_adapter.mjs +434 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/markdown_node_runtime.py +274 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/markdown_query.py +227 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/artifacts.py +605 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/canonical_merge.py +277 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/markdown_zones.py +85 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/meaning_planner.py +307 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_iter.py +67 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_merge.py +278 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_plan.py +409 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_policy.py +22 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/__init__.py +79 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/fixes.py +264 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/frontmatter.py +435 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/models.py +208 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/prompts.py +37 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/tables.py +236 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/validate.py +404 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/provenance.py +478 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/raw_chats.py +273 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/sources_backfill.py +235 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/__init__.py +10 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/anchors.py +16 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/captions.py +47 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/cli.py +179 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/cloud.py +52 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/config.py +196 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/context_packets.py +76 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/db.py +81 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/doctor.py +102 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/figure_ids.py +42 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/ingest.py +326 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/insert.py +316 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/mentions.py +57 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/ocr.py +71 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/paths.py +35 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/pdf_engine.py +77 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/schema.py +155 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/search.py +188 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/app.py +89 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/image_backend.py +29 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/state.py +65 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/publish.py +1139 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/publish_receipts.py +365 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/publish_recovery.py +240 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/agent_behavior_corpus.py +2069 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/agent_report_validation.py +4448 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/agent_run_audit.py +852 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/architect_prompt_eval.py +341 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/body_linker_eval.py +240 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/curator_output_validation.py +175 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/curator_prompt_eval.py +865 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/triager_prompt_eval.py +1295 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/related_notes/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/related_notes/related_notes.py +1920 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/related_notes/related_notes_headless.py +1186 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/plan_attestation.py +148 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/specialist_receipts.py +360 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/specialist_runtime.py +52 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/specialist_task_runner.py +2470 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/style/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/style/style.py +1952 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/subagents/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/subagents/agents.py +1767 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/alias_projection.py +331 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/link_terms.py +151 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/llm_disambiguation.py +182 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/__init__.py +116 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/audit.py +201 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/migration.py +314 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/normalize.py +72 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/policy.py +135 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/resolve.py +413 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/schema.py +157 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/status.py +137 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_bootstrap.py +509 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_curator_batch.py +1115 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_ingestion.py +632 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_map.py +930 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_recovery.py +1388 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/cli.py +6665 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/common.py +69 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/config.py +210 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/__init__.py +74 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/agent_report.py +242 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/agent_run_audit.py +196 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/agents.py +601 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/curator.py +256 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/effect_payloads.py +519 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/happy_path.py +190 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/link_git.py +110 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/link_runtime_artifact.py +52 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/note_plan.py +75 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/paths.py +114 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/public_report.py +53 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/publish.py +111 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/raw_coverage.py +217 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/related_notes.py +136 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/related_notes_headless.py +153 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/related_notes_runtime.py +395 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/schema_registry.py +637 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/specialist.py +432 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/status.py +62 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/style_rewrite.py +568 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/vocabulary_ingestion.py +223 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_blockers.py +510 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_guardrails.py +637 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_outcomes.py +121 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_receipts.py +100 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/__main__.py +4 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/cli.py +275 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/__init__.py +2 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/candidates.py +193 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/cli.py +189 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/gemini.py +220 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/inputs.py +120 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/models.py +34 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/parsing.py +48 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/prompts.py +216 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/quality.py +54 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/reporting.py +24 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/runner.py +433 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/utils.py +39 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/vault_guard_bridge.py +17 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_context_packets.py +454 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_decision_projection.py +133 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_effects.py +1260 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_fsm.py +2768 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_machine.py +1588 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_plan.py +306 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_primary_objective.py +316 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_problem.py +153 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_receipt_evidence.py +306 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_states.py +290 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_user_report.py +342 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/health.py +6332 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_fsm.py +1119 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_git.py +638 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_machine.py +1106 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_retry_governance.py +374 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_runtime_result.py +485 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_triggers.py +183 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/linking.py +2758 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/reference_repair.py +718 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/related_notes_fsm.py +1855 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link_related/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link_related/link_related_machine.py +834 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_fsm.py +1592 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_machine.py +3097 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_primary_objective.py +28 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_runtime_result.py +185 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/performance.py +97 -0
- package/.opencode/mednotes/src/mednotes/kernel/__init__.py +6 -0
- package/.opencode/mednotes/src/mednotes/kernel/agent_directive.py +336 -0
- package/.opencode/mednotes/src/mednotes/kernel/base.py +51 -0
- package/.opencode/mednotes/src/mednotes/kernel/blockers.py +39 -0
- package/.opencode/mednotes/src/mednotes/kernel/effect_executor.py +55 -0
- package/.opencode/mednotes/src/mednotes/kernel/effect_intent.py +69 -0
- package/.opencode/mednotes/src/mednotes/kernel/effects.py +160 -0
- package/.opencode/mednotes/src/mednotes/kernel/errors.py +38 -0
- package/.opencode/mednotes/src/mednotes/kernel/fsm_event.py +35 -0
- package/.opencode/mednotes/src/mednotes/kernel/fsm_model.py +55 -0
- package/.opencode/mednotes/src/mednotes/kernel/fsm_transition_result.py +75 -0
- package/.opencode/mednotes/src/mednotes/kernel/guardrails.py +188 -0
- package/.opencode/mednotes/src/mednotes/kernel/progress.py +319 -0
- package/.opencode/mednotes/src/mednotes/kernel/public_report.py +346 -0
- package/.opencode/mednotes/src/mednotes/kernel/state_machine.py +164 -0
- package/.opencode/mednotes/src/mednotes/kernel/workflow.py +619 -0
- package/.opencode/mednotes/src/mednotes/platform/__init__.py +5 -0
- package/.opencode/mednotes/src/mednotes/platform/backup_policy.py +382 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/__init__.py +62 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/cli.py +275 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/contracts.py +83 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/core.py +4168 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/integrity.py +989 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/operational_contract.py +2293 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/telemetry.py +875 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/telemetry_config.py +65 -0
- package/.opencode/mednotes/src/mednotes/platform/opencode_runtime_config.py +182 -0
- package/.opencode/mednotes/src/mednotes/platform/paths/__init__.py +1560 -0
- package/.opencode/mednotes/src/mednotes/platform/secrets.py +89 -0
- package/.opencode/mednotes/src/mednotes/platform/user_config.py +103 -0
- package/.opencode/mednotes/src/mednotes/platform/vault_guard.py +214 -0
- package/.opencode/mednotes/uv.lock +932 -0
- package/.opencode/mednotes.generated.json +395 -0
- package/.opencode/opencode.json +31 -0
- package/.opencode/plugins/mednotes-fsm.mjs +7 -0
- package/.opencode/plugins/mednotes_hook/adapters/antigravity.mjs +169 -0
- package/.opencode/plugins/mednotes_hook/adapters/harness_payload.mjs +103 -0
- package/.opencode/plugins/mednotes_hook/adapters/opencode_plugin.mjs +341 -0
- package/.opencode/plugins/mednotes_hook/adapters/opencode_user_config_sync.mjs +177 -0
- package/.opencode/plugins/mednotes_hook/anki_preflight.mjs +214 -0
- package/.opencode/plugins/mednotes_hook/cli.mjs +143 -0
- package/.opencode/plugins/mednotes_hook/diagnostics.mjs +11 -0
- package/.opencode/plugins/mednotes_hook/domain/agent_directive_core.mjs +160 -0
- package/.opencode/plugins/mednotes_hook/fsm_directive.mjs +1470 -0
- package/.opencode/plugins/mednotes_hook/hook_errors.mjs +120 -0
- package/.opencode/plugins/mednotes_hook/retention.mjs +114 -0
- package/.opencode/plugins/mednotes_hook/runtime.mjs +174 -0
- package/.opencode/plugins/mednotes_hook/telemetry_capture.mjs +511 -0
- package/.opencode/plugins/mednotes_hook/vault_guard.mjs +624 -0
- package/AGENTS.md +57 -0
- package/README.md +194 -0
- package/adapters/antigravity/agents.json +80 -0
- package/adapters/antigravity/templates/med-chat-triager.md +214 -0
- package/adapters/antigravity/templates/med-flashcard-maker.md +72 -0
- package/adapters/antigravity/templates/med-knowledge-architect.md +241 -0
- package/adapters/antigravity/templates/med-link-graph-curator.md +187 -0
- package/adapters/antigravity/templates/med-publish-guard.md +71 -0
- package/adapters/gemini-cli/gemini-extension.json +14 -0
- package/adapters/gemini-cli/package.json +15 -0
- package/adapters/gemini-cli/pyproject.toml +48 -0
- package/bin/mednotes-opencode.mjs +155 -0
- package/contracts/agents.json +116 -0
- package/core/agents/med-chat-triager.md +197 -0
- package/core/agents/med-flashcard-maker.md +56 -0
- package/core/agents/med-knowledge-architect.md +224 -0
- package/core/agents/med-link-graph-curator.md +171 -0
- package/core/agents/med-publish-guard.md +55 -0
- package/core/commands/flashcards.toml +22 -0
- package/core/commands/mednotes/create.toml +22 -0
- package/core/commands/mednotes/enrich.toml +24 -0
- package/core/commands/mednotes/fix-wiki.toml +24 -0
- package/core/commands/mednotes/history.toml +19 -0
- package/core/commands/mednotes/link-body.toml +22 -0
- package/core/commands/mednotes/link-related.toml +24 -0
- package/core/commands/mednotes/link.toml +24 -0
- package/core/commands/mednotes/pdf-library.toml +24 -0
- package/core/commands/mednotes/process-chats.toml +20 -0
- package/core/commands/mednotes/setup.toml +18 -0
- package/core/commands/mednotes/status.toml +24 -0
- package/core/commands/mednotes/telemetry.toml +24 -0
- package/core/commands/report.toml +23 -0
- package/core/skills/THIRD_PARTY_NOTICES.md +45 -0
- package/core/skills/create-medical-flashcards/SKILL.md +113 -0
- package/core/skills/create-medical-note/SKILL.md +90 -0
- package/core/skills/enrich-medical-note/SKILL.md +120 -0
- package/core/skills/fix-medical-wiki/SKILL.md +559 -0
- package/core/skills/link-medical-wiki/SKILL.md +224 -0
- package/core/skills/obsidian-cli/SKILL.md +118 -0
- package/core/skills/obsidian-markdown/SKILL.md +207 -0
- package/core/skills/obsidian-markdown/references/CALLOUTS.md +58 -0
- package/core/skills/obsidian-markdown/references/EMBEDS.md +63 -0
- package/core/skills/obsidian-markdown/references/PROPERTIES.md +61 -0
- package/core/skills/obsidian-ops/SKILL.md +136 -0
- package/core/skills/pdf-library/SKILL.md +45 -0
- package/core/skills/process-medical-chats/SKILL.md +246 -0
- package/core/skills/workflow-report/SKILL.md +100 -0
- package/package.json +45 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
from pydantic import ConfigDict, Field, model_validator
|
|
8
|
+
|
|
9
|
+
from mednotes.kernel.base import ContractModel, JsonObject
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class HappyPathViolationCategory(StrEnum):
|
|
13
|
+
ROUTE = "route"
|
|
14
|
+
TOOL_CONTRACT = "tool_contract"
|
|
15
|
+
FSM_CONTRACT = "fsm_contract"
|
|
16
|
+
ARTIFACT_INTEGRITY = "artifact_integrity"
|
|
17
|
+
PUBLIC_COMMUNICATION = "public_communication"
|
|
18
|
+
PERFORMANCE = "performance"
|
|
19
|
+
PRIMARY_OBJECTIVE = "primary_objective"
|
|
20
|
+
HUMAN_ATTENTION = "human_attention"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class HappyPathViolation(ContractModel):
|
|
24
|
+
code: str = Field(min_length=1)
|
|
25
|
+
category: HappyPathViolationCategory
|
|
26
|
+
severity: Literal["low", "medium", "high", "critical"]
|
|
27
|
+
message: str = Field(min_length=1)
|
|
28
|
+
evidence: JsonObject = Field(default_factory=dict)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class HappyPathFindingInput(ContractModel):
|
|
32
|
+
# Agent report findings carry richer audit fields; happy-path scoring only
|
|
33
|
+
# consumes the stable violation surface below.
|
|
34
|
+
model_config = ConfigDict(extra="ignore", populate_by_name=True, validate_assignment=True)
|
|
35
|
+
|
|
36
|
+
code: str = Field(min_length=1)
|
|
37
|
+
severity: Literal["low", "medium", "high", "critical"]
|
|
38
|
+
message: str = Field(min_length=1)
|
|
39
|
+
evidence: JsonObject = Field(default_factory=dict)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class HappyPathRunMetrics(ContractModel):
|
|
43
|
+
schema_: Literal["medical-notes-workbench.happy-path-run-metrics.v1"] = Field(
|
|
44
|
+
"medical-notes-workbench.happy-path-run-metrics.v1",
|
|
45
|
+
alias="schema",
|
|
46
|
+
)
|
|
47
|
+
workflow: str = Field(min_length=1)
|
|
48
|
+
run_id: str = Field(min_length=1)
|
|
49
|
+
status: Literal["happy", "degraded", "failed"]
|
|
50
|
+
score: int = Field(ge=0, le=100)
|
|
51
|
+
primary_objective_completed: bool
|
|
52
|
+
legitimate_stop_reason: str = ""
|
|
53
|
+
violation_count: int = Field(ge=0)
|
|
54
|
+
violations: list[HappyPathViolation] = Field(default_factory=list)
|
|
55
|
+
|
|
56
|
+
@model_validator(mode="after")
|
|
57
|
+
def _count_and_score_match(self) -> HappyPathRunMetrics:
|
|
58
|
+
if self.violation_count != len(self.violations):
|
|
59
|
+
raise ValueError("violation_count must match violations length")
|
|
60
|
+
if self.violation_count == 0 and self.score != 100:
|
|
61
|
+
raise ValueError("perfect happy path must score 100")
|
|
62
|
+
if self.status == "happy" and self.violation_count != 0:
|
|
63
|
+
raise ValueError("happy status cannot include violations")
|
|
64
|
+
return self
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class HappyPathRoundMetrics(ContractModel):
|
|
68
|
+
schema_: Literal["medical-notes-workbench.happy-path-round-metrics.v1"] = Field(
|
|
69
|
+
"medical-notes-workbench.happy-path-round-metrics.v1",
|
|
70
|
+
alias="schema",
|
|
71
|
+
)
|
|
72
|
+
workflow: str = Field(min_length=1)
|
|
73
|
+
run_count: int = Field(ge=0)
|
|
74
|
+
happy_run_count: int = Field(ge=0)
|
|
75
|
+
happy_path_prevalence_percent: int = Field(ge=0, le=100)
|
|
76
|
+
target_prevalence_percent: int = 100
|
|
77
|
+
runs: list[HappyPathRunMetrics] = Field(default_factory=list)
|
|
78
|
+
|
|
79
|
+
@model_validator(mode="after")
|
|
80
|
+
def _round_counts_match_runs(self) -> HappyPathRoundMetrics:
|
|
81
|
+
if self.run_count != len(self.runs):
|
|
82
|
+
raise ValueError("run_count must match runs length")
|
|
83
|
+
if self.happy_run_count != sum(1 for run in self.runs if run.status == "happy"):
|
|
84
|
+
raise ValueError("happy_run_count must match happy runs")
|
|
85
|
+
expected = 100 if self.run_count == 0 else int((self.happy_run_count / self.run_count) * 100)
|
|
86
|
+
if self.happy_path_prevalence_percent != expected:
|
|
87
|
+
raise ValueError("happy_path_prevalence_percent must match run counts")
|
|
88
|
+
return self
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
_FINDING_CATEGORY_BY_CODE: dict[str, HappyPathViolationCategory] = {
|
|
92
|
+
"agent.public_output_internal_term_leak": HappyPathViolationCategory.PUBLIC_COMMUNICATION,
|
|
93
|
+
"agent.final_report_success_claim_mismatch": HappyPathViolationCategory.PUBLIC_COMMUNICATION,
|
|
94
|
+
"agent.final_report_progress_status_mismatch": HappyPathViolationCategory.FSM_CONTRACT,
|
|
95
|
+
"agent.final_report_receipt_status_mismatch": HappyPathViolationCategory.FSM_CONTRACT,
|
|
96
|
+
"agent.workflow_contract_contradiction": HappyPathViolationCategory.FSM_CONTRACT,
|
|
97
|
+
"agent.final_report_tool_deviation_omitted": HappyPathViolationCategory.ROUTE,
|
|
98
|
+
"agent.runtime_route_probe_observed": HappyPathViolationCategory.ROUTE,
|
|
99
|
+
"agent.waiting_external_continuation_attempted": HappyPathViolationCategory.ROUTE,
|
|
100
|
+
"agent.ready_continuation_stopped": HappyPathViolationCategory.ROUTE,
|
|
101
|
+
"agent.runtime_performance_bug": HappyPathViolationCategory.PERFORMANCE,
|
|
102
|
+
"agent.final_report_primary_objective_omitted": HappyPathViolationCategory.PRIMARY_OBJECTIVE,
|
|
103
|
+
"agent.process_chats_primary_objective_unresolved": HappyPathViolationCategory.PRIMARY_OBJECTIVE,
|
|
104
|
+
"agent.process_chats_vault_deletion_without_receipt": HappyPathViolationCategory.ARTIFACT_INTEGRITY,
|
|
105
|
+
"agent.specialist_model_policy_violation": HappyPathViolationCategory.ROUTE,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def happy_path_metrics_from_findings(
|
|
110
|
+
*,
|
|
111
|
+
workflow: str,
|
|
112
|
+
run_id: str,
|
|
113
|
+
findings: Sequence[object],
|
|
114
|
+
primary_objective_completed: bool,
|
|
115
|
+
legitimate_stop_reason: str,
|
|
116
|
+
) -> HappyPathRunMetrics:
|
|
117
|
+
violations = [_violation_from_finding(finding) for finding in findings]
|
|
118
|
+
if not primary_objective_completed and not legitimate_stop_reason:
|
|
119
|
+
violations.append(
|
|
120
|
+
HappyPathViolation(
|
|
121
|
+
code="workflow.primary_objective_not_completed",
|
|
122
|
+
category=HappyPathViolationCategory.PRIMARY_OBJECTIVE,
|
|
123
|
+
severity="high",
|
|
124
|
+
message="Workflow did not complete its declared primary objective.",
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
score = max(0, 100 - sum(_severity_penalty(item.severity) for item in violations))
|
|
128
|
+
status: Literal["happy", "degraded", "failed"]
|
|
129
|
+
if not violations:
|
|
130
|
+
status = "happy"
|
|
131
|
+
elif any(item.severity in {"high", "critical"} for item in violations):
|
|
132
|
+
status = "failed"
|
|
133
|
+
else:
|
|
134
|
+
status = "degraded"
|
|
135
|
+
return HappyPathRunMetrics(
|
|
136
|
+
workflow=workflow,
|
|
137
|
+
run_id=run_id,
|
|
138
|
+
status=status,
|
|
139
|
+
score=score,
|
|
140
|
+
primary_objective_completed=primary_objective_completed,
|
|
141
|
+
legitimate_stop_reason=legitimate_stop_reason,
|
|
142
|
+
violation_count=len(violations),
|
|
143
|
+
violations=violations,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def happy_path_round_metrics(*, workflow: str, runs: list[HappyPathRunMetrics]) -> HappyPathRoundMetrics:
|
|
148
|
+
happy_run_count = sum(1 for run in runs if run.status == "happy")
|
|
149
|
+
run_count = len(runs)
|
|
150
|
+
prevalence = 100 if run_count == 0 else int((happy_run_count / run_count) * 100)
|
|
151
|
+
return HappyPathRoundMetrics(
|
|
152
|
+
workflow=workflow,
|
|
153
|
+
run_count=run_count,
|
|
154
|
+
happy_run_count=happy_run_count,
|
|
155
|
+
happy_path_prevalence_percent=prevalence,
|
|
156
|
+
runs=runs,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _violation_from_finding(finding: object) -> HappyPathViolation:
|
|
161
|
+
typed = _finding_input(finding)
|
|
162
|
+
return HappyPathViolation(
|
|
163
|
+
code=typed.code,
|
|
164
|
+
category=_FINDING_CATEGORY_BY_CODE.get(typed.code, HappyPathViolationCategory.ROUTE),
|
|
165
|
+
severity=typed.severity,
|
|
166
|
+
message=typed.message,
|
|
167
|
+
evidence=typed.evidence,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _finding_input(finding: object) -> HappyPathFindingInput:
|
|
172
|
+
if isinstance(finding, HappyPathFindingInput):
|
|
173
|
+
return finding
|
|
174
|
+
if isinstance(finding, ContractModel):
|
|
175
|
+
return HappyPathFindingInput.model_validate(finding.to_payload())
|
|
176
|
+
return HappyPathFindingInput.model_validate(finding)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _severity_penalty(severity: str) -> int:
|
|
180
|
+
match severity:
|
|
181
|
+
case "critical":
|
|
182
|
+
return 100
|
|
183
|
+
case "high":
|
|
184
|
+
return 50
|
|
185
|
+
case "medium":
|
|
186
|
+
return 25
|
|
187
|
+
case "low":
|
|
188
|
+
return 10
|
|
189
|
+
case _:
|
|
190
|
+
return 25
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Typed Git/linker state contracts.
|
|
2
|
+
|
|
3
|
+
Git status, worktree diffs and persisted link-state JSON are external adapter
|
|
4
|
+
inputs. These models are the first trusted shape before link diagnosis decides
|
|
5
|
+
trigger context, stale diagnosis or redundant blocked reruns.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Literal
|
|
11
|
+
|
|
12
|
+
from pydantic import Field
|
|
13
|
+
|
|
14
|
+
from mednotes.kernel.base import ContractModel, JsonObject, JsonObjectAdapter
|
|
15
|
+
|
|
16
|
+
LINK_GIT_CONTEXT_SCHEMA = "medical-notes-workbench.link-git-context.v1"
|
|
17
|
+
LINK_STATE_SCHEMA = "medical-notes-workbench.link-state.v1"
|
|
18
|
+
LINK_STATE_SCHEMA_V2 = "medical-notes-workbench.link-state.v2"
|
|
19
|
+
GIT_TRIGGER_SOURCE = "/mednotes:link.git"
|
|
20
|
+
|
|
21
|
+
GitChangeType = Literal["created", "modified", "deleted", "renamed", "moved"]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _without_empty_strings(payload: JsonObject) -> JsonObject:
|
|
25
|
+
"""Keep public Git payloads compact while the internal model stays explicit."""
|
|
26
|
+
|
|
27
|
+
return JsonObjectAdapter.validate_python(
|
|
28
|
+
{key: value for key, value in payload.items() if value not in ("", None, [])}
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class LinkGitChangeEvent(ContractModel):
|
|
33
|
+
change_type: GitChangeType
|
|
34
|
+
content_change: str = ""
|
|
35
|
+
path: str = ""
|
|
36
|
+
old_path: str = ""
|
|
37
|
+
title: str = ""
|
|
38
|
+
old_title: str = ""
|
|
39
|
+
replacement_path: str = ""
|
|
40
|
+
replacement_title: str = ""
|
|
41
|
+
before_hash: str = ""
|
|
42
|
+
after_hash: str = ""
|
|
43
|
+
|
|
44
|
+
def to_payload(self) -> JsonObject:
|
|
45
|
+
return _without_empty_strings(super().to_payload())
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class LinkGitChangedPath(ContractModel):
|
|
49
|
+
change_type: GitChangeType
|
|
50
|
+
path: str = ""
|
|
51
|
+
old_path: str = ""
|
|
52
|
+
|
|
53
|
+
def to_payload(self) -> JsonObject:
|
|
54
|
+
return _without_empty_strings(super().to_payload())
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class LinkGitContext(ContractModel):
|
|
58
|
+
schema_id: Literal["medical-notes-workbench.link-git-context.v1"] = Field(alias="schema")
|
|
59
|
+
available: bool = Field(strict=True)
|
|
60
|
+
repo_root: str
|
|
61
|
+
branch: str
|
|
62
|
+
head: str
|
|
63
|
+
previous_link_head: str
|
|
64
|
+
dirty: bool = Field(strict=True)
|
|
65
|
+
changed_note_count: int = Field(ge=0, strict=True)
|
|
66
|
+
changed_notes: list[LinkGitChangeEvent] = Field(default_factory=list)
|
|
67
|
+
changed_paths: list[LinkGitChangedPath] = Field(default_factory=list)
|
|
68
|
+
trigger_context_available: bool = Field(strict=True)
|
|
69
|
+
status_hash: str
|
|
70
|
+
unavailable_reason: str = ""
|
|
71
|
+
|
|
72
|
+
def to_payload(self) -> JsonObject:
|
|
73
|
+
payload = super().to_payload()
|
|
74
|
+
if not self.unavailable_reason:
|
|
75
|
+
payload.pop("unavailable_reason", None)
|
|
76
|
+
payload["changed_notes"] = [event.to_payload() for event in self.changed_notes]
|
|
77
|
+
payload["changed_paths"] = [event.to_payload() for event in self.changed_paths]
|
|
78
|
+
return JsonObjectAdapter.validate_python(payload)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class LinkTriggerContextFromGit(ContractModel):
|
|
82
|
+
schema_id: Literal["medical-notes-workbench.link-trigger-context.v1"] = Field(alias="schema")
|
|
83
|
+
source_workflow: Literal["/mednotes:link.git"]
|
|
84
|
+
changed_notes: list[LinkGitChangeEvent]
|
|
85
|
+
catalog_changed: bool = Field(default=False, strict=True)
|
|
86
|
+
related_notes_export_changed: bool = Field(default=False, strict=True)
|
|
87
|
+
|
|
88
|
+
def to_payload(self) -> JsonObject:
|
|
89
|
+
payload = super().to_payload()
|
|
90
|
+
payload["changed_notes"] = [event.to_payload() for event in self.changed_notes]
|
|
91
|
+
return JsonObjectAdapter.validate_python(payload)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class LinkState(ContractModel):
|
|
95
|
+
schema_id: Literal[
|
|
96
|
+
"medical-notes-workbench.link-state.v1",
|
|
97
|
+
"medical-notes-workbench.link-state.v2",
|
|
98
|
+
] = Field(alias="schema")
|
|
99
|
+
generated_at: str = ""
|
|
100
|
+
snapshot_hash: str = ""
|
|
101
|
+
git_head: str = ""
|
|
102
|
+
git_status_hash: str = ""
|
|
103
|
+
receipt_path: str = ""
|
|
104
|
+
last_diagnosis_attempt: JsonObject | None = None
|
|
105
|
+
|
|
106
|
+
def to_payload(self) -> JsonObject:
|
|
107
|
+
payload = super().to_payload()
|
|
108
|
+
if self.last_diagnosis_attempt is None:
|
|
109
|
+
payload.pop("last_diagnosis_attempt", None)
|
|
110
|
+
return JsonObjectAdapter.validate_python(payload)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Canonical `/mednotes:link` artifact boundary for parent workflows.
|
|
2
|
+
|
|
3
|
+
Lower-level link adapters may still translate raw runtime output, but a parent
|
|
4
|
+
workflow can only consume the public child FSM payload. Accepting `link-run.v1`
|
|
5
|
+
here would recreate a second source of workflow state.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Literal
|
|
11
|
+
|
|
12
|
+
from pydantic import ConfigDict, Field
|
|
13
|
+
|
|
14
|
+
from mednotes.kernel.base import ContractModel, JsonObject
|
|
15
|
+
from mednotes.kernel.progress import WorkflowProgressViewModel
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class LinkFsmArtifact(ContractModel):
|
|
19
|
+
# Parent workflows consume a stable subset of the child FSM payload. The
|
|
20
|
+
# schema discriminator rejects legacy artifacts; extra fields are ignored so
|
|
21
|
+
# public child-FSM additions do not become a second parent-state contract.
|
|
22
|
+
model_config = ConfigDict(extra="ignore", strict=True)
|
|
23
|
+
|
|
24
|
+
schema_id: Literal["medical-notes-workbench.link-fsm-result.v1"] = Field(alias="schema")
|
|
25
|
+
workflow: Literal["/mednotes:link", "/mednotes:link-body"]
|
|
26
|
+
run_id: str = Field(min_length=1)
|
|
27
|
+
state_machine_snapshot: JsonObject
|
|
28
|
+
progress_view_model: WorkflowProgressViewModel
|
|
29
|
+
decision: JsonObject | None = None
|
|
30
|
+
human_decision_packet: JsonObject | None = None
|
|
31
|
+
receipt: JsonObject
|
|
32
|
+
reports: JsonObject
|
|
33
|
+
agent_directive: JsonObject
|
|
34
|
+
artifacts: JsonObject
|
|
35
|
+
version_control_safety: JsonObject
|
|
36
|
+
diagnostic_context: JsonObject | None = None
|
|
37
|
+
error_context: JsonObject
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def operation_status(self) -> str:
|
|
41
|
+
return self.progress_view_model.status.value
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def normalize_link_runtime_artifact(payload: object) -> LinkFsmArtifact:
|
|
45
|
+
"""Validate the child FSM payload before its status can affect the parent."""
|
|
46
|
+
|
|
47
|
+
if not isinstance(payload, dict):
|
|
48
|
+
raise ValueError("link artifact must be an object")
|
|
49
|
+
schema = payload["schema"] if "schema" in payload else ""
|
|
50
|
+
if schema != "medical-notes-workbench.link-fsm-result.v1":
|
|
51
|
+
raise ValueError("link artifact must be medical-notes-workbench.link-fsm-result.v1")
|
|
52
|
+
return LinkFsmArtifact.model_validate(payload)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import Field, model_validator
|
|
6
|
+
|
|
7
|
+
from mednotes.domains.wiki.contracts.raw_coverage import (
|
|
8
|
+
CoverageAction as CoverageAction,
|
|
9
|
+
)
|
|
10
|
+
from mednotes.domains.wiki.contracts.raw_coverage import (
|
|
11
|
+
RawCoverage as RawCoverage,
|
|
12
|
+
)
|
|
13
|
+
from mednotes.domains.wiki.contracts.raw_coverage import (
|
|
14
|
+
RawCoverageItem as RawCoverageItem,
|
|
15
|
+
)
|
|
16
|
+
from mednotes.kernel.base import ContractModel
|
|
17
|
+
|
|
18
|
+
PlanAction = Literal["planned_meaning", "attach_to_planned_meaning", "not_a_note", "needs_context"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MeaningClaim(ContractModel):
|
|
22
|
+
label: str = Field(min_length=1)
|
|
23
|
+
scope: str = Field(min_length=1)
|
|
24
|
+
boundaries: list[str]
|
|
25
|
+
kind: Literal[
|
|
26
|
+
"clinical_concept",
|
|
27
|
+
"drug_concept",
|
|
28
|
+
"diagnostic_criterion",
|
|
29
|
+
"management_strategy",
|
|
30
|
+
"procedure",
|
|
31
|
+
"physiology_or_mechanism",
|
|
32
|
+
"epidemiology_or_definition",
|
|
33
|
+
]
|
|
34
|
+
evidence_summary: str = Field(min_length=1)
|
|
35
|
+
id: str | None = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TriageNotePlanItem(ContractModel):
|
|
39
|
+
id: str = Field(min_length=1)
|
|
40
|
+
action: PlanAction
|
|
41
|
+
title: str | None = None
|
|
42
|
+
staged_title: str | None = None
|
|
43
|
+
meaning_claim: MeaningClaim | None = None
|
|
44
|
+
taxonomy_hint: str | None = None
|
|
45
|
+
aliases: list[str] | None = None
|
|
46
|
+
target_item_id: str | None = None
|
|
47
|
+
reason_code: str | None = None
|
|
48
|
+
reason: str | None = None
|
|
49
|
+
|
|
50
|
+
@model_validator(mode="after")
|
|
51
|
+
def validate_action_payload(self) -> TriageNotePlanItem:
|
|
52
|
+
if self.action == "planned_meaning":
|
|
53
|
+
if not self.title or not self.staged_title or self.meaning_claim is None:
|
|
54
|
+
raise ValueError("planned_meaning items require title, staged_title and meaning_claim")
|
|
55
|
+
elif self.action == "attach_to_planned_meaning":
|
|
56
|
+
if not self.target_item_id or not self.reason_code or not self.reason:
|
|
57
|
+
raise ValueError("attach_to_planned_meaning items require target_item_id, reason_code and reason")
|
|
58
|
+
elif self.action in {"not_a_note", "needs_context"} and (not self.reason_code or not self.reason):
|
|
59
|
+
raise ValueError(f"{self.action} items require reason_code and reason")
|
|
60
|
+
return self
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class TriageNotePlan(ContractModel):
|
|
64
|
+
schema_id: Literal["medical-notes-workbench.triage-note-plan.v2"] = Field(alias="schema")
|
|
65
|
+
raw_file: str = Field(min_length=1)
|
|
66
|
+
exhaustive: Literal[True] = True
|
|
67
|
+
items: list[TriageNotePlanItem] = Field(min_length=1)
|
|
68
|
+
batch_id: str | None = None
|
|
69
|
+
run_id: str | None = None
|
|
70
|
+
source_artifact_hash: str | None = None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# RawCoverage lives in raw_coverage.py because it is a publish boundary, not a
|
|
74
|
+
# triage-plan concept. These imports remain as a stable public import path for
|
|
75
|
+
# existing schema/test code while the implementation has a single source.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
from pydantic import Field, field_validator, model_validator
|
|
7
|
+
|
|
8
|
+
from mednotes.kernel.base import ContractModel, JsonObject
|
|
9
|
+
|
|
10
|
+
PathBlockerReason = Literal[
|
|
11
|
+
"paths.wiki_dir_missing",
|
|
12
|
+
"paths.raw_dir_missing",
|
|
13
|
+
"paths.ambiguous",
|
|
14
|
+
"paths.config_invalid",
|
|
15
|
+
]
|
|
16
|
+
PathRequiredInput = Literal["wiki_dir", "raw_dir", "config_path"]
|
|
17
|
+
_WIKI_REASON_MAP: dict[str, PathBlockerReason] = {
|
|
18
|
+
"missing_wiki_dir": "paths.wiki_dir_missing",
|
|
19
|
+
"ambiguous_wiki_dir": "paths.ambiguous",
|
|
20
|
+
"invalid_wiki_dir": "paths.config_invalid",
|
|
21
|
+
}
|
|
22
|
+
_DEFAULT_REASON: PathBlockerReason = "paths.config_invalid"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class WorkbenchPathsConfig(ContractModel):
|
|
26
|
+
wiki_dir: Path
|
|
27
|
+
raw_dir: Path | None = None
|
|
28
|
+
|
|
29
|
+
@field_validator("wiki_dir")
|
|
30
|
+
@classmethod
|
|
31
|
+
def wiki_dir_must_exist(cls, value: Path) -> Path:
|
|
32
|
+
if not value.exists() or not value.is_dir():
|
|
33
|
+
raise ValueError("wiki_dir must be an existing directory")
|
|
34
|
+
return value
|
|
35
|
+
|
|
36
|
+
@field_validator("raw_dir")
|
|
37
|
+
@classmethod
|
|
38
|
+
def raw_dir_must_exist_when_present(cls, value: Path | None) -> Path | None:
|
|
39
|
+
if value is not None and (not value.exists() or not value.is_dir()):
|
|
40
|
+
raise ValueError("raw_dir must be an existing directory when provided")
|
|
41
|
+
return value
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class PathResolutionBlocker(ContractModel):
|
|
45
|
+
blocked_reason: PathBlockerReason
|
|
46
|
+
legacy_blocked_reason: str = ""
|
|
47
|
+
next_action: str = Field(min_length=1)
|
|
48
|
+
required_inputs: list[PathRequiredInput] = Field(default_factory=list)
|
|
49
|
+
human_decision_required: bool = False
|
|
50
|
+
human_decision_packet: JsonObject | None = None
|
|
51
|
+
|
|
52
|
+
@model_validator(mode="after")
|
|
53
|
+
def human_decision_requires_packet(self) -> PathResolutionBlocker:
|
|
54
|
+
if self.human_decision_required and self.human_decision_packet is None:
|
|
55
|
+
raise ValueError("human_decision_required requires human_decision_packet")
|
|
56
|
+
return self
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class PathResolutionResult(ContractModel):
|
|
60
|
+
status: Literal["ready", "blocked"]
|
|
61
|
+
paths: WorkbenchPathsConfig | None = None
|
|
62
|
+
blocker: PathResolutionBlocker | None = None
|
|
63
|
+
|
|
64
|
+
@model_validator(mode="after")
|
|
65
|
+
def status_matches_payload(self) -> PathResolutionResult:
|
|
66
|
+
if self.status == "ready" and self.blocker is not None:
|
|
67
|
+
raise ValueError("ready path resolution cannot include blocker")
|
|
68
|
+
if self.status == "ready" and self.paths is None:
|
|
69
|
+
raise ValueError("ready path resolution requires paths")
|
|
70
|
+
if self.status == "blocked" and self.blocker is None:
|
|
71
|
+
raise ValueError("blocked path resolution requires blocker")
|
|
72
|
+
return self
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class WikiPathResolutionPayload(ContractModel):
|
|
76
|
+
schema_: Literal["medical-notes-workbench.path-resolution.v1"] = Field(
|
|
77
|
+
"medical-notes-workbench.path-resolution.v1",
|
|
78
|
+
alias="schema",
|
|
79
|
+
)
|
|
80
|
+
phase: str = Field(min_length=1)
|
|
81
|
+
status: Literal["completed", "blocked"]
|
|
82
|
+
blocked_reason: str = ""
|
|
83
|
+
legacy_blocked_reason: str = ""
|
|
84
|
+
next_action: str = ""
|
|
85
|
+
required_inputs: list[str] = Field(default_factory=list)
|
|
86
|
+
wiki_dir: str = ""
|
|
87
|
+
wiki_source: str = ""
|
|
88
|
+
wiki_dir_source: str = ""
|
|
89
|
+
memory_path: str = ""
|
|
90
|
+
config_path: str = ""
|
|
91
|
+
candidates: list[JsonObject] = Field(default_factory=list)
|
|
92
|
+
compat_warnings: list[str] = Field(default_factory=list)
|
|
93
|
+
human_decision_required: bool = False
|
|
94
|
+
human_decision_packet: JsonObject | None = None
|
|
95
|
+
human_decision_packets: list[JsonObject] = Field(default_factory=list)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def blocker_from_wiki_resolution(payload: dict[str, object]) -> PathResolutionBlocker:
|
|
99
|
+
resolution = WikiPathResolutionPayload.model_validate(payload)
|
|
100
|
+
raw_reason = resolution.blocked_reason
|
|
101
|
+
mapped_reason = _WIKI_REASON_MAP.get(raw_reason, _DEFAULT_REASON)
|
|
102
|
+
required_inputs: list[PathRequiredInput] = (
|
|
103
|
+
["wiki_dir"]
|
|
104
|
+
if mapped_reason in {"paths.wiki_dir_missing", "paths.ambiguous", "paths.config_invalid"}
|
|
105
|
+
else []
|
|
106
|
+
)
|
|
107
|
+
return PathResolutionBlocker(
|
|
108
|
+
blocked_reason=mapped_reason,
|
|
109
|
+
legacy_blocked_reason=raw_reason,
|
|
110
|
+
next_action=resolution.next_action or "Configurar wiki_dir valido em /mednotes:setup.",
|
|
111
|
+
required_inputs=required_inputs,
|
|
112
|
+
human_decision_required=resolution.human_decision_required,
|
|
113
|
+
human_decision_packet=resolution.human_decision_packet,
|
|
114
|
+
)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
from pydantic import Field, model_validator
|
|
7
|
+
|
|
8
|
+
from mednotes.kernel.base import ContractModel
|
|
9
|
+
|
|
10
|
+
WorkflowPublicObjectiveAnswer = Literal[
|
|
11
|
+
"yes",
|
|
12
|
+
"partial",
|
|
13
|
+
"no",
|
|
14
|
+
"waiting_agent",
|
|
15
|
+
"waiting_external",
|
|
16
|
+
"waiting_human",
|
|
17
|
+
"failed",
|
|
18
|
+
]
|
|
19
|
+
WorkflowPublicMutationState = Literal["changed", "unchanged", "not_applicable"]
|
|
20
|
+
|
|
21
|
+
POSITIVE_FILE_COUNT_RE = re.compile(r"\b[1-9]\d*\s+arquivo")
|
|
22
|
+
ZERO_FILE_COUNT_RE = re.compile(r"\b0\s+arquivo")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class WorkflowPublicReportViewModel(ContractModel):
|
|
26
|
+
schema_: Literal["medical-notes-workbench.workflow-public-report-view-model.v1"] = Field(
|
|
27
|
+
"medical-notes-workbench.workflow-public-report-view-model.v1",
|
|
28
|
+
alias="schema",
|
|
29
|
+
)
|
|
30
|
+
workflow: str = Field(min_length=1)
|
|
31
|
+
run_id: str = Field(min_length=1)
|
|
32
|
+
objective_answer: WorkflowPublicObjectiveAnswer
|
|
33
|
+
headline: str = Field(min_length=1)
|
|
34
|
+
mutation_state: WorkflowPublicMutationState
|
|
35
|
+
mutation_summary: str = Field(min_length=1)
|
|
36
|
+
remaining_work_summary: str = Field(min_length=1)
|
|
37
|
+
next_step_summary: str = Field(min_length=1)
|
|
38
|
+
user_attention_required: bool
|
|
39
|
+
human_reason: str = ""
|
|
40
|
+
internal_terms_present: bool = False
|
|
41
|
+
|
|
42
|
+
@model_validator(mode="after")
|
|
43
|
+
def _public_report_is_coherent(self) -> WorkflowPublicReportViewModel:
|
|
44
|
+
folded_mutation = self.mutation_summary.casefold()
|
|
45
|
+
has_positive_file_count = POSITIVE_FILE_COUNT_RE.search(folded_mutation) is not None
|
|
46
|
+
has_only_zero_file_count = ZERO_FILE_COUNT_RE.search(folded_mutation) is not None and not has_positive_file_count
|
|
47
|
+
if self.mutation_state == "changed" and ("nada foi alterado" in folded_mutation or has_only_zero_file_count):
|
|
48
|
+
raise ValueError("public report mutation contradiction")
|
|
49
|
+
if self.user_attention_required and not self.human_reason.strip():
|
|
50
|
+
raise ValueError("public report user attention requires human reason")
|
|
51
|
+
if self.internal_terms_present:
|
|
52
|
+
raise ValueError("public report cannot expose internal terms")
|
|
53
|
+
return self
|