memstrata-conversational 0.6.1__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 (236) hide show
  1. memstrata_conversational-0.6.1/LICENSE-COMMERCIAL +26 -0
  2. memstrata_conversational-0.6.1/PKG-INFO +80 -0
  3. memstrata_conversational-0.6.1/billing/__init__.py +18 -0
  4. memstrata_conversational-0.6.1/billing/credit.py +109 -0
  5. memstrata_conversational-0.6.1/billing/email_notify.py +282 -0
  6. memstrata_conversational-0.6.1/billing/reconcile.py +120 -0
  7. memstrata_conversational-0.6.1/billing/webhook.py +345 -0
  8. memstrata_conversational-0.6.1/harness/__init__.py +8 -0
  9. memstrata_conversational-0.6.1/harness/__main__.py +7 -0
  10. memstrata_conversational-0.6.1/harness/cli.py +1161 -0
  11. memstrata_conversational-0.6.1/harness/config.py +199 -0
  12. memstrata_conversational-0.6.1/harness/counterfactual.py +340 -0
  13. memstrata_conversational-0.6.1/harness/degrade.py +63 -0
  14. memstrata_conversational-0.6.1/harness/emission.py +35 -0
  15. memstrata_conversational-0.6.1/harness/injection.py +277 -0
  16. memstrata_conversational-0.6.1/harness/memory_layer_client.py +237 -0
  17. memstrata_conversational-0.6.1/harness/providers/__init__.py +1 -0
  18. memstrata_conversational-0.6.1/harness/providers/anthropic.py +237 -0
  19. memstrata_conversational-0.6.1/harness/providers/base.py +65 -0
  20. memstrata_conversational-0.6.1/harness/providers/gemini.py +296 -0
  21. memstrata_conversational-0.6.1/harness/providers/ollama.py +230 -0
  22. memstrata_conversational-0.6.1/harness/providers/openai.py +159 -0
  23. memstrata_conversational-0.6.1/harness/server.py +2028 -0
  24. memstrata_conversational-0.6.1/harness/service/__init__.py +27 -0
  25. memstrata_conversational-0.6.1/harness/service/base.py +81 -0
  26. memstrata_conversational-0.6.1/harness/service/launchd.py +108 -0
  27. memstrata_conversational-0.6.1/harness/service/systemd.py +126 -0
  28. memstrata_conversational-0.6.1/harness/service/windows.py +188 -0
  29. memstrata_conversational-0.6.1/harness/streaming.py +137 -0
  30. memstrata_conversational-0.6.1/harness/telemetry.py +207 -0
  31. memstrata_conversational-0.6.1/harness/tokenizers/__init__.py +1 -0
  32. memstrata_conversational-0.6.1/harness/tokenizers/anthropic_counter.py +50 -0
  33. memstrata_conversational-0.6.1/harness/tokenizers/estimate_counter.py +54 -0
  34. memstrata_conversational-0.6.1/harness/tokenizers/tiktoken_counter.py +89 -0
  35. memstrata_conversational-0.6.1/memory_layer_pro/__init__.py +16 -0
  36. memstrata_conversational-0.6.1/memory_layer_pro/__main__.py +23 -0
  37. memstrata_conversational-0.6.1/memory_layer_pro/_paths.py +155 -0
  38. memstrata_conversational-0.6.1/memory_layer_pro/api_overlay.py +1235 -0
  39. memstrata_conversational-0.6.1/memory_layer_pro/baseline/__init__.py +0 -0
  40. memstrata_conversational-0.6.1/memory_layer_pro/baseline/cohort.py +211 -0
  41. memstrata_conversational-0.6.1/memory_layer_pro/closed_form_api.py +26 -0
  42. memstrata_conversational-0.6.1/memory_layer_pro/coder_product.py +96 -0
  43. memstrata_conversational-0.6.1/memory_layer_pro/dashboard_accuracy_tab.py +291 -0
  44. memstrata_conversational-0.6.1/memory_layer_pro/doctor.py +440 -0
  45. memstrata_conversational-0.6.1/memory_layer_pro/editor_config.py +179 -0
  46. memstrata_conversational-0.6.1/memory_layer_pro/env_setup.py +401 -0
  47. memstrata_conversational-0.6.1/memory_layer_pro/feature_gate.py +78 -0
  48. memstrata_conversational-0.6.1/memory_layer_pro/governance_outbox.py +653 -0
  49. memstrata_conversational-0.6.1/memory_layer_pro/hardware.py +328 -0
  50. memstrata_conversational-0.6.1/memory_layer_pro/identity.py +128 -0
  51. memstrata_conversational-0.6.1/memory_layer_pro/ingest/__init__.py +51 -0
  52. memstrata_conversational-0.6.1/memory_layer_pro/ingest/schema_v0.py +107 -0
  53. memstrata_conversational-0.6.1/memory_layer_pro/ingest/transport.py +561 -0
  54. memstrata_conversational-0.6.1/memory_layer_pro/integrations/__init__.py +3 -0
  55. memstrata_conversational-0.6.1/memory_layer_pro/integrations/langchain.py +173 -0
  56. memstrata_conversational-0.6.1/memory_layer_pro/kernel.py +663 -0
  57. memstrata_conversational-0.6.1/memory_layer_pro/licensing/__init__.py +37 -0
  58. memstrata_conversational-0.6.1/memory_layer_pro/licensing/exceptions.py +24 -0
  59. memstrata_conversational-0.6.1/memory_layer_pro/licensing/hwid.py +275 -0
  60. memstrata_conversational-0.6.1/memory_layer_pro/licensing/keychain.py +108 -0
  61. memstrata_conversational-0.6.1/memory_layer_pro/licensing/verifier.py +323 -0
  62. memstrata_conversational-0.6.1/memory_layer_pro/mcp_retrieval.py +163 -0
  63. memstrata_conversational-0.6.1/memory_layer_pro/memory/__init__.py +16 -0
  64. memstrata_conversational-0.6.1/memory_layer_pro/memory/absence_certificate.py +394 -0
  65. memstrata_conversational-0.6.1/memory_layer_pro/memory/absence_gold.py +339 -0
  66. memstrata_conversational-0.6.1/memory_layer_pro/memory/anchor_algebra_veto.py +618 -0
  67. memstrata_conversational-0.6.1/memory_layer_pro/memory/anchor_coverage.py +329 -0
  68. memstrata_conversational-0.6.1/memory_layer_pro/memory/anchor_index.py +317 -0
  69. memstrata_conversational-0.6.1/memory_layer_pro/memory/anchor_verify.py +815 -0
  70. memstrata_conversational-0.6.1/memory_layer_pro/memory/answer_citation.py +44 -0
  71. memstrata_conversational-0.6.1/memory_layer_pro/memory/answer_claim_repair.py +87 -0
  72. memstrata_conversational-0.6.1/memory_layer_pro/memory/answer_program.py +128 -0
  73. memstrata_conversational-0.6.1/memory_layer_pro/memory/answer_shape_router.py +95 -0
  74. memstrata_conversational-0.6.1/memory_layer_pro/memory/answer_tier.py +265 -0
  75. memstrata_conversational-0.6.1/memory_layer_pro/memory/answer_value_address.py +93 -0
  76. memstrata_conversational-0.6.1/memory_layer_pro/memory/binding_accept.py +107 -0
  77. memstrata_conversational-0.6.1/memory_layer_pro/memory/binding_certificate.py +155 -0
  78. memstrata_conversational-0.6.1/memory_layer_pro/memory/budget_router.py +161 -0
  79. memstrata_conversational-0.6.1/memory_layer_pro/memory/calibrate.py +432 -0
  80. memstrata_conversational-0.6.1/memory_layer_pro/memory/calibrated_score.py +51 -0
  81. memstrata_conversational-0.6.1/memory_layer_pro/memory/candidate_lattice.py +834 -0
  82. memstrata_conversational-0.6.1/memory_layer_pro/memory/canonicalize.py +114 -0
  83. memstrata_conversational-0.6.1/memory_layer_pro/memory/card_loci.py +1830 -0
  84. memstrata_conversational-0.6.1/memory_layer_pro/memory/certified_emission.py +49 -0
  85. memstrata_conversational-0.6.1/memory_layer_pro/memory/change_policy.py +56 -0
  86. memstrata_conversational-0.6.1/memory_layer_pro/memory/claim_disposition.py +43 -0
  87. memstrata_conversational-0.6.1/memory_layer_pro/memory/closed_form_resolver.py +168 -0
  88. memstrata_conversational-0.6.1/memory_layer_pro/memory/code_chunk_index.py +245 -0
  89. memstrata_conversational-0.6.1/memory_layer_pro/memory/code_index_cache.py +97 -0
  90. memstrata_conversational-0.6.1/memory_layer_pro/memory/code_symbol_oracle.py +90 -0
  91. memstrata_conversational-0.6.1/memory_layer_pro/memory/compiler_ir.py +102 -0
  92. memstrata_conversational-0.6.1/memory_layer_pro/memory/confidence_o10.py +212 -0
  93. memstrata_conversational-0.6.1/memory_layer_pro/memory/confidence_surface.py +270 -0
  94. memstrata_conversational-0.6.1/memory_layer_pro/memory/config.py +851 -0
  95. memstrata_conversational-0.6.1/memory_layer_pro/memory/consolidate.py +501 -0
  96. memstrata_conversational-0.6.1/memory_layer_pro/memory/context_mass.py +158 -0
  97. memstrata_conversational-0.6.1/memory_layer_pro/memory/context_provider.py +582 -0
  98. memstrata_conversational-0.6.1/memory_layer_pro/memory/contradiction.py +251 -0
  99. memstrata_conversational-0.6.1/memory_layer_pro/memory/contradiction_census.py +342 -0
  100. memstrata_conversational-0.6.1/memory_layer_pro/memory/dashboard_money_tab.py +508 -0
  101. memstrata_conversational-0.6.1/memory_layer_pro/memory/date_instruction.py +72 -0
  102. memstrata_conversational-0.6.1/memory_layer_pro/memory/dep_closure.py +29 -0
  103. memstrata_conversational-0.6.1/memory_layer_pro/memory/derivability.py +225 -0
  104. memstrata_conversational-0.6.1/memory_layer_pro/memory/diff_classifier.py +86 -0
  105. memstrata_conversational-0.6.1/memory_layer_pro/memory/discourse_order.py +201 -0
  106. memstrata_conversational-0.6.1/memory_layer_pro/memory/embed_backfill.py +115 -0
  107. memstrata_conversational-0.6.1/memory_layer_pro/memory/embeddings.py +55 -0
  108. memstrata_conversational-0.6.1/memory_layer_pro/memory/entity_alias_o8.py +720 -0
  109. memstrata_conversational-0.6.1/memory_layer_pro/memory/entity_registry.py +189 -0
  110. memstrata_conversational-0.6.1/memory_layer_pro/memory/entity_resolution.py +327 -0
  111. memstrata_conversational-0.6.1/memory_layer_pro/memory/episodic_store.py +62 -0
  112. memstrata_conversational-0.6.1/memory_layer_pro/memory/escalation_router.py +463 -0
  113. memstrata_conversational-0.6.1/memory_layer_pro/memory/event_date_bind.py +206 -0
  114. memstrata_conversational-0.6.1/memory_layer_pro/memory/event_graph.py +677 -0
  115. memstrata_conversational-0.6.1/memory_layer_pro/memory/evidence_pack.py +173 -0
  116. memstrata_conversational-0.6.1/memory_layer_pro/memory/explanations.py +33 -0
  117. memstrata_conversational-0.6.1/memory_layer_pro/memory/external_membership.py +64 -0
  118. memstrata_conversational-0.6.1/memory_layer_pro/memory/extraction_repair.py +575 -0
  119. memstrata_conversational-0.6.1/memory_layer_pro/memory/extractor.py +242 -0
  120. memstrata_conversational-0.6.1/memory_layer_pro/memory/fact_ingest.py +593 -0
  121. memstrata_conversational-0.6.1/memory_layer_pro/memory/fact_ledger.py +2137 -0
  122. memstrata_conversational-0.6.1/memory_layer_pro/memory/fact_oracle.py +913 -0
  123. memstrata_conversational-0.6.1/memory_layer_pro/memory/fact_span_extract.py +392 -0
  124. memstrata_conversational-0.6.1/memory_layer_pro/memory/flash_extract.py +185 -0
  125. memstrata_conversational-0.6.1/memory_layer_pro/memory/free_form_disagreement.py +194 -0
  126. memstrata_conversational-0.6.1/memory_layer_pro/memory/gate.py +594 -0
  127. memstrata_conversational-0.6.1/memory_layer_pro/memory/graph_traversal.py +60 -0
  128. memstrata_conversational-0.6.1/memory_layer_pro/memory/grounding.py +162 -0
  129. memstrata_conversational-0.6.1/memory_layer_pro/memory/information_density.py +60 -0
  130. memstrata_conversational-0.6.1/memory_layer_pro/memory/ledger.py +672 -0
  131. memstrata_conversational-0.6.1/memory_layer_pro/memory/line_ids.py +55 -0
  132. memstrata_conversational-0.6.1/memory_layer_pro/memory/llm.py +230 -0
  133. memstrata_conversational-0.6.1/memory_layer_pro/memory/logic_hash.py +71 -0
  134. memstrata_conversational-0.6.1/memory_layer_pro/memory/logic_verify.py +134 -0
  135. memstrata_conversational-0.6.1/memory_layer_pro/memory/mention_collapse.py +427 -0
  136. memstrata_conversational-0.6.1/memory_layer_pro/memory/micro_extract.py +758 -0
  137. memstrata_conversational-0.6.1/memory_layer_pro/memory/multihop_qa.py +80 -0
  138. memstrata_conversational-0.6.1/memory_layer_pro/memory/nli_semantic_provider.py +347 -0
  139. memstrata_conversational-0.6.1/memory_layer_pro/memory/numeric_normalization.py +178 -0
  140. memstrata_conversational-0.6.1/memory_layer_pro/memory/ontology_counting.py +152 -0
  141. memstrata_conversational-0.6.1/memory_layer_pro/memory/pack.py +220 -0
  142. memstrata_conversational-0.6.1/memory_layer_pro/memory/plan_compiler.py +405 -0
  143. memstrata_conversational-0.6.1/memory_layer_pro/memory/plan_lint.py +99 -0
  144. memstrata_conversational-0.6.1/memory_layer_pro/memory/proof_report.py +140 -0
  145. memstrata_conversational-0.6.1/memory_layer_pro/memory/propose.py +502 -0
  146. memstrata_conversational-0.6.1/memory_layer_pro/memory/quantity_bind.py +160 -0
  147. memstrata_conversational-0.6.1/memory_layer_pro/memory/quantity_gate.py +131 -0
  148. memstrata_conversational-0.6.1/memory_layer_pro/memory/query_class.py +85 -0
  149. memstrata_conversational-0.6.1/memory_layer_pro/memory/query_router.py +237 -0
  150. memstrata_conversational-0.6.1/memory_layer_pro/memory/question_aware_nli_provider.py +266 -0
  151. memstrata_conversational-0.6.1/memory_layer_pro/memory/recall_gate.py +26 -0
  152. memstrata_conversational-0.6.1/memory_layer_pro/memory/recall_router.py +74 -0
  153. memstrata_conversational-0.6.1/memory_layer_pro/memory/recommended_prompt.py +188 -0
  154. memstrata_conversational-0.6.1/memory_layer_pro/memory/refusal_ledger.py +449 -0
  155. memstrata_conversational-0.6.1/memory_layer_pro/memory/relative_dates.py +241 -0
  156. memstrata_conversational-0.6.1/memory_layer_pro/memory/resurrect.py +89 -0
  157. memstrata_conversational-0.6.1/memory_layer_pro/memory/routable.py +194 -0
  158. memstrata_conversational-0.6.1/memory_layer_pro/memory/rung2_category_router.py +59 -0
  159. memstrata_conversational-0.6.1/memory_layer_pro/memory/rung2_policy.py +192 -0
  160. memstrata_conversational-0.6.1/memory_layer_pro/memory/rung2_router.py +223 -0
  161. memstrata_conversational-0.6.1/memory_layer_pro/memory/runtime_test_index.py +717 -0
  162. memstrata_conversational-0.6.1/memory_layer_pro/memory/schema.py +333 -0
  163. memstrata_conversational-0.6.1/memory_layer_pro/memory/scope_envelope.py +158 -0
  164. memstrata_conversational-0.6.1/memory_layer_pro/memory/scoring.py +111 -0
  165. memstrata_conversational-0.6.1/memory_layer_pro/memory/session_intel.py +108 -0
  166. memstrata_conversational-0.6.1/memory_layer_pro/memory/session_recall.py +594 -0
  167. memstrata_conversational-0.6.1/memory_layer_pro/memory/set_coverage.py +139 -0
  168. memstrata_conversational-0.6.1/memory_layer_pro/memory/signals.py +72 -0
  169. memstrata_conversational-0.6.1/memory_layer_pro/memory/sketch.py +171 -0
  170. memstrata_conversational-0.6.1/memory_layer_pro/memory/sketch_calibrate.py +165 -0
  171. memstrata_conversational-0.6.1/memory_layer_pro/memory/speculative_plan.py +354 -0
  172. memstrata_conversational-0.6.1/memory_layer_pro/memory/sqlite_util.py +21 -0
  173. memstrata_conversational-0.6.1/memory_layer_pro/memory/state_delta.py +87 -0
  174. memstrata_conversational-0.6.1/memory_layer_pro/memory/state_index.py +1454 -0
  175. memstrata_conversational-0.6.1/memory_layer_pro/memory/state_rung3.py +1324 -0
  176. memstrata_conversational-0.6.1/memory_layer_pro/memory/symbols.py +70 -0
  177. memstrata_conversational-0.6.1/memory_layer_pro/memory/temporal_date_math.py +404 -0
  178. memstrata_conversational-0.6.1/memory_layer_pro/memory/temporal_order_o9.py +372 -0
  179. memstrata_conversational-0.6.1/memory_layer_pro/memory/temporal_reducer.py +221 -0
  180. memstrata_conversational-0.6.1/memory_layer_pro/memory/tenant_scope.py +52 -0
  181. memstrata_conversational-0.6.1/memory_layer_pro/memory/test_closure.py +204 -0
  182. memstrata_conversational-0.6.1/memory_layer_pro/memory/toon.py +43 -0
  183. memstrata_conversational-0.6.1/memory_layer_pro/memory/treesitter_chunk.py +141 -0
  184. memstrata_conversational-0.6.1/memory_layer_pro/memory/trust_tier.py +181 -0
  185. memstrata_conversational-0.6.1/memory_layer_pro/memory/trust_wiring.py +120 -0
  186. memstrata_conversational-0.6.1/memory_layer_pro/memory/turboquant.py +146 -0
  187. memstrata_conversational-0.6.1/memory_layer_pro/memory/unit_shape.py +210 -0
  188. memstrata_conversational-0.6.1/memory_layer_pro/memory/verify.py +252 -0
  189. memstrata_conversational-0.6.1/memory_layer_pro/memory/watcher.py +258 -0
  190. memstrata_conversational-0.6.1/memory_layer_pro/memory/wisdom_layer.py +91 -0
  191. memstrata_conversational-0.6.1/memory_layer_pro/memory/witness_edit_integration.py +246 -0
  192. memstrata_conversational-0.6.1/memory_layer_pro/memory/witness_sandbox.py +240 -0
  193. memstrata_conversational-0.6.1/memory_layer_pro/memory/witness_test_writer.py +139 -0
  194. memstrata_conversational-0.6.1/memory_layer_pro/memory/witnessability.py +144 -0
  195. memstrata_conversational-0.6.1/memory_layer_pro/memory/zone_certificate.py +211 -0
  196. memstrata_conversational-0.6.1/memory_layer_pro/pro_schema.py +61 -0
  197. memstrata_conversational-0.6.1/memory_layer_pro/product_profiles.py +96 -0
  198. memstrata_conversational-0.6.1/memory_layer_pro/tray/__init__.py +28 -0
  199. memstrata_conversational-0.6.1/memory_layer_pro/tray/__main__.py +13 -0
  200. memstrata_conversational-0.6.1/memory_layer_pro/tray/app.py +641 -0
  201. memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_gray.png +0 -0
  202. memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_gray_128.png +0 -0
  203. memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_gray_256.png +0 -0
  204. memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_gray_64.png +0 -0
  205. memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_green.png +0 -0
  206. memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_green_128.png +0 -0
  207. memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_green_256.png +0 -0
  208. memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_green_64.png +0 -0
  209. memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_red.png +0 -0
  210. memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_red_128.png +0 -0
  211. memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_red_256.png +0 -0
  212. memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_red_64.png +0 -0
  213. memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_template.png +0 -0
  214. memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_template_22.png +0 -0
  215. memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_template_44.png +0 -0
  216. memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_template_88.png +0 -0
  217. memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_yellow.png +0 -0
  218. memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_yellow_128.png +0 -0
  219. memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_yellow_256.png +0 -0
  220. memstrata_conversational-0.6.1/memory_layer_pro/tray/assets/icon_yellow_64.png +0 -0
  221. memstrata_conversational-0.6.1/memory_layer_pro/tray/daemon_client.py +207 -0
  222. memstrata_conversational-0.6.1/memory_layer_pro/tray/macos_launchagent.py +191 -0
  223. memstrata_conversational-0.6.1/memory_layer_pro/tray/state.py +205 -0
  224. memstrata_conversational-0.6.1/memory_layer_pro/wizard.py +1225 -0
  225. memstrata_conversational-0.6.1/memstrata_conversational.egg-info/PKG-INFO +80 -0
  226. memstrata_conversational-0.6.1/memstrata_conversational.egg-info/SOURCES.txt +234 -0
  227. memstrata_conversational-0.6.1/memstrata_conversational.egg-info/dependency_links.txt +1 -0
  228. memstrata_conversational-0.6.1/memstrata_conversational.egg-info/entry_points.txt +2 -0
  229. memstrata_conversational-0.6.1/memstrata_conversational.egg-info/requires.txt +39 -0
  230. memstrata_conversational-0.6.1/memstrata_conversational.egg-info/top_level.txt +3 -0
  231. memstrata_conversational-0.6.1/pyproject.toml +118 -0
  232. memstrata_conversational-0.6.1/setup.cfg +4 -0
  233. memstrata_conversational-0.6.1/tests/test_api_overlay.py +320 -0
  234. memstrata_conversational-0.6.1/tests/test_mcp_retrieval.py +144 -0
  235. memstrata_conversational-0.6.1/tests/test_product_profiles.py +90 -0
  236. memstrata_conversational-0.6.1/tests/test_windows_sku_contract.py +106 -0
