Docs.

Day-rate pricing

business/pricing.md

Owner-directed, 2026-05-30. Rental day rates are derived from replacement value via a straight-line cost-recovery model per equipment class, calibrated to the Colombian market. (This supersedes an earlier banded-divisor draft.) The engine lives in src/lib/office/pricing.ts; its tunable parameters are stored under pricing.* keys (pricing.pricing_classes + pricing.iva_rate, pricing.day_rate_floor_usd_cents, pricing.day_rate_round_usd_cents, pricing.deposit_percent, pricing.deposit_minimum_usd_cents) in the generic settings key/value store (baseline 0001, rebuilt append-only in 0002, reshaped into the append-only key/value model in 0007) — not a dedicated pricing migration. A save writes a new row only for the keys that changed.

Basis: replacement value (not price paid)

  • Replacement value is per-item → identical gear rents at the same rate. Price paid (assets.acquired_cost_usd_cents) is per-asset and varies (the three FX6 bodies were $6,000 / $5,200 / $4,800) — pricing off it would rent identical cameras differently.
  • Replacement value tracks current market; price paid is historical and feeds ROI/payback, a separate metric.
  • Any item with a replacement value is priceable; items without one are flagged as unpriced in the UI.

Earlier drafts cited exact catalog counts (≈201 items, ≈210 assets with a price-paid figure). Those predate the 0002 duplicate-SKU consolidation, which collapsed the legacy "one items row per unit" rows — so the live item count is lower now. Re-query before quoting exact totals.

The formula (cost recovery)

For each item, using its equipment class parameters:

AnnualCost = (RC − RC·residual)/life  +  RC·(maintenance + insurance + overhead)
NetDayRate = max( floor, AnnualCost / (365 · utilization · (1 − margin)) )
            → rounded to the nearest $5

RC = replacement value. The rate is net of IVA; 19% IVA is applied to the quote total, not the per-day rate (owner's choice). A manual per-item override (items.rate_day_usd_cents) always wins.

Calibration: utilization = 5% (match market)

Utilization is the dominant lever. Cinemarket's published FX6 (≈COP 950k ≈ $264/day at 3,600 COP/USD) equals the cost-recovery rate at ~5% billable utilization (≈18 rental days/yr/item) — realistic for a boutique shelf. So all classes default to 5% utilization, which lands the FX6 at market:

  • FX6 ($7,000): net $220/day, +IVA ≈ $262 (Cinemarket $264). ✓

Important: cost-recovery is ~linear; the market is regressive

Cost recovery scales ~proportionally with value, so cheaper bodies price below the regressive market (which charges a higher % on cheap gear):

ItemReplacementnet/day+IVA(market)
Sony FX6$7,000$220$262$264
Sony FX30$2,098$65$77$153
Sony α7 V$2,899$90$107

This is expected and was an explicit trade-off. To lift a specific cheap item toward market, set a per-item override or lower that class's utilization.

Equipment classes & parameters (in settings.pricing_classes, tunable)

Categories map to 6 classes (map in src/lib/office/pricing.ts): camera_body, lens, lighting, support (grip/tripod/stand/stabilizer/cases), electronics (monitors/audio/recorders/storage/computers), accessory (batteries/filters/adapters).

classliferesidualutilm+i+omargin
camera_body3.5y20%5%22%21%
lens6y33%5%16%20%
lighting4y15%5%19.3%21%
support8y28%5%12.4%18%
electronics4y20%5%18%20%
accessory3y10%5%19.5%20%

Plus iva_rate (0.19), day_rate_floor_usd_cents ($15 min charge), day_rate_round_usd_cents ($5). Edit all of this in /office/pricing (the class grid + scalars are a live form) — saving reprices the catalog instantly. Declining-balance is an internal management overlay, not the list engine, per the research.

Deposit

deposit = max(deposit_percent × Σ replacement, deposit_minimum_usd_cents). The deposit is refundable, separate from the rental fee, and not IVA'd.

The deposit is a 100% replacement-value hold (deposit_percent = 1.0, set in migration 0005), so FX6 → $7,000. There's no gear-insurance market in Colombia, so a full-replacement deposit is the protection. At 100% the percentage always dominates deposit_minimum_usd_cents ($500), so the floor is dormant until the percentage is ever lowered.

The deposit is the whole protection model — it is deposit-only. There is no insurance, no COI, and no damage waiver. The 100% replacement-value hold covers loss or damage; that is the entire downside cover (owner-decided, 2026-06).

Discounts: none (for now)

There are no multi-item or volume discounts, and no student or first-time discounts (owner-decided, 2026-06). Every line is quoted at its own day rate × days. The schema keeps a discount_usd_cents field on reservations for a manual desk adjustment, but there is no automatic discount rule and none is planned for now.

Cleaning fee: a condition penalty, not a flat fee

Policy (owner-decided, 2026-06): cleaning is not a standing line item or a flat add-on. It is a condition penalty — a percentage charge applied at return/inspection only if an item comes back in worse condition than it left (not cleaned, dirtied, etc.). The percentage is {TBD: cleaning penalty %}.

Not built. There is no cleaning-fee surface today — no charge field, no automatic computation, no UI to apply it. The claims table can already record a cleaning claim kind, but the percentage-of-value penalty described here is a documented policy only, not a wired feature. Don't quote a number until the percentage is decided and the charge path is built.

Weekend: three calendar days at the day rate

A weekend is Friday + Saturday + Sunday = 3 days, each at the normal day rate — no weekend surcharge and no weekend discount (owner-decided, 2026-06). The engine already bills calendar days, so a Fri–Sun rental is simply three billed days at the day rate; nothing special happens for weekends today.

settings keeps a weekend_multiplier lever (default 1.00 = the plain day rate), but it is not applied per-day in the current engine — it is a dormant no-op kept for a future weekend-pricing scheme. As long as it stays at 1.00 it has no effect; changing it does nothing until the engine is wired to read it.

Where it shows

  • Inventory list / item detail — net derived rate with auto vs override.
  • New-reservation picker — each item shows its $X/day.
  • Quote (computeQuote) — net subtotal, then IVA line, then total-with-IVA; deposit is separate (refundable, not IVA'd). Line items snapshot the effective net rate so prices freeze even if the curve later changes.
  • Settings page — the full class table + IVA + floor.

To verify / adjust

  • FX6 anchors to market; spot-check the rest on the inventory list and override outliers. Cheaper bodies are intentionally below the regressive market.
  • The 5 accessory replacement values I estimated (AMBITFUL snoot, KONYEAD enclosure, Proaim spike foot, Sony FX6 handle block, Sony XLR-H1 handle) are estimates — confirm them, they drive deposit + rate. The 6 Vespid2 primes derive from the existing kit value ($6,289 ÷ 6 ≈ $1,048).
  • FX: the day-rate market calibration anchors to 3,600 COP/USD (the rate at which the FX6 matched Cinemarket). Note this is the calibration reference, not the live display rate: settings.cop_per_usd_snapshot ships at 4,000 (baseline default) and is what COP figures are converted at — update it when the rate moves. (Day rates are derived in USD and only displayed in COP, so the two numbers serve different jobs.)
  • Catastrophic risk (loss/total damage) stays out of the day rate — handled by the refundable 100% replacement-value deposit alone. There is no COI or insurance component (the earlier tiered-COI idea was dropped, 2026-06).