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.
Files changed (241) hide show
  1. {cellarbrain-0.3.4/src/cellarbrain.egg-info → cellarbrain-0.3.6}/PKG-INFO +1 -1
  2. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/pyproject.toml +1 -1
  3. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/_query_base.py +52 -0
  4. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dossier_ops.py +82 -46
  5. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/mcp_server.py +302 -71
  6. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/report.py +114 -5
  7. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/query.py +1 -1
  8. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/search.py +19 -10
  9. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/similarity.py +13 -1
  10. {cellarbrain-0.3.4 → cellarbrain-0.3.6/src/cellarbrain.egg-info}/PKG-INFO +1 -1
  11. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain.egg-info/SOURCES.txt +1 -0
  12. cellarbrain-0.3.6/tests/test_format_param.py +129 -0
  13. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_mcp_server.py +5 -0
  14. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_query.py +17 -0
  15. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/LICENSE +0 -0
  16. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/README.md +0 -0
  17. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/setup.cfg +0 -0
  18. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/__init__.py +0 -0
  19. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/__main__.py +0 -0
  20. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/anomaly.py +0 -0
  21. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/backup.py +0 -0
  22. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/cli.py +0 -0
  23. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/companion_markdown.py +0 -0
  24. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/computed.py +0 -0
  25. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/__init__.py +0 -0
  26. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/app.py +0 -0
  27. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/cellar_queries.py +0 -0
  28. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/dossier_render.py +0 -0
  29. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/ingest_queries.py +0 -0
  30. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/queries.py +0 -0
  31. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/sidecars.py +0 -0
  32. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/static/dashboard.js +0 -0
  33. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/static/js/drink-tonight.js +0 -0
  34. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/static/workbench.js +0 -0
  35. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/anomalies.html +0 -0
  36. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/base.html +0 -0
  37. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/bottles.html +0 -0
  38. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/cellar.html +0 -0
  39. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/drink_tonight.html +0 -0
  40. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/drinking.html +0 -0
  41. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/drinking_timeline.html +0 -0
  42. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/error.html +0 -0
  43. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/errors.html +0 -0
  44. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/heatmap.html +0 -0
  45. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/index.html +0 -0
  46. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/ingest.html +0 -0
  47. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/latency.html +0 -0
  48. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/live.html +0 -0
  49. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/pairing.html +0 -0
  50. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/anomaly_banner.html +0 -0
  51. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/bottle_rows.html +0 -0
  52. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/consumed_button.html +0 -0
  53. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/consumption_velocity.html +0 -0
  54. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/dashboard_notes.html +0 -0
  55. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/dossier_section.html +0 -0
  56. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/drink_tonight_button.html +0 -0
  57. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/error_detail.html +0 -0
  58. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/error_rows.html +0 -0
  59. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/event_stream.html +0 -0
  60. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/ingest_rows.html +0 -0
  61. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/pairing_results.html +0 -0
  62. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/recommend_results.html +0 -0
  63. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/reminder_banner.html +0 -0
  64. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/session_detail.html +0 -0
  65. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/sql_results.html +0 -0
  66. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/stats_content.html +0 -0
  67. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/tool_rows.html +0 -0
  68. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/turn_events.html +0 -0
  69. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/wine_of_the_day.html +0 -0
  70. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/wine_rows.html +0 -0
  71. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/partials/workbench_response.html +0 -0
  72. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/pending_consumed.html +0 -0
  73. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/recommend.html +0 -0
  74. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/sessions.html +0 -0
  75. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/sql.html +0 -0
  76. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/stats.html +0 -0
  77. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/tools.html +0 -0
  78. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/tracked.html +0 -0
  79. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/tracked_detail.html +0 -0
  80. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/wine_detail.html +0 -0
  81. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/workbench_batch.html +0 -0
  82. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/workbench_list.html +0 -0
  83. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/templates/workbench_tool.html +0 -0
  84. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dashboard/workbench.py +0 -0
  85. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/data/learning_frameworks.toml +0 -0
  86. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/digest.py +0 -0
  87. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/dinner.py +0 -0
  88. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/doctor.py +0 -0
  89. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/education.py +0 -0
  90. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/email_poll/__init__.py +0 -0
  91. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/email_poll/credentials.py +0 -0
  92. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/email_poll/etl_runner.py +0 -0
  93. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/email_poll/grouping.py +0 -0
  94. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/email_poll/imap.py +0 -0
  95. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/email_poll/placement.py +0 -0
  96. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/exceptions.py +0 -0
  97. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/flat.py +0 -0
  98. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/gifting.py +0 -0
  99. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/hybrid_pairing.py +0 -0
  100. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/incremental.py +0 -0
  101. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/info.py +0 -0
  102. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/log.py +0 -0
  103. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/markdown.py +0 -0
  104. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/mcp_responses.py +0 -0
  105. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/migrate.py +0 -0
  106. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/migrations/__init__.py +0 -0
  107. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/migrations/m001_baseline.py +0 -0
  108. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/observability.py +0 -0
  109. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/pairing.py +0 -0
  110. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/parsers.py +0 -0
  111. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/phonetic.py +0 -0
  112. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/price.py +0 -0
  113. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/__init__.py +0 -0
  114. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/fetch.py +0 -0
  115. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/matching.py +0 -0
  116. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/models.py +0 -0
  117. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/parsers/__init__.py +0 -0
  118. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/parsers/base.py +0 -0
  119. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/parsers/coop.py +0 -0
  120. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/parsers/denner.py +0 -0
  121. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/parsers/kapweine.py +0 -0
  122. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/parsers/millesima.py +0 -0
  123. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/parsers/moevenpick.py +0 -0
  124. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/persistence.py +0 -0
  125. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/price_utils.py +0 -0
  126. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/registry.py +0 -0
  127. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/promotions/state.py +0 -0
  128. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/query_cache.py +0 -0
  129. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/recommend.py +0 -0
  130. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/service.py +0 -0
  131. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/settings.py +0 -0
  132. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/README.md +0 -0
  133. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/__init__.py +0 -0
  134. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/cellar-gaps/SKILL.md +0 -0
  135. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/cellar-stats/SKILL.md +0 -0
  136. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/consumption-velocity/SKILL.md +0 -0
  137. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/digest/SKILL.md +0 -0
  138. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/dinner-party/SKILL.md +0 -0
  139. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/drinking-window/SKILL.md +0 -0
  140. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/food-pairing/SKILL.md +0 -0
  141. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/hilfe/SKILL.md +0 -0
  142. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/manage/SKILL.md +0 -0
  143. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/monthly-report/SKILL.md +0 -0
  144. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/price-scan/SKILL.md +0 -0
  145. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/promotion-analysis/SKILL.md +0 -0
  146. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/research/SKILL.md +0 -0
  147. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/research-completeness/SKILL.md +0 -0
  148. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/tonight/SKILL.md +0 -0
  149. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/wine-info/SKILL.md +0 -0
  150. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/skills/wine-of-the-day/SKILL.md +0 -0
  151. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/slugify.py +0 -0
  152. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/sommelier/__init__.py +0 -0
  153. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/sommelier/catalogue.py +0 -0
  154. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/sommelier/data/food_catalogue.parquet +0 -0
  155. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/sommelier/data/pairing_dataset.parquet +0 -0
  156. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/sommelier/engine.py +0 -0
  157. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/sommelier/index.py +0 -0
  158. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/sommelier/model.py +0 -0
  159. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/sommelier/schemas.py +0 -0
  160. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/sommelier/seed.py +0 -0
  161. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/sommelier/text_builder.py +0 -0
  162. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/sommelier/training.py +0 -0
  163. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/transform.py +0 -0
  164. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/travel.py +0 -0
  165. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/validate.py +0 -0
  166. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/vinocell_parsers.py +0 -0
  167. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/vinocell_reader.py +0 -0
  168. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/wotd.py +0 -0
  169. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain/writer.py +0 -0
  170. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain.egg-info/dependency_links.txt +0 -0
  171. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain.egg-info/entry_points.txt +0 -0
  172. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain.egg-info/requires.txt +0 -0
  173. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/src/cellarbrain.egg-info/top_level.txt +0 -0
  174. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_anomaly.py +0 -0
  175. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_backup.py +0 -0
  176. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_catalogue.py +0 -0
  177. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_cli.py +0 -0
  178. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_companion_markdown.py +0 -0
  179. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_computed.py +0 -0
  180. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_coop_parser.py +0 -0
  181. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dashboard_actions.py +0 -0
  182. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dashboard_app.py +0 -0
  183. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dashboard_cellar.py +0 -0
  184. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dashboard_dossier.py +0 -0
  185. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dashboard_ingest.py +0 -0
  186. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dashboard_pairing.py +0 -0
  187. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dashboard_queries.py +0 -0
  188. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dashboard_sidecars.py +0 -0
  189. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dashboard_workbench.py +0 -0
  190. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dataset_factory.py +0 -0
  191. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_denner_parser.py +0 -0
  192. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_digest.py +0 -0
  193. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dinner.py +0 -0
  194. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_doctor.py +0 -0
  195. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_dossier_ops.py +0 -0
  196. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_education.py +0 -0
  197. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_email_poll.py +0 -0
  198. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_flat.py +0 -0
  199. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_gifting.py +0 -0
  200. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_hybrid_pairing.py +0 -0
  201. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_incremental.py +0 -0
  202. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_info.py +0 -0
  203. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_integration.py +0 -0
  204. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_integration_install.py +0 -0
  205. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_kapweine_parser.py +0 -0
  206. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_log.py +0 -0
  207. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_markdown.py +0 -0
  208. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_mcp_responses.py +0 -0
  209. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_migrate.py +0 -0
  210. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_millesima_parser.py +0 -0
  211. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_moevenpick_parser.py +0 -0
  212. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_observability.py +0 -0
  213. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_pairing.py +0 -0
  214. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_parsers.py +0 -0
  215. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_phonetic.py +0 -0
  216. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_plain_format.py +0 -0
  217. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_price.py +0 -0
  218. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_promotions.py +0 -0
  219. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_query_cache.py +0 -0
  220. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_query_forbidden.py +0 -0
  221. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_reader.py +0 -0
  222. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_recommend.py +0 -0
  223. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_search.py +0 -0
  224. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_service.py +0 -0
  225. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_settings.py +0 -0
  226. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_similarity.py +0 -0
  227. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_skills.py +0 -0
  228. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_smoke_verify.py +0 -0
  229. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_sommelier.py +0 -0
  230. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_sommelier_data.py +0 -0
  231. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_sommelier_mcp.py +0 -0
  232. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_sommelier_quality.py +0 -0
  233. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_sommelier_seed.py +0 -0
  234. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_sommelier_training.py +0 -0
  235. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_transform.py +0 -0
  236. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_travel.py +0 -0
  237. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_validate.py +0 -0
  238. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_vinocell_parsers.py +0 -0
  239. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_vinocell_reader.py +0 -0
  240. {cellarbrain-0.3.4 → cellarbrain-0.3.6}/tests/test_wotd.py +0 -0
  241. {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.4
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.4"
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 as Markdown table
879
- lines = [
880
- "| wine_id | Name | Vintage | Bottles | Favorite | Pending |",
881
- "|---|---|---|---|---|---|",
882
- ]
883
- for item in pending_data:
884
- fav = "⭐" if item["is_favorite"] else ""
885
- lines.append(
886
- f"| {item['wine_id']} "
887
- f"| {item['wine_name'] or '—'} "
888
- f"| {item['vintage'] or 'NV'} "
889
- f"| {item['bottles']} "
890
- f"| {fav} "
891
- f"| {item['pending_count']} |"
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
- lines.append("")
899
- lines.append("### Stale Research (>" + str(stale_months) + " months)")
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 Markdown table sorted by oldest-first.
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
- lines = [
985
- "| wine_id | Name | Section | Date | Age | Agent |",
986
- "|---|---|---|---|---|---|",
987
- ]
988
- for item in stale_items:
989
- name = wine_meta.get(item["wine_id"], "")
990
- lines.append(
991
- f"| {item['wine_id']} "
992
- f"| {name} "
993
- f"| {item['section']} "
994
- f"| {item['date']} "
995
- f"| {item['months_ago']}mo "
996
- f"| {item['agent']} |"
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 Markdown table sorted by tracked_wine_id.
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
- lines = [
1436
- "| tracked_wine_id | Winery | Wine | Pending |",
1437
- "|---|---|---|---|",
1438
- ]
1439
- for item in pending_data:
1440
- lines.append(
1441
- f"| {item['tracked_wine_id']} "
1442
- f"| {item['winery'] or '—'} "
1443
- f"| {item['wine_name'] or '—'} "
1444
- f"| {item['pending_count']} |"
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)