@@ -0,0 +1,26 @@
1
+ MEMORY LAYER PRO — COMMERCIAL LICENSE
2
+
3
+ Copyright (c) 2026 Neeraj Yadav (yadu9989). All Rights Reserved.
4
+
5
+ This software and associated documentation files (the "Software") are the
6
+ proprietary and confidential property of the copyright holder. Unauthorized
7
+ copying, distribution, modification, or use of this software, in whole or in
8
+ part, is strictly prohibited without express written permission.
9
+
10
+ PERMITTED USE
11
+ A valid Memory Layer Pro license grants the licensee the right to:
12
+ 1. Install and run the Software on machines owned or controlled by the licensee.
13
+ 2. Use the Software for the licensee's own development workflows.
14
+
15
+ RESTRICTIONS
16
+ The licensee may not:
17
+ 1. Sublicense, sell, rent, lease, or transfer rights to the Software.
18
+ 2. Reverse-engineer, decompile, or disassemble the Software.
19
+ 3. Remove or alter any proprietary notices or labels on the Software.
20
+ 4. Use the Software to build a competing product.
21
+
22
+ NO WARRANTY
23
+ The Software is provided "AS IS" without warranty of any kind. The copyright
24
+ holder shall not be liable for any damages arising from use of the Software.
25
+
26
+ For licensing inquiries: yadavneeraj8@gmail.com
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.4
2
+ Name: memstrata-conversational
3
+ Version: 0.6.1
4
+ Summary: MemStrata Pro — token-budgeted context harness, licensing, and packaging atop the memstrata open core
5
+ License: MEMORY LAYER PRO — COMMERCIAL LICENSE
6
+
7
+ Copyright (c) 2026 Neeraj Yadav (yadu9989). All Rights Reserved.
8
+
9
+ This software and associated documentation files (the "Software") are the
10
+ proprietary and confidential property of the copyright holder. Unauthorized
11
+ copying, distribution, modification, or use of this software, in whole or in
12
+ part, is strictly prohibited without express written permission.
13
+
14
+ PERMITTED USE
15
+ A valid Memory Layer Pro license grants the licensee the right to:
16
+ 1. Install and run the Software on machines owned or controlled by the licensee.
17
+ 2. Use the Software for the licensee's own development workflows.
18
+
19
+ RESTRICTIONS
20
+ The licensee may not:
21
+ 1. Sublicense, sell, rent, lease, or transfer rights to the Software.
22
+ 2. Reverse-engineer, decompile, or disassemble the Software.
23
+ 3. Remove or alter any proprietary notices or labels on the Software.
24
+ 4. Use the Software to build a competing product.
25
+
26
+ NO WARRANTY
27
+ The Software is provided "AS IS" without warranty of any kind. The copyright
28
+ holder shall not be liable for any damages arising from use of the Software.
29
+
30
+ For licensing inquiries: yadavneeraj8@gmail.com
31
+
32
+ Keywords: llm,context,proxy,harness,memory,telemetry,mcp,licensing
33
+ Classifier: Development Status :: 4 - Beta
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: License :: Other/Proprietary License
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3.10
38
+ Classifier: Programming Language :: Python :: 3.11
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Topic :: Software Development
41
+ Requires-Python: >=3.10
42
+ Description-Content-Type: text/plain
43
+ License-File: LICENSE-COMMERCIAL
44
+ Requires-Dist: memstrata<0.7.0,>=0.6.1
45
+ Requires-Dist: fastapi>=0.115
46
+ Requires-Dist: uvicorn[standard]>=0.32
47
+ Requires-Dist: httpx>=0.27
48
+ Requires-Dist: pydantic>=2.10
49
+ Requires-Dist: python-dotenv>=1.0
50
+ Requires-Dist: tiktoken>=0.7
51
+ Requires-Dist: pystray>=0.19
52
+ Requires-Dist: Pillow>=10.0
53
+ Requires-Dist: keyring>=25.0
54
+ Requires-Dist: cryptography>=42
55
+ Requires-Dist: tomli>=2.0; python_version < "3.11"
56
+ Requires-Dist: numpy<3,>=1.26
57
+ Requires-Dist: tree-sitter-go<0.24,>=0.23
58
+ Requires-Dist: tree-sitter-rust<0.24,>=0.23
59
+ Requires-Dist: tree-sitter-java<0.24,>=0.23
60
+ Requires-Dist: tree-sitter-c<0.24,>=0.23
61
+ Requires-Dist: tree-sitter-cpp<0.24,>=0.23
62
+ Requires-Dist: tree-sitter-c-sharp<0.24,>=0.23
63
+ Requires-Dist: tree-sitter-ruby<0.24,>=0.23
64
+ Requires-Dist: tree-sitter-php<0.24,>=0.23
65
+ Requires-Dist: tree-sitter-bash<0.24,>=0.23
66
+ Provides-Extra: langchain
67
+ Requires-Dist: langchain-core<2,>=1.4; extra == "langchain"
68
+ Provides-Extra: nli
69
+ Requires-Dist: onnxruntime<2,>=1.18; extra == "nli"
70
+ Requires-Dist: tokenizers<1,>=0.19; extra == "nli"
71
+ Requires-Dist: truststore<1,>=0.10; extra == "nli"
72
+ Provides-Extra: dev
73
+ Requires-Dist: pytest>=8; extra == "dev"
74
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
75
+ Requires-Dist: respx>=0.21; extra == "dev"
76
+ Requires-Dist: ruff>=0.6; extra == "dev"
77
+ Requires-Dist: mypy>=1.11; extra == "dev"
78
+ Dynamic: license-file
79
+
80
+ MemStrata Conversational product package.
@@ -0,0 +1,18 @@
1
+ """
2
+ memory-layer-pro billing subsystem — Phase 23 (Money-Back Guarantee).
3
+
4
+ Public API:
5
+ reconcile.compute_cycle_savings — aggregate measured savings for a billing cycle
6
+ credit.compute_credit — derive credit amount from savings vs subscription
7
+ webhook.handle_invoice_upcoming — Stripe webhook handler
8
+ email_notify.render_reconciliation_email / send_reconciliation_email
9
+ """
10
+ from .credit import Credit, compute_credit
11
+ from .reconcile import CycleSavings, compute_cycle_savings
12
+
13
+ __all__ = [
14
+ "CycleSavings",
15
+ "compute_cycle_savings",
16
+ "Credit",
17
+ "compute_credit",
18
+ ]
@@ -0,0 +1,109 @@
1
+ """
2
+ Phase 23.1 — credit calculation rules.
3
+
4
+ Single source of truth for the money-back guarantee math. The credit cap rule
5
+ (credit ≤ subscription_usd) must be enforced here and nowhere else; callers
6
+ must not re-implement it.
7
+
8
+ Money math uses integer cents (Kimi C3 / K3-H7) so half-cent boundaries do not
9
+ depend on IEEE-754 banker's rounding. Float dollars only appear at the API edge.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass
14
+ from decimal import ROUND_HALF_UP, Decimal
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class Credit:
19
+ credit_usd: float
20
+ shortfall_usd: float
21
+ subscription_usd: float
22
+ savings_usd: float
23
+ rationale: str
24
+
25
+ @property
26
+ def is_due(self) -> bool:
27
+ return self.credit_usd > 0.0
28
+
29
+
30
+ def usd_to_cents(amount_usd: float | Decimal | int | str | None) -> int:
31
+ """Convert a dollar amount to integer cents with half-up rounding.
32
+
33
+ Public so dashboard / reconcile / webhooks share one money substrate (K3-M5).
34
+ ``None`` and non-numeric → 0.
35
+ """
36
+ if amount_usd is None:
37
+ return 0
38
+ try:
39
+ d = Decimal(str(amount_usd))
40
+ except Exception:
41
+ return 0
42
+ if d.is_nan() or d.is_infinite():
43
+ return 0
44
+ return int((d * 100).quantize(Decimal("1"), rounding=ROUND_HALF_UP))
45
+
46
+
47
+ def cents_to_usd(cents: int) -> float:
48
+ """Integer cents → float dollars (display / API edge only)."""
49
+ return float(Decimal(int(cents)) / Decimal(100))
50
+
51
+
52
+ # Back-compat private aliases
53
+ _to_cents = usd_to_cents
54
+ _cents_to_usd = cents_to_usd
55
+
56
+
57
+ def sum_usd_as_cents(amounts: list[float | Decimal | int | str | None]) -> int:
58
+ """Sum dollar amounts in integer cents (no binary float accumulation)."""
59
+ return sum(usd_to_cents(a) for a in amounts)
60
+
61
+
62
+ def compute_credit(savings_usd: float, subscription_usd: float) -> Credit:
63
+ """
64
+ Derive the credit due to a user from measured savings vs subscription cost.
65
+
66
+ Rules (AV5.3 §4.1):
67
+ shortfall = max(0, subscription_usd - savings_usd)
68
+ credit = min(shortfall, subscription_usd) ← cap: never owe beyond subscription
69
+
70
+ The cap prevents a degenerate case where a subscription amount of $0 or a
71
+ negative savings figure could produce a nonsensical result.
72
+
73
+ Raises ValueError for negative inputs — savings and subscription must both
74
+ be non-negative dollar amounts.
75
+ """
76
+ if savings_usd < 0:
77
+ raise ValueError(f"savings_usd must be ≥ 0, got {savings_usd}")
78
+ if subscription_usd < 0:
79
+ raise ValueError(f"subscription_usd must be ≥ 0, got {subscription_usd}")
80
+
81
+ savings_cents = usd_to_cents(savings_usd)
82
+ sub_cents = usd_to_cents(subscription_usd)
83
+ shortfall_cents = max(0, sub_cents - savings_cents)
84
+ credit_cents = min(shortfall_cents, sub_cents)
85
+
86
+ credit = cents_to_usd(credit_cents)
87
+ shortfall = cents_to_usd(shortfall_cents)
88
+ sub_usd = cents_to_usd(sub_cents)
89
+ sav_usd = cents_to_usd(savings_cents)
90
+
91
+ if credit_cents == 0:
92
+ rationale = (
93
+ f"Savings ${sav_usd:.2f} ≥ subscription ${sub_usd:.2f}; "
94
+ f"no credit due."
95
+ )
96
+ else:
97
+ rationale = (
98
+ f"Savings ${sav_usd:.2f} < subscription ${sub_usd:.2f}; "
99
+ f"crediting ${credit:.2f} (shortfall capped at subscription ceiling "
100
+ f"${sub_usd:.2f})."
101
+ )
102
+
103
+ return Credit(
104
+ credit_usd=credit,
105
+ shortfall_usd=shortfall,
106
+ subscription_usd=sub_usd,
107
+ savings_usd=sav_usd,
108
+ rationale=rationale,
109
+ )
@@ -0,0 +1,282 @@
1
+ """
2
+ Phase 23.3 — end-of-cycle reconciliation email.
3
+
4
+ Renders and (optionally) sends the transactional email showing the user's
5
+ cost-vs-savings footprint for the billing cycle.
6
+
7
+ Template content per AV5.3 §4.1. The sender backend is configurable via
8
+ MEMORY_LAYER_EMAIL_BACKEND env var: 'sendgrid' | 'resend' | 'smtp' | 'noop'.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ import os
14
+ import smtplib
15
+ import textwrap
16
+ from dataclasses import dataclass
17
+ from email.mime.multipart import MIMEMultipart
18
+ from email.mime.text import MIMEText
19
+
20
+ from .credit import Credit
21
+ from .reconcile import CycleSavings
22
+
23
+ log = logging.getLogger(__name__)
24
+
25
+
26
+ # ---------------------------------------------------------------------------
27
+ # Rendered email
28
+ # ---------------------------------------------------------------------------
29
+
30
+ @dataclass
31
+ class ReconciliationEmail:
32
+ to_address: str
33
+ subject: str
34
+ body_text: str
35
+ body_html: str
36
+
37
+
38
+ def render_reconciliation_email(
39
+ user_email: str,
40
+ month_label: str,
41
+ savings: CycleSavings,
42
+ credit: Credit,
43
+ ) -> ReconciliationEmail:
44
+ """
45
+ Render the end-of-cycle reconciliation email per AV5.3 §4.1.
46
+
47
+ Parameters
48
+ ----------
49
+ user_email: Recipient address.
50
+ month_label: Human-readable billing period, e.g. "May 2026".
51
+ savings: CycleSavings from reconcile.compute_cycle_savings.
52
+ credit: Credit from credit.compute_credit.
53
+ """
54
+ subject = f"Your Memory Layer reconciliation for {month_label}"
55
+
56
+ if credit.is_due:
57
+ credit_line = f"Credit on next invoice: ${credit.credit_usd:.2f}"
58
+ footer = (
59
+ "Your dashboard remains active. Memory Layer learns and improves with use;\n"
60
+ "next month's savings should grow as your project state densifies."
61
+ )
62
+ else:
63
+ credit_line = (
64
+ f"No credit due — your savings (${savings.total_usd:.2f}) exceeded "
65
+ f"your subscription (${credit.subscription_usd:.2f})."
66
+ )
67
+ footer = (
68
+ "You're net positive this month! Your savings exceeded your subscription "
69
+ "cost.\nKeep using Memory Layer — your project knowledge is compounding."
70
+ )
71
+
72
+ body_text = textwrap.dedent(f"""\
73
+ Subject: {subject}
74
+
75
+ This cycle:
76
+ Input savings: ${savings.input_savings_usd:.2f} (measured)
77
+ Cache savings: ${savings.cache_savings_usd:.2f} (measured)
78
+ Output savings: ${savings.output_savings_usd:.2f} (cohort-measured)
79
+ Total savings: ${savings.total_usd:.2f}
80
+ Subscription: ${credit.subscription_usd:.2f}
81
+
82
+ {credit_line}
83
+
84
+ {footer}
85
+
86
+ — The Memory Layer Team
87
+ """)
88
+
89
+ body_html = _render_html(
90
+ month_label=month_label,
91
+ savings=savings,
92
+ credit=credit,
93
+ credit_line=credit_line,
94
+ footer=footer,
95
+ )
96
+
97
+ return ReconciliationEmail(
98
+ to_address=user_email,
99
+ subject=subject,
100
+ body_text=body_text,
101
+ body_html=body_html,
102
+ )
103
+
104
+
105
+ def _render_html(
106
+ *,
107
+ month_label: str,
108
+ savings: CycleSavings,
109
+ credit: Credit,
110
+ credit_line: str,
111
+ footer: str,
112
+ ) -> str:
113
+ highlight = "#2ecc71" if not credit.is_due else "#e67e22"
114
+ return f"""\
115
+ <!DOCTYPE html>
116
+ <html lang="en">
117
+ <head>
118
+ <meta charset="utf-8">
119
+ <title>Memory Layer Reconciliation</title>
120
+ <style>
121
+ body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
122
+ background: #f9f9f9; margin: 0; padding: 20px; }}
123
+ .card {{ background: #fff; border-radius: 8px; max-width: 540px; margin: 0 auto;
124
+ padding: 32px; box-shadow: 0 2px 8px rgba(0,0,0,.08); }}
125
+ h1 {{ font-size: 1.2rem; color: #111; margin-bottom: 24px; }}
126
+ table {{ width: 100%; border-collapse: collapse; margin-bottom: 16px; }}
127
+ td {{ padding: 6px 0; color: #333; font-size: .95rem; }}
128
+ td.label {{ color: #666; }}
129
+ td.amount {{ text-align: right; font-variant-numeric: tabular-nums; }}
130
+ .divider {{ border-top: 1px solid #eee; margin: 12px 0; }}
131
+ .credit-line {{ font-size: 1rem; font-weight: 600; color: {highlight};
132
+ margin: 16px 0; }}
133
+ .footer {{ margin-top: 24px; font-size: .85rem; color: #888; }}
134
+ </style>
135
+ </head>
136
+ <body>
137
+ <div class="card">
138
+ <h1>Memory Layer — {month_label} reconciliation</h1>
139
+ <table>
140
+ <tr><td class="label">Input savings</td>
141
+ <td class="amount">${savings.input_savings_usd:.2f} <small>measured</small></td></tr>
142
+ <tr><td class="label">Cache savings</td>
143
+ <td class="amount">${savings.cache_savings_usd:.2f} <small>measured</small></td></tr>
144
+ <tr><td class="label">Output savings</td>
145
+ <td class="amount">${savings.output_savings_usd:.2f} <small>cohort-measured</small></td></tr>
146
+ </table>
147
+ <div class="divider"></div>
148
+ <table>
149
+ <tr><td class="label">Total savings</td>
150
+ <td class="amount"><strong>${savings.total_usd:.2f}</strong></td></tr>
151
+ <tr><td class="label">Subscription</td>
152
+ <td class="amount">${credit.subscription_usd:.2f}</td></tr>
153
+ </table>
154
+ <div class="credit-line">{credit_line}</div>
155
+ <div class="footer">{footer.replace(chr(10), '<br>')}</div>
156
+ <div class="footer" style="margin-top:12px">— The Memory Layer Team</div>
157
+ </div>
158
+ </body>
159
+ </html>"""
160
+
161
+
162
+ # ---------------------------------------------------------------------------
163
+ # Send backends
164
+ # ---------------------------------------------------------------------------
165
+
166
+ def send_reconciliation_email(
167
+ email: ReconciliationEmail,
168
+ *,
169
+ backend: str | None = None,
170
+ sendgrid_api_key: str | None = None,
171
+ resend_api_key: str | None = None,
172
+ smtp_host: str = "localhost",
173
+ smtp_port: int = 587,
174
+ smtp_username: str | None = None,
175
+ smtp_password: str | None = None,
176
+ from_address: str = "noreply@memorylayer.dev",
177
+ ) -> bool:
178
+ """
179
+ Send `email` via the configured backend. Returns True on success.
180
+
181
+ Backend priority:
182
+ 1. `backend` argument if supplied.
183
+ 2. MEMORY_LAYER_EMAIL_BACKEND env var.
184
+ 3. Falls back to 'noop' (logs only, no send).
185
+ """
186
+ resolved = backend or os.environ.get("MEMORY_LAYER_EMAIL_BACKEND", "noop")
187
+
188
+ if resolved == "sendgrid":
189
+ return _send_sendgrid(
190
+ email, api_key=sendgrid_api_key or os.environ["SENDGRID_API_KEY"],
191
+ from_address=from_address,
192
+ )
193
+ if resolved == "resend":
194
+ return _send_resend(
195
+ email, api_key=resend_api_key or os.environ["RESEND_API_KEY"],
196
+ from_address=from_address,
197
+ )
198
+ if resolved == "smtp":
199
+ return _send_smtp(
200
+ email, host=smtp_host, port=smtp_port,
201
+ username=smtp_username, password=smtp_password,
202
+ from_address=from_address,
203
+ )
204
+
205
+ # noop — log and return True so callers don't treat this as an error
206
+ log.info(
207
+ "email_notify [noop] would send '%s' to %s",
208
+ email.subject, email.to_address,
209
+ )
210
+ return True
211
+
212
+
213
+ def _send_sendgrid(email: ReconciliationEmail, *, api_key: str, from_address: str) -> bool:
214
+ try:
215
+ import sendgrid
216
+ from sendgrid.helpers.mail import Mail, To, From, Subject, HtmlContent, Content
217
+
218
+ sg = sendgrid.SendGridAPIClient(api_key=api_key)
219
+ message = Mail(
220
+ from_email=from_address,
221
+ to_emails=email.to_address,
222
+ subject=email.subject,
223
+ html_content=email.body_html,
224
+ )
225
+ message.add_content(Content("text/plain", email.body_text))
226
+ response = sg.send(message)
227
+ success = 200 <= response.status_code < 300
228
+ if not success:
229
+ log.error("SendGrid returned %s", response.status_code)
230
+ return success
231
+ except Exception as exc:
232
+ log.error("SendGrid send failed: %s", exc)
233
+ return False
234
+
235
+
236
+ def _send_resend(email: ReconciliationEmail, *, api_key: str, from_address: str) -> bool:
237
+ try:
238
+ import resend
239
+
240
+ resend.api_key = api_key
241
+ params = {
242
+ "from": from_address,
243
+ "to": [email.to_address],
244
+ "subject": email.subject,
245
+ "html": email.body_html,
246
+ "text": email.body_text,
247
+ }
248
+ resend.Emails.send(params)
249
+ return True
250
+ except Exception as exc:
251
+ log.error("Resend send failed: %s", exc)
252
+ return False
253
+
254
+
255
+ def _send_smtp(
256
+ email: ReconciliationEmail,
257
+ *,
258
+ host: str,
259
+ port: int,
260
+ username: str | None,
261
+ password: str | None,
262
+ from_address: str,
263
+ ) -> bool:
264
+ try:
265
+ msg = MIMEMultipart("alternative")
266
+ msg["Subject"] = email.subject
267
+ msg["From"] = from_address
268
+ msg["To"] = email.to_address
269
+ msg.attach(MIMEText(email.body_text, "plain"))
270
+ msg.attach(MIMEText(email.body_html, "html"))
271
+
272
+ with smtplib.SMTP(host, port) as server:
273
+ server.ehlo()
274
+ if port != 25:
275
+ server.starttls()
276
+ if username and password:
277
+ server.login(username, password)
278
+ server.sendmail(from_address, [email.to_address], msg.as_string())
279
+ return True
280
+ except Exception as exc:
281
+ log.error("SMTP send failed: %s", exc)
282
+ return False
@@ -0,0 +1,120 @@
1
+ """
2
+ Phase 23.1 — billing reconciliation.
3
+
4
+ Aggregates measured savings from telemetry_session_timeline for a user's billing
5
+ cycle. Only the three honest measurement bases are included; output_estimated is
6
+ excluded from all credit-eligible totals (Hard Rule 60).
7
+
8
+ Money accumulation is integer cents end-to-end (Kimi K3-M5 / H7): SQLite float
9
+ SUM is never trusted for credit math.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import sqlite3
14
+ from dataclasses import dataclass
15
+ from datetime import datetime
16
+
17
+ from billing.credit import cents_to_usd, sum_usd_as_cents, usd_to_cents
18
+
19
+
20
+ _CREDIT_ELIGIBLE_BASES = ("input_measured", "cache_measured", "output_cohort_measured")
21
+
22
+
23
+ @dataclass
24
+ class CycleSavings:
25
+ user_id: str
26
+ cycle_start: datetime
27
+ cycle_end: datetime
28
+ input_savings_usd: float
29
+ cache_savings_usd: float
30
+ output_savings_usd: float
31
+ # Integer cents retained for exact credit handoff (optional consumers).
32
+ input_savings_cents: int = 0
33
+ cache_savings_cents: int = 0
34
+ output_savings_cents: int = 0
35
+
36
+ @property
37
+ def total_cents(self) -> int:
38
+ return (
39
+ self.input_savings_cents
40
+ + self.cache_savings_cents
41
+ + self.output_savings_cents
42
+ )
43
+
44
+ @property
45
+ def total_usd(self) -> float:
46
+ return cents_to_usd(self.total_cents)
47
+
48
+ def to_dict(self) -> dict:
49
+ return {
50
+ "user_id": self.user_id,
51
+ "cycle_start": self.cycle_start.isoformat(),
52
+ "cycle_end": self.cycle_end.isoformat(),
53
+ "input_savings_usd": round(self.input_savings_usd, 2),
54
+ "cache_savings_usd": round(self.cache_savings_usd, 2),
55
+ "output_savings_usd": round(self.output_savings_usd, 2),
56
+ "total_usd": round(self.total_usd, 2),
57
+ "input_savings_cents": self.input_savings_cents,
58
+ "cache_savings_cents": self.cache_savings_cents,
59
+ "output_savings_cents": self.output_savings_cents,
60
+ "total_cents": self.total_cents,
61
+ }
62
+
63
+
64
+ def compute_cycle_savings(
65
+ user_id: str,
66
+ cycle_start: datetime,
67
+ cycle_end: datetime,
68
+ *,
69
+ conn: sqlite3.Connection,
70
+ ) -> CycleSavings:
71
+ """
72
+ Sum credit-eligible savings for `user_id` between `cycle_start` and `cycle_end`.
73
+
74
+ Filters to measurement_basis IN ('input_measured', 'cache_measured',
75
+ 'output_cohort_measured'). Rows tagged 'output_estimated' are intentionally
76
+ excluded — they are not auditable and never credited (Hard Rule 60).
77
+
78
+ Each row is converted to integer cents with half-up rounding, then summed —
79
+ never ``SUM(float)`` in SQLite / Python float accumulate.
80
+ """
81
+ placeholders = ",".join("?" for _ in _CREDIT_ELIGIBLE_BASES)
82
+ rows = conn.execute(
83
+ f"""
84
+ SELECT
85
+ saved_input_cost_usd,
86
+ saved_cache_cost_usd,
87
+ saved_output_cost_usd
88
+ FROM telemetry_session_timeline
89
+ WHERE user_id = ?
90
+ AND recorded_at BETWEEN ? AND ?
91
+ AND measurement_basis IN ({placeholders})
92
+ """,
93
+ (
94
+ user_id,
95
+ cycle_start.isoformat(),
96
+ cycle_end.isoformat(),
97
+ *_CREDIT_ELIGIBLE_BASES,
98
+ ),
99
+ ).fetchall()
100
+
101
+ in_c = sum_usd_as_cents([r[0] for r in rows])
102
+ ca_c = sum_usd_as_cents([r[1] for r in rows])
103
+ ou_c = sum_usd_as_cents([r[2] for r in rows])
104
+
105
+ return CycleSavings(
106
+ user_id=user_id,
107
+ cycle_start=cycle_start,
108
+ cycle_end=cycle_end,
109
+ input_savings_usd=cents_to_usd(in_c),
110
+ cache_savings_usd=cents_to_usd(ca_c),
111
+ output_savings_usd=cents_to_usd(ou_c),
112
+ input_savings_cents=in_c,
113
+ cache_savings_cents=ca_c,
114
+ output_savings_cents=ou_c,
115
+ )
116
+
117
+
118
+ def savings_total_usd(*parts: float) -> float:
119
+ """Sum display-side USD parts via cents (dashboard / API helpers)."""
120
+ return cents_to_usd(sum(usd_to_cents(p) for p in parts))