Compare changes
Choose two branches to see what one has that the other does not, then open a pull request for it.
3 commits
- Models' margin read -14%: usage nothing paid for is valued at price, not $0Chase Pierce9b979d8
- Billing and Usage reconcile: own-provider runs leave Billing's at-price total, and Usage's not-charged part is at price less chargedChase Pierce7dd8455
- Billing's usage total is labeled at price, and Usage says what part of it was paid forChase Pierce9662a50
9 files+74−90/9 viewed
| 118 | 118 | **This month's usage**: what each kind of usage has come to so far, in | |
| 119 | 119 | dollars and in what was used (*Agents & sandboxes*, *Builds*, *Requests & | |
| 120 | 120 | CPU*, *Custom domains*, *Git operations & storage*, *Search & security | |
| 121 | − | scans*). App traffic, custom domains, storage and git operations are | |
| 122 | − | counted through the month and charged when it closes. **Manage on Stripe** opens the card, invoices and | |
| 121 | + | scans*). Its **Total at price** is g1t's usage at cost plus 20%, before the | |
| 122 | + | included usage, the trial, a pool or a free period paid their part, so it | |
| 123 | + | can be more than what was charged. Runs on your own model provider are not | |
| 124 | + | in it: your provider bills those. The workspace's **Usage** page shows what | |
| 125 | + | was charged as **Spent**, and how much of the total was not charged. App traffic, custom domains, storage and git | |
| 126 | + | operations are counted through the month and charged when it closes. **Manage on Stripe** opens the card, invoices and | |
| 123 | 127 | billing details. **End at the end of the period** ends the plan then, with | |
| 124 | 128 | nothing more charged after; **Keep the plan** takes that back until then. | |
| 125 | 129 | If a renewal payment fails, the card says **Payment failed**, with |
| 346 | 346 | ))} | |
| 347 | 347 | </ul> | |
| 348 | 348 | <div className="mt-1 flex items-baseline justify-between gap-4 border-t border-line pt-2.5 text-sm"> | |
| 349 | − | <span className="font-medium">Total</span> | |
| 349 | + | {/* At price, before the plan's included usage, the trial and the pools paid | |
| 350 | + | their part: Usage shows what was charged. */} | |
| 351 | + | <span className="font-medium">Total at price</span> | |
| 350 | 352 | <span className="font-medium tabular-nums">{dollars(total)}</span> | |
| 351 | 353 | </div> | |
| 352 | 354 | <p className="mt-2 text-xs text-faint"> | |
| 354 | 356 | ? "What this workspace's usage would cost. g1t covers it." | |
| 355 | 357 | : on | |
| 356 | 358 | ? `Drawn from the included usage first, then charged up to your spend limit. Projects, previews and repositories are never charged, and the first ${freeStorage} of private storage and ${freeGit} git operations a month are free. App traffic, custom domains, storage and git operations are counted through the month and charged when it closes.` | |
| 357 | − | : `The forge is free: ${freeStorage} of private storage and ${freeGit} git operations a month. Agents run from the trial or the open-source pool.`} | |
| 359 | + | : `The forge is free: ${freeStorage} of private storage and ${freeGit} git operations a month. Agents run from the trial or the open-source pool, which pay part of this total: Usage shows what was charged.`} | |
| 358 | 360 | </p> | |
| 359 | 361 | </div> | |
| 360 | 362 | ); |
| 228 | 228 | label="Spent" | |
| 229 | 229 | value={dollars(usage.spentMicros)} | |
| 230 | 230 | note={ | |
| 231 | − | usage.providerMicros > 0 | |
| 232 | − | ? `Plus about ${dollars(usage.providerMicros)} billed by your own model provider` | |
| 233 | − | : `${dollars(usage.costMicros)} of it the model provider's` | |
| 231 | + | (usage.coveredMicros ?? 0) > 0 | |
| 232 | + | ? `Of ${dollars(usage.spentMicros + (usage.coveredMicros ?? 0))} at price: ${dollars(usage.coveredMicros ?? 0)} of it was not charged (the included usage, the trial, a pool, or a free period)` | |
| 233 | + | : usage.providerMicros > 0 | |
| 234 | + | ? `Plus about ${dollars(usage.providerMicros)} billed by your own model provider` | |
| 235 | + | : `${dollars(usage.costMicros)} of it the model provider's` | |
| 234 | 236 | } | |
| 235 | 237 | /> | |
| 236 | 238 | )} |
| 324 | 324 | pub since: String, | |
| 325 | 325 | /// Charged, including g1t's margin. | |
| 326 | 326 | pub spent_micros: i64, | |
| 327 | + | /// What g1t's usage came to at price, less what was charged: the plan's | |
| 328 | + | /// included usage, the trial, a pool or a free period paid it. Usage at | |
| 329 | + | /// price is `spent_micros` plus this. | |
| 330 | + | #[serde(default)] | |
| 331 | + | pub covered_micros: i64, | |
| 327 | 332 | /// What g1t's model provider charged, before the margin. | |
| 328 | 333 | pub cost_micros: i64, | |
| 329 | 334 | /// What runs on the workspace's own provider cost there, as the harness |
| 186 | 186 | (raw counts with the mapping) for `cost_operations`. Migration 0023 drops | |
| 187 | 187 | the `billable_units` table 0022 made for this, which was never written. | |
| 188 | 188 | ||
| 189 | + | **Charged at price** (a product's value) is what each day's usage was paid: | |
| 190 | + | charged to a card or credit, or drawn from the plan's included usage, a | |
| 191 | + | trial, a pool or a gift. Usage nothing paid for, as in a free period, is | |
| 192 | + | valued at price (cost plus the margin), since it was given away at its price | |
| 193 | + | rather than sold for nothing; so are g1t's own workspaces. Runs on a | |
| 194 | + | workspace's own model provider have no cost to g1t. Every daily run | |
| 195 | + | reconciles the whole 31-day window from what is already kept, so a change in | |
| 196 | + | how a day is valued reaches every day sudo shows. | |
| 197 | + | ||
| 189 | 198 | ## Alerts runbook | |
| 190 | 199 | ||
| 191 | 200 | | Alert | Raised when | First steps | |
| 1014 | 1014 | since: string; | |
| 1015 | 1015 | /** Charged, including g1t's margin. */ | |
| 1016 | 1016 | spentMicros: number; | |
| 1017 | + | /** What g1t's usage came to at price, less what was charged: the plan's included usage, the trial, a pool or a free period paid it. Usage at price is `spentMicros` plus this. */ | |
| 1018 | + | coveredMicros?: number; | |
| 1017 | 1019 | /** What g1t's model provider charged, before the margin. */ | |
| 1018 | 1020 | costMicros: number; | |
| 1019 | 1021 | /** What runs on the workspace's own provider cost there, estimated. Not charged by g1t. */ |
| 428 | 428 | #[derive(serde::Deserialize)] | |
| 429 | 429 | struct Totals { | |
| 430 | 430 | spent: Option<i64>, | |
| 431 | + | g1t_cost: Option<i64>, | |
| 431 | 432 | cost: Option<i64>, | |
| 432 | 433 | provider: Option<i64>, | |
| 433 | 434 | runs: Option<u32>, | |
| 438 | 439 | .prepare( | |
| 439 | 440 | "SELECT | |
| 440 | 441 | -SUM(CASE WHEN kind = 'usage' THEN amount_micros END) AS spent, | |
| 442 | + | SUM(CASE WHEN kind = 'usage' AND COALESCE(billed_to, 'g1t') = 'g1t' THEN cost_micros END) AS g1t_cost, | |
| 441 | 443 | SUM(CASE WHEN kind = 'usage' AND COALESCE(billed_to, 'g1t') = 'g1t' THEN cost_micros END) AS cost, | |
| 442 | 444 | SUM(CASE WHEN kind = 'usage' AND billed_to = 'workspace' THEN cost_micros END) AS provider, | |
| 443 | 445 | SUM(CASE WHEN kind = 'usage' THEN 1 ELSE 0 END) AS runs, | |
| 463 | 465 | .await?; | |
| 464 | 466 | let totals = totals.unwrap_or(Totals { | |
| 465 | 467 | spent: None, | |
| 468 | + | g1t_cost: None, | |
| 466 | 469 | cost: None, | |
| 467 | 470 | provider: None, | |
| 468 | 471 | runs: None, | |
| 470 | 473 | }); | |
| 471 | 474 | Ok(Outcome::Ok(Usage { | |
| 472 | 475 | spent_micros: totals.spent.unwrap_or_default(), | |
| 476 | + | // At price, what g1t's usage came to, less what was charged: the | |
| 477 | + | // part the included usage, the trial, a pool, or a free period paid. | |
| 478 | + | covered_micros: (crate::credits::with_margin(totals.g1t_cost.unwrap_or_default(), self.margin_percent) | |
| 479 | + | - totals.spent.unwrap_or_default()) | |
| 480 | + | .max(0), | |
| 473 | 481 | cost_micros: totals.cost.unwrap_or_default(), | |
| 474 | 482 | provider_micros: totals.provider.unwrap_or_default(), | |
| 475 | 483 | used_micros: totals.cost.unwrap_or_default() + totals.provider.unwrap_or_default(), |
| 405 | 405 | /// Workspaces that cost g1t more than `factor` times what they paid, with | |
| 406 | 406 | /// at least `floor_micros` of cost: each (workspace, cost, revenue), the | |
| 407 | 407 | /// biggest gap first. | |
| 408 | + | /// What a day's usage was worth at price. g1t's own workspaces are valued | |
| 409 | + | /// at price. So is usage nothing paid for, neither charged nor drawn from | |
| 410 | + | /// the plan, a trial, a pool or a gift (a free period): it was given away at | |
| 411 | + | /// its price, not sold for nothing. Anything paid keeps what it was paid, so | |
| 412 | + | /// a discount still shows as one. | |
| 413 | + | pub(crate) fn usage_value(internal: bool, cost: i64, paid: i64, margin_percent: u32) -> i64 { | |
| 414 | + | if internal || (paid == 0 && cost > 0) { | |
| 415 | + | return crate::credits::with_margin(cost, margin_percent); | |
| 416 | + | } | |
| 417 | + | paid | |
| 418 | + | } | |
| 419 | + | ||
| 408 | 420 | /// What the overall alert says: the money as money, and a percentage only | |
| 409 | 421 | /// while there is enough coming in for one to mean something (a few cents | |
| 410 | 422 | /// against dollars of cost reads as -8000%). | |
| 597 | 609 | run.problems.push(format!("g1t's own counts could not be read: {error}")); | |
| 598 | 610 | } | |
| 599 | 611 | self.snapshot_pending(&until).await?; | |
| 600 | − | run.days = self.reconcile_range(&since, &until).await?; | |
| 612 | + | // Reconciled over the whole window sudo shows, not only the days the | |
| 613 | + | // bill was read for: it reads only what is already kept, so a change | |
| 614 | + | // in how a day is valued reaches every day shown at the next run. | |
| 615 | + | let window = day_before(&until, costs::BACKFILL_DAYS - 1); | |
| 616 | + | let reconcile_from = if window < since { window } else { since.clone() }; | |
| 617 | + | run.days = self.reconcile_range(&reconcile_from, &until).await?; | |
| 601 | 618 | let drift = self.find_drift(&until).await?; | |
| 602 | 619 | run.proposals = self.measure_units(&until).await?; | |
| 603 | 620 | self.apply_due_versions().await?; | |
| 668 | 685 | // to g1t. g1t's own workspaces are valued at price. | |
| 669 | 686 | let cost = if r.own_provider == 1 { 0 } else { r.cost.unwrap_or(0) }; | |
| 670 | 687 | let cash = r.cash.unwrap_or(0); | |
| 671 | − | let value = if r.internal == 1 { crate::credits::with_margin(cost, self.margin_percent) } else { cash + r.drawn.unwrap_or(0) }; | |
| 688 | + | let paid = cash + r.drawn.unwrap_or(0); | |
| 689 | + | let value = usage_value(r.internal == 1, cost, paid, self.margin_percent); | |
| 672 | 690 | UsageRow { day: r.day, workspace: r.workspace, key: r.key, value, cash, cost } | |
| 673 | 691 | }) | |
| 674 | 692 | .collect(); | |
| 1473 | 1491 | use super::*; | |
| 1474 | 1492 | ||
| 1475 | 1493 | #[test] | |
| 1494 | + | fn usage_nothing_paid_for_is_valued_at_price_and_paid_usage_at_what_was_paid() { | |
| 1495 | + | // A free period: charged nothing, drawn from nothing. | |
| 1496 | + | assert_eq!(usage_value(false, 1_000_000, 0, 20), 1_200_000); | |
| 1497 | + | // Charged, or drawn from a trial: what was paid. | |
| 1498 | + | assert_eq!(usage_value(false, 1_000_000, 1_200_000, 20), 1_200_000); | |
| 1499 | + | assert_eq!(usage_value(false, 1_000_000, 900_000, 20), 900_000); | |
| 1500 | + | // g1t's own: at price. | |
| 1501 | + | assert_eq!(usage_value(true, 1_000_000, 0, 20), 1_200_000); | |
| 1502 | + | // No cost, nothing paid: nothing. | |
| 1503 | + | assert_eq!(usage_value(false, 0, 0, 20), 0); | |
| 1504 | + | } | |
| 1505 | + | ||
| 1506 | + | #[test] | |
| 1476 | 1507 | fn the_overall_alert_says_dollars_while_little_comes_in() { | |
| 1477 | 1508 | let small = overall_detail(90_000, 7_500_000, 3, 10.0, -8239.7); | |
| 1478 | 1509 | assert!(small.contains("took in $0.09 against $7.50"), "{small}"); |
| 269 | 269 | "SELECT {METER_SQL} AS meter, COUNT(*) AS count, SUM(cost_micros) AS cost | |
| 270 | 270 | FROM ledger WHERE workspace = ?1 AND kind = 'usage' AND created_at >= ?2 AND created_at < ?3 | |
| 271 | 271 | AND COALESCE(reference, '') NOT LIKE 'deployments/%' | |
| 272 | + | -- Runs on the workspace's own model provider are its provider's to bill, never g1t's. | |
| 273 | + | AND COALESCE(billed_to, 'g1t') = 'g1t' | |
| 272 | 274 | GROUP BY 1" | |
| 273 | 275 | )) | |
| 274 | 276 | .bind(&[workspace.as_str().into(), from.as_str().into(), until.as_str().into()])? |