cellarbrain 0.3.4__tar.gz → 0.3.6__tar.gz
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.
- {cellarbrain-0.3.4/src/cellarbrain.egg-info → cellarbrain-0.3.6}/PKG-INFO +1 -1
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/pyproject.toml +1 -1
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/_query_base.py +52 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dossier_ops.py +82 -46
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/mcp_server.py +302 -71
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/report.py +114 -5
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/query.py +1 -1
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/search.py +19 -10
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/similarity.py +13 -1
- {cellarbrain-0.3.4 → cellarbrain-0.3.6/src/cellarbrain.egg-info}/PKG-INFO +1 -1
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain.egg-info/SOURCES.txt +1 -0
- cellarbrain-0.3.6/tests/test_format_param.py +129 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_mcp_server.py +5 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_query.py +17 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/LICENSE +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/README.md +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/setup.cfg +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/__init__.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/__main__.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/anomaly.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/backup.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/cli.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/companion_markdown.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/computed.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/__init__.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/app.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/cellar_queries.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/dossier_render.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/ingest_queries.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/queries.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/sidecars.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/static/dashboard.js +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/static/js/drink-tonight.js +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/static/workbench.js +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/anomalies.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/base.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/bottles.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/cellar.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/drink_tonight.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/drinking.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/drinking_timeline.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/error.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/errors.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/heatmap.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/index.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/ingest.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/latency.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/live.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/pairing.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/anomaly_banner.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/bottle_rows.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/consumed_button.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/consumption_velocity.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/dashboard_notes.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/dossier_section.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/drink_tonight_button.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/error_detail.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/error_rows.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/event_stream.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/ingest_rows.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/pairing_results.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/recommend_results.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/reminder_banner.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/session_detail.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/sql_results.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/stats_content.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/tool_rows.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/turn_events.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/wine_of_the_day.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/wine_rows.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/workbench_response.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/pending_consumed.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/recommend.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/sessions.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/sql.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/stats.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/tools.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/tracked.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/tracked_detail.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/wine_detail.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/workbench_batch.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/workbench_list.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/workbench_tool.html +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/workbench.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/data/learning_frameworks.toml +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/digest.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dinner.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/doctor.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/education.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/email_poll/__init__.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/email_poll/credentials.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/email_poll/etl_runner.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/email_poll/grouping.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/email_poll/imap.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/email_poll/placement.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/exceptions.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/flat.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/gifting.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/hybrid_pairing.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/incremental.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/info.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/log.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/markdown.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/mcp_responses.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/migrate.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/migrations/__init__.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/migrations/m001_baseline.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/observability.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/pairing.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/parsers.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/phonetic.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/price.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/__init__.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/fetch.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/matching.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/models.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/parsers/__init__.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/parsers/base.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/parsers/coop.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/parsers/denner.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/parsers/kapweine.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/parsers/millesima.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/parsers/moevenpick.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/persistence.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/price_utils.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/registry.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/state.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/query_cache.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/recommend.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/service.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/settings.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/README.md +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/__init__.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/cellar-gaps/SKILL.md +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/cellar-stats/SKILL.md +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/consumption-velocity/SKILL.md +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/digest/SKILL.md +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/dinner-party/SKILL.md +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/drinking-window/SKILL.md +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/food-pairing/SKILL.md +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/hilfe/SKILL.md +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/manage/SKILL.md +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/monthly-report/SKILL.md +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/price-scan/SKILL.md +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/promotion-analysis/SKILL.md +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/research/SKILL.md +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/research-completeness/SKILL.md +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/tonight/SKILL.md +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/wine-info/SKILL.md +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/wine-of-the-day/SKILL.md +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/slugify.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/sommelier/__init__.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/sommelier/catalogue.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/sommelier/data/food_catalogue.parquet +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/sommelier/data/pairing_dataset.parquet +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/sommelier/engine.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/sommelier/index.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/sommelier/model.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/sommelier/schemas.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/sommelier/seed.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/sommelier/text_builder.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/sommelier/training.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/transform.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/travel.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/validate.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/vinocell_parsers.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/vinocell_reader.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/wotd.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/writer.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain.egg-info/dependency_links.txt +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain.egg-info/entry_points.txt +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain.egg-info/requires.txt +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain.egg-info/top_level.txt +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_anomaly.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_backup.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_catalogue.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_cli.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_companion_markdown.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_computed.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_coop_parser.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dashboard_actions.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dashboard_app.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dashboard_cellar.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dashboard_dossier.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dashboard_ingest.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dashboard_pairing.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dashboard_queries.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dashboard_sidecars.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dashboard_workbench.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dataset_factory.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_denner_parser.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_digest.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dinner.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_doctor.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dossier_ops.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_education.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_email_poll.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_flat.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_gifting.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_hybrid_pairing.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_incremental.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_info.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_integration.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_integration_install.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_kapweine_parser.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_log.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_markdown.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_mcp_responses.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_migrate.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_millesima_parser.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_moevenpick_parser.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_observability.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_pairing.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_parsers.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_phonetic.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_plain_format.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_price.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_promotions.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_query_cache.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_query_forbidden.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_reader.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_recommend.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_search.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_service.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_settings.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_similarity.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_skills.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_smoke_verify.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_sommelier.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_sommelier_data.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_sommelier_mcp.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_sommelier_quality.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_sommelier_seed.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_sommelier_training.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_transform.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_travel.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_validate.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_vinocell_parsers.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_vinocell_reader.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_wotd.py +0 -0
- {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_writer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cellarbrain
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.6
|
|
4
4
|
Summary: AI sommelier for your wine cellar — ETL pipeline, DuckDB query layer, Markdown dossiers, and MCP server for wine cellar CSV exports
|
|
5
5
|
Author-email: Urban Busslinger <urbanb@me.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cellarbrain"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.6"
|
|
8
8
|
description = "AI sommelier for your wine cellar — ETL pipeline, DuckDB query layer, Markdown dossiers, and MCP server for wine cellar CSV exports"
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
license = "MIT"
|
|
@@ -12,6 +12,31 @@ def _to_md(df: pd.DataFrame) -> str:
|
|
|
12
12
|
return df.astype(object).where(df.notna(), "").to_markdown(index=False)
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
def _to_aligned(df: pd.DataFrame) -> str:
|
|
16
|
+
"""Render a DataFrame as a space-aligned monospace code block."""
|
|
17
|
+
if df.empty:
|
|
18
|
+
return "*No results.*"
|
|
19
|
+
|
|
20
|
+
clean = df.astype(object).where(df.notna(), "")
|
|
21
|
+
cols = list(clean.columns)
|
|
22
|
+
|
|
23
|
+
# Compute column widths
|
|
24
|
+
widths = [len(c) for c in cols]
|
|
25
|
+
for _, row in clean.iterrows():
|
|
26
|
+
for i, c in enumerate(cols):
|
|
27
|
+
widths[i] = max(widths[i], len(str(row[c])))
|
|
28
|
+
|
|
29
|
+
# Build header + rows
|
|
30
|
+
lines: list[str] = []
|
|
31
|
+
header = " ".join(str(c).ljust(widths[i]) for i, c in enumerate(cols))
|
|
32
|
+
lines.append(header)
|
|
33
|
+
for _, row in clean.iterrows():
|
|
34
|
+
line = " ".join(str(row[c]).ljust(widths[i]) for i, c in enumerate(cols))
|
|
35
|
+
lines.append(line)
|
|
36
|
+
|
|
37
|
+
return "```text\n" + "\n".join(lines) + "\n```"
|
|
38
|
+
|
|
39
|
+
|
|
15
40
|
def _to_plain(df: pd.DataFrame, style: str = "list") -> str:
|
|
16
41
|
"""Render a DataFrame as plain text (no Markdown) for iMessage delivery.
|
|
17
42
|
|
|
@@ -19,10 +44,26 @@ def _to_plain(df: pd.DataFrame, style: str = "list") -> str:
|
|
|
19
44
|
list — numbered list, one row per item with key columns on one line
|
|
20
45
|
kv — key-value pairs, one per line for each row
|
|
21
46
|
compact — single line per row with middle-dot separators
|
|
47
|
+
aligned — space-aligned monospace code block
|
|
48
|
+
auto — pick style based on row/column count (iMessage heuristic)
|
|
22
49
|
"""
|
|
23
50
|
if df.empty:
|
|
24
51
|
return "*No results.*"
|
|
25
52
|
|
|
53
|
+
if style == "auto":
|
|
54
|
+
nrows = len(df)
|
|
55
|
+
ncols = len(df.columns)
|
|
56
|
+
if nrows <= 5:
|
|
57
|
+
style = "kv"
|
|
58
|
+
elif nrows <= 15 and ncols <= 4:
|
|
59
|
+
style = "aligned"
|
|
60
|
+
else:
|
|
61
|
+
# Large table: summary + first rows
|
|
62
|
+
return _to_plain_summary(df)
|
|
63
|
+
|
|
64
|
+
if style == "aligned":
|
|
65
|
+
return _to_aligned(df)
|
|
66
|
+
|
|
26
67
|
clean = df.astype(object).where(df.notna(), "")
|
|
27
68
|
cols = list(clean.columns)
|
|
28
69
|
|
|
@@ -51,6 +92,17 @@ def _to_plain(df: pd.DataFrame, style: str = "list") -> str:
|
|
|
51
92
|
return "\n".join(lines)
|
|
52
93
|
|
|
53
94
|
|
|
95
|
+
def _to_plain_summary(df: pd.DataFrame, max_rows: int = 10) -> str:
|
|
96
|
+
"""Summarise a large DataFrame: show first N rows + count."""
|
|
97
|
+
total = len(df)
|
|
98
|
+
shown = min(max_rows, total)
|
|
99
|
+
head = df.head(shown)
|
|
100
|
+
body = _to_aligned(head)
|
|
101
|
+
if total > shown:
|
|
102
|
+
body += f"\n\n…and {total - shown} more rows."
|
|
103
|
+
return body
|
|
104
|
+
|
|
105
|
+
|
|
54
106
|
def _format_df(df: pd.DataFrame, fmt: str = "markdown", style: str = "list") -> str:
|
|
55
107
|
"""Dispatch DataFrame formatting based on format preference."""
|
|
56
108
|
if fmt == "plain":
|
|
@@ -769,11 +769,12 @@ def pending_research(
|
|
|
769
769
|
section: str | None = None,
|
|
770
770
|
include_stale: bool = False,
|
|
771
771
|
stale_months: int = 6,
|
|
772
|
+
fmt: str = "markdown",
|
|
772
773
|
) -> str:
|
|
773
774
|
"""List wines with pending agent sections, sorted by priority.
|
|
774
775
|
|
|
775
776
|
Priority: favorites first, then by bottles_in_cellar descending.
|
|
776
|
-
Returns a Markdown table
|
|
777
|
+
Returns a Markdown table or plain text depending on *fmt*.
|
|
777
778
|
|
|
778
779
|
Args:
|
|
779
780
|
data_dir: Path to the data directory.
|
|
@@ -784,6 +785,7 @@ def pending_research(
|
|
|
784
785
|
sections (research older than *stale_months*). These appear after
|
|
785
786
|
genuinely pending wines.
|
|
786
787
|
stale_months: Threshold for stale research (default 6 months).
|
|
788
|
+
fmt: Output format ("markdown" or "plain").
|
|
787
789
|
"""
|
|
788
790
|
d = pathlib.Path(data_dir)
|
|
789
791
|
wines_dir = d / "wines"
|
|
@@ -875,28 +877,42 @@ def pending_research(
|
|
|
875
877
|
# Truncate
|
|
876
878
|
pending_data = pending_data[:limit]
|
|
877
879
|
|
|
878
|
-
# Format
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
880
|
+
# Format output
|
|
881
|
+
if fmt == "plain":
|
|
882
|
+
lines: list[str] = []
|
|
883
|
+
for item in pending_data:
|
|
884
|
+
fav = " ⭐" if item["is_favorite"] else ""
|
|
885
|
+
lines.append(
|
|
886
|
+
f"{item['wine_id']}. {item['wine_name'] or '—'}"
|
|
887
|
+
f" ({item['vintage'] or 'NV'}){fav}"
|
|
888
|
+
f" — {item['bottles']} btl, {item['pending_count']} pending"
|
|
889
|
+
)
|
|
890
|
+
else:
|
|
891
|
+
lines = [
|
|
892
|
+
"| wine_id | Name | Vintage | Bottles | Favorite | Pending |",
|
|
893
|
+
"|---|---|---|---|---|---|",
|
|
894
|
+
]
|
|
895
|
+
for item in pending_data:
|
|
896
|
+
fav = "⭐" if item["is_favorite"] else ""
|
|
897
|
+
lines.append(
|
|
898
|
+
f"| {item['wine_id']} "
|
|
899
|
+
f"| {item['wine_name'] or '—'} "
|
|
900
|
+
f"| {item['vintage'] or 'NV'} "
|
|
901
|
+
f"| {item['bottles']} "
|
|
902
|
+
f"| {fav} "
|
|
903
|
+
f"| {item['pending_count']} |"
|
|
904
|
+
)
|
|
893
905
|
|
|
894
906
|
# Optionally append stale research items
|
|
895
907
|
if include_stale:
|
|
896
|
-
stale_result = stale_research(data_dir, months=stale_months, limit=limit, section=section)
|
|
908
|
+
stale_result = stale_research(data_dir, months=stale_months, limit=limit, section=section, fmt=fmt)
|
|
897
909
|
if "*No stale research found.*" not in stale_result:
|
|
898
|
-
|
|
899
|
-
|
|
910
|
+
if fmt == "plain":
|
|
911
|
+
lines.append("")
|
|
912
|
+
lines.append(f"STALE RESEARCH (>{stale_months} months)")
|
|
913
|
+
else:
|
|
914
|
+
lines.append("")
|
|
915
|
+
lines.append("### Stale Research (>" + str(stale_months) + " months)")
|
|
900
916
|
lines.append("")
|
|
901
917
|
lines.append(stale_result)
|
|
902
918
|
|
|
@@ -909,6 +925,7 @@ def stale_research(
|
|
|
909
925
|
limit: int = 20,
|
|
910
926
|
section: str | None = None,
|
|
911
927
|
settings: Settings | None = None,
|
|
928
|
+
fmt: str = "markdown",
|
|
912
929
|
) -> str:
|
|
913
930
|
"""Find wine dossier sections where research metadata is older than *months*.
|
|
914
931
|
|
|
@@ -916,7 +933,7 @@ def stale_research(
|
|
|
916
933
|
with a parseable date. Sections without metadata are ignored (use
|
|
917
934
|
``pending_research`` for those).
|
|
918
935
|
|
|
919
|
-
Returns a
|
|
936
|
+
Returns a table sorted by oldest-first.
|
|
920
937
|
"""
|
|
921
938
|
d = pathlib.Path(data_dir)
|
|
922
939
|
wines_dir = d / "wines"
|
|
@@ -981,20 +998,29 @@ def stale_research(
|
|
|
981
998
|
|
|
982
999
|
stale_items = stale_items[:limit]
|
|
983
1000
|
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1001
|
+
if fmt == "plain":
|
|
1002
|
+
lines: list[str] = []
|
|
1003
|
+
for item in stale_items:
|
|
1004
|
+
name = wine_meta.get(item["wine_id"], "")
|
|
1005
|
+
lines.append(
|
|
1006
|
+
f"{item['wine_id']}. {name} — {item['section']}"
|
|
1007
|
+
f" ({item['date']}, {item['months_ago']}mo ago, agent: {item['agent']})"
|
|
1008
|
+
)
|
|
1009
|
+
else:
|
|
1010
|
+
lines = [
|
|
1011
|
+
"| wine_id | Name | Section | Date | Age | Agent |",
|
|
1012
|
+
"|---|---|---|---|---|---|",
|
|
1013
|
+
]
|
|
1014
|
+
for item in stale_items:
|
|
1015
|
+
name = wine_meta.get(item["wine_id"], "")
|
|
1016
|
+
lines.append(
|
|
1017
|
+
f"| {item['wine_id']} "
|
|
1018
|
+
f"| {name} "
|
|
1019
|
+
f"| {item['section']} "
|
|
1020
|
+
f"| {item['date']} "
|
|
1021
|
+
f"| {item['months_ago']}mo "
|
|
1022
|
+
f"| {item['agent']} |"
|
|
1023
|
+
)
|
|
998
1024
|
|
|
999
1025
|
return "\n".join(lines)
|
|
1000
1026
|
|
|
@@ -1361,11 +1387,12 @@ def update_companion_dossier(
|
|
|
1361
1387
|
def pending_companion_research(
|
|
1362
1388
|
data_dir: str | pathlib.Path,
|
|
1363
1389
|
limit: int = 20,
|
|
1390
|
+
fmt: str = "markdown",
|
|
1364
1391
|
) -> str:
|
|
1365
1392
|
"""List tracked wines with pending companion sections.
|
|
1366
1393
|
|
|
1367
1394
|
Scans companion dossier frontmatter for ``agent_sections_pending`` entries.
|
|
1368
|
-
Returns a
|
|
1395
|
+
Returns a table sorted by tracked_wine_id.
|
|
1369
1396
|
"""
|
|
1370
1397
|
d = pathlib.Path(data_dir)
|
|
1371
1398
|
wishlist_dir = d / "wines" / "tracked"
|
|
@@ -1432,16 +1459,25 @@ def pending_companion_research(
|
|
|
1432
1459
|
pending_data.sort(key=lambda x: x["tracked_wine_id"])
|
|
1433
1460
|
pending_data = pending_data[:limit]
|
|
1434
1461
|
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1462
|
+
if fmt == "plain":
|
|
1463
|
+
lines: list[str] = []
|
|
1464
|
+
for item in pending_data:
|
|
1465
|
+
lines.append(
|
|
1466
|
+
f"{item['tracked_wine_id']}. {item['winery'] or '—'}"
|
|
1467
|
+
f" — {item['wine_name'] or '—'}"
|
|
1468
|
+
f" ({item['pending_count']} pending)"
|
|
1469
|
+
)
|
|
1470
|
+
else:
|
|
1471
|
+
lines = [
|
|
1472
|
+
"| tracked_wine_id | Winery | Wine | Pending |",
|
|
1473
|
+
"|---|---|---|---|",
|
|
1474
|
+
]
|
|
1475
|
+
for item in pending_data:
|
|
1476
|
+
lines.append(
|
|
1477
|
+
f"| {item['tracked_wine_id']} "
|
|
1478
|
+
f"| {item['winery'] or '—'} "
|
|
1479
|
+
f"| {item['wine_name'] or '—'} "
|
|
1480
|
+
f"| {item['pending_count']} |"
|
|
1481
|
+
)
|
|
1446
1482
|
|
|
1447
1483
|
return "\n".join(lines)
|