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,120 @@
|
|
|
1
|
+
"""Input expansion and note path validation."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import glob
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from mednotes.domains.wiki.flows.enrich.workflow.models import NoteResult
|
|
9
|
+
|
|
10
|
+
_EXCLUDED_NOTE_DIR_NAMES = {
|
|
11
|
+
".git",
|
|
12
|
+
".hg",
|
|
13
|
+
".obsidian",
|
|
14
|
+
".svn",
|
|
15
|
+
".venv",
|
|
16
|
+
"__pycache__",
|
|
17
|
+
"_attachments",
|
|
18
|
+
"anexos",
|
|
19
|
+
"attachments",
|
|
20
|
+
"build",
|
|
21
|
+
"dist",
|
|
22
|
+
"images",
|
|
23
|
+
"node_modules",
|
|
24
|
+
"venv",
|
|
25
|
+
}
|
|
26
|
+
_GLOB_CHARS_RE = re.compile(r"[*?\[]")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _has_glob(value: str) -> bool:
|
|
30
|
+
return bool(_GLOB_CHARS_RE.search(value))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _sort_key(path: Path) -> str:
|
|
34
|
+
return path.as_posix().casefold()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _note_key(path: Path) -> str:
|
|
38
|
+
try:
|
|
39
|
+
return str(path.resolve(strict=False))
|
|
40
|
+
except OSError:
|
|
41
|
+
return str(path.absolute())
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _is_excluded_note_path(path: Path) -> bool:
|
|
45
|
+
return any(part.casefold() in _EXCLUDED_NOTE_DIR_NAMES for part in path.parts)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _markdown_files_in_dir(directory: Path) -> list[Path]:
|
|
49
|
+
files = []
|
|
50
|
+
for path in directory.rglob("*.md"):
|
|
51
|
+
try:
|
|
52
|
+
relative = path.relative_to(directory)
|
|
53
|
+
except ValueError:
|
|
54
|
+
relative = path
|
|
55
|
+
if _is_excluded_note_path(relative):
|
|
56
|
+
continue
|
|
57
|
+
if path.is_file():
|
|
58
|
+
files.append(path)
|
|
59
|
+
return sorted(files, key=_sort_key)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _resolve_note_inputs(inputs: list[Path]) -> tuple[list[Path], list[NoteResult]]:
|
|
63
|
+
notes: list[Path] = []
|
|
64
|
+
errors: list[NoteResult] = []
|
|
65
|
+
seen: set[str] = set()
|
|
66
|
+
|
|
67
|
+
def add_note(path: Path) -> None:
|
|
68
|
+
key = _note_key(path)
|
|
69
|
+
if key in seen:
|
|
70
|
+
return
|
|
71
|
+
seen.add(key)
|
|
72
|
+
notes.append(path)
|
|
73
|
+
|
|
74
|
+
def add_error(path: Path, message: str) -> None:
|
|
75
|
+
errors.append(NoteResult(note=path, code=2, status="failed", message=message))
|
|
76
|
+
|
|
77
|
+
for raw in inputs:
|
|
78
|
+
path = raw.expanduser()
|
|
79
|
+
raw_text = str(path)
|
|
80
|
+
if _has_glob(raw_text):
|
|
81
|
+
matches = sorted((Path(match) for match in glob.glob(raw_text, recursive=True)), key=_sort_key)
|
|
82
|
+
if not matches:
|
|
83
|
+
add_error(path, f"glob sem correspondências: {raw}")
|
|
84
|
+
continue
|
|
85
|
+
found: list[Path] = []
|
|
86
|
+
for match in matches:
|
|
87
|
+
if match.is_dir():
|
|
88
|
+
found.extend(_markdown_files_in_dir(match))
|
|
89
|
+
elif match.is_file() and match.suffix.lower() == ".md":
|
|
90
|
+
if not _is_excluded_note_path(match):
|
|
91
|
+
found.append(match)
|
|
92
|
+
if not found:
|
|
93
|
+
add_error(path, f"glob não encontrou notas .md: {raw}")
|
|
94
|
+
continue
|
|
95
|
+
for note in sorted(found, key=_sort_key):
|
|
96
|
+
add_note(note)
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
if path.is_dir():
|
|
100
|
+
found = _markdown_files_in_dir(path)
|
|
101
|
+
if not found:
|
|
102
|
+
add_error(path, f"diretório sem notas .md: {path}")
|
|
103
|
+
continue
|
|
104
|
+
for note in found:
|
|
105
|
+
add_note(note)
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
add_note(path)
|
|
109
|
+
|
|
110
|
+
return notes, errors
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _validate_note_path(note: Path) -> str | None:
|
|
114
|
+
if note.suffix.lower() != ".md":
|
|
115
|
+
return f"caminho não é uma nota .md: {note}"
|
|
116
|
+
if not note.exists():
|
|
117
|
+
return f"caminho não encontrado: {note}"
|
|
118
|
+
if not note.is_file():
|
|
119
|
+
return f"caminho não é arquivo: {note}"
|
|
120
|
+
return None
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Shared models and constants for the image enrichment workflow."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from mednotes.domains.wiki.capabilities.illustrate.sources import ImageCandidate
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GeminiError(RuntimeError):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_DEFAULT_GEMINI_TIMEOUT_SECONDS = 120
|
|
15
|
+
_EXIT_SOURCE_QUOTA = 9
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class CandidateReport:
|
|
20
|
+
candidates: list[ImageCandidate]
|
|
21
|
+
counts_by_source: dict[str, int]
|
|
22
|
+
failed_queries: list[tuple[str, str, str]]
|
|
23
|
+
capped: bool = False
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class NoteResult:
|
|
28
|
+
note: Path
|
|
29
|
+
code: int
|
|
30
|
+
status: str
|
|
31
|
+
inserted_count: int = 0
|
|
32
|
+
sources_count: dict[str, int] = field(default_factory=dict)
|
|
33
|
+
message: str = ""
|
|
34
|
+
quality_report: dict[str, object] | None = None
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""JSON response parsing for Gemini outputs."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
_JSON_FENCE_RE = re.compile(r"```(?:json)?\s*(.+?)\s*```", re.DOTALL)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _strip_fences(s: str) -> str:
|
|
11
|
+
m = _JSON_FENCE_RE.search(s)
|
|
12
|
+
return m.group(1) if m else s.strip()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def parse_anchors_json(raw: str) -> list[dict]:
|
|
16
|
+
cleaned = _strip_fences(raw)
|
|
17
|
+
data = json.loads(cleaned)
|
|
18
|
+
if not isinstance(data, list):
|
|
19
|
+
raise ValueError(f"esperava lista de âncoras, recebi {type(data).__name__}")
|
|
20
|
+
out = []
|
|
21
|
+
for i, a in enumerate(data):
|
|
22
|
+
for k in ("section_path", "concept", "visual_type", "search_queries"):
|
|
23
|
+
if k not in a:
|
|
24
|
+
raise ValueError(f"âncora #{i} sem chave obrigatória {k!r}")
|
|
25
|
+
a.setdefault("anchor_id", f"a{i+1}")
|
|
26
|
+
out.append(a)
|
|
27
|
+
return out
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def parse_rerank_json(raw: str) -> dict:
|
|
31
|
+
cleaned = _strip_fences(raw)
|
|
32
|
+
data = json.loads(cleaned)
|
|
33
|
+
if "chosen_index" not in data:
|
|
34
|
+
raise ValueError("rerank sem chave 'chosen_index'")
|
|
35
|
+
if "minimum_quality_met" not in data:
|
|
36
|
+
data["minimum_quality_met"] = data.get("chosen_index") is not None
|
|
37
|
+
candidates = data.get("candidates", [])
|
|
38
|
+
if candidates is None:
|
|
39
|
+
candidates = []
|
|
40
|
+
if not isinstance(candidates, list):
|
|
41
|
+
raise ValueError("rerank candidates precisa ser lista")
|
|
42
|
+
for item in candidates:
|
|
43
|
+
if not isinstance(item, dict):
|
|
44
|
+
raise ValueError("cada item de candidates precisa ser objeto")
|
|
45
|
+
if "index" not in item:
|
|
46
|
+
raise ValueError("candidate rubric sem index")
|
|
47
|
+
data["candidates"] = candidates
|
|
48
|
+
return data
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Prompt templates and builders for image enrichment."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
|
|
7
|
+
from mednotes.domains.wiki.capabilities.illustrate.sources import ImageCandidate
|
|
8
|
+
|
|
9
|
+
_ANCHORS_PROMPT_TEMPLATE = """Você é um curador de imagens médicas para uma nota de estudo de alto rigor.
|
|
10
|
+
|
|
11
|
+
CONTEXTO DE QUALIDADE:
|
|
12
|
+
- O usuário estuda medicina em português do Brasil e quer material útil para prova/residência e revisão clínica.
|
|
13
|
+
- A imagem deve parecer material didático confiável: foto clínica real, lâmina, radiologia, anatomia, gráfico ou esquema técnico. Não escolha imagem decorativa, caricatura, meme, stock genérico ou desenho infantil.
|
|
14
|
+
- Priorize precisão visual sobre quantidade. Uma figura errada ensina errado.
|
|
15
|
+
|
|
16
|
+
Leia a NOTA abaixo e devolva até {max_anchors} ÂNCORAS — pontos onde uma figura tornaria o aprendizado mais eficiente.
|
|
17
|
+
|
|
18
|
+
REGRAS DE SELEÇÃO (importantes):
|
|
19
|
+
|
|
20
|
+
1. **Prefira seções-folha (sem subseções)** sobre seções com filhos. Se uma seção tem subseções listadas em SECTIONS, escolha a subseção em vez do pai — a inserção vai pro fim do trilho escolhido, e seções com filhos têm o "fim" depois das subseções (posicionamento ruim).
|
|
21
|
+
|
|
22
|
+
2. **Cada visual_type bem específico**:
|
|
23
|
+
- `diagram`: esquema/fluxograma técnico de mecanismo molecular, fisiopatologia ou via metabólica
|
|
24
|
+
- `anatomy`: anatomia macro realista (órgão, sistema, corte, peça ou atlas)
|
|
25
|
+
- `histology`: lâmina histológica/microscopia com coloração, imunofluorescência ou achado anatomopatológico
|
|
26
|
+
- `radiology`: imagem radiológica real (RX, TC, RM, US), com modalidade/achado quando possível
|
|
27
|
+
- `chart`: gráfico/curva clinicamente interpretável (dose-resposta, sobrevida, ECG, algoritmo)
|
|
28
|
+
- `photo`: foto clínica real (lesão, sinal semiológico, exame físico), não ilustração genérica
|
|
29
|
+
|
|
30
|
+
3. **Conceito curto e visual**: o que a figura PRECISA MOSTRAR, não o que a seção fala em geral. Ex: "binding do ISRS ao SERT bloqueando recaptação", não "mecanismo dos ISRS". Termine SEM ponto final.
|
|
31
|
+
|
|
32
|
+
4. **Queries — siga a regra de IDIOMA abaixo**:
|
|
33
|
+
{language_guidance}
|
|
34
|
+
|
|
35
|
+
5. **Use operadores de busca quando isso aumentar precisão.** Para `web_search`, inclua em pelo menos uma query termos acadêmicos em inglês e, quando fizer sentido, operadores `site:` apontando fontes confiáveis:
|
|
36
|
+
- histology/photo/mecanismo: `site:nih.gov`, `site:ncbi.nlm.nih.gov`, `site:nejm.org`, `site:dermnetnz.org`
|
|
37
|
+
- radiology: `site:radiopaedia.org`, `site:acr.org`, `site:rsna.org`
|
|
38
|
+
- anatomy/diagram: `site:openstax.org`, `site:teachmeanatomy.info`, `site:kenhub.com`
|
|
39
|
+
Não force `site:` se isso piorar a query; use como "virtual adapter" apenas quando o domínio combina com o tipo visual.
|
|
40
|
+
|
|
41
|
+
6. **Não force âncoras fracas.** Se uma seção é puramente lista de fármacos ou texto sem imagem natural, pule. Melhor 2 âncoras boas que 5 medíocres. Mas também não seja tímido — uma nota didática quase sempre tem >=1 ponto que se beneficia.
|
|
42
|
+
|
|
43
|
+
Devolva APENAS um JSON válido (sem ```fences), no formato:
|
|
44
|
+
[{{"section_path": [...], "concept": "...", "visual_type": "...", "search_queries": ["...", "..."], "anchor_id": "a1"}}]
|
|
45
|
+
|
|
46
|
+
Lista vazia `[]` se realmente nenhum ponto pede figura.
|
|
47
|
+
|
|
48
|
+
SECTIONS (paths e níveis — note quais têm filhos):
|
|
49
|
+
{sections_json}
|
|
50
|
+
|
|
51
|
+
NOTA:
|
|
52
|
+
{note_text}
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
_LANGUAGE_GUIDANCE = {
|
|
57
|
+
"pt-br": (
|
|
58
|
+
" Gere 3-5 queries: pelo menos **1 em português** (termos médicos canônicos PT-BR, "
|
|
59
|
+
"ex.: \"mecanismo de ação ISRS SERT sinapse\") e **1-2 em inglês** "
|
|
60
|
+
"(ex.: \"SSRI mechanism action SERT\"). Inclua termos de prova/achado quando existirem "
|
|
61
|
+
"(ex.: marcador, sinal, modalidade, coloração, classificação). Variar as duas línguas "
|
|
62
|
+
"amplia a chance de achar figura com legenda em PT (preferida) sem perder o material em EN."
|
|
63
|
+
),
|
|
64
|
+
"en": (
|
|
65
|
+
" Gere 3-5 queries em **inglês**, médicas, canônicas e específicas. "
|
|
66
|
+
"Inclua marcador, sinal, modalidade, coloração ou classificação quando existirem. "
|
|
67
|
+
"Ex.: [\"SSRI mechanism action SERT\", \"selective serotonin reuptake inhibitor binding\"]."
|
|
68
|
+
),
|
|
69
|
+
"any": (
|
|
70
|
+
" Gere 3-5 queries em **inglês**, médicas, canônicas e específicas (cobertura máxima). "
|
|
71
|
+
"Inclua marcador, sinal, modalidade, coloração ou classificação quando existirem. "
|
|
72
|
+
"Ex.: [\"SSRI mechanism action SERT\", \"selective serotonin reuptake inhibitor binding\"]."
|
|
73
|
+
),
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
_LANGUAGE_RERANK_HINT = {
|
|
78
|
+
"pt-br": (
|
|
79
|
+
"\n6. **PREFERÊNCIA DE IDIOMA**: quando 2+ candidatas tiverem qualidade equivalente, "
|
|
80
|
+
"prefira figura com texto em **português** ou **sem texto**. Figura em inglês é "
|
|
81
|
+
"aceitável se claramente superior nos outros critérios."
|
|
82
|
+
),
|
|
83
|
+
"en": "",
|
|
84
|
+
"any": "",
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
_RERANK_PROMPT_TEMPLATE = """Você é um curador EXIGENTE de imagens médicas. Para a ÂNCORA abaixo, escolha a melhor candidata olhando as miniaturas anexadas — OU recuse todas se nenhuma é boa.
|
|
89
|
+
|
|
90
|
+
PADRÃO DE QUALIDADE:
|
|
91
|
+
- Pense como alguém selecionando material para revisão clínica/prova de residência.
|
|
92
|
+
- Prefira imagem tecnicamente precisa, real ou acadêmica, com fonte rastreável.
|
|
93
|
+
- Não premie imagem bonita se ela for genérica, antiga sem valor didático atual, decorativa ou apenas vagamente relacionada.
|
|
94
|
+
|
|
95
|
+
ÂNCORA:
|
|
96
|
+
- Conceito (o que a figura precisa mostrar): {concept}
|
|
97
|
+
- Tipo visual desejado: {visual_type}
|
|
98
|
+
- Queries usadas: {queries}
|
|
99
|
+
|
|
100
|
+
CANDIDATAS (índice 0-based, miniatura inline via @arquivo):
|
|
101
|
+
{candidates_block}
|
|
102
|
+
|
|
103
|
+
REGRAS DE DECISÃO:
|
|
104
|
+
|
|
105
|
+
1. **Match temático ESTRITO.** A figura tem que mostrar exatamente o conceito. Se mostra um tópico vizinho (mesmo que mesma molécula/órgão), NÃO É MATCH. Ex: conceito "ISRS bloqueando SERT" — uma figura de "MDMA causando efflux via SERT" é vizinha mas NÃO é match. Devolva `null`.
|
|
106
|
+
|
|
107
|
+
2. **Tipo visual tem que bater.** Se pediram `diagram`, uma foto de medicamento não serve. Se pediram `radiology`, um esquema desenhado não serve.
|
|
108
|
+
|
|
109
|
+
3. **Qualidade visual mínima**: legível, sem watermark, sem texto em idioma absurdo, sem mistura de figuras desconexas no mesmo arquivo.
|
|
110
|
+
|
|
111
|
+
4. **Filtro de confiabilidade e atualidade.** Recuse imagem histórica/obsoleta quando o objetivo for conduta, achado radiológico moderno, classificação atual ou foto clínica contemporânea. Aceite imagem antiga só se ela for claramente anatomia/histologia clássica e ainda didática.
|
|
112
|
+
|
|
113
|
+
5. **Recusar é melhor que escolher meia-certo.** Uma figura ruim na nota é pior que nenhuma figura — quem estuda fica confuso. Em dúvida, escolha `null`.
|
|
114
|
+
|
|
115
|
+
6. **Justifique em UMA frase concreta** apontando elemento da figura (ex: "mostra exatamente SSRI ligando ao SERT bloqueando 5HT", ou "a figura é sobre MDMA causando efflux, não bloqueio por ISRS — vizinho mas off-topic").
|
|
116
|
+
|
|
117
|
+
7. **Rubrica estruturada obrigatória.** Avalie cada candidata com notas 0-5:
|
|
118
|
+
- `topic_match`: mostra exatamente o conceito?
|
|
119
|
+
- `visual_type_match`: bate com o tipo visual pedido?
|
|
120
|
+
- `clinical_reliability`: parece fonte médica/acadêmica confiável?
|
|
121
|
+
- `legibility`: dá para estudar pela imagem?
|
|
122
|
+
- `source_traceability`: há página/fonte rastreável?
|
|
123
|
+
- `obsolete_or_decorative_risk`: risco de ser obsoleta, decorativa, stock, clipart ou off-topic.
|
|
124
|
+
|
|
125
|
+
Só use `"minimum_quality_met": true` quando a candidata escolhida tiver match temático estrito, tipo visual correto, legibilidade suficiente e fonte rastreável. Se qualquer eixo essencial falhar, use `chosen_index: null`.
|
|
126
|
+
|
|
127
|
+
Devolva APENAS um JSON válido (sem ```fences), neste formato:
|
|
128
|
+
{{
|
|
129
|
+
"chosen_index": <int ou null>,
|
|
130
|
+
"minimum_quality_met": <true ou false>,
|
|
131
|
+
"reason": "<uma frase concreta>",
|
|
132
|
+
"candidates": [
|
|
133
|
+
{{
|
|
134
|
+
"index": <int>,
|
|
135
|
+
"topic_match": <0-5>,
|
|
136
|
+
"visual_type_match": <0-5>,
|
|
137
|
+
"clinical_reliability": <0-5>,
|
|
138
|
+
"legibility": <0-5>,
|
|
139
|
+
"source_traceability": <0-5>,
|
|
140
|
+
"obsolete_or_decorative_risk": <0-5>,
|
|
141
|
+
"decision": "accept|reject"
|
|
142
|
+
}}
|
|
143
|
+
]
|
|
144
|
+
}}
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def build_anchors_prompt(
|
|
149
|
+
note_text: str,
|
|
150
|
+
sections: list[dict],
|
|
151
|
+
*,
|
|
152
|
+
max_anchors: int,
|
|
153
|
+
preferred_language: str = "any",
|
|
154
|
+
) -> str:
|
|
155
|
+
# Anota `has_children`: True se a próxima seção é descendente (level maior).
|
|
156
|
+
annotated = []
|
|
157
|
+
for i, s in enumerate(sections):
|
|
158
|
+
has_children = (
|
|
159
|
+
i + 1 < len(sections) and sections[i + 1]["level"] > s["level"]
|
|
160
|
+
)
|
|
161
|
+
annotated.append(
|
|
162
|
+
{
|
|
163
|
+
"section_path": s["section_path"],
|
|
164
|
+
"level": s["level"],
|
|
165
|
+
"has_children": has_children,
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
guidance = _LANGUAGE_GUIDANCE.get(preferred_language.lower(), _LANGUAGE_GUIDANCE["any"])
|
|
169
|
+
return _ANCHORS_PROMPT_TEMPLATE.format(
|
|
170
|
+
max_anchors=max_anchors,
|
|
171
|
+
language_guidance=guidance,
|
|
172
|
+
sections_json=json.dumps(annotated, ensure_ascii=False, indent=2),
|
|
173
|
+
note_text=note_text,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def build_rerank_prompt(
|
|
178
|
+
anchor: dict,
|
|
179
|
+
candidates: list[ImageCandidate],
|
|
180
|
+
*,
|
|
181
|
+
thumb_basenames: Sequence[str | None] | None = None,
|
|
182
|
+
preferred_language: str = "any",
|
|
183
|
+
) -> str:
|
|
184
|
+
"""``thumb_basenames[i]`` é o nome do arquivo do thumb de ``candidates[i]``,
|
|
185
|
+
referenciável via ``@<basename>`` quando o caller passar a pasta dos thumbs
|
|
186
|
+
em ``--include-directories``. ``None`` na posição = thumb falhou; ainda
|
|
187
|
+
listamos a candidata como texto pra preservar o índice."""
|
|
188
|
+
if thumb_basenames is None:
|
|
189
|
+
thumb_basenames = [None] * len(candidates)
|
|
190
|
+
lines = []
|
|
191
|
+
for i, (c, tb) in enumerate(zip(candidates, thumb_basenames, strict=False)):
|
|
192
|
+
thumb_ref = f"@{tb}" if tb else "(thumb indisponível)"
|
|
193
|
+
lines.append(
|
|
194
|
+
f" [{i}] {thumb_ref}\n"
|
|
195
|
+
f" title={c.title!r} | source={c.source} | "
|
|
196
|
+
f"profile={c.source_profile or '-'} | domain={c.page_domain or '-'} | "
|
|
197
|
+
f"trust={c.trust_score if c.trust_score is not None else '-'} | "
|
|
198
|
+
f"size={c.width}x{c.height} | license={c.license}\n"
|
|
199
|
+
f" quality_hints: {', '.join(c.quality_hints) if c.quality_hints else '-'}\n"
|
|
200
|
+
f" description: {c.description}\n"
|
|
201
|
+
f" url: {c.image_url}"
|
|
202
|
+
)
|
|
203
|
+
base = _RERANK_PROMPT_TEMPLATE.format(
|
|
204
|
+
concept=anchor["concept"],
|
|
205
|
+
visual_type=anchor["visual_type"],
|
|
206
|
+
queries=", ".join(anchor.get("search_queries", [])),
|
|
207
|
+
candidates_block="\n".join(lines),
|
|
208
|
+
)
|
|
209
|
+
hint = _LANGUAGE_RERANK_HINT.get(preferred_language.lower(), "")
|
|
210
|
+
if hint:
|
|
211
|
+
# Insere a regra extra antes da instrução final de "Devolva APENAS um JSON".
|
|
212
|
+
base = base.replace(
|
|
213
|
+
"Devolva APENAS um JSON",
|
|
214
|
+
hint + "\n\nDevolva APENAS um JSON",
|
|
215
|
+
)
|
|
216
|
+
return base
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Deterministic quality helpers for image candidate selection."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from mednotes.domains.wiki.capabilities.illustrate.sources import ImageCandidate
|
|
7
|
+
|
|
8
|
+
DECORATIVE_MARKERS = ("stock", "icon", "clipart", "vector", "wallpaper", "logo")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class CandidateQuality:
|
|
13
|
+
candidate: ImageCandidate
|
|
14
|
+
score: float
|
|
15
|
+
flags: tuple[str, ...]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def assess_candidate(visual_type: str, candidate: ImageCandidate) -> CandidateQuality:
|
|
19
|
+
flags: list[str] = []
|
|
20
|
+
width = candidate.width or 0
|
|
21
|
+
height = candidate.height or 0
|
|
22
|
+
trust = candidate.trust_score if candidate.trust_score is not None else 0.50
|
|
23
|
+
resolution_score = min(max(min(width, height) / 1200, 0.0), 1.0)
|
|
24
|
+
text = f"{candidate.title} {candidate.description}".lower()
|
|
25
|
+
|
|
26
|
+
if min(width, height) and min(width, height) < 600:
|
|
27
|
+
flags.append("low_resolution")
|
|
28
|
+
if any(marker in text for marker in DECORATIVE_MARKERS):
|
|
29
|
+
flags.append("possibly_decorative")
|
|
30
|
+
if (
|
|
31
|
+
visual_type == "radiology"
|
|
32
|
+
and candidate.source not in {"radiopaedia", "nih_open_i", "web_search"}
|
|
33
|
+
):
|
|
34
|
+
flags.append("weak_radiology_source")
|
|
35
|
+
|
|
36
|
+
penalty = 0.15 * len(flags)
|
|
37
|
+
score = max(0.0, min(1.0, (0.65 * trust) + (0.35 * resolution_score) - penalty))
|
|
38
|
+
return CandidateQuality(candidate=candidate, score=round(score, 4), flags=tuple(flags))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def rank_candidates_for_rerank(
|
|
42
|
+
visual_type: str,
|
|
43
|
+
candidates: list[ImageCandidate],
|
|
44
|
+
) -> list[ImageCandidate]:
|
|
45
|
+
assessed = [assess_candidate(visual_type, item) for item in candidates]
|
|
46
|
+
assessed.sort(
|
|
47
|
+
key=lambda item: (
|
|
48
|
+
-item.score,
|
|
49
|
+
item.candidate.source,
|
|
50
|
+
item.candidate.title.lower(),
|
|
51
|
+
item.candidate.image_url,
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
return [item.candidate for item in assessed]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Local reporting helpers for image-enrichment quality decisions."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from mednotes.kernel.base import JsonObject, JsonObjectAdapter
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def write_quality_report(path: Path, payload: JsonObject) -> None:
|
|
11
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
12
|
+
validated = JsonObjectAdapter.validate_python(payload)
|
|
13
|
+
path.write_text(
|
|
14
|
+
json.dumps(validated, ensure_ascii=False, indent=2) + "\n",
|
|
15
|
+
encoding="utf-8",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def note_report(path: Path, status: str, anchors: list[JsonObject]) -> JsonObject:
|
|
20
|
+
return {
|
|
21
|
+
"path": str(path),
|
|
22
|
+
"status": status,
|
|
23
|
+
"anchors": anchors,
|
|
24
|
+
}
|