@aglyn/plugins-ai 1.0.0-beta.270 → 1.0.0-beta.272

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 (281) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.d.ts +4 -3
  3. package/src/lib/activity/ai-activity-actions.js +4 -0
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/assets/site-styles/beauty.webp +0 -0
  6. package/src/lib/assets/site-styles/blog.webp +0 -0
  7. package/src/lib/assets/site-styles/business.webp +0 -0
  8. package/src/lib/assets/site-styles/coming-soon.webp +0 -0
  9. package/src/lib/assets/site-styles/education.webp +0 -0
  10. package/src/lib/assets/site-styles/events.webp +0 -0
  11. package/src/lib/assets/site-styles/fitness.webp +0 -0
  12. package/src/lib/assets/site-styles/landing.webp +0 -0
  13. package/src/lib/assets/site-styles/music.webp +0 -0
  14. package/src/lib/assets/site-styles/nonprofit.webp +0 -0
  15. package/src/lib/assets/site-styles/personal.webp +0 -0
  16. package/src/lib/assets/site-styles/photography.webp +0 -0
  17. package/src/lib/assets/site-styles/portfolio.webp +0 -0
  18. package/src/lib/assets/site-styles/professional.webp +0 -0
  19. package/src/lib/assets/site-styles/realestate.webp +0 -0
  20. package/src/lib/assets/site-styles/restaurant.webp +0 -0
  21. package/src/lib/assets/site-styles/store.webp +0 -0
  22. package/src/lib/assets/site-styles/studio.webp +0 -0
  23. package/src/lib/assets/site-styles/trades.webp +0 -0
  24. package/src/lib/assets/site-styles/wellness.webp +0 -0
  25. package/src/lib/assets/site-styles/yoga.webp +0 -0
  26. package/src/lib/components/ai-credits-card.component.d.ts +4 -3
  27. package/src/lib/components/ai-credits-card.component.js +15 -15
  28. package/src/lib/components/ai-credits-card.component.js.map +1 -1
  29. package/src/lib/components/ai-job-input-form.component.d.ts +36 -0
  30. package/src/lib/components/ai-job-input-form.component.js +100 -0
  31. package/src/lib/components/ai-job-input-form.component.js.map +1 -0
  32. package/src/lib/components/ai-job-requests.d.ts +4 -0
  33. package/src/lib/components/ai-job-requests.js +4 -0
  34. package/src/lib/components/ai-job-requests.js.map +1 -1
  35. package/src/lib/components/ai-site-build-page.component.js +18 -0
  36. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  37. package/src/lib/components/ai-site-import-job.component.d.ts +41 -0
  38. package/src/lib/components/ai-site-import-job.component.js +707 -0
  39. package/src/lib/components/ai-site-import-job.component.js.map +1 -0
  40. package/src/lib/components/ai-site-import-start.component.d.ts +64 -0
  41. package/src/lib/components/ai-site-import-start.component.js +335 -0
  42. package/src/lib/components/ai-site-import-start.component.js.map +1 -0
  43. package/src/lib/components/ai-site-ownership-proof.component.d.ts +42 -0
  44. package/src/lib/components/ai-site-ownership-proof.component.js +353 -0
  45. package/src/lib/components/ai-site-ownership-proof.component.js.map +1 -0
  46. package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
  47. package/src/lib/components/ai-site-start-card.component.js +261 -119
  48. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  49. package/src/lib/components/ai-site-start-steps.component.d.ts +43 -2
  50. package/src/lib/components/ai-site-start-steps.component.js +651 -15
  51. package/src/lib/components/ai-site-start-steps.component.js.map +1 -1
  52. package/src/lib/components/ai-site-start-tracking.d.ts +10 -0
  53. package/src/lib/components/ai-site-start-tracking.js +14 -0
  54. package/src/lib/components/ai-site-start-tracking.js.map +1 -1
  55. package/src/lib/components/ai-site-style-previews.js +26 -22
  56. package/src/lib/components/ai-site-style-previews.js.map +1 -1
  57. package/src/lib/components/ai-upsell-dialog.component.d.ts +7 -5
  58. package/src/lib/components/ai-upsell-dialog.component.js +8 -6
  59. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  60. package/src/lib/components/assist-build-card.component.js +11 -1
  61. package/src/lib/components/assist-build-card.component.js.map +1 -1
  62. package/src/lib/components/assist-jobs-drawer.component.js +9 -1
  63. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  64. package/src/lib/components/assist-panel.component.js +8 -4
  65. package/src/lib/components/assist-panel.component.js.map +1 -1
  66. package/src/lib/components/staff-org-ai-card.component.js +4 -7
  67. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  68. package/src/lib/declarations.d.ts +2 -2
  69. package/src/lib/declarations.js +9 -5
  70. package/src/lib/declarations.js.map +1 -1
  71. package/src/lib/jobs/ai-build-capabilities.js +3 -1
  72. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  73. package/src/lib/jobs/ai-business-profile-prefill.d.ts +1 -1
  74. package/src/lib/jobs/ai-business-profile-prefill.js +14 -3
  75. package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -1
  76. package/src/lib/jobs/ai-job-build-step.js +5 -3
  77. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  78. package/src/lib/jobs/ai-job-drafts.d.ts +14 -0
  79. package/src/lib/jobs/ai-job-drafts.js +24 -0
  80. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  81. package/src/lib/jobs/ai-job-form-step.d.ts +15 -1
  82. package/src/lib/jobs/ai-job-form-step.js +75 -8
  83. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  84. package/src/lib/jobs/ai-job-import-step.d.ts +136 -0
  85. package/src/lib/jobs/ai-job-import-step.js +809 -0
  86. package/src/lib/jobs/ai-job-import-step.js.map +1 -0
  87. package/src/lib/jobs/ai-job-page-language.d.ts +52 -1
  88. package/src/lib/jobs/ai-job-page-language.js +99 -5
  89. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  90. package/src/lib/jobs/ai-job-page-step.d.ts +19 -2
  91. package/src/lib/jobs/ai-job-page-step.js +42 -18
  92. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  93. package/src/lib/jobs/ai-job-plan-step.d.ts +25 -2
  94. package/src/lib/jobs/ai-job-plan-step.js +103 -29
  95. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  96. package/src/lib/jobs/ai-job-settings-step.js +4 -3
  97. package/src/lib/jobs/ai-job-settings-step.js.map +1 -1
  98. package/src/lib/jobs/ai-job-site-datasets.d.ts +27 -0
  99. package/src/lib/jobs/ai-job-site-datasets.js +140 -14
  100. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  101. package/src/lib/jobs/ai-job-site-look.js +18 -5
  102. package/src/lib/jobs/ai-job-site-look.js.map +1 -1
  103. package/src/lib/jobs/ai-job-site-step.d.ts +16 -1
  104. package/src/lib/jobs/ai-job-site-step.js +95 -22
  105. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  106. package/src/lib/jobs/ai-job-text-step.d.ts +15 -1
  107. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  108. package/src/lib/jobs/ai-job-tracking-step.d.ts +14 -3
  109. package/src/lib/jobs/ai-job-tracking-step.js +28 -5
  110. package/src/lib/jobs/ai-job-tracking-step.js.map +1 -1
  111. package/src/lib/jobs/ai-jobs.d.ts +13 -1
  112. package/src/lib/jobs/ai-jobs.js +77 -10
  113. package/src/lib/jobs/ai-jobs.js.map +1 -1
  114. package/src/lib/jobs/ai-layout-owner-photos.d.ts +16 -1
  115. package/src/lib/jobs/ai-layout-owner-photos.js +47 -0
  116. package/src/lib/jobs/ai-layout-owner-photos.js.map +1 -1
  117. package/src/lib/jobs/ai-layout-stock-photos.d.ts +27 -2
  118. package/src/lib/jobs/ai-layout-stock-photos.js +190 -19
  119. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  120. package/src/lib/jobs/ai-site-inspiration-signals.d.ts +98 -0
  121. package/src/lib/jobs/ai-site-inspiration-signals.js +585 -0
  122. package/src/lib/jobs/ai-site-inspiration-signals.js.map +1 -0
  123. package/src/lib/jobs/ai-site-inspiration.d.ts +157 -0
  124. package/src/lib/jobs/ai-site-inspiration.js +346 -0
  125. package/src/lib/jobs/ai-site-inspiration.js.map +1 -0
  126. package/src/lib/jobs/ai-site-publish.d.ts +26 -1
  127. package/src/lib/jobs/ai-site-publish.js +162 -13
  128. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  129. package/src/lib/jobs/ai-theme-brand-inputs.d.ts +77 -3
  130. package/src/lib/jobs/ai-theme-brand-inputs.js +135 -31
  131. package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
  132. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.d.ts +97 -0
  133. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.js +137 -0
  134. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.js.map +1 -0
  135. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.d.ts +38 -0
  136. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.js +103 -0
  137. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.js.map +1 -0
  138. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.d.ts +25 -0
  139. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.js +174 -0
  140. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.js.map +1 -0
  141. package/src/lib/layout-language/ai-layout-compiler.d.ts +1 -1
  142. package/src/lib/layout-language/ai-layout-compiler.js +438 -25
  143. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  144. package/src/lib/layout-language/ai-layout-contact.d.ts +57 -0
  145. package/src/lib/layout-language/ai-layout-contact.js +137 -0
  146. package/src/lib/layout-language/ai-layout-contact.js.map +1 -0
  147. package/src/lib/layout-language/ai-layout-copy.d.ts +12 -0
  148. package/src/lib/layout-language/ai-layout-copy.js +34 -2
  149. package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
  150. package/src/lib/layout-language/ai-layout-design.d.ts +65 -8
  151. package/src/lib/layout-language/ai-layout-design.js +411 -97
  152. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  153. package/src/lib/layout-language/ai-layout-frame.js +23 -0
  154. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  155. package/src/lib/layout-language/ai-layout-labels.d.ts +2 -0
  156. package/src/lib/layout-language/ai-layout-labels.js +12 -6
  157. package/src/lib/layout-language/ai-layout-labels.js.map +1 -1
  158. package/src/lib/layout-language/ai-layout-language.d.ts +3 -2
  159. package/src/lib/layout-language/ai-layout-language.js +4 -3
  160. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  161. package/src/lib/layout-language/ai-layout-links.d.ts +13 -0
  162. package/src/lib/layout-language/ai-layout-links.js +30 -0
  163. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  164. package/src/lib/layout-language/ai-layout-pictures.d.ts +49 -6
  165. package/src/lib/layout-language/ai-layout-pictures.js +145 -22
  166. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  167. package/src/lib/model/ai-build-job.d.ts +16 -1
  168. package/src/lib/model/ai-build-job.js +40 -0
  169. package/src/lib/model/ai-build-job.js.map +1 -1
  170. package/src/lib/model/ai-credit-estimate.d.ts +20 -0
  171. package/src/lib/model/ai-credit-estimate.js +13 -0
  172. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  173. package/src/lib/model/ai-job-activity.d.ts +2 -2
  174. package/src/lib/model/ai-job-activity.js +6 -4
  175. package/src/lib/model/ai-job-activity.js.map +1 -1
  176. package/src/lib/model/ai-job-input.d.ts +33 -0
  177. package/src/lib/model/ai-job-input.js +59 -0
  178. package/src/lib/model/ai-job-input.js.map +1 -0
  179. package/src/lib/model/ai-job-notice.d.ts +4 -0
  180. package/src/lib/model/ai-job-notice.js +28 -4
  181. package/src/lib/model/ai-job-notice.js.map +1 -1
  182. package/src/lib/model/ai-jobs.types.d.ts +69 -3
  183. package/src/lib/model/ai-jobs.types.js +2 -1
  184. package/src/lib/model/ai-jobs.types.js.map +1 -1
  185. package/src/lib/model/ai-site-details.d.ts +122 -0
  186. package/src/lib/model/ai-site-details.js +300 -0
  187. package/src/lib/model/ai-site-details.js.map +1 -0
  188. package/src/lib/model/ai-site-entity.d.ts +181 -0
  189. package/src/lib/model/ai-site-entity.js +390 -0
  190. package/src/lib/model/ai-site-entity.js.map +1 -0
  191. package/src/lib/model/ai-site-import-view.d.ts +130 -0
  192. package/src/lib/model/ai-site-import-view.js +182 -0
  193. package/src/lib/model/ai-site-import-view.js.map +1 -0
  194. package/src/lib/model/ai-site-import.d.ts +302 -0
  195. package/src/lib/model/ai-site-import.js +526 -0
  196. package/src/lib/model/ai-site-import.js.map +1 -0
  197. package/src/lib/model/ai-site-inspiration.d.ts +72 -0
  198. package/src/lib/model/ai-site-inspiration.js +109 -0
  199. package/src/lib/model/ai-site-inspiration.js.map +1 -0
  200. package/src/lib/model/ai-site-job.d.ts +13 -1
  201. package/src/lib/model/ai-site-job.js +15 -2
  202. package/src/lib/model/ai-site-job.js.map +1 -1
  203. package/src/lib/model/ai-site-kinds.js +109 -21
  204. package/src/lib/model/ai-site-kinds.js.map +1 -1
  205. package/src/lib/model/ai-site-look.d.ts +10 -0
  206. package/src/lib/model/ai-site-look.js +24 -0
  207. package/src/lib/model/ai-site-look.js.map +1 -1
  208. package/src/lib/model/ai-site-start-seo.d.ts +19 -5
  209. package/src/lib/model/ai-site-start-seo.js +25 -7
  210. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  211. package/src/lib/model/ai-site-start-steps.d.ts +9 -3
  212. package/src/lib/model/ai-site-start-steps.js +67 -5
  213. package/src/lib/model/ai-site-start-steps.js.map +1 -1
  214. package/src/lib/model/ai-site-start.d.ts +81 -4
  215. package/src/lib/model/ai-site-start.js +207 -13
  216. package/src/lib/model/ai-site-start.js.map +1 -1
  217. package/src/lib/model/ai-upsell-copy.js +4 -1
  218. package/src/lib/model/ai-upsell-copy.js.map +1 -1
  219. package/src/lib/plan-entitlements.d.ts +4 -3
  220. package/src/lib/plan-entitlements.js +3 -8
  221. package/src/lib/plan-entitlements.js.map +1 -1
  222. package/src/lib/runtime/ai-dataset-generation.d.ts +8 -1
  223. package/src/lib/runtime/ai-dataset-generation.js +12 -2
  224. package/src/lib/runtime/ai-dataset-generation.js.map +1 -1
  225. package/src/lib/runtime/ai-doctrine-validators.d.ts +26 -33
  226. package/src/lib/runtime/ai-doctrine-validators.js +54 -44
  227. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  228. package/src/lib/runtime/ai-doctrine.js +1 -1
  229. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  230. package/src/lib/runtime/ai-node-tree.d.ts +7 -0
  231. package/src/lib/runtime/ai-node-tree.js +9 -1
  232. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  233. package/src/lib/server/ai-create-route.d.ts +1 -1
  234. package/src/lib/server/ai-create-route.js +3 -4
  235. package/src/lib/server/ai-create-route.js.map +1 -1
  236. package/src/lib/server/ai-jobs-resume.js +27 -3
  237. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  238. package/src/lib/server/ai-jobs-route.js +18 -0
  239. package/src/lib/server/ai-jobs-route.js.map +1 -1
  240. package/src/lib/server/ai-site-import-route.d.ts +89 -0
  241. package/src/lib/server/ai-site-import-route.js +356 -0
  242. package/src/lib/server/ai-site-import-route.js.map +1 -0
  243. package/src/lib/server.js +14 -0
  244. package/src/lib/server.js.map +1 -1
  245. package/src/lib/site-import/fixtures/site-import-crawl-recordings.d.ts +45 -0
  246. package/src/lib/site-import/fixtures/site-import-crawl-recordings.js +313 -0
  247. package/src/lib/site-import/fixtures/site-import-crawl-recordings.js.map +1 -0
  248. package/src/lib/site-import/fixtures/site-import-wave-recordings.d.ts +50 -0
  249. package/src/lib/site-import/fixtures/site-import-wave-recordings.js +217 -0
  250. package/src/lib/site-import/fixtures/site-import-wave-recordings.js.map +1 -0
  251. package/src/lib/site-import/site-import-crawl.d.ts +163 -0
  252. package/src/lib/site-import/site-import-crawl.js +467 -0
  253. package/src/lib/site-import/site-import-crawl.js.map +1 -0
  254. package/src/lib/site-import/site-import-flag.d.ts +23 -0
  255. package/src/lib/site-import/site-import-flag.js +23 -0
  256. package/src/lib/site-import/site-import-flag.js.map +1 -0
  257. package/src/lib/site-import/site-import-html.d.ts +94 -0
  258. package/src/lib/site-import/site-import-html.js +462 -0
  259. package/src/lib/site-import/site-import-html.js.map +1 -0
  260. package/src/lib/site-import/site-import-images.d.ts +54 -0
  261. package/src/lib/site-import/site-import-images.js +204 -0
  262. package/src/lib/site-import/site-import-images.js.map +1 -0
  263. package/src/lib/site-import/site-import-page.d.ts +48 -0
  264. package/src/lib/site-import/site-import-page.js +150 -0
  265. package/src/lib/site-import/site-import-page.js.map +1 -0
  266. package/src/lib/site-import/site-import-review.d.ts +40 -0
  267. package/src/lib/site-import/site-import-review.js +149 -0
  268. package/src/lib/site-import/site-import-review.js.map +1 -0
  269. package/src/lib/site-import/site-import-robots.d.ts +52 -0
  270. package/src/lib/site-import/site-import-robots.js +126 -0
  271. package/src/lib/site-import/site-import-robots.js.map +1 -0
  272. package/src/lib/usage/assist-credits.d.ts +4 -4
  273. package/src/lib/usage/assist-credits.js +4 -4
  274. package/src/lib/usage/assist-credits.js.map +1 -1
  275. package/src/lib/usage/staff-org-ai-view.js +6 -3
  276. package/src/lib/usage/staff-org-ai-view.js.map +1 -1
  277. package/src/lib/usage/staff-org-ai.d.ts +4 -2
  278. package/src/lib/usage/staff-org-ai.js +7 -7
  279. package/src/lib/usage/staff-org-ai.js.map +1 -1
  280. package/src/lib/usage/staff-org-usage-ai.js +2 -2
  281. package/src/lib/usage/staff-org-usage-ai.js.map +1 -1
