@aglyn/plugins-ai 1.0.0-beta.225 → 1.0.0-beta.227
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/package.json +17 -17
- package/src/lib/billing/ai-month-meter.js +5 -3
- package/src/lib/billing/ai-month-meter.js.map +1 -1
- package/src/lib/billing/ai-overage-ledger.js +3 -1
- package/src/lib/billing/ai-overage-ledger.js.map +1 -1
- package/src/lib/components/ai-brief-dialog.component.d.ts +6 -2
- package/src/lib/components/ai-brief-dialog.component.js +35 -17
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-describe-automation.component.js +17 -2
- package/src/lib/components/ai-describe-automation.component.js.map +1 -1
- package/src/lib/components/ai-insight-dialog.component.js +24 -2
- package/src/lib/components/ai-insight-dialog.component.js.map +1 -1
- package/src/lib/components/ai-job-events.d.ts +14 -0
- package/src/lib/components/ai-job-events.js +76 -0
- package/src/lib/components/ai-job-events.js.map +1 -0
- package/src/lib/components/ai-job-follow.component.d.ts +49 -0
- package/src/lib/components/ai-job-follow.component.js +210 -0
- package/src/lib/components/ai-job-follow.component.js.map +1 -0
- package/src/lib/components/ai-job-links.d.ts +42 -0
- package/src/lib/components/ai-job-links.js +140 -0
- package/src/lib/components/ai-job-links.js.map +1 -0
- package/src/lib/components/ai-job-plan.component.d.ts +13 -6
- package/src/lib/components/ai-job-plan.component.js +29 -3
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-job-requests.d.ts +24 -0
- package/src/lib/components/ai-job-requests.js +76 -0
- package/src/lib/components/ai-job-requests.js.map +1 -0
- package/src/lib/components/ai-jobs-indicator.component.d.ts +25 -0
- package/src/lib/components/ai-jobs-indicator.component.js +80 -0
- package/src/lib/components/ai-jobs-indicator.component.js.map +1 -0
- package/src/lib/components/ai-jobs-store.d.ts +81 -0
- package/src/lib/components/ai-jobs-store.js +218 -0
- package/src/lib/components/ai-jobs-store.js.map +1 -0
- package/src/lib/components/ai-page-brief-dialog.component.d.ts +3 -3
- package/src/lib/components/ai-page-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.d.ts +23 -0
- package/src/lib/components/ai-site-build-page.component.js +377 -0
- package/src/lib/components/ai-site-build-page.component.js.map +1 -0
- package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
- package/src/lib/components/ai-site-start-card.component.js +522 -130
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-site-starter-fallback.component.d.ts +24 -0
- package/src/lib/components/ai-site-starter-fallback.component.js +112 -0
- package/src/lib/components/ai-site-starter-fallback.component.js.map +1 -0
- package/src/lib/components/assist-jobs-drawer.component.d.ts +18 -20
- package/src/lib/components/assist-jobs-drawer.component.js +152 -172
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.js +62 -6
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/staff-ai-credit-return.component.d.ts +55 -0
- package/src/lib/components/staff-ai-credit-return.component.js +346 -0
- package/src/lib/components/staff-ai-credit-return.component.js.map +1 -0
- package/src/lib/components/staff-org-ai-card.component.js +364 -279
- package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
- package/src/lib/components/staff-user-ai-usage-card.component.d.ts +4 -0
- package/src/lib/components/staff-user-ai-usage-card.component.js +83 -28
- package/src/lib/components/staff-user-ai-usage-card.component.js.map +1 -1
- package/src/lib/components/use-ai-job-run.js +5 -18
- package/src/lib/components/use-ai-job-run.js.map +1 -1
- package/src/lib/jobs/ai-job-auto-confirm.d.ts +52 -0
- package/src/lib/jobs/ai-job-auto-confirm.js +62 -0
- package/src/lib/jobs/ai-job-auto-confirm.js.map +1 -0
- package/src/lib/jobs/ai-job-plan-step.d.ts +33 -2
- package/src/lib/jobs/ai-job-plan-step.js +161 -17
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +7 -0
- package/src/lib/jobs/ai-job-site-step.js +34 -4
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.d.ts +7 -0
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs-notify.d.ts +37 -0
- package/src/lib/jobs/ai-jobs-notify.js +51 -0
- package/src/lib/jobs/ai-jobs-notify.js.map +1 -0
- package/src/lib/jobs/ai-jobs.d.ts +31 -4
- package/src/lib/jobs/ai-jobs.js +196 -32
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/model/ai-job-activity.d.ts +112 -0
- package/src/lib/model/ai-job-activity.js +144 -0
- package/src/lib/model/ai-job-activity.js.map +1 -0
- package/src/lib/model/ai-job-failure-copy.d.ts +69 -0
- package/src/lib/model/ai-job-failure-copy.js +78 -0
- package/src/lib/model/ai-job-failure-copy.js.map +1 -0
- package/src/lib/model/ai-job-notice.d.ts +61 -0
- package/src/lib/model/ai-job-notice.js +112 -0
- package/src/lib/model/ai-job-notice.js.map +1 -0
- package/src/lib/model/ai-jobs.types.d.ts +26 -0
- package/src/lib/model/ai-jobs.types.js +1 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-plan-capabilities.d.ts +7 -0
- package/src/lib/model/ai-plan-capabilities.js.map +1 -1
- package/src/lib/model/ai-site-build-progress.d.ts +45 -0
- package/src/lib/model/ai-site-build-progress.js +76 -0
- package/src/lib/model/ai-site-build-progress.js.map +1 -0
- package/src/lib/model/ai-site-inventory.d.ts +9 -0
- package/src/lib/model/ai-site-inventory.js +7 -2
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +93 -2
- package/src/lib/model/ai-site-job.js +88 -7
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-start.d.ts +3 -1
- package/src/lib/model/ai-site-start.js +6 -4
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/model/ai-template-subjects.d.ts +4 -3
- package/src/lib/model/ai-template-subjects.js +5 -4
- package/src/lib/model/ai-template-subjects.js.map +1 -1
- package/src/lib/plan-entitlements.d.ts +3 -3
- package/src/lib/plan-entitlements.js +3 -3
- package/src/lib/plan-entitlements.js.map +1 -1
- package/src/lib/plugin.js +28 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/runtime/ai-abuse-guards.d.ts +9 -6
- package/src/lib/runtime/ai-abuse-guards.js +10 -7
- package/src/lib/runtime/ai-abuse-guards.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.js +31 -4
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-gate.d.ts +3 -2
- package/src/lib/runtime/ai-gate.js +3 -2
- package/src/lib/runtime/ai-gate.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +12 -4
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/site-inventory.d.ts +16 -1
- package/src/lib/runtime/site-inventory.js +43 -3
- package/src/lib/runtime/site-inventory.js.map +1 -1
- package/src/lib/server/ai-admin-credits.d.ts +21 -0
- package/src/lib/server/ai-admin-credits.js +213 -0
- package/src/lib/server/ai-admin-credits.js.map +1 -0
- package/src/lib/server/ai-admin-org.js +12 -5
- package/src/lib/server/ai-admin-org.js.map +1 -1
- package/src/lib/server/ai-admin-signals.js +3 -1
- package/src/lib/server/ai-admin-signals.js.map +1 -1
- package/src/lib/server/ai-admin-user.js +12 -1
- package/src/lib/server/ai-admin-user.js.map +1 -1
- package/src/lib/server/ai-allotments.js +3 -2
- package/src/lib/server/ai-allotments.js.map +1 -1
- package/src/lib/server/ai-jobs-route.js +20 -2
- package/src/lib/server/ai-jobs-route.js.map +1 -1
- package/src/lib/server/assist-chat.js +3 -2
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/billing-credits.js +4 -3
- package/src/lib/server/billing-credits.js.map +1 -1
- package/src/lib/server/billing-overage.js +3 -2
- package/src/lib/server/billing-overage.js.map +1 -1
- package/src/lib/server.js +11 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/usage/assist-credit-returns-write.d.ts +75 -0
- package/src/lib/usage/assist-credit-returns-write.js +150 -0
- package/src/lib/usage/assist-credit-returns-write.js.map +1 -0
- package/src/lib/usage/assist-credit-returns.d.ts +68 -0
- package/src/lib/usage/assist-credit-returns.js +79 -0
- package/src/lib/usage/assist-credit-returns.js.map +1 -0
- package/src/lib/usage/assist-free-taste.js +6 -3
- package/src/lib/usage/assist-free-taste.js.map +1 -1
- package/src/lib/usage/assist-job-refund.d.ts +100 -0
- package/src/lib/usage/assist-job-refund.js +109 -0
- package/src/lib/usage/assist-job-refund.js.map +1 -0
- package/src/lib/usage/assist-usage.js +5 -2
- package/src/lib/usage/assist-usage.js.map +1 -1
- package/src/lib/usage/staff-org-ai.d.ts +31 -1
- package/src/lib/usage/staff-org-ai.js +19 -2
- package/src/lib/usage/staff-org-ai.js.map +1 -1
|
@@ -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 {\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 /** Credits drawn this month, rounded up from the measured spend. */\n usedCredits: 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\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 const billedUsd = finite(monthDoc?.['estCostUsd'])\n const providerUsd = assistProviderCostUsd(\n billedUsd,\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 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","AI_USAGE_MONTH_KINDS_FIELD","aiCacheHitRate","readAiKindMonths","finite","value","parsed","Number","isFinite","monthProgress","now","year","getUTCFullYear","month","getUTCMonth","elapsed","getUTCDate","length","Date","UTC","projectMonthEnd","usedSoFar","Math","ceil","assistBandOverride","org","raw","entitlements","floor","composeStaffOrgAiPool","monthDoc","plan","addon","billedUsd","providerUsd","usedCredits","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, 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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"}
|