items.replacement_value_usd_cents holds each item's real replacement
(re-purchase) value — what it would cost to buy the item new today. It drives
rental deposits and the insurance/COI threshold, so it needs to reflect current
market price, not what we paid.
It is an admin-maintained field. The inventory importer
(scripts/import-inventory.ts) does not write it (it is in the same
not-touched bucket as cover_image, name_es, and the rate columns). The
sheet's "Approximate Value" column is the purchase price the owner paid and
feeds only assets.acquired_cost_usd_cents — that is often far below
replacement cost (e.g. a lens bought used at $1,300 that is ~$3,500 new), which
is exactly why the two must be tracked separately.
Each time a real value is set, items.replacement_value_updated_at is stamped
with now(). A NULL value means "never researched"; an old stamp means
"possibly stale, re-check."
See the schema reference in ../archive/inventory-schema-sheet-era.md.
When to run this
- After a fresh inventory import that added new items (they arrive with
replacement_value_usd_cents = NULL). - Periodically, to refresh stale values (street prices drift; new models supersede discontinued ones).
Step 1 — Get the worklist
List the items that need a value (NULL, or set longer ago than the freshness window):
# Human-readable worklist (default freshness window: 365 days)
pnpm tsx scripts/list-replacement-prices.ts
# Tighter window — anything set more than 180 days ago is "stale"
pnpm tsx scripts/list-replacement-prices.ts --stale-days 180
# Seed scripts/replacement-prices.csv with current rows (CSV to stdout)
pnpm tsx scripts/list-replacement-prices.ts --csv > scripts/replacement-prices.csv
# List EVERY item regardless of state
pnpm tsx scripts/list-replacement-prices.ts --all
Each line shows sku, manufacturer + name, category, and mpn — enough to look
the item up.
Step 2 — Research current replacement cost
For each item, find what it costs to buy new today and record where you got the number:
- Prefer, in order: the manufacturer's own store/MSRP → B&H Photo →
Full Compass → another reputable US retailer. These are the same
retailers used to source the product images, so the
mpnusually resolves to an exact product page. - Use USD; the DB stores USD cents and converts to COP for display.
- If a model is discontinued with no current listing, use the closest current equivalent (and note it in the source).
- Record a
source_urlper item — the exact product page. This is your audit trail and survives initems.notes_internalas areplacement-source:<url>token. - If you can't price something confidently, leave its
replacement_value_usdblank. The apply step will skip it (it staysNULL) and report it so a human can finish it. Do not guess wildly.
Step 3 — Fill in scripts/replacement-prices.csv
The reviewable artifact is a CSV keyed by sku (or id):
sku,name,replacement_value_usd,source_url
sony-fx6,FX6,5999.00,https://www.bhphotovideo.com/c/product/...
sony-e-pz-18-110mm-f4-g-oss,E PZ 18–110mm F4 G OSS,3499.00,https://www.bhphotovideo.com/c/product/...
nameis for human review only; the importer ignores it.- Matching by
skuupdates all items sharing that sku (the sheet reuses a sku across multiple units of the same model). To target one specific row, use anidcolumn with the item UUID instead. - A blank
replacement_value_usdis skipped (leftNULL).
JSON is also accepted (--file path/to/prices.json): an array of objects, or an
object keyed by sku/id, each with replacement_value_usd + source_url.
Step 4 — Apply
# Preview without writing
pnpm tsx scripts/apply-replacement-prices.ts --file scripts/replacement-prices.csv --dry-run
# Apply for real (sets the value + stamps replacement_value_updated_at = now())
pnpm tsx scripts/apply-replacement-prices.ts --file scripts/replacement-prices.csv
The apply step is idempotent — re-running with an unchanged file is a no-op
(it only writes rows whose value or source actually changed, and only those
rows get a fresh replacement_value_updated_at). It reports how many rows were
updated, how many were already current, how many were skipped (blank value), and
any sku/id it couldn't find.
Verify
# Should print 0 once everything researchable has been applied
pnpm tsx scripts/list-replacement-prices.ts | head -1
Anything still listed either has a blank value in the CSV (needs a human) or is genuinely new since the last run.