@@ -15,6 +15,7 @@
15
15
  * limitations under the License.
16
16
  */ // Imported by the staff card (client) and by the App Route that serves it, so it
17
17
  // imports neither entry barrel (see `staff-org-ai.ts`).
18
+ import { AI_TIERS } from "@aglyn/aglyn/app-utils/plan-entitlements";
18
19
  import { aiUsageMonthLabel } from "./ai-usage-wire.js";
19
20
  import { aiCreditsCell, aiOverageCell, assistCostCell, assistPoolSentence } from "./staff-org-usage-ai.js";
20
21
  /*
@@ -45,7 +46,7 @@ export const rate = (value)=>value == null ? '—' : `${Math.round(value * 100)}
45
46
  parts.push(`${credits(pool.planCredits)} plan band`);
46
47
  }
47
48
  if (pool.addonCredits > 0) {
48
- parts.push(`${credits(pool.addonCredits)} ${addonName} add-on`);
49
+ parts.push(`${credits(pool.addonCredits)} ${addonName} tier`);
49
50
  }
50
51
  return parts.join(' + ');
51
52
  }
@@ -157,8 +158,10 @@ export const rate = (value)=>value == null ? '—' : `${Math.round(value * 100)}
157
158
  month: data.month,
158
159
  addon: {
159
160
  on: addon.on,
160
- status: addon.on ? `${addonName} on` : `${addonName} off`,
161
- price: addon.priceUsd !== null ? `${usd(addon.priceUsd)}/mo on this plan` : 'Not sold on this plan',
161
+ // The tier held (AGL-3965): sold on its own, at one price on any paid
162
+ // plan, so the price is the tier's rather than "on this plan".
163
+ status: addon.on ? `${addonName}${addon.tier ? ` ${AI_TIERS[addon.tier].label}` : ''} on` : `${addonName} off`,
164
+ price: addon.priceUsd !== null ? `${usd(addon.priceUsd)}/mo` : 'No tier',
162
165
  since: addon.on ? addon.since : null,
163
166
  sinceNote: addon.on ? addonSinceNote(addon) : ''
164
167
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/usage/staff-org-ai-view.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Imported by the staff card (client) and by the App Route that serves it, so it\n// imports neither entry barrel (see `staff-org-ai.ts`).\nimport { aiUsageMonthLabel } from './ai-usage-wire'\nimport {\n aiCreditsCell,\n aiOverageCell,\n assistCostCell,\n assistPoolSentence,\n type StaffAssistPool,\n type StaffOrgUsageAiMonth,\n} from './staff-org-usage-ai'\nimport type {\n StaffAiAccountAllowance,\n StaffOrgAiMargin,\n StaffOrgAiOverage,\n StaffOrgAiPool,\n StaffOrgAiRefusals,\n StaffOrgAiResponse,\n} from './staff-org-ai'\n\n/*\n * THE STAFF AI CARD, AS IT PRINTS (AGL-3667).\n *\n * `/admin/orgs/:orgId` draws the card in the browser: whole credits, dollars to\n * four places under a dollar, the pool as the parts that make it up, the\n * overage's state in one sentence, a red or amber or green margin. The staff\n * apps read the same figures through `/api/ai/admin/org?view=prepared`, which\n * sends each sentence the card prints, so a figure or a sentence on the card is\n * decided once. The card imports the helpers below for the same reason.\n */\n\nexport const credits = (value: number | null | undefined): string =>\n value == null ? '—' : Math.round(value).toLocaleString('en-US')\n\n/**\n * Four decimals under a dollar, two above (the assist-signals page's rule):\n * a month that really cost eight cents must not read as `$0.00`.\n */\nexport const usd = (value: number | null | undefined): string => {\n if (value == null) return '—'\n const amount = Number(value) || 0\n return `$${amount.toFixed(Math.abs(amount) < 1 ? 4 : 2)}`\n}\n\nexport const tokenCount = (value: number): string => Math.round(value).toLocaleString('en-US')\n\nexport const rate = (value: number | null): string => (value == null ? '—' : `${Math.round(value * 100)}%`)\n\n/** The pool as one sentence naming every part that is non-zero. */\nexport function poolPartsSentence(pool: StaffOrgAiPool, addonName: string): string {\n const parts: string[] = []\n if (pool.overrideCredits !== null) {\n parts.push(`${credits(pool.overrideCredits)} staff override`)\n } else {\n parts.push(`${credits(pool.planCredits)} plan band`)\n }\n if (pool.addonCredits > 0) {\n parts.push(`${credits(pool.addonCredits)} ${addonName} add-on`)\n }\n return parts.join(' + ')\n}\n\n/**\n * The overage's state as one line: sold, walled, or capped — or, for an\n * uncapped staff comp (AGL-3049), nothing to sell past at all.\n */\nexport function overageStateSentence(overage: StaffOrgAiOverage, uncapped = false): string {\n if (uncapped) {\n return 'Nothing is sold past a band: an uncapped staff comp has none, and bills nothing.'\n }\n if (overage.hardCap) return 'Stopped at the band — the org’s band switch is on.'\n if (!overage.sellsOverage) return 'Not sold past the band on this plan.'\n const per = overage.rateUsdPer1k === null ? '' : ` at ${usd(overage.rateUsdPer1k)} per 1,000`\n if (overage.capUsd !== null) {\n return overage.capReached\n ? `Stopped — the org’s ${usd(overage.capUsd)} overage ceiling is reached.`\n : `Sold${per}, up to the org’s ${usd(overage.capUsd)} ceiling.`\n }\n return `Sold${per}, no ceiling set.`\n}\n\n/** Whether the margin section renders red. */\nexport function marginTone(margin: StaffOrgAiMargin): 'error' | 'warning' | 'success' {\n if (margin.underwater) return 'error'\n if (margin.multiple >= 1) return 'warning'\n return 'success'\n}\n\n/** The words for each reason the gate refused, in the order the card lists them. */\nexport const REFUSAL_LABELS: Record<keyof Omit<StaffOrgAiRefusals, 'total'>, string> = {\n band: 'band',\n cap: 'ceiling',\n messages: 'messages',\n budget: 'operator backstop',\n // A hard allotment a manager set on a member, a collaborator or a site\n // (AGL-2942).\n allotment: 'allotments',\n // The Free taste's rungs (AGL-2925): only a Free workspace collects these.\n account: 'free account',\n requests: 'free requests/day',\n refusals: 'free refusals/day',\n platform: 'platform ceiling',\n}\n\n/**\n * Each reason as the chip the card draws: `band 3`. A reason the answering\n * server does not yet count reads as zero.\n */\nexport function refusalChips(refusals: StaffOrgAiRefusals): Array<{ label: string; count: number }> {\n return (Object.keys(REFUSAL_LABELS) as Array<keyof typeof REFUSAL_LABELS>).map((reason) => ({\n label: REFUSAL_LABELS[reason],\n count: Number(refusals[reason] ?? 0),\n }))\n}\n\n/** The statuses the jobs row names, and the ones that turn it amber or red. */\nexport const JOB_CHIP_STATUSES = ['queued', 'running', 'needs_input', 'needs_review', 'failed'] as const\n\n/** What the add-on row says about when it was bought. */\nexport function addonSinceNote(addon: StaffOrgAiResponse['addon']): string {\n if (addon.since) return ''\n return addon.sinceSource === 'unavailable' ? 'Stripe not reachable' : 'no subscription item'\n}\n\n/** Which meters a give-back may go to, as the dialog offers them, and the one it starts on. */\nexport function creditReturnMeters(\n hasWorkspace: boolean,\n hasAccount: boolean,\n): Array<{ value: 'both' | 'workspace' | 'account'; label: string }> {\n const labels = { both: 'Both', workspace: 'Workspace band', account: 'Owner’s Free allowance' } as const\n return (['both', 'workspace', 'account'] as const)\n .filter((meter) => (meter === 'both' ? hasWorkspace && hasAccount : meter === 'workspace' ? hasWorkspace : hasAccount))\n .map((value) => ({ value, label: labels[value] }))\n}\n\n/** The most one give-back may return to the meter(s) chosen: the lower of the two for both. */\nexport function creditReturnable(\n meter: 'both' | 'workspace' | 'account',\n workspaceUsed: number | null,\n accountUsed: number | null,\n): number {\n const figures = meter === 'both' ? [workspaceUsed, accountUsed] : meter === 'workspace' ? [workspaceUsed] : [accountUsed]\n if (figures.some((figure) => figure == null)) return 0\n return Math.max(0, Math.min(...figures.map((figure) => Math.floor(figure ?? 0))))\n}\n\n/** The sentence under the give-back form: what each meter used, which is all it can return. */\nexport function creditReturnSummary(workspaceUsed: number | null, accountUsed: number | null): string {\n const parts: string[] = []\n if (workspaceUsed != null) parts.push(`workspace band ${credits(workspaceUsed)}`)\n if (accountUsed != null) parts.push(`owner’s Free allowance ${credits(accountUsed)}`)\n return parts.length ? `Used this month, and so the most to give back: ${parts.join(', ')}.` : 'Nothing is metered here.'\n}\n\n/** One line of the allowance, as the org card and the user card print it. */\nexport function allowanceSentence(account: StaffAiAccountAllowance, scope: 'org' | 'user'): string {\n const given = account.returnedCredits ? ` (${credits(account.returnedCredits)} given back)` : ''\n return scope === 'org'\n ? `Owner’s Free allowance: ${credits(account.usedCredits)} of ${credits(account.limitCredits)} used across their free workspaces${given}`\n : `Free AI allowance this month: ${credits(account.usedCredits)} of ${credits(account.limitCredits)} credits used across the free workspaces this account owns${given}.`\n}\n\nexport interface StaffOrgAiView {\n month: string\n addon: { status: string; price: string; since: string | null; sinceNote: string; on: boolean }\n pool: {\n band: string\n used: string\n returned: string\n allowance: string\n projected: string\n }\n overage: { line: string; state: string }\n refusals: { heading: string; chips: string[] }\n jobs: {\n failedRead: boolean\n empty: boolean\n chips: string[]\n floor: string\n recent: Array<{\n id: string\n kind: string\n status: string\n credits: string\n startedAt: string | null\n startedBy: string | null\n giveBack: number\n }>\n }\n tokens: {\n totals: string\n cacheHit: string\n empty: boolean\n kinds: Array<{ kind: string; headline: string; detail: string }>\n } | null\n users: Array<{ uid: string; name: string; headline: string; detail: string }>\n margin: { tone: 'error' | 'warning' | 'success'; text: string; contribution: string }\n giveBack: {\n meters: Array<{ value: string; label: string }>\n defaultMeter: string\n summary: string\n returnable: number\n }\n /**\n * The AI columns of the org page's monthly usage table, and the pool line it draws above them\n * (`staffOrgUsageColumn`); null where the reader did not hand the rollups over.\n */\n usage: { pool: string; months: Array<{ month: string; headline: string; detail: string }> } | null\n}\n\n/** The AI fields of one monthly rollup, with the month they belong to. */\nexport type StaffOrgAiUsageRollup = StaffOrgUsageAiMonth & { month: string }\n\n/** The card's figures as the sentences it prints. */\nexport function staffOrgAiView(\n data: StaffOrgAiResponse,\n addonName: string,\n usage?: { pool: StaffAssistPool; months: readonly StaffOrgAiUsageRollup[] },\n): StaffOrgAiView {\n const { addon, pool, overage, refusals, jobs, margin } = data\n const uncapped = pool.uncapped === true\n const sentence =\n pool.totalCredits === null && uncapped\n ? 'Uncapped staff comp — no AI credit band. Bounded only by the message cap and an operator’s explicit spend ceiling.'\n : pool.totalCredits === null\n ? 'No AI credit band on this plan — bounded by the message cap and the operator backstop.'\n : `${credits(pool.totalCredits)} credits = ${poolPartsSentence(pool, addonName)}`\n\n const hasAccount = Boolean(data.account)\n const meters = creditReturnMeters(true, hasAccount)\n const defaultMeter = meters[0]?.value ?? 'workspace'\n const returnable = creditReturnable(defaultMeter, pool.usedCredits, data.account?.usedCredits ?? null)\n\n return {\n month: data.month,\n addon: {\n on: addon.on,\n status: addon.on ? `${addonName} on` : `${addonName} off`,\n price: addon.priceUsd !== null ? `${usd(addon.priceUsd)}/mo on this plan` : 'Not sold on this plan',\n since: addon.on ? addon.since : null,\n sinceNote: addon.on ? addonSinceNote(addon) : '',\n },\n pool: {\n band: sentence,\n used:\n `Used ${credits(pool.usedCredits)} credits (${usd(pool.providerUsd)} provider spend)` +\n (pool.remainingCredits !== null ? ` · ${credits(pool.remainingCredits)} remaining` : ''),\n returned: pool.returnedCredits\n ? `${credits(pool.returnedCredits)} credits given back this month — already taken off the figure above.`\n : '',\n allowance: data.account ? allowanceSentence(data.account, 'org') : '',\n projected: `Projected month-end at this pace: ${credits(pool.projectedCredits)} credits (${usd(pool.projectedUsd)}) · ${pool.messages.toLocaleString('en-US')} model turns, ${pool.deflected.toLocaleString('en-US')} answered from the docs`,\n },\n overage: {\n line: `${credits(overage.overageCredits)} credits over the band · ${usd(overage.accruedUsd)} accrued`,\n state: overageStateSentence(overage, uncapped),\n },\n refusals: {\n heading: `Refusals this month · ${refusals.total.toLocaleString('en-US')}`,\n chips: refusalChips(refusals).map((chip) => `${chip.label} ${chip.count.toLocaleString('en-US')}`),\n },\n jobs: {\n failedRead: jobs === null,\n empty: jobs !== null && Object.values(jobs.counts).reduce((sum, n) => sum + n, 0) === 0,\n chips: jobs\n ? [\n ...JOB_CHIP_STATUSES.map((status) => `${status.replace('_', ' ')} ${jobs.counts[status]}`),\n `done ${jobs.counts.done}`,\n ]\n : [],\n floor: jobs?.truncated ? 'Counted from the newest jobs only — these counts are a floor.' : '',\n recent: (jobs?.recent ?? []).map((job) => ({\n id: job.id,\n kind: job.kind || '—',\n status: String(job.status ?? '').replace('_', ' '),\n credits: `${credits(job.creditsSpent)} / ${credits(job.creditsReserved)}`,\n startedAt: job.createdAt,\n startedBy: job.createdBy,\n // What a give-back of this job starts at: its spend, never more than the month used.\n giveBack: job.creditsSpent > 0 ? Math.min(Math.ceil(job.creditsSpent), returnable) : 0,\n })),\n },\n tokens: data.tokens\n ? {\n totals: `${tokenCount(data.tokens.total.input)} sent · ${tokenCount(data.tokens.total.cached)} read from cache · ${tokenCount(data.tokens.total.cacheWrite)} written to cache · ${tokenCount(data.tokens.total.output)} generated`,\n cacheHit: `cache hit rate ${rate(data.tokens.cacheHitRate)}`,\n empty: data.tokens.kinds.length === 0,\n kinds: data.tokens.kinds.map((row) => ({\n kind: row.kind,\n headline: `${row.requests.toLocaleString('en-US')} requests · ${usd(row.costPerRequestUsd)} per request`,\n detail: `${tokenCount(row.tokens.input)} sent · ${tokenCount(row.tokens.cached)} from cache · ${tokenCount(row.tokens.output)} generated · cache hit ${rate(row.cacheHitRate)}`,\n })),\n }\n : null,\n users: data.users.map((user) => ({\n uid: user.uid,\n name: user.name,\n headline: `${credits(user.credits)} credits · ${Math.round(user.share * 100)}% · ${usd(user.estCostUsd)} provider`,\n detail: `${user.requests.toLocaleString('en-US')} requests · ${user.refusals.toLocaleString('en-US')} refusals`,\n })),\n margin: {\n tone: marginTone(margin),\n text:\n `Spend ${usd(margin.spendUsd)} against ${usd(margin.revenueUsd)} in — ` +\n `${usd(margin.addonRevenueUsd)} add-on, ${usd(margin.planAssistShareUsd)} plan assist share, ` +\n `${usd(margin.overageRevenueUsd)} overage.` +\n (margin.underwater ? ' Spend exceeds the add-on plus the plan’s assist share.' : '') +\n (margin.multiple >= 1 ? ` ${margin.multiple}× the ${usd(margin.thresholdUsd)} staff review threshold.` : ''),\n contribution:\n margin.contribution.marginPct !== null\n ? `Whole-org contribution margin ${Math.round(margin.contribution.marginPct * 100)}% on the ${margin.contribution.month} rollup with this month’s AI spend.`\n : '',\n },\n giveBack: {\n meters,\n defaultMeter,\n summary: creditReturnSummary(pool.usedCredits, data.account?.usedCredits ?? null),\n returnable,\n },\n usage: usage\n ? {\n pool: assistPoolSentence(usage.pool),\n months: usage.months.map((row) => ({\n month: row.month,\n headline: `Assist ${assistCostCell(row.assistCostUsd)}`,\n detail: `AI credits used ${aiCreditsCell(row.assistCredits)} \\u00b7 AI overage billed ${aiOverageCell(row.assistOverageUsd)}`,\n })),\n }\n : null,\n }\n}\n\n/** One row of `/api/ai/admin/user`: a workspace's month for the account. */\nexport interface StaffUserAiRow {\n orgId: string\n orgName: string | null\n slug: string | null\n month: string\n credits: number\n requests: number\n refusals: number\n}\n\nexport interface StaffUserAiView {\n allowance: string\n giveBack: { summary: string; returnable: number }\n empty: string\n rows: Array<{ orgId: string; workspace: string; month: string; headline: string; detail: string }>\n}\n\n/** The account card as it prints: the Free allowance, and a row per workspace per month kept. */\nexport function staffUserAiView(data: {\n rows: StaffUserAiRow[]\n allowance: StaffAiAccountAllowance | null\n}): StaffUserAiView {\n const used = data.allowance?.usedCredits ?? null\n return {\n allowance: data.allowance ? allowanceSentence(data.allowance, 'user') : '',\n giveBack: {\n summary: creditReturnSummary(null, used),\n returnable: creditReturnable('account', null, used),\n },\n empty: data.rows.length ? '' : 'No AI usage attributed to this account in any workspace it belongs to.',\n rows: data.rows.map((row) => ({\n orgId: row.orgId,\n workspace: row.orgName ?? row.orgId,\n month: aiUsageMonthLabel(row.month, 'en-US'),\n headline: `${credits(row.credits)} credits`,\n detail: `${row.requests.toLocaleString('en-US')} requests \\u00b7 ${row.refusals.toLocaleString('en-US')} refusals`,\n })),\n }\n}\n"],"names":["aiUsageMonthLabel","aiCreditsCell","aiOverageCell","assistCostCell","assistPoolSentence","credits","value","Math","round","toLocaleString","usd","amount","Number","toFixed","abs","tokenCount","rate","poolPartsSentence","pool","addonName","parts","overrideCredits","push","planCredits","addonCredits","join","overageStateSentence","overage","uncapped","hardCap","sellsOverage","per","rateUsdPer1k","capUsd","capReached","marginTone","margin","underwater","multiple","REFUSAL_LABELS","band","cap","messages","budget","allotment","account","requests","refusals","platform","refusalChips","Object","keys","map","reason","label","count","JOB_CHIP_STATUSES","addonSinceNote","addon","since","sinceSource","creditReturnMeters","hasWorkspace","hasAccount","labels","both","workspace","filter","meter","creditReturnable","workspaceUsed","accountUsed","figures","some","figure","max","min","floor","creditReturnSummary","length","allowanceSentence","scope","given","returnedCredits","usedCredits","limitCredits","staffOrgAiView","data","usage","meters","jobs","sentence","totalCredits","Boolean","defaultMeter","returnable","month","on","status","price","priceUsd","sinceNote","used","providerUsd","remainingCredits","returned","allowance","projected","projectedCredits","projectedUsd","deflected","line","overageCredits","accruedUsd","state","heading","total","chips","chip","failedRead","empty","values","counts","reduce","sum","n","replace","done","truncated","recent","job","id","kind","String","creditsSpent","creditsReserved","startedAt","createdAt","startedBy","createdBy","giveBack","ceil","tokens","totals","input","cached","cacheWrite","output","cacheHit","cacheHitRate","kinds","row","headline","costPerRequestUsd","detail","users","user","uid","name","share","estCostUsd","tone","text","spendUsd","revenueUsd","addonRevenueUsd","planAssistShareUsd","overageRevenueUsd","thresholdUsd","contribution","marginPct","summary","months","assistCostUsd","assistCredits","assistOverageUsd","staffUserAiView","rows","orgId","orgName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/usage/staff-org-ai-view.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Imported by the staff card (client) and by the App Route that serves it, so it\n// imports neither entry barrel (see `staff-org-ai.ts`).\nimport { AI_TIERS } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { aiUsageMonthLabel } from './ai-usage-wire'\nimport {\n aiCreditsCell,\n aiOverageCell,\n assistCostCell,\n assistPoolSentence,\n type StaffAssistPool,\n type StaffOrgUsageAiMonth,\n} from './staff-org-usage-ai'\nimport type {\n StaffAiAccountAllowance,\n StaffOrgAiMargin,\n StaffOrgAiOverage,\n StaffOrgAiPool,\n StaffOrgAiRefusals,\n StaffOrgAiResponse,\n} from './staff-org-ai'\n\n/*\n * THE STAFF AI CARD, AS IT PRINTS (AGL-3667).\n *\n * `/admin/orgs/:orgId` draws the card in the browser: whole credits, dollars to\n * four places under a dollar, the pool as the parts that make it up, the\n * overage's state in one sentence, a red or amber or green margin. The staff\n * apps read the same figures through `/api/ai/admin/org?view=prepared`, which\n * sends each sentence the card prints, so a figure or a sentence on the card is\n * decided once. The card imports the helpers below for the same reason.\n */\n\nexport const credits = (value: number | null | undefined): string =>\n value == null ? '—' : Math.round(value).toLocaleString('en-US')\n\n/**\n * Four decimals under a dollar, two above (the assist-signals page's rule):\n * a month that really cost eight cents must not read as `$0.00`.\n */\nexport const usd = (value: number | null | undefined): string => {\n if (value == null) return '—'\n const amount = Number(value) || 0\n return `$${amount.toFixed(Math.abs(amount) < 1 ? 4 : 2)}`\n}\n\nexport const tokenCount = (value: number): string => Math.round(value).toLocaleString('en-US')\n\nexport const rate = (value: number | null): string => (value == null ? '—' : `${Math.round(value * 100)}%`)\n\n/** The pool as one sentence naming every part that is non-zero. */\nexport function poolPartsSentence(pool: StaffOrgAiPool, addonName: string): string {\n const parts: string[] = []\n if (pool.overrideCredits !== null) {\n parts.push(`${credits(pool.overrideCredits)} staff override`)\n } else {\n parts.push(`${credits(pool.planCredits)} plan band`)\n }\n if (pool.addonCredits > 0) {\n parts.push(`${credits(pool.addonCredits)} ${addonName} tier`)\n }\n return parts.join(' + ')\n}\n\n/**\n * The overage's state as one line: sold, walled, or capped — or, for an\n * uncapped staff comp (AGL-3049), nothing to sell past at all.\n */\nexport function overageStateSentence(overage: StaffOrgAiOverage, uncapped = false): string {\n if (uncapped) {\n return 'Nothing is sold past a band: an uncapped staff comp has none, and bills nothing.'\n }\n if (overage.hardCap) return 'Stopped at the band — the org’s band switch is on.'\n if (!overage.sellsOverage) return 'Not sold past the band on this plan.'\n const per = overage.rateUsdPer1k === null ? '' : ` at ${usd(overage.rateUsdPer1k)} per 1,000`\n if (overage.capUsd !== null) {\n return overage.capReached\n ? `Stopped — the org’s ${usd(overage.capUsd)} overage ceiling is reached.`\n : `Sold${per}, up to the org’s ${usd(overage.capUsd)} ceiling.`\n }\n return `Sold${per}, no ceiling set.`\n}\n\n/** Whether the margin section renders red. */\nexport function marginTone(margin: StaffOrgAiMargin): 'error' | 'warning' | 'success' {\n if (margin.underwater) return 'error'\n if (margin.multiple >= 1) return 'warning'\n return 'success'\n}\n\n/** The words for each reason the gate refused, in the order the card lists them. */\nexport const REFUSAL_LABELS: Record<keyof Omit<StaffOrgAiRefusals, 'total'>, string> = {\n band: 'band',\n cap: 'ceiling',\n messages: 'messages',\n budget: 'operator backstop',\n // A hard allotment a manager set on a member, a collaborator or a site\n // (AGL-2942).\n allotment: 'allotments',\n // The Free taste's rungs (AGL-2925): only a Free workspace collects these.\n account: 'free account',\n requests: 'free requests/day',\n refusals: 'free refusals/day',\n platform: 'platform ceiling',\n}\n\n/**\n * Each reason as the chip the card draws: `band 3`. A reason the answering\n * server does not yet count reads as zero.\n */\nexport function refusalChips(refusals: StaffOrgAiRefusals): Array<{ label: string; count: number }> {\n return (Object.keys(REFUSAL_LABELS) as Array<keyof typeof REFUSAL_LABELS>).map((reason) => ({\n label: REFUSAL_LABELS[reason],\n count: Number(refusals[reason] ?? 0),\n }))\n}\n\n/** The statuses the jobs row names, and the ones that turn it amber or red. */\nexport const JOB_CHIP_STATUSES = ['queued', 'running', 'needs_input', 'needs_review', 'failed'] as const\n\n/** What the add-on row says about when it was bought. */\nexport function addonSinceNote(addon: StaffOrgAiResponse['addon']): string {\n if (addon.since) return ''\n return addon.sinceSource === 'unavailable' ? 'Stripe not reachable' : 'no subscription item'\n}\n\n/** Which meters a give-back may go to, as the dialog offers them, and the one it starts on. */\nexport function creditReturnMeters(\n hasWorkspace: boolean,\n hasAccount: boolean,\n): Array<{ value: 'both' | 'workspace' | 'account'; label: string }> {\n const labels = { both: 'Both', workspace: 'Workspace band', account: 'Owner’s Free allowance' } as const\n return (['both', 'workspace', 'account'] as const)\n .filter((meter) => (meter === 'both' ? hasWorkspace && hasAccount : meter === 'workspace' ? hasWorkspace : hasAccount))\n .map((value) => ({ value, label: labels[value] }))\n}\n\n/** The most one give-back may return to the meter(s) chosen: the lower of the two for both. */\nexport function creditReturnable(\n meter: 'both' | 'workspace' | 'account',\n workspaceUsed: number | null,\n accountUsed: number | null,\n): number {\n const figures = meter === 'both' ? [workspaceUsed, accountUsed] : meter === 'workspace' ? [workspaceUsed] : [accountUsed]\n if (figures.some((figure) => figure == null)) return 0\n return Math.max(0, Math.min(...figures.map((figure) => Math.floor(figure ?? 0))))\n}\n\n/** The sentence under the give-back form: what each meter used, which is all it can return. */\nexport function creditReturnSummary(workspaceUsed: number | null, accountUsed: number | null): string {\n const parts: string[] = []\n if (workspaceUsed != null) parts.push(`workspace band ${credits(workspaceUsed)}`)\n if (accountUsed != null) parts.push(`owner’s Free allowance ${credits(accountUsed)}`)\n return parts.length ? `Used this month, and so the most to give back: ${parts.join(', ')}.` : 'Nothing is metered here.'\n}\n\n/** One line of the allowance, as the org card and the user card print it. */\nexport function allowanceSentence(account: StaffAiAccountAllowance, scope: 'org' | 'user'): string {\n const given = account.returnedCredits ? ` (${credits(account.returnedCredits)} given back)` : ''\n return scope === 'org'\n ? `Owner’s Free allowance: ${credits(account.usedCredits)} of ${credits(account.limitCredits)} used across their free workspaces${given}`\n : `Free AI allowance this month: ${credits(account.usedCredits)} of ${credits(account.limitCredits)} credits used across the free workspaces this account owns${given}.`\n}\n\nexport interface StaffOrgAiView {\n month: string\n addon: { status: string; price: string; since: string | null; sinceNote: string; on: boolean }\n pool: {\n band: string\n used: string\n returned: string\n allowance: string\n projected: string\n }\n overage: { line: string; state: string }\n refusals: { heading: string; chips: string[] }\n jobs: {\n failedRead: boolean\n empty: boolean\n chips: string[]\n floor: string\n recent: Array<{\n id: string\n kind: string\n status: string\n credits: string\n startedAt: string | null\n startedBy: string | null\n giveBack: number\n }>\n }\n tokens: {\n totals: string\n cacheHit: string\n empty: boolean\n kinds: Array<{ kind: string; headline: string; detail: string }>\n } | null\n users: Array<{ uid: string; name: string; headline: string; detail: string }>\n margin: { tone: 'error' | 'warning' | 'success'; text: string; contribution: string }\n giveBack: {\n meters: Array<{ value: string; label: string }>\n defaultMeter: string\n summary: string\n returnable: number\n }\n /**\n * The AI columns of the org page's monthly usage table, and the pool line it draws above them\n * (`staffOrgUsageColumn`); null where the reader did not hand the rollups over.\n */\n usage: { pool: string; months: Array<{ month: string; headline: string; detail: string }> } | null\n}\n\n/** The AI fields of one monthly rollup, with the month they belong to. */\nexport type StaffOrgAiUsageRollup = StaffOrgUsageAiMonth & { month: string }\n\n/** The card's figures as the sentences it prints. */\nexport function staffOrgAiView(\n data: StaffOrgAiResponse,\n addonName: string,\n usage?: { pool: StaffAssistPool; months: readonly StaffOrgAiUsageRollup[] },\n): StaffOrgAiView {\n const { addon, pool, overage, refusals, jobs, margin } = data\n const uncapped = pool.uncapped === true\n const sentence =\n pool.totalCredits === null && uncapped\n ? 'Uncapped staff comp — no AI credit band. Bounded only by the message cap and an operator’s explicit spend ceiling.'\n : pool.totalCredits === null\n ? 'No AI credit band on this plan — bounded by the message cap and the operator backstop.'\n : `${credits(pool.totalCredits)} credits = ${poolPartsSentence(pool, addonName)}`\n\n const hasAccount = Boolean(data.account)\n const meters = creditReturnMeters(true, hasAccount)\n const defaultMeter = meters[0]?.value ?? 'workspace'\n const returnable = creditReturnable(defaultMeter, pool.usedCredits, data.account?.usedCredits ?? null)\n\n return {\n month: data.month,\n addon: {\n on: addon.on,\n // The tier held (AGL-3965): sold on its own, at one price on any paid\n // plan, so the price is the tier's rather than \"on this plan\".\n status: addon.on\n ? `${addonName}${addon.tier ? ` ${AI_TIERS[addon.tier].label}` : ''} on`\n : `${addonName} off`,\n price: addon.priceUsd !== null ? `${usd(addon.priceUsd)}/mo` : 'No tier',\n since: addon.on ? addon.since : null,\n sinceNote: addon.on ? addonSinceNote(addon) : '',\n },\n pool: {\n band: sentence,\n used:\n `Used ${credits(pool.usedCredits)} credits (${usd(pool.providerUsd)} provider spend)` +\n (pool.remainingCredits !== null ? ` · ${credits(pool.remainingCredits)} remaining` : ''),\n returned: pool.returnedCredits\n ? `${credits(pool.returnedCredits)} credits given back this month — already taken off the figure above.`\n : '',\n allowance: data.account ? allowanceSentence(data.account, 'org') : '',\n projected: `Projected month-end at this pace: ${credits(pool.projectedCredits)} credits (${usd(pool.projectedUsd)}) · ${pool.messages.toLocaleString('en-US')} model turns, ${pool.deflected.toLocaleString('en-US')} answered from the docs`,\n },\n overage: {\n line: `${credits(overage.overageCredits)} credits over the band · ${usd(overage.accruedUsd)} accrued`,\n state: overageStateSentence(overage, uncapped),\n },\n refusals: {\n heading: `Refusals this month · ${refusals.total.toLocaleString('en-US')}`,\n chips: refusalChips(refusals).map((chip) => `${chip.label} ${chip.count.toLocaleString('en-US')}`),\n },\n jobs: {\n failedRead: jobs === null,\n empty: jobs !== null && Object.values(jobs.counts).reduce((sum, n) => sum + n, 0) === 0,\n chips: jobs\n ? [\n ...JOB_CHIP_STATUSES.map((status) => `${status.replace('_', ' ')} ${jobs.counts[status]}`),\n `done ${jobs.counts.done}`,\n ]\n : [],\n floor: jobs?.truncated ? 'Counted from the newest jobs only — these counts are a floor.' : '',\n recent: (jobs?.recent ?? []).map((job) => ({\n id: job.id,\n kind: job.kind || '—',\n status: String(job.status ?? '').replace('_', ' '),\n credits: `${credits(job.creditsSpent)} / ${credits(job.creditsReserved)}`,\n startedAt: job.createdAt,\n startedBy: job.createdBy,\n // What a give-back of this job starts at: its spend, never more than the month used.\n giveBack: job.creditsSpent > 0 ? Math.min(Math.ceil(job.creditsSpent), returnable) : 0,\n })),\n },\n tokens: data.tokens\n ? {\n totals: `${tokenCount(data.tokens.total.input)} sent · ${tokenCount(data.tokens.total.cached)} read from cache · ${tokenCount(data.tokens.total.cacheWrite)} written to cache · ${tokenCount(data.tokens.total.output)} generated`,\n cacheHit: `cache hit rate ${rate(data.tokens.cacheHitRate)}`,\n empty: data.tokens.kinds.length === 0,\n kinds: data.tokens.kinds.map((row) => ({\n kind: row.kind,\n headline: `${row.requests.toLocaleString('en-US')} requests · ${usd(row.costPerRequestUsd)} per request`,\n detail: `${tokenCount(row.tokens.input)} sent · ${tokenCount(row.tokens.cached)} from cache · ${tokenCount(row.tokens.output)} generated · cache hit ${rate(row.cacheHitRate)}`,\n })),\n }\n : null,\n users: data.users.map((user) => ({\n uid: user.uid,\n name: user.name,\n headline: `${credits(user.credits)} credits · ${Math.round(user.share * 100)}% · ${usd(user.estCostUsd)} provider`,\n detail: `${user.requests.toLocaleString('en-US')} requests · ${user.refusals.toLocaleString('en-US')} refusals`,\n })),\n margin: {\n tone: marginTone(margin),\n text:\n `Spend ${usd(margin.spendUsd)} against ${usd(margin.revenueUsd)} in — ` +\n `${usd(margin.addonRevenueUsd)} add-on, ${usd(margin.planAssistShareUsd)} plan assist share, ` +\n `${usd(margin.overageRevenueUsd)} overage.` +\n (margin.underwater ? ' Spend exceeds the add-on plus the plan’s assist share.' : '') +\n (margin.multiple >= 1 ? ` ${margin.multiple}× the ${usd(margin.thresholdUsd)} staff review threshold.` : ''),\n contribution:\n margin.contribution.marginPct !== null\n ? `Whole-org contribution margin ${Math.round(margin.contribution.marginPct * 100)}% on the ${margin.contribution.month} rollup with this month’s AI spend.`\n : '',\n },\n giveBack: {\n meters,\n defaultMeter,\n summary: creditReturnSummary(pool.usedCredits, data.account?.usedCredits ?? null),\n returnable,\n },\n usage: usage\n ? {\n pool: assistPoolSentence(usage.pool),\n months: usage.months.map((row) => ({\n month: row.month,\n headline: `Assist ${assistCostCell(row.assistCostUsd)}`,\n detail: `AI credits used ${aiCreditsCell(row.assistCredits)} \\u00b7 AI overage billed ${aiOverageCell(row.assistOverageUsd)}`,\n })),\n }\n : null,\n }\n}\n\n/** One row of `/api/ai/admin/user`: a workspace's month for the account. */\nexport interface StaffUserAiRow {\n orgId: string\n orgName: string | null\n slug: string | null\n month: string\n credits: number\n requests: number\n refusals: number\n}\n\nexport interface StaffUserAiView {\n allowance: string\n giveBack: { summary: string; returnable: number }\n empty: string\n rows: Array<{ orgId: string; workspace: string; month: string; headline: string; detail: string }>\n}\n\n/** The account card as it prints: the Free allowance, and a row per workspace per month kept. */\nexport function staffUserAiView(data: {\n rows: StaffUserAiRow[]\n allowance: StaffAiAccountAllowance | null\n}): StaffUserAiView {\n const used = data.allowance?.usedCredits ?? null\n return {\n allowance: data.allowance ? allowanceSentence(data.allowance, 'user') : '',\n giveBack: {\n summary: creditReturnSummary(null, used),\n returnable: creditReturnable('account', null, used),\n },\n empty: data.rows.length ? '' : 'No AI usage attributed to this account in any workspace it belongs to.',\n rows: data.rows.map((row) => ({\n orgId: row.orgId,\n workspace: row.orgName ?? row.orgId,\n month: aiUsageMonthLabel(row.month, 'en-US'),\n headline: `${credits(row.credits)} credits`,\n detail: `${row.requests.toLocaleString('en-US')} requests \\u00b7 ${row.refusals.toLocaleString('en-US')} refusals`,\n })),\n }\n}\n"],"names":["AI_TIERS","aiUsageMonthLabel","aiCreditsCell","aiOverageCell","assistCostCell","assistPoolSentence","credits","value","Math","round","toLocaleString","usd","amount","Number","toFixed","abs","tokenCount","rate","poolPartsSentence","pool","addonName","parts","overrideCredits","push","planCredits","addonCredits","join","overageStateSentence","overage","uncapped","hardCap","sellsOverage","per","rateUsdPer1k","capUsd","capReached","marginTone","margin","underwater","multiple","REFUSAL_LABELS","band","cap","messages","budget","allotment","account","requests","refusals","platform","refusalChips","Object","keys","map","reason","label","count","JOB_CHIP_STATUSES","addonSinceNote","addon","since","sinceSource","creditReturnMeters","hasWorkspace","hasAccount","labels","both","workspace","filter","meter","creditReturnable","workspaceUsed","accountUsed","figures","some","figure","max","min","floor","creditReturnSummary","length","allowanceSentence","scope","given","returnedCredits","usedCredits","limitCredits","staffOrgAiView","data","usage","meters","jobs","sentence","totalCredits","Boolean","defaultMeter","returnable","month","on","status","tier","price","priceUsd","sinceNote","used","providerUsd","remainingCredits","returned","allowance","projected","projectedCredits","projectedUsd","deflected","line","overageCredits","accruedUsd","state","heading","total","chips","chip","failedRead","empty","values","counts","reduce","sum","n","replace","done","truncated","recent","job","id","kind","String","creditsSpent","creditsReserved","startedAt","createdAt","startedBy","createdBy","giveBack","ceil","tokens","totals","input","cached","cacheWrite","output","cacheHit","cacheHitRate","kinds","row","headline","costPerRequestUsd","detail","users","user","uid","name","share","estCostUsd","tone","text","spendUsd","revenueUsd","addonRevenueUsd","planAssistShareUsd","overageRevenueUsd","thresholdUsd","contribution","marginPct","summary","months","assistCostUsd","assistCredits","assistOverageUsd","staffUserAiView","rows","orgId","orgName"],"mappings":"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"}
@@ -15,7 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  import type { AiJobStatus } from '../model/ai-jobs.types';
18
- import type { AglynOrgBilling } from '@aglyn/aglyn/foundation';
18
+ import type { AglynOrgBilling, AiTierId } from '@aglyn/aglyn/foundation';
19
19
  import { type DiscountMarginRating } from '@aglyn/aglyn/app-utils/plan-entitlements';
20
20
  import type { AssistRefusalCounts } from './assist-refusals';
21
21
  import { type AiKindMonth, type AiTokenTotals } from '../model/ai-tokens';
@@ -34,7 +34,9 @@ export type StaffOrgAiRefusals = AssistRefusalCounts;
34
34
  export interface StaffOrgAiAddon {
35
35
  /** Bought and on a subscription still paying for it — `hasAiAddon`. */
36
36
  on: boolean;
37
- /** The add-on's list price on this plan, or null where the plan sells none. */
37
+ /** The Aglyn AI tier held (AGL-3965), or null for none. */
38
+ tier: AiTierId | null;
39
+ /** The held tier's list price, or null with no tier. */
38
40
  priceUsd: number | null;
39
41
  /** ISO instant the subscription item was created, when Stripe could say. */
40
42
  since: string | null;
@@ -18,7 +18,7 @@
18
18
  // `margin-utilization.ts` states at its own head.
19
19
  import { _ as _extends } from "@swc/helpers/_/_extends";
20
20
  import { ASSIST_PROVIDER_COST_FIELD, assistBandRefuses, assistCreditsFromUsd, assistProviderCostUsd, assistMonthOverage, assistOverageCapReached, assistUsdFromCredits, resolveAssistCreditBudget, resolveAssistHardCap, resolveAssistOverageCapUsd } from "./assist-credits.js";
21
- import { AI_ADDON_CREDITS_PER_MONTH, hasAiAddon, isUncappedPlanComp, PLAN_ENTITLEMENTS, resolveEffectivePlan, resolvePlanPricing } from "@aglyn/aglyn/app-utils/plan-entitlements";
21
+ import { AI_TIERS, aiTierCreditsPerMonth, hasAiAddon, isUncappedPlanComp, PLAN_ENTITLEMENTS, resolveAiTier, resolveEffectivePlan } from "@aglyn/aglyn/app-utils/plan-entitlements";
22
22
  import { assistMonthSpendUsd, assistReturnedCredits } from "./assist-credit-returns.js";
23
23
  import { FREE_AI_TASTE_CREDITS_PER_MONTH } from "../plan-entitlements.js";
24
24
  import { AI_USAGE_MONTH_KINDS_FIELD, aiCacheHitRate, readAiKindMonths } from "../model/ai-tokens.js";
@@ -65,7 +65,6 @@ const finite = (value)=>{
65
65
  export function composeStaffOrgAiPool(org, monthDoc, now) {
66
66
  var _PLAN_ENTITLEMENTS_plan;
67
67
  const plan = resolveEffectivePlan(org);
68
- const addon = hasAiAddon(org);
69
68
  // Two questions, two figures (AGL-3015). What the workspace DREW is the
70
69
  // billed figure, because that is what its credits came out of and what its
71
70
  // band is measured in. What the month COST US is the provider figure, and
@@ -83,7 +82,7 @@ export function composeStaffOrgAiPool(org, monthDoc, now) {
83
82
  return {
84
83
  planCredits: finite((_PLAN_ENTITLEMENTS_plan = PLAN_ENTITLEMENTS[plan]) == null ? void 0 : _PLAN_ENTITLEMENTS_plan.assistCreditsPerMonth),
85
84
  overrideCredits: assistBandOverride(org),
86
- addonCredits: addon ? AI_ADDON_CREDITS_PER_MONTH[plan] : 0,
85
+ addonCredits: aiTierCreditsPerMonth(org),
87
86
  totalCredits,
88
87
  uncapped: isUncappedPlanComp(org),
89
88
  usedCredits,
@@ -137,12 +136,13 @@ export function composeStaffOrgAiPool(org, monthDoc, now) {
137
136
  };
138
137
  }
139
138
  export function composeStaffOrgAiAddon(org, since) {
140
- var _resolvePlanPricing_aiAddonMonthlyUsd;
139
+ // The tier held and what it is charged at (AGL-3965); a staff comp has
140
+ // no subscription to carry one (AGL-3034), so it reads none.
141
+ const tier = resolveAiTier(org);
141
142
  return _extends({
142
143
  on: hasAiAddon(org),
143
- // What this workspace would be charged — none for a staff comp, which
144
- // has no subscription to carry the item (AGL-3034).
145
- priceUsd: (_resolvePlanPricing_aiAddonMonthlyUsd = resolvePlanPricing(org).aiAddonMonthlyUsd) != null ? _resolvePlanPricing_aiAddonMonthlyUsd : null
144
+ tier,
145
+ priceUsd: tier ? AI_TIERS[tier].monthlyUsd : null
146
146
  }, since);
147
147
  }
148
148
  export function composeStaffOrgAiMargin(input) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/usage/staff-org-ai.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Imported from BOTH graphs — the staff card (client) and the App Route that\n// serves it — so it may import neither entry barrel, for the reason\n// `margin-utilization.ts` states at its own head.\nimport type { AiJobStatus } from '../model/ai-jobs.types'\nimport type { AglynOrgBilling, OrgPlan } from '@aglyn/aglyn/foundation'\nimport {\n ASSIST_PROVIDER_COST_FIELD,\n assistBandRefuses,\n assistCreditsFromUsd,\n assistProviderCostUsd,\n assistMonthOverage,\n assistOverageCapReached,\n assistUsdFromCredits,\n resolveAssistCreditBudget,\n resolveAssistHardCap,\n resolveAssistOverageCapUsd,\n} from './assist-credits'\nimport {\n AI_ADDON_CREDITS_PER_MONTH,\n hasAiAddon,\n isUncappedPlanComp,\n PLAN_ENTITLEMENTS,\n resolveEffectivePlan,\n resolvePlanPricing,\n type DiscountMarginRating,\n} from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport type { AssistRefusalCounts } from './assist-refusals'\nimport { assistMonthSpendUsd, assistReturnedCredits } from './assist-credit-returns'\nimport { FREE_AI_TASTE_CREDITS_PER_MONTH } from '../plan-entitlements'\nimport {\n AI_USAGE_MONTH_KINDS_FIELD,\n aiCacheHitRate,\n readAiKindMonths,\n type AiKindMonth,\n type AiTokenTotals,\n} from '../model/ai-tokens'\n\n/**\n * THE STAFF AI CARD'S FIGURES (AGL-2930), composed from the same helpers the\n * meter, the invoice and the customer's own billing page read.\n *\n * Nothing here runs a query and nothing here holds a rate of its own. The\n * pool is `resolveAssistCreditBudget`, the overage is `assistMonthOverage`,\n * the ceiling is `assistOverageCapReached` — the ONE derivation each of those\n * already is — so the card cannot show a staff member a different band,\n * rate or ceiling from the one the gate refused a customer at.\n */\n\n/** The four ways the reservation refuses, plus their sum. */\nexport type StaffOrgAiRefusals = AssistRefusalCounts\n\nexport interface StaffOrgAiAddon {\n /** Bought and on a subscription still paying for it — `hasAiAddon`. */\n on: boolean\n /** The add-on's list price on this plan, or null where the plan sells none. */\n priceUsd: number | null\n /** ISO instant the subscription item was created, when Stripe could say. */\n since: string | null\n /**\n * Where `since` came from, so an absent date is legible: `stripe` answered,\n * the org has no Stripe subscription to ask, or Stripe could not be asked\n * (no key on this deployment, or the lookup failed).\n */\n sinceSource: 'stripe' | 'no-subscription' | 'unavailable'\n}\n\nexport interface StaffOrgAiPool {\n /** The plan's own band, from the catalogue row. */\n planCredits: number\n /** A staff override of the band, or null when none is written. */\n overrideCredits: number | null\n /** The add-on's band, 0 without the add-on. */\n addonCredits: number\n /** The whole pool as the meter resolves it, or null where no band applies. */\n totalCredits: number | null\n /**\n * Whether an uncapped staff comp is why no band applies (AGL-3049) — a\n * boolean, so \"no band because uncapped\" crosses JSON as itself rather\n * than as the `null` an `Infinity` would become. Absent from a route older\n * than the flag.\n */\n uncapped?: boolean\n /**\n * Credits drawn this month, rounded up from the measured spend, net of\n * any staff gave back.\n */\n usedCredits: number\n /**\n * Credits given back to this month's band (AGL-3595). Optional: a route\n * older than give-backs does not send it.\n */\n returnedCredits?: number\n /**\n * What the month DREW, at the catalog's billed rates — the dollars\n * `usedCredits` is rounded up from, and the figure the overage line is\n * priced off. Not a cost (AGL-3015).\n */\n billedUsd: number\n /**\n * What the month COST US, at the provider's own rates: the one figure a\n * margin or a spend alert may be taken against.\n */\n providerUsd: number\n /** Left in the pool, clamped at zero, or null where no band applies. */\n remainingCredits: number | null\n /** Where `usedCredits` lands at month end at the pace so far. */\n projectedCredits: number\n /** `projectedCredits` back in dollars — billed, as the credits were. */\n projectedUsd: number\n /** Model turns and docs-deflected turns, off the month document. */\n messages: number\n deflected: number\n}\n\nexport interface StaffOrgAiOverage {\n overageCredits: number\n rateUsdPer1k: number | null\n accruedUsd: number\n /** Whether credits past the band are sold at all on this org. */\n sellsOverage: boolean\n /** The org's own band switch — `assistOverage.hardCap`. */\n hardCap: boolean\n /** The org's own dollar ceiling on overage — `assistOverage.capUsd`. */\n capUsd: number | null\n capReached: boolean\n /** Whether the band is a wall for any reason (the switch, or no rate). */\n bandRefuses: boolean\n}\n\nexport interface StaffOrgAiMargin {\n /** The add-on's list price when it is on, else 0. */\n addonRevenueUsd: number\n /**\n * The dollar value of the plan's own band — what the plan price sets aside\n * for assist. Each plan band is sized against the billed rate (a thousand\n * credits are a dollar), so this is the share of the subscription that was\n * sold as AI, and it belongs on the REVENUE side beside the add-on's price.\n */\n planAssistShareUsd: number\n /** Overage priced at the plan's rate, which the invoice will bill. */\n overageRevenueUsd: number\n /** The three above, summed. */\n revenueUsd: number\n /** What the month cost us: measured provider spend, never what it drew. */\n spendUsd: number\n /**\n * Spend past what the add-on and the plan's assist share bring in — the\n * condition the card renders red. Overage revenue is left OUT of the\n * comparison on purpose: it is billed at a margin, so an org deep in\n * overage is not the one losing money.\n */\n underwater: boolean\n /** The staff review threshold and the multiple of it the spend has reached. */\n thresholdUsd: number\n multiple: number\n /** The whole-org contribution margin, when a usage rollup exists. */\n contribution: {\n month: string | null\n marginPct: number | null\n rating: DiscountMarginRating | null\n }\n}\n\n/** The job model's own status set: the card renders every one by name. */\nexport type StaffOrgAiJobStatus = AiJobStatus\n\nexport interface StaffOrgAiJob {\n id: string\n kind: string\n status: StaffOrgAiJobStatus | string\n creditsReserved: number\n creditsSpent: number\n createdAt: string | null\n createdBy: string | null\n}\n\nexport interface StaffOrgAiJobs {\n counts: Record<StaffOrgAiJobStatus, number>\n recent: StaffOrgAiJob[]\n /** True when the scan stopped at its ceiling — the counts are a floor. */\n truncated: boolean\n}\n\n/** One row of the per-user rollup (AGL-2928), as the staff card lists it. */\nexport interface StaffOrgAiUser {\n uid: string\n /** The roster's name for them — display name, else email, else the uid. */\n name: string\n credits: number\n /** The measured provider spend behind `credits`; staff may see dollars. */\n estCostUsd: number\n /** Of the org's measured spend for the month, in `[0, 1]`. */\n share: number\n requests: number\n refusals: number\n byKind: Record<string, number>\n byHost: Record<string, number>\n}\n\n/** One kind of model request this month, as the card lists it (AGL-2937). */\nexport interface StaffOrgAiKindTokens extends AiKindMonth {\n /**\n * What one request of this kind COST US on average; `null` with none. The\n * provider figure, not what the kind drew — the reader is sizing a step\n * against a bill, and a marked-up model would otherwise make the step look\n * dearer to run than it is (AGL-3015).\n */\n costPerRequestUsd: number | null\n /** The share of this kind's prompt tokens the cache served — `aiCacheHitRate`. */\n cacheHitRate: number | null\n}\n\n/** The month's tokens (AGL-2937): its totals, its cache hit rate, and each kind. */\nexport interface StaffOrgAiTokens {\n /** The month document's own four totals. */\n total: AiTokenTotals\n cacheHitRate: number | null\n /** Dearest first; empty for a month written before kinds were kept. */\n kinds: StaffOrgAiKindTokens[]\n}\n\nexport interface StaffOrgAiResponse {\n month: string\n addon: StaffOrgAiAddon\n pool: StaffOrgAiPool\n overage: StaffOrgAiOverage\n refusals: StaffOrgAiRefusals\n /** Null when the jobs read failed — distinct from an org with no jobs. */\n jobs: StaffOrgAiJobs | null\n /** The dearest people this month; empty when nobody's month is written. */\n users: StaffOrgAiUser[]\n margin: StaffOrgAiMargin\n /** Tokens by kind (AGL-2937); absent from a route older than the card. */\n tokens?: StaffOrgAiTokens\n /**\n * The OWNER's Free account allowance (AGL-3595), shared across every free\n * workspace they own: `null` for a paid workspace and for a free one that\n * names no owner; absent from a route older than give-backs.\n */\n account?: StaffAiAccountAllowance | null\n}\n\n/**\n * One account's Free allowance this month (AGL-2925's per-person meter), as\n * the staff org card and the staff user card show it.\n */\nexport interface StaffAiAccountAllowance {\n uid: string\n month: string\n /** Credits drawn, net of any given back — the figure the gate refuses on. */\n usedCredits: number\n /** The allowance: `FREE_AI_TASTE_CREDITS_PER_MONTH`. */\n limitCredits: number\n /** Credits given back to this month's allowance. */\n returnedCredits: number\n}\n\n/** The allowance off `users/{uid}/aiUsage/{month}`'s data (absent reads as none spent). */\nexport function composeStaffAiAccountAllowance(\n uid: string,\n month: string,\n monthDoc: Record<string, unknown> | null | undefined,\n): StaffAiAccountAllowance {\n return {\n uid,\n month,\n usedCredits: assistCreditsFromUsd(assistMonthSpendUsd(monthDoc)),\n limitCredits: FREE_AI_TASTE_CREDITS_PER_MONTH,\n returnedCredits: assistReturnedCredits(monthDoc),\n }\n}\n\nconst finite = (value: unknown): number => {\n const parsed = Number(value ?? 0)\n return Number.isFinite(parsed) && parsed > 0 ? parsed : 0\n}\n\n/** The UTC day-of-month and month length `projectMonthEnd` scales by. */\nexport function monthProgress(now: Date): { elapsed: number; length: number } {\n const year = now.getUTCFullYear()\n const month = now.getUTCMonth()\n return {\n elapsed: now.getUTCDate(),\n length: new Date(Date.UTC(year, month + 1, 0)).getUTCDate(),\n }\n}\n\n/**\n * Where a month-to-date figure lands at month end at the pace so far.\n *\n * Linear, on whole UTC days, and deliberately no cleverer: the reader is\n * asking \"will this org clear its band\" and a straight line answers that\n * within the margin the question needs. Day one projects the first day's\n * spend across the month, which over-reads a burst — the direction a\n * monitoring figure should err in.\n */\nexport function projectMonthEnd(usedSoFar: number, now: Date): number {\n const { elapsed, length } = monthProgress(now)\n if (!(usedSoFar > 0) || elapsed <= 0) return 0\n return Math.ceil((usedSoFar / elapsed) * length)\n}\n\n/** The staff override of the assist band, or null when none is written. */\nfunction assistBandOverride(\n org: Partial<AglynOrgBilling> | null | undefined,\n): number | null {\n const raw = (org?.entitlements as Record<string, unknown> | undefined)?.[\n 'assistCreditsPerMonth'\n ]\n if (typeof raw !== 'number' || !Number.isFinite(raw) || raw < 0) return null\n return Math.floor(raw)\n}\n\nexport function composeStaffOrgAiPool(\n org: Partial<AglynOrgBilling> | null | undefined,\n monthDoc: Record<string, unknown> | null | undefined,\n now: Date,\n): StaffOrgAiPool {\n const plan = resolveEffectivePlan(org) as OrgPlan\n const addon = hasAiAddon(org)\n // Two questions, two figures (AGL-3015). What the workspace DREW is the\n // billed figure, because that is what its credits came out of and what its\n // band is measured in. What the month COST US is the provider figure, and\n // it is the only one a margin below may be taken against.\n //\n // What it drew is NET of credits staff gave back (AGL-3595), which is the\n // figure the gate admits on and the invoice prices; what it cost is not,\n // because a give-back refunds the customer, not the model provider.\n const grossBilledUsd = finite(monthDoc?.['estCostUsd'])\n const billedUsd = assistMonthSpendUsd(monthDoc)\n const providerUsd = assistProviderCostUsd(\n grossBilledUsd,\n monthDoc?.[ASSIST_PROVIDER_COST_FIELD],\n )\n const usedCredits = assistCreditsFromUsd(billedUsd)\n const totalCredits = resolveAssistCreditBudget(org)\n const projectedCredits = projectMonthEnd(usedCredits, now)\n return {\n planCredits: finite(PLAN_ENTITLEMENTS[plan]?.assistCreditsPerMonth),\n overrideCredits: assistBandOverride(org),\n addonCredits: addon ? AI_ADDON_CREDITS_PER_MONTH[plan] : 0,\n totalCredits,\n uncapped: isUncappedPlanComp(org),\n usedCredits,\n returnedCredits: assistReturnedCredits(monthDoc),\n billedUsd,\n providerUsd,\n remainingCredits:\n totalCredits === null ? null : Math.max(0, totalCredits - usedCredits),\n projectedCredits,\n projectedUsd: assistUsdFromCredits(projectedCredits),\n messages: Math.floor(finite(monthDoc?.['messages'])),\n deflected: Math.floor(finite(monthDoc?.['deflected'])),\n }\n}\n\n/**\n * The month's tokens as the card reads them: the totals the month document\n * has always kept, and its `kinds` map (`model/ai-tokens.ts`) with the cost\n * per request and the cache hit rate worked out per kind.\n */\nexport function composeStaffOrgAiTokens(\n monthDoc: Record<string, unknown> | null | undefined,\n): StaffOrgAiTokens {\n const total: AiTokenTotals = {\n input: Math.floor(finite(monthDoc?.['inputTokens'])),\n cached: Math.floor(finite(monthDoc?.['cacheReadTokens'])),\n cacheWrite: Math.floor(finite(monthDoc?.['cacheWriteTokens'])),\n output: Math.floor(finite(monthDoc?.['outputTokens'])),\n }\n return {\n total,\n cacheHitRate: aiCacheHitRate(total),\n kinds: readAiKindMonths(monthDoc?.[AI_USAGE_MONTH_KINDS_FIELD]).map((row) => ({\n ...row,\n costPerRequestUsd:\n row.requests > 0\n ? Math.round((row.providerCostUsd / row.requests) * 1_000_000) / 1_000_000\n : null,\n cacheHitRate: aiCacheHitRate(row.tokens),\n })),\n }\n}\n\n/**\n * Takes the BILLED figure, never the provider one (AGL-3015): every line\n * here is what the customer owes — credits past the band, the rate they sell\n * at, the dollars accrued, whether the org's own cap is reached — and an\n * invoice priced off our cost would bill a different month than the meter\n * the customer reads.\n */\nexport function composeStaffOrgAiOverage(\n org: Partial<AglynOrgBilling> | null | undefined,\n billedUsd: number,\n): StaffOrgAiOverage {\n const overage = assistMonthOverage(org, billedUsd)\n return {\n overageCredits: overage.overageCredits,\n rateUsdPer1k: overage.overageRateUsd,\n accruedUsd: overage.overageMonthlyUsd,\n sellsOverage: overage.bandCredits !== null && overage.overageRateUsd !== null,\n hardCap: resolveAssistHardCap(org),\n capUsd: resolveAssistOverageCapUsd(org),\n capReached: assistOverageCapReached(org, billedUsd),\n bandRefuses: assistBandRefuses(org),\n }\n}\n\nexport function composeStaffOrgAiAddon(\n org: Partial<AglynOrgBilling> | null | undefined,\n since: Pick<StaffOrgAiAddon, 'since' | 'sinceSource'>,\n): StaffOrgAiAddon {\n return {\n on: hasAiAddon(org),\n // What this workspace would be charged — none for a staff comp, which\n // has no subscription to carry the item (AGL-3034).\n priceUsd: resolvePlanPricing(org).aiAddonMonthlyUsd ?? null,\n ...since,\n }\n}\n\nexport function composeStaffOrgAiMargin(input: {\n org: Partial<AglynOrgBilling> | null | undefined\n pool: StaffOrgAiPool\n overage: StaffOrgAiOverage\n addon: StaffOrgAiAddon\n thresholdUsd: number\n multiple: number\n contribution: StaffOrgAiMargin['contribution']\n}): StaffOrgAiMargin {\n const { pool, overage, addon } = input\n const addonRevenueUsd = addon.on ? finite(addon.priceUsd) : 0\n // The band the PLAN sells — the override, when one is written, is what the\n // customer was actually given, so it is the figure the price is carrying.\n const planAssistShareUsd = assistUsdFromCredits(\n pool.overrideCredits ?? pool.planCredits,\n )\n const overageRevenueUsd = overage.accruedUsd\n // THE ONE PLACE THE TWO RATES MEET (AGL-3015). Revenue is what the\n // customer pays — an add-on price, a band sized in credits, overage at the\n // retail rate — and spend is what the provider charges us. Taking both\n // sides off one figure was what made a fully-drawn band read as exactly\n // break-even no matter what the tokens actually cost.\n const revenueUsd =\n Math.round((addonRevenueUsd + planAssistShareUsd + overageRevenueUsd) * 100) /\n 100\n return {\n addonRevenueUsd,\n planAssistShareUsd,\n overageRevenueUsd,\n revenueUsd,\n spendUsd: pool.providerUsd,\n underwater: pool.providerUsd > addonRevenueUsd + planAssistShareUsd,\n thresholdUsd: input.thresholdUsd,\n multiple: input.multiple,\n contribution: input.contribution,\n }\n}\n\nexport const STAFF_ORG_AI_JOB_STATUSES: readonly StaffOrgAiJobStatus[] = [\n 'queued',\n 'running',\n 'needs_input',\n 'needs_review',\n 'done',\n 'failed',\n 'canceled',\n]\n\n/** Zero-filled counts, so the card renders every status by name. */\nexport function emptyJobCounts(): Record<StaffOrgAiJobStatus, number> {\n return Object.fromEntries(\n STAFF_ORG_AI_JOB_STATUSES.map((status) => [status, 0]),\n ) as Record<StaffOrgAiJobStatus, number>\n}\n"],"names":["ASSIST_PROVIDER_COST_FIELD","assistBandRefuses","assistCreditsFromUsd","assistProviderCostUsd","assistMonthOverage","assistOverageCapReached","assistUsdFromCredits","resolveAssistCreditBudget","resolveAssistHardCap","resolveAssistOverageCapUsd","AI_ADDON_CREDITS_PER_MONTH","hasAiAddon","isUncappedPlanComp","PLAN_ENTITLEMENTS","resolveEffectivePlan","resolvePlanPricing","assistMonthSpendUsd","assistReturnedCredits","FREE_AI_TASTE_CREDITS_PER_MONTH","AI_USAGE_MONTH_KINDS_FIELD","aiCacheHitRate","readAiKindMonths","composeStaffAiAccountAllowance","uid","month","monthDoc","usedCredits","limitCredits","returnedCredits","finite","value","parsed","Number","isFinite","monthProgress","now","year","getUTCFullYear","getUTCMonth","elapsed","getUTCDate","length","Date","UTC","projectMonthEnd","usedSoFar","Math","ceil","assistBandOverride","org","raw","entitlements","floor","composeStaffOrgAiPool","plan","addon","grossBilledUsd","billedUsd","providerUsd","totalCredits","projectedCredits","planCredits","assistCreditsPerMonth","overrideCredits","addonCredits","uncapped","remainingCredits","max","projectedUsd","messages","deflected","composeStaffOrgAiTokens","total","input","cached","cacheWrite","output","cacheHitRate","kinds","map","row","costPerRequestUsd","requests","round","providerCostUsd","tokens","composeStaffOrgAiOverage","overage","overageCredits","rateUsdPer1k","overageRateUsd","accruedUsd","overageMonthlyUsd","sellsOverage","bandCredits","hardCap","capUsd","capReached","bandRefuses","composeStaffOrgAiAddon","since","on","priceUsd","aiAddonMonthlyUsd","composeStaffOrgAiMargin","pool","addonRevenueUsd","planAssistShareUsd","overageRevenueUsd","revenueUsd","spendUsd","underwater","thresholdUsd","multiple","contribution","STAFF_ORG_AI_JOB_STATUSES","emptyJobCounts","Object","fromEntries","status"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/usage/staff-org-ai.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Imported from BOTH graphs — the staff card (client) and the App Route that\n// serves it — so it may import neither entry barrel, for the reason\n// `margin-utilization.ts` states at its own head.\nimport type { AiJobStatus } from '../model/ai-jobs.types'\nimport type { AglynOrgBilling, AiTierId, OrgPlan } from '@aglyn/aglyn/foundation'\nimport {\n ASSIST_PROVIDER_COST_FIELD,\n assistBandRefuses,\n assistCreditsFromUsd,\n assistProviderCostUsd,\n assistMonthOverage,\n assistOverageCapReached,\n assistUsdFromCredits,\n resolveAssistCreditBudget,\n resolveAssistHardCap,\n resolveAssistOverageCapUsd,\n} from './assist-credits'\nimport {\n AI_TIERS,\n aiTierCreditsPerMonth,\n hasAiAddon,\n isUncappedPlanComp,\n PLAN_ENTITLEMENTS,\n resolveAiTier,\n resolveEffectivePlan,\n type DiscountMarginRating,\n} from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport type { AssistRefusalCounts } from './assist-refusals'\nimport { assistMonthSpendUsd, assistReturnedCredits } from './assist-credit-returns'\nimport { FREE_AI_TASTE_CREDITS_PER_MONTH } from '../plan-entitlements'\nimport {\n AI_USAGE_MONTH_KINDS_FIELD,\n aiCacheHitRate,\n readAiKindMonths,\n type AiKindMonth,\n type AiTokenTotals,\n} from '../model/ai-tokens'\n\n/**\n * THE STAFF AI CARD'S FIGURES (AGL-2930), composed from the same helpers the\n * meter, the invoice and the customer's own billing page read.\n *\n * Nothing here runs a query and nothing here holds a rate of its own. The\n * pool is `resolveAssistCreditBudget`, the overage is `assistMonthOverage`,\n * the ceiling is `assistOverageCapReached` — the ONE derivation each of those\n * already is — so the card cannot show a staff member a different band,\n * rate or ceiling from the one the gate refused a customer at.\n */\n\n/** The four ways the reservation refuses, plus their sum. */\nexport type StaffOrgAiRefusals = AssistRefusalCounts\n\nexport interface StaffOrgAiAddon {\n /** Bought and on a subscription still paying for it — `hasAiAddon`. */\n on: boolean\n /** The Aglyn AI tier held (AGL-3965), or null for none. */\n tier: AiTierId | null\n /** The held tier's list price, or null with no tier. */\n priceUsd: number | null\n /** ISO instant the subscription item was created, when Stripe could say. */\n since: string | null\n /**\n * Where `since` came from, so an absent date is legible: `stripe` answered,\n * the org has no Stripe subscription to ask, or Stripe could not be asked\n * (no key on this deployment, or the lookup failed).\n */\n sinceSource: 'stripe' | 'no-subscription' | 'unavailable'\n}\n\nexport interface StaffOrgAiPool {\n /** The plan's own band, from the catalogue row. */\n planCredits: number\n /** A staff override of the band, or null when none is written. */\n overrideCredits: number | null\n /** The add-on's band, 0 without the add-on. */\n addonCredits: number\n /** The whole pool as the meter resolves it, or null where no band applies. */\n totalCredits: number | null\n /**\n * Whether an uncapped staff comp is why no band applies (AGL-3049) — a\n * boolean, so \"no band because uncapped\" crosses JSON as itself rather\n * than as the `null` an `Infinity` would become. Absent from a route older\n * than the flag.\n */\n uncapped?: boolean\n /**\n * Credits drawn this month, rounded up from the measured spend, net of\n * any staff gave back.\n */\n usedCredits: number\n /**\n * Credits given back to this month's band (AGL-3595). Optional: a route\n * older than give-backs does not send it.\n */\n returnedCredits?: number\n /**\n * What the month DREW, at the catalog's billed rates — the dollars\n * `usedCredits` is rounded up from, and the figure the overage line is\n * priced off. Not a cost (AGL-3015).\n */\n billedUsd: number\n /**\n * What the month COST US, at the provider's own rates: the one figure a\n * margin or a spend alert may be taken against.\n */\n providerUsd: number\n /** Left in the pool, clamped at zero, or null where no band applies. */\n remainingCredits: number | null\n /** Where `usedCredits` lands at month end at the pace so far. */\n projectedCredits: number\n /** `projectedCredits` back in dollars — billed, as the credits were. */\n projectedUsd: number\n /** Model turns and docs-deflected turns, off the month document. */\n messages: number\n deflected: number\n}\n\nexport interface StaffOrgAiOverage {\n overageCredits: number\n rateUsdPer1k: number | null\n accruedUsd: number\n /** Whether credits past the band are sold at all on this org. */\n sellsOverage: boolean\n /** The org's own band switch — `assistOverage.hardCap`. */\n hardCap: boolean\n /** The org's own dollar ceiling on overage — `assistOverage.capUsd`. */\n capUsd: number | null\n capReached: boolean\n /** Whether the band is a wall for any reason (the switch, or no rate). */\n bandRefuses: boolean\n}\n\nexport interface StaffOrgAiMargin {\n /** The add-on's list price when it is on, else 0. */\n addonRevenueUsd: number\n /**\n * The dollar value of the plan's own band — what the plan price sets aside\n * for assist. Each plan band is sized against the billed rate (a thousand\n * credits are a dollar), so this is the share of the subscription that was\n * sold as AI, and it belongs on the REVENUE side beside the add-on's price.\n */\n planAssistShareUsd: number\n /** Overage priced at the plan's rate, which the invoice will bill. */\n overageRevenueUsd: number\n /** The three above, summed. */\n revenueUsd: number\n /** What the month cost us: measured provider spend, never what it drew. */\n spendUsd: number\n /**\n * Spend past what the add-on and the plan's assist share bring in — the\n * condition the card renders red. Overage revenue is left OUT of the\n * comparison on purpose: it is billed at a margin, so an org deep in\n * overage is not the one losing money.\n */\n underwater: boolean\n /** The staff review threshold and the multiple of it the spend has reached. */\n thresholdUsd: number\n multiple: number\n /** The whole-org contribution margin, when a usage rollup exists. */\n contribution: {\n month: string | null\n marginPct: number | null\n rating: DiscountMarginRating | null\n }\n}\n\n/** The job model's own status set: the card renders every one by name. */\nexport type StaffOrgAiJobStatus = AiJobStatus\n\nexport interface StaffOrgAiJob {\n id: string\n kind: string\n status: StaffOrgAiJobStatus | string\n creditsReserved: number\n creditsSpent: number\n createdAt: string | null\n createdBy: string | null\n}\n\nexport interface StaffOrgAiJobs {\n counts: Record<StaffOrgAiJobStatus, number>\n recent: StaffOrgAiJob[]\n /** True when the scan stopped at its ceiling — the counts are a floor. */\n truncated: boolean\n}\n\n/** One row of the per-user rollup (AGL-2928), as the staff card lists it. */\nexport interface StaffOrgAiUser {\n uid: string\n /** The roster's name for them — display name, else email, else the uid. */\n name: string\n credits: number\n /** The measured provider spend behind `credits`; staff may see dollars. */\n estCostUsd: number\n /** Of the org's measured spend for the month, in `[0, 1]`. */\n share: number\n requests: number\n refusals: number\n byKind: Record<string, number>\n byHost: Record<string, number>\n}\n\n/** One kind of model request this month, as the card lists it (AGL-2937). */\nexport interface StaffOrgAiKindTokens extends AiKindMonth {\n /**\n * What one request of this kind COST US on average; `null` with none. The\n * provider figure, not what the kind drew — the reader is sizing a step\n * against a bill, and a marked-up model would otherwise make the step look\n * dearer to run than it is (AGL-3015).\n */\n costPerRequestUsd: number | null\n /** The share of this kind's prompt tokens the cache served — `aiCacheHitRate`. */\n cacheHitRate: number | null\n}\n\n/** The month's tokens (AGL-2937): its totals, its cache hit rate, and each kind. */\nexport interface StaffOrgAiTokens {\n /** The month document's own four totals. */\n total: AiTokenTotals\n cacheHitRate: number | null\n /** Dearest first; empty for a month written before kinds were kept. */\n kinds: StaffOrgAiKindTokens[]\n}\n\nexport interface StaffOrgAiResponse {\n month: string\n addon: StaffOrgAiAddon\n pool: StaffOrgAiPool\n overage: StaffOrgAiOverage\n refusals: StaffOrgAiRefusals\n /** Null when the jobs read failed — distinct from an org with no jobs. */\n jobs: StaffOrgAiJobs | null\n /** The dearest people this month; empty when nobody's month is written. */\n users: StaffOrgAiUser[]\n margin: StaffOrgAiMargin\n /** Tokens by kind (AGL-2937); absent from a route older than the card. */\n tokens?: StaffOrgAiTokens\n /**\n * The OWNER's Free account allowance (AGL-3595), shared across every free\n * workspace they own: `null` for a paid workspace and for a free one that\n * names no owner; absent from a route older than give-backs.\n */\n account?: StaffAiAccountAllowance | null\n}\n\n/**\n * One account's Free allowance this month (AGL-2925's per-person meter), as\n * the staff org card and the staff user card show it.\n */\nexport interface StaffAiAccountAllowance {\n uid: string\n month: string\n /** Credits drawn, net of any given back — the figure the gate refuses on. */\n usedCredits: number\n /** The allowance: `FREE_AI_TASTE_CREDITS_PER_MONTH`. */\n limitCredits: number\n /** Credits given back to this month's allowance. */\n returnedCredits: number\n}\n\n/** The allowance off `users/{uid}/aiUsage/{month}`'s data (absent reads as none spent). */\nexport function composeStaffAiAccountAllowance(\n uid: string,\n month: string,\n monthDoc: Record<string, unknown> | null | undefined,\n): StaffAiAccountAllowance {\n return {\n uid,\n month,\n usedCredits: assistCreditsFromUsd(assistMonthSpendUsd(monthDoc)),\n limitCredits: FREE_AI_TASTE_CREDITS_PER_MONTH,\n returnedCredits: assistReturnedCredits(monthDoc),\n }\n}\n\nconst finite = (value: unknown): number => {\n const parsed = Number(value ?? 0)\n return Number.isFinite(parsed) && parsed > 0 ? parsed : 0\n}\n\n/** The UTC day-of-month and month length `projectMonthEnd` scales by. */\nexport function monthProgress(now: Date): { elapsed: number; length: number } {\n const year = now.getUTCFullYear()\n const month = now.getUTCMonth()\n return {\n elapsed: now.getUTCDate(),\n length: new Date(Date.UTC(year, month + 1, 0)).getUTCDate(),\n }\n}\n\n/**\n * Where a month-to-date figure lands at month end at the pace so far.\n *\n * Linear, on whole UTC days, and deliberately no cleverer: the reader is\n * asking \"will this org clear its band\" and a straight line answers that\n * within the margin the question needs. Day one projects the first day's\n * spend across the month, which over-reads a burst — the direction a\n * monitoring figure should err in.\n */\nexport function projectMonthEnd(usedSoFar: number, now: Date): number {\n const { elapsed, length } = monthProgress(now)\n if (!(usedSoFar > 0) || elapsed <= 0) return 0\n return Math.ceil((usedSoFar / elapsed) * length)\n}\n\n/** The staff override of the assist band, or null when none is written. */\nfunction assistBandOverride(\n org: Partial<AglynOrgBilling> | null | undefined,\n): number | null {\n const raw = (org?.entitlements as Record<string, unknown> | undefined)?.[\n 'assistCreditsPerMonth'\n ]\n if (typeof raw !== 'number' || !Number.isFinite(raw) || raw < 0) return null\n return Math.floor(raw)\n}\n\nexport function composeStaffOrgAiPool(\n org: Partial<AglynOrgBilling> | null | undefined,\n monthDoc: Record<string, unknown> | null | undefined,\n now: Date,\n): StaffOrgAiPool {\n const plan = resolveEffectivePlan(org) as OrgPlan\n // Two questions, two figures (AGL-3015). What the workspace DREW is the\n // billed figure, because that is what its credits came out of and what its\n // band is measured in. What the month COST US is the provider figure, and\n // it is the only one a margin below may be taken against.\n //\n // What it drew is NET of credits staff gave back (AGL-3595), which is the\n // figure the gate admits on and the invoice prices; what it cost is not,\n // because a give-back refunds the customer, not the model provider.\n const grossBilledUsd = finite(monthDoc?.['estCostUsd'])\n const billedUsd = assistMonthSpendUsd(monthDoc)\n const providerUsd = assistProviderCostUsd(\n grossBilledUsd,\n monthDoc?.[ASSIST_PROVIDER_COST_FIELD],\n )\n const usedCredits = assistCreditsFromUsd(billedUsd)\n const totalCredits = resolveAssistCreditBudget(org)\n const projectedCredits = projectMonthEnd(usedCredits, now)\n return {\n planCredits: finite(PLAN_ENTITLEMENTS[plan]?.assistCreditsPerMonth),\n overrideCredits: assistBandOverride(org),\n addonCredits: aiTierCreditsPerMonth(org),\n totalCredits,\n uncapped: isUncappedPlanComp(org),\n usedCredits,\n returnedCredits: assistReturnedCredits(monthDoc),\n billedUsd,\n providerUsd,\n remainingCredits:\n totalCredits === null ? null : Math.max(0, totalCredits - usedCredits),\n projectedCredits,\n projectedUsd: assistUsdFromCredits(projectedCredits),\n messages: Math.floor(finite(monthDoc?.['messages'])),\n deflected: Math.floor(finite(monthDoc?.['deflected'])),\n }\n}\n\n/**\n * The month's tokens as the card reads them: the totals the month document\n * has always kept, and its `kinds` map (`model/ai-tokens.ts`) with the cost\n * per request and the cache hit rate worked out per kind.\n */\nexport function composeStaffOrgAiTokens(\n monthDoc: Record<string, unknown> | null | undefined,\n): StaffOrgAiTokens {\n const total: AiTokenTotals = {\n input: Math.floor(finite(monthDoc?.['inputTokens'])),\n cached: Math.floor(finite(monthDoc?.['cacheReadTokens'])),\n cacheWrite: Math.floor(finite(monthDoc?.['cacheWriteTokens'])),\n output: Math.floor(finite(monthDoc?.['outputTokens'])),\n }\n return {\n total,\n cacheHitRate: aiCacheHitRate(total),\n kinds: readAiKindMonths(monthDoc?.[AI_USAGE_MONTH_KINDS_FIELD]).map((row) => ({\n ...row,\n costPerRequestUsd:\n row.requests > 0\n ? Math.round((row.providerCostUsd / row.requests) * 1_000_000) / 1_000_000\n : null,\n cacheHitRate: aiCacheHitRate(row.tokens),\n })),\n }\n}\n\n/**\n * Takes the BILLED figure, never the provider one (AGL-3015): every line\n * here is what the customer owes — credits past the band, the rate they sell\n * at, the dollars accrued, whether the org's own cap is reached — and an\n * invoice priced off our cost would bill a different month than the meter\n * the customer reads.\n */\nexport function composeStaffOrgAiOverage(\n org: Partial<AglynOrgBilling> | null | undefined,\n billedUsd: number,\n): StaffOrgAiOverage {\n const overage = assistMonthOverage(org, billedUsd)\n return {\n overageCredits: overage.overageCredits,\n rateUsdPer1k: overage.overageRateUsd,\n accruedUsd: overage.overageMonthlyUsd,\n sellsOverage: overage.bandCredits !== null && overage.overageRateUsd !== null,\n hardCap: resolveAssistHardCap(org),\n capUsd: resolveAssistOverageCapUsd(org),\n capReached: assistOverageCapReached(org, billedUsd),\n bandRefuses: assistBandRefuses(org),\n }\n}\n\nexport function composeStaffOrgAiAddon(\n org: Partial<AglynOrgBilling> | null | undefined,\n since: Pick<StaffOrgAiAddon, 'since' | 'sinceSource'>,\n): StaffOrgAiAddon {\n // The tier held and what it is charged at (AGL-3965); a staff comp has\n // no subscription to carry one (AGL-3034), so it reads none.\n const tier = resolveAiTier(org)\n return {\n on: hasAiAddon(org),\n tier,\n priceUsd: tier ? AI_TIERS[tier].monthlyUsd : null,\n ...since,\n }\n}\n\nexport function composeStaffOrgAiMargin(input: {\n org: Partial<AglynOrgBilling> | null | undefined\n pool: StaffOrgAiPool\n overage: StaffOrgAiOverage\n addon: StaffOrgAiAddon\n thresholdUsd: number\n multiple: number\n contribution: StaffOrgAiMargin['contribution']\n}): StaffOrgAiMargin {\n const { pool, overage, addon } = input\n const addonRevenueUsd = addon.on ? finite(addon.priceUsd) : 0\n // The band the PLAN sells — the override, when one is written, is what the\n // customer was actually given, so it is the figure the price is carrying.\n const planAssistShareUsd = assistUsdFromCredits(\n pool.overrideCredits ?? pool.planCredits,\n )\n const overageRevenueUsd = overage.accruedUsd\n // THE ONE PLACE THE TWO RATES MEET (AGL-3015). Revenue is what the\n // customer pays — an add-on price, a band sized in credits, overage at the\n // retail rate — and spend is what the provider charges us. Taking both\n // sides off one figure was what made a fully-drawn band read as exactly\n // break-even no matter what the tokens actually cost.\n const revenueUsd =\n Math.round((addonRevenueUsd + planAssistShareUsd + overageRevenueUsd) * 100) /\n 100\n return {\n addonRevenueUsd,\n planAssistShareUsd,\n overageRevenueUsd,\n revenueUsd,\n spendUsd: pool.providerUsd,\n underwater: pool.providerUsd > addonRevenueUsd + planAssistShareUsd,\n thresholdUsd: input.thresholdUsd,\n multiple: input.multiple,\n contribution: input.contribution,\n }\n}\n\nexport const STAFF_ORG_AI_JOB_STATUSES: readonly StaffOrgAiJobStatus[] = [\n 'queued',\n 'running',\n 'needs_input',\n 'needs_review',\n 'done',\n 'failed',\n 'canceled',\n]\n\n/** Zero-filled counts, so the card renders every status by name. */\nexport function emptyJobCounts(): Record<StaffOrgAiJobStatus, number> {\n return Object.fromEntries(\n STAFF_ORG_AI_JOB_STATUSES.map((status) => [status, 0]),\n ) as Record<StaffOrgAiJobStatus, number>\n}\n"],"names":["ASSIST_PROVIDER_COST_FIELD","assistBandRefuses","assistCreditsFromUsd","assistProviderCostUsd","assistMonthOverage","assistOverageCapReached","assistUsdFromCredits","resolveAssistCreditBudget","resolveAssistHardCap","resolveAssistOverageCapUsd","AI_TIERS","aiTierCreditsPerMonth","hasAiAddon","isUncappedPlanComp","PLAN_ENTITLEMENTS","resolveAiTier","resolveEffectivePlan","assistMonthSpendUsd","assistReturnedCredits","FREE_AI_TASTE_CREDITS_PER_MONTH","AI_USAGE_MONTH_KINDS_FIELD","aiCacheHitRate","readAiKindMonths","composeStaffAiAccountAllowance","uid","month","monthDoc","usedCredits","limitCredits","returnedCredits","finite","value","parsed","Number","isFinite","monthProgress","now","year","getUTCFullYear","getUTCMonth","elapsed","getUTCDate","length","Date","UTC","projectMonthEnd","usedSoFar","Math","ceil","assistBandOverride","org","raw","entitlements","floor","composeStaffOrgAiPool","plan","grossBilledUsd","billedUsd","providerUsd","totalCredits","projectedCredits","planCredits","assistCreditsPerMonth","overrideCredits","addonCredits","uncapped","remainingCredits","max","projectedUsd","messages","deflected","composeStaffOrgAiTokens","total","input","cached","cacheWrite","output","cacheHitRate","kinds","map","row","costPerRequestUsd","requests","round","providerCostUsd","tokens","composeStaffOrgAiOverage","overage","overageCredits","rateUsdPer1k","overageRateUsd","accruedUsd","overageMonthlyUsd","sellsOverage","bandCredits","hardCap","capUsd","capReached","bandRefuses","composeStaffOrgAiAddon","since","tier","on","priceUsd","monthlyUsd","composeStaffOrgAiMargin","pool","addon","addonRevenueUsd","planAssistShareUsd","overageRevenueUsd","revenueUsd","spendUsd","underwater","thresholdUsd","multiple","contribution","STAFF_ORG_AI_JOB_STATUSES","emptyJobCounts","Object","fromEntries","status"],"mappings":"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"}
@@ -14,7 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */ import { _ as _extends } from "@swc/helpers/_/_extends";
17
- import { AI_ADDON_CREDITS_PER_MONTH, aiAddonName, hasAiAddon, isUncappedPlanComp, resolveEffectivePlan, resolveOrgEntitlements } from "@aglyn/aglyn/app-utils/plan-entitlements";
17
+ import { aiAddonName, aiTierCreditsPerMonth, hasAiAddon, isUncappedPlanComp, resolveOrgEntitlements } from "@aglyn/aglyn/app-utils/plan-entitlements";
18
18
  /**
19
19
  * The Assist column's text. Four decimals, not two: spend arrives in
20
20
  * thousandths of a dollar per exchange, and `$0.00` for a month that really
@@ -39,7 +39,7 @@ import { AI_ADDON_CREDITS_PER_MONTH, aiAddonName, hasAiAddon, isUncappedPlanComp
39
39
  const creditsPerMonth = resolveOrgEntitlements(org).assistCreditsPerMonth;
40
40
  return _extends({
41
41
  aiAddon,
42
- addonCredits: aiAddon ? AI_ADDON_CREDITS_PER_MONTH[resolveEffectivePlan(org)] : 0,
42
+ addonCredits: aiTierCreditsPerMonth(org),
43
43
  // Finite only: an uncapped comp resolves the band `UNLIMITED`, which is
44
44
  // no band to name a figure for.
45
45
  creditsPerMonth: Number.isFinite(creditsPerMonth) && creditsPerMonth > 0 ? creditsPerMonth : null
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/usage/staff-org-usage-ai.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AglynOrgBilling } from '@aglyn/aglyn'\nimport {\n AI_ADDON_CREDITS_PER_MONTH,\n aiAddonName,\n hasAiAddon,\n isUncappedPlanComp,\n resolveEffectivePlan,\n resolveOrgEntitlements,\n} from '@aglyn/aglyn/app-utils/plan-entitlements'\n\n/**\n * THE STAFF ORG USAGE TABLE'S AI FIGURES (AGL-2984).\n *\n * The staff org usage table — on the staff org page and in the Organizations\n * list's Usage dialog — draws this plugin's three columns through its\n * `staffOrgUsageColumn` zone, and the org page draws the credit pool line\n * above it. These are the texts they draw, apart from React so each shape\n * is pinned on its own.\n */\n\n/** The AI fields of one monthly rollup row, as the org usage route serves it. */\nexport interface StaffOrgUsageAiMonth {\n /**\n * Provider spend for the month in dollars (AGL-2280). Its own column rather\n * than folded into the metering estimate: this is a real provider bill.\n */\n assistCostUsd?: number | null\n /**\n * The credit view of the same spend (AGL-2930): what the month drew\n * against the band, and the part past it that was billed. `null` on a\n * rollup written before the credit meter existed — a `0` there would\n * state, as measurement, that the org used no AI in a month nobody\n * measured.\n */\n assistCredits?: number | null\n assistOverageUsd?: number | null\n}\n\n/**\n * The Assist column's text. Four decimals, not two: spend arrives in\n * thousandths of a dollar per exchange, and `$0.00` for a month that really\n * cost eight cents is a silence.\n */\nexport function assistCostCell(value: number | null | undefined): string {\n return `$${Number(value ?? 0).toFixed(4)}`\n}\n\n/** A recorded count, or the dash that says the rollup predates it. */\nexport function aiCreditsCell(value: number | null | undefined): string {\n return value == null ? '—' : Math.round(value).toLocaleString()\n}\n\n/** A billed dollar figure, or the dash that says the rollup predates it. */\nexport function aiOverageCell(value: number | null | undefined): string {\n return value == null ? '—' : `$${Number(value).toFixed(2)}`\n}\n\n/**\n * The org's AI credit pool (AGL-2899): whether the AI add-on is on, the\n * credits the add-on contributes, and the whole band the Assist column is\n * drawn against.\n */\nexport interface StaffAssistPool {\n aiAddon: boolean\n /** The add-on's band alone; 0 without the add-on. */\n addonCredits: number\n /** Plan band plus add-on band, or null where the plan sells no band. */\n creditsPerMonth: number | null\n /**\n * Present, and true, only where an uncapped staff comp lifts the band\n * (AGL-3049) — the other reason `creditsPerMonth` is null.\n */\n uncapped?: true\n}\n\n/**\n * The pool as the meter resolves it from the org document: the plan's band\n * plus the add-on's, folded by `resolveOrgEntitlements`, with the add-on\n * read off the EFFECTIVE plan through `hasAiAddon` — so a dead\n * subscription's add-on reads as off here exactly as it does on the\n * customer's own meter.\n */\nexport function staffAssistPool(\n org: Partial<AglynOrgBilling> | null | undefined,\n): StaffAssistPool {\n const aiAddon = hasAiAddon(org)\n const creditsPerMonth = resolveOrgEntitlements(org).assistCreditsPerMonth\n return {\n aiAddon,\n addonCredits: aiAddon\n ? AI_ADDON_CREDITS_PER_MONTH[resolveEffectivePlan(org)]\n : 0,\n // Finite only: an uncapped comp resolves the band `UNLIMITED`, which is\n // no band to name a figure for.\n creditsPerMonth:\n Number.isFinite(creditsPerMonth) && creditsPerMonth > 0\n ? creditsPerMonth\n : null,\n ...(isUncappedPlanComp(org) ? { uncapped: true as const } : {}),\n }\n}\n\n/**\n * The pool as one line: the reader of the Assist column needs to know what\n * band the dollars are drawn against, and whether an add-on is part of it.\n */\nexport function assistPoolSentence(pool: StaffAssistPool): string {\n const band =\n pool.creditsPerMonth !== null\n ? `${pool.creditsPerMonth.toLocaleString()} AI credits/mo`\n : pool.uncapped\n ? 'no AI credit band (uncapped staff comp)'\n : 'no AI credit band'\n const name = aiAddonName()\n return pool.aiAddon\n ? `${name} add-on on — ${band}, ${pool.addonCredits.toLocaleString()} ` +\n 'of them from the add-on.'\n : `${name} add-on off — ${band}.`\n}\n"],"names":["AI_ADDON_CREDITS_PER_MONTH","aiAddonName","hasAiAddon","isUncappedPlanComp","resolveEffectivePlan","resolveOrgEntitlements","assistCostCell","value","Number","toFixed","aiCreditsCell","Math","round","toLocaleString","aiOverageCell","staffAssistPool","org","aiAddon","creditsPerMonth","assistCreditsPerMonth","addonCredits","isFinite","uncapped","assistPoolSentence","pool","band","name"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAGD,SACEA,0BAA0B,EAC1BC,WAAW,EACXC,UAAU,EACVC,kBAAkB,EAClBC,oBAAoB,EACpBC,sBAAsB,QACjB,2CAA0C;AA8BjD;;;;CAIC,GACD,OAAO,SAASC,eAAeC,KAAgC;IAC7D,OAAO,CAAC,CAAC,EAAEC,OAAOD,gBAAAA,QAAS,GAAGE,OAAO,CAAC,IAAI;AAC5C;AAEA,oEAAoE,GACpE,OAAO,SAASC,cAAcH,KAAgC;IAC5D,OAAOA,SAAS,OAAO,MAAMI,KAAKC,KAAK,CAACL,OAAOM,cAAc;AAC/D;AAEA,0EAA0E,GAC1E,OAAO,SAASC,cAAcP,KAAgC;IAC5D,OAAOA,SAAS,OAAO,MAAM,CAAC,CAAC,EAAEC,OAAOD,OAAOE,OAAO,CAAC,IAAI;AAC7D;AAoBA;;;;;;CAMC,GACD,OAAO,SAASM,gBACdC,GAAgD;IAEhD,MAAMC,UAAUf,WAAWc;IAC3B,MAAME,kBAAkBb,uBAAuBW,KAAKG,qBAAqB;IACzE,OAAO;QACLF;QACAG,cAAcH,UACVjB,0BAA0B,CAACI,qBAAqBY,KAAK,GACrD;QACJ,wEAAwE;QACxE,gCAAgC;QAChCE,iBACEV,OAAOa,QAAQ,CAACH,oBAAoBA,kBAAkB,IAClDA,kBACA;OACFf,mBAAmBa,OAAO;QAAEM,UAAU;IAAc,IAAI,CAAC;AAEjE;AAEA;;;CAGC,GACD,OAAO,SAASC,mBAAmBC,IAAqB;IACtD,MAAMC,OACJD,KAAKN,eAAe,KAAK,OACrB,GAAGM,KAAKN,eAAe,CAACL,cAAc,GAAG,cAAc,CAAC,GACxDW,KAAKF,QAAQ,GACX,4CACA;IACR,MAAMI,OAAOzB;IACb,OAAOuB,KAAKP,OAAO,GACf,GAAGS,KAAK,aAAa,EAAED,KAAK,EAAE,EAAED,KAAKJ,YAAY,CAACP,cAAc,GAAG,CAAC,CAAC,GACnE,6BACF,GAAGa,KAAK,cAAc,EAAED,KAAK,CAAC,CAAC;AACrC"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/usage/staff-org-usage-ai.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AglynOrgBilling } from '@aglyn/aglyn'\nimport {\n aiAddonName,\n aiTierCreditsPerMonth,\n hasAiAddon,\n isUncappedPlanComp,\n resolveOrgEntitlements,\n} from '@aglyn/aglyn/app-utils/plan-entitlements'\n\n/**\n * THE STAFF ORG USAGE TABLE'S AI FIGURES (AGL-2984).\n *\n * The staff org usage table — on the staff org page and in the Organizations\n * list's Usage dialog — draws this plugin's three columns through its\n * `staffOrgUsageColumn` zone, and the org page draws the credit pool line\n * above it. These are the texts they draw, apart from React so each shape\n * is pinned on its own.\n */\n\n/** The AI fields of one monthly rollup row, as the org usage route serves it. */\nexport interface StaffOrgUsageAiMonth {\n /**\n * Provider spend for the month in dollars (AGL-2280). Its own column rather\n * than folded into the metering estimate: this is a real provider bill.\n */\n assistCostUsd?: number | null\n /**\n * The credit view of the same spend (AGL-2930): what the month drew\n * against the band, and the part past it that was billed. `null` on a\n * rollup written before the credit meter existed — a `0` there would\n * state, as measurement, that the org used no AI in a month nobody\n * measured.\n */\n assistCredits?: number | null\n assistOverageUsd?: number | null\n}\n\n/**\n * The Assist column's text. Four decimals, not two: spend arrives in\n * thousandths of a dollar per exchange, and `$0.00` for a month that really\n * cost eight cents is a silence.\n */\nexport function assistCostCell(value: number | null | undefined): string {\n return `$${Number(value ?? 0).toFixed(4)}`\n}\n\n/** A recorded count, or the dash that says the rollup predates it. */\nexport function aiCreditsCell(value: number | null | undefined): string {\n return value == null ? '—' : Math.round(value).toLocaleString()\n}\n\n/** A billed dollar figure, or the dash that says the rollup predates it. */\nexport function aiOverageCell(value: number | null | undefined): string {\n return value == null ? '—' : `$${Number(value).toFixed(2)}`\n}\n\n/**\n * The org's AI credit pool (AGL-2899): whether the AI add-on is on, the\n * credits the add-on contributes, and the whole band the Assist column is\n * drawn against.\n */\nexport interface StaffAssistPool {\n aiAddon: boolean\n /** The add-on's band alone; 0 without the add-on. */\n addonCredits: number\n /** Plan band plus add-on band, or null where the plan sells no band. */\n creditsPerMonth: number | null\n /**\n * Present, and true, only where an uncapped staff comp lifts the band\n * (AGL-3049) — the other reason `creditsPerMonth` is null.\n */\n uncapped?: true\n}\n\n/**\n * The pool as the meter resolves it from the org document: the plan's band\n * plus the add-on's, folded by `resolveOrgEntitlements`, with the add-on\n * read off the EFFECTIVE plan through `hasAiAddon` — so a dead\n * subscription's add-on reads as off here exactly as it does on the\n * customer's own meter.\n */\nexport function staffAssistPool(\n org: Partial<AglynOrgBilling> | null | undefined,\n): StaffAssistPool {\n const aiAddon = hasAiAddon(org)\n const creditsPerMonth = resolveOrgEntitlements(org).assistCreditsPerMonth\n return {\n aiAddon,\n addonCredits: aiTierCreditsPerMonth(org),\n // Finite only: an uncapped comp resolves the band `UNLIMITED`, which is\n // no band to name a figure for.\n creditsPerMonth:\n Number.isFinite(creditsPerMonth) && creditsPerMonth > 0\n ? creditsPerMonth\n : null,\n ...(isUncappedPlanComp(org) ? { uncapped: true as const } : {}),\n }\n}\n\n/**\n * The pool as one line: the reader of the Assist column needs to know what\n * band the dollars are drawn against, and whether an add-on is part of it.\n */\nexport function assistPoolSentence(pool: StaffAssistPool): string {\n const band =\n pool.creditsPerMonth !== null\n ? `${pool.creditsPerMonth.toLocaleString()} AI credits/mo`\n : pool.uncapped\n ? 'no AI credit band (uncapped staff comp)'\n : 'no AI credit band'\n const name = aiAddonName()\n return pool.aiAddon\n ? `${name} add-on on — ${band}, ${pool.addonCredits.toLocaleString()} ` +\n 'of them from the add-on.'\n : `${name} add-on off — ${band}.`\n}\n"],"names":["aiAddonName","aiTierCreditsPerMonth","hasAiAddon","isUncappedPlanComp","resolveOrgEntitlements","assistCostCell","value","Number","toFixed","aiCreditsCell","Math","round","toLocaleString","aiOverageCell","staffAssistPool","org","aiAddon","creditsPerMonth","assistCreditsPerMonth","addonCredits","isFinite","uncapped","assistPoolSentence","pool","band","name"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAGD,SACEA,WAAW,EACXC,qBAAqB,EACrBC,UAAU,EACVC,kBAAkB,EAClBC,sBAAsB,QACjB,2CAA0C;AA8BjD;;;;CAIC,GACD,OAAO,SAASC,eAAeC,KAAgC;IAC7D,OAAO,CAAC,CAAC,EAAEC,OAAOD,gBAAAA,QAAS,GAAGE,OAAO,CAAC,IAAI;AAC5C;AAEA,oEAAoE,GACpE,OAAO,SAASC,cAAcH,KAAgC;IAC5D,OAAOA,SAAS,OAAO,MAAMI,KAAKC,KAAK,CAACL,OAAOM,cAAc;AAC/D;AAEA,0EAA0E,GAC1E,OAAO,SAASC,cAAcP,KAAgC;IAC5D,OAAOA,SAAS,OAAO,MAAM,CAAC,CAAC,EAAEC,OAAOD,OAAOE,OAAO,CAAC,IAAI;AAC7D;AAoBA;;;;;;CAMC,GACD,OAAO,SAASM,gBACdC,GAAgD;IAEhD,MAAMC,UAAUd,WAAWa;IAC3B,MAAME,kBAAkBb,uBAAuBW,KAAKG,qBAAqB;IACzE,OAAO;QACLF;QACAG,cAAclB,sBAAsBc;QACpC,wEAAwE;QACxE,gCAAgC;QAChCE,iBACEV,OAAOa,QAAQ,CAACH,oBAAoBA,kBAAkB,IAClDA,kBACA;OACFd,mBAAmBY,OAAO;QAAEM,UAAU;IAAc,IAAI,CAAC;AAEjE;AAEA;;;CAGC,GACD,OAAO,SAASC,mBAAmBC,IAAqB;IACtD,MAAMC,OACJD,KAAKN,eAAe,KAAK,OACrB,GAAGM,KAAKN,eAAe,CAACL,cAAc,GAAG,cAAc,CAAC,GACxDW,KAAKF,QAAQ,GACX,4CACA;IACR,MAAMI,OAAOzB;IACb,OAAOuB,KAAKP,OAAO,GACf,GAAGS,KAAK,aAAa,EAAED,KAAK,EAAE,EAAED,KAAKJ,YAAY,CAACP,cAAc,GAAG,CAAC,CAAC,GACnE,6BACF,GAAGa,KAAK,cAAc,EAAED,KAAK,CAAC,CAAC;AACrC"}