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
0002duplicate-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):
| Item | Replacement | net/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).
| class | life | residual | util | m+i+o | margin |
|---|---|---|---|---|---|
| camera_body | 3.5y | 20% | 5% | 22% | 21% |
| lens | 6y | 33% | 5% | 16% | 20% |
| lighting | 4y | 15% | 5% | 19.3% | 21% |
| support | 8y | 28% | 5% | 12.4% | 18% |
| electronics | 4y | 20% | 5% | 18% | 20% |
| accessory | 3y | 10% | 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 migration0005), 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 dominatesdeposit_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
claimstable can already record acleaningclaim 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.
settingskeeps aweekend_multiplierlever (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
autovsoverride. - 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_snapshotships 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).