{"slug":"lifestyle-asset-intel","total":46,"limit":100,"offset":0,"since":null,"commits":[{"hash":"8b08cbd","date":"2026-07-03 08:08:07 -0700","author":"Steve","subject":"chore: macstudio3 migration — reconcile from mac2 + repoint paths (stevestudio2→macstudio3, node/npm/npx→/opt/homebrew)","body":""},{"hash":"e6fa244","date":"2026-05-31 16:32:34 -0700","author":"Steve Abrams","subject":"Add per-site favicon (kills /favicon.ico 404)","body":""},{"hash":"ae0a5ef","date":"2026-05-19 21:11:05 -0700","author":"Steve Abrams","subject":"add density slider to /family grid","body":"Families cohort grid was hardcoded to --cols:3 with no density control.\nAdds a density range input (3-8) and an id=\"grid\" hook so the existing\npublic/js/app.js wiring picks it up — applies, persists to localStorage\nkey lai-density, and hydrates from storage on next page load. Sort stays\nserver-side via the existing form-submit select (no id, so app.js leaves\nit alone).\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"bf3e286","date":"2026-05-19 21:09:58 -0700","author":"Steve Abrams","subject":"add noreferrer to topnav external-target links","body":"The Sources / OpenAPI / API links open in a new tab and had rel=\"noopener\"\nbut no noreferrer, leaking the originating page URL via Referer. Hardens\nthem to rel=\"noopener noreferrer\" — matches the cite-link convention in\nviews/asset.ejs.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"f402188","date":"2026-05-19 21:09:40 -0700","author":"Steve Abrams","subject":"gitignore snapshot files + 404-guard .bak/.pre-/.orig paths under static","body":"Broadens .gitignore to exclude editor/snapshot backups (*.bak, *.bak.*,\n*.pre-*, *.orig, *.rej, *.swp, *~) and adds an Express middleware that\n404s any request matching those patterns BEFORE express.static can serve\nthem, so a stray backup landing in public/ can never leak.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"3c385b4","date":"2026-05-19 11:35:47 -0700","author":"Steve","subject":"snapshot — gitify backup 2026-05-19","body":""},{"hash":"9b2ec0e","date":"2026-05-14 09:47:18 -0700","author":"Steve Abrams","subject":"yolo tick #34: /api/audit/recent ?since= ISO-8601 lower bound filter (called_at >= since; composes with route+method+status+asset_slug; unparseable/empty silently ignored; echoes normalized ISO); OpenAPI documents the new param","body":""},{"hash":"674dc23","date":"2026-05-14 09:17:01 -0700","author":"Steve Abrams","subject":"yolo tick #33: /api/audit/recent ?status= filter (exact 3-digit code OR 1-digit class prefix 2/4/5; composes with route+method+asset_slug; invalid/empty silently ignored); OpenAPI documents the new param","body":""},{"hash":"369b470","date":"2026-05-14 08:50:46 -0700","author":"Steve Abrams","subject":"yolo tick #32: /api/audit/recent ?method= filter (HTTP verb, case-insensitive, composes with route+asset_slug); OpenAPI documents route+method params","body":""},{"hash":"01bd2e3","date":"2026-05-14 08:16:22 -0700","author":"Steve Abrams","subject":"yolo tick #31: /api/audit/recent ?route= substring filter (case-insensitive, composes with asset_slug)","body":""},{"hash":"456f827","date":"2026-05-14 01:49:12 -0700","author":"Steve Abrams","subject":"yolo tick #30: /api/sources ?q= filter (slug/name/kind/notes, case-insensitive, composes with sort)","body":"Author: Steve Abrams <steve@designerwallcoverings.com>"},{"hash":"4206774","date":"2026-05-14 01:29:32 -0700","author":"Steve Abrams","subject":"yolo tick #29: /api/index ?q= filter (slug/name, case-insensitive, composes with sort)","body":""},{"hash":"5b70e7f","date":"2026-05-14 00:45:46 -0700","author":"Steve Abrams","subject":"yolo tick #28: /api/family ?q= filter (slug/name/brand, case-insensitive, composes with sort)","body":""},{"hash":"c3d5177","date":"2026-05-13 23:46:08 -0700","author":"Steve Abrams","subject":"yolo tick #27: /api/sources ?sort= (weight/tier/name/slug/kind/enabled, default+fallback)","body":""},{"hash":"a2a7ff0","date":"2026-05-13 22:51:33 -0700","author":"Steve Abrams","subject":"yolo tick #26: /api/index list endpoint with sort + latest-point summary","body":"Returns one row per benchmark index: identity (slug/name/definition/\nmethodology_version), point_count, first_as_of, latest_as_of,\nlatest_value, latest_change_pct. Single SQL pass with two lateral\nsubqueries so it stays cheap as indices and index_points grow.\n\nSort modes: slug (default), name, value-desc, value-asc, change-desc,\nchange-asc, points-desc, recent-desc. Unknown sort values fall back to\ndefault ordering instead of erroring.\n\nOpenAPI 3.1 spec updated. Three new smoke tests cover the list shape,\nvalue-desc ordering with nulls-last, and bogus-sort fallback.\n58/58 tests pass.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"41093f5","date":"2026-05-13 20:47:54 -0700","author":"Steve Abrams","subject":"yolo tick #25: family-detail grid gets sort + density + search controls","body":"Family detail page (/family/:slug) was missing the standing-rule controls\nthat index.ejs has — Steve's MEMORY says every grid view needs sort +\ndensity + grid-search. Added the same controls block + data-* attrs on\neach card + id=grid so the existing public/js/app.js machinery wires\nitself up automatically. No server changes; client-side only."},{"hash":"e863c10","date":"2026-05-13 20:31:05 -0700","author":"Steve Abrams","subject":"yolo tick #24: /api/family ?sort= (assets/comps/value/name/slug, default+UI)","body":""},{"hash":"0e64230","date":"2026-05-13 19:16:40 -0700","author":"Steve Abrams","subject":"yolo tick #23: source citation links on comp rows","body":"LEFT JOIN raw_observations in valueAsset() to expose payload.source_url\nper comp. Asset detail view renders an external ↗ cite link (target=_blank,\nrel=noopener noreferrer nofollow) next to each comp's source slug when a\nURL is available. Adds .cite styling to app.css. Sotheby's editorial scrape\nalready populates raw_observations.payload.source_url, so 2 comps on the\nbirkin-20 detail page now resolve to live article URLs."},{"hash":"69ece51","date":"2026-05-13 17:15:43 -0700","author":"Steve Abrams","subject":"yolo tick #22: source-weight-aware comparable_similarity_weight + methodology v0-stub.1","body":"- comparable_similarity_weight scales the 0.18 cap by avg comp source weight\n  (tier-1 pools score full 0.18; tier-4 pools drop to ~0.04). Real CLIP cosine\n  similarity still scheduled for v0.3.\n- METHODOLOGY_VERSION constant exported from lib/valuation.js; audit middleware,\n  /api/version, and tests now read from one source of truth.\n- Bumps methodology_version to v0-stub.1 per METHODOLOGY § 7 (component\n  semantics changed).\n- METHODOLOGY.md change log + table row updated.\n- 52/52 tests green."},{"hash":"3a8e130","date":"2026-05-13 15:21:11 -0700","author":"Steve Abrams","subject":"yolo tick #21: /family HTML index page (families list with cohort summary)","body":"Adds GET /family — HTML listing of every model family with one-row\ncohort summary cards (brand, family name, avg Q50, asset count, total\ncomps 24mo). Each card links to /family/:slug detail page.\n\n- routes/console.js — new /family handler ordered before /family/:slug\n- views/families.ejs — new view, reuses .grid + .card styles\n- views/partials/head.ejs — adds 'Families' nav link beside Console\n- tests/smoke.test.js — new smoke test (52/52 green)\n\nAuthor: steve@designerwallcoverings.com"},{"hash":"39ba614","date":"2026-05-13 14:45:37 -0700","author":"Steve Abrams","subject":"yolo tick #20: sparkline Y-axis min/max labels + descriptive aria-label","body":"Adds a left-side label gutter to the SSR sparkline partial showing the\nformatted min and max values ($1.2K / $14M style), plus a vertical\naxis tick line. aria-label now describes the range and point count\nrather than the static word \"sparkline\".\n\nROADMAP: 'Sparkline Y-axis labels' — done.\n\nAuthor: Steve Abrams <steve@designerwallcoverings.com>"},{"hash":"9cba52d","date":"2026-05-13 13:46:14 -0700","author":"Steve Abrams","subject":"yolo tick #19: condition_facets chips on asset detail comps","body":""},{"hash":"ced9794","date":"2026-05-13 12:46:03 -0700","author":"Steve Abrams","subject":"yolo tick #18: /admin/audit HTML view over recent valuation_calls","body":"Read-only inspector for the immutable audit log shipped in tick #7.\nSupports ?slug=<asset-slug> and ?limit=<1..1000> filters, no-store\ncache header, 4xx/5xx status-class coloring. Two new smoke tests\n(render + slug filter)."},{"hash":"1df0cc4","date":"2026-05-13 11:47:12 -0700","author":"Steve Abrams","subject":"yolo tick #17: /api/assets sort param (value/liquidity/comps/newest/slug)","body":""},{"hash":"06d377f","date":"2026-05-13 08:57:56 -0700","author":"Steve","subject":"snapshot: 6 file(s) changed, ~6 modified","body":""},{"hash":"447537f","date":"2026-05-10 07:55:57 -0700","author":"Steve Abrams","subject":"yolo tick #16: exotic / special-edition badges + audit-test race fix","body":"Surface canonical_assets.attrs.exotic and attrs.special_edition on the\nconsole grid, family pages, and asset detail h1 — quick visual signal\nfor the 14 Sotheby's-sourced exotic configs added in tick #13. Pure\nEJS conditional + CSS, no schema changes.\n\n  .exotic-badge   — accent-tinted pill (e.g., \"EXOTIC\" on Niloticus\n                    Himalaya, Crocodile, Alligator, Lizard configs)\n  .edition-badge  — warn-tinted pill (e.g., \"FAUBOURG\", \"RAINY DAYS\",\n                    \"MIDAS\" on the Sotheby's special-edition lots)\n\nlib/valuation.listAssets now returns ca.attrs in the SELECT so the\ntemplates can read attrs.exotic and attrs.special_edition without a\nsecond query. valueAsset() already returned attrs as part of the\nasset block.\n\nAudit test fix: tests/audit.test.js \"GET /api/health is NOT logged\"\nwas racing against parallel test files (node --test runs files\nconcurrently) — other tests' audit rows landed during the 200ms\nsleep, inflating the unfiltered COUNT(*). Tightened the assertion\nto filter `WHERE route = '/api/health'` so the test only counts\nits own writes. Caught a real flake, not a regression.\n\n44/44 tests green.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"db84b94","date":"2026-05-10 07:21:00 -0700","author":"Steve Abrams","subject":"yolo tick #15: launchd-driven overnight autonomous loop","body":"Steve is going to sleep and wants the lifestyle-asset-intel build\nrunning autonomously overnight. ScheduleWakeup-based loop dies when\nthe active Claude Code session closes, so set up a more durable\nlaunchd-driven path that fires every 30 minutes regardless of\nsession state.\n\nFiles (all under .yolo/):\n  tick-prompt.md   — self-contained instructions for each fire:\n                     working dir, PG env, pm2 process, standing-rule\n                     guardrails (no DNS, no email, no eBay scrape,\n                     local-only), candidate next-tick items, hard\n                     exit conditions (\"≥8 turns elapsed → wrap up\").\n  run-tick.sh      — shell wrapper. mkdir-atomic lock (no flock on\n                     macOS), 12-minute stale-lock recovery, claude\n                     CLI invoke with --print --dangerously-skip-\n                     permissions --max-turns 12, append to log.\n                     Unsets ANTHROPIC_API_KEY/AUTH_TOKEN per Steve's\n                     standing rule (Max plan only).\n  README.md        — operator runbook: where the plist lives, how\n                     to pause/resume/tail, why launchd over\n                     ScheduleWakeup, safety knobs.\n\nLaunchAgent: ~/Library/LaunchAgents/com.steve.lai-yolo.plist (not in\nthis repo — per-machine config). StartCalendarInterval = :15 and :45\nof every hour. WorkingDirectory pinned to project root. PATH includes\n/Users/stevestudio2/.local/bin so the claude binary resolves\ncorrectly.\n\n.gitignore extended to keep .yolo/yolo-loop.log,\n.yolo/launchd-{out,err}.log, and .yolo/.lock.d/ out of the repo;\nscripts and prompt stay tracked.\n\nValidated: lock acquires + refuses + releases correctly; launchctl\nlist shows the entry loaded with LastExitStatus 0; next fire 28\nminutes from this commit.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"012be8e","date":"2026-05-10 00:53:35 -0700","author":"Steve Abrams","subject":"yolo tick #14: auto-generate valuation_snapshots from real comps","body":"The 14 exotic / Birkin 20 / Constance canonical_assets added in tick\n#13 had real Sotheby's comps but no valuation_snapshot rows, so they\nshowed up in the grid as null Q10/Q50/Q90. Adding a one-shot seed\nblock that auto-fills v0-stub snapshots for any canonical_asset that\nhas ≥1 transaction but no snapshot for today's date.\n\nAlgorithm (per METHODOLOGY § 6 heuristic):\n  q50 = PERCENTILE_CONT(0.5) of gross_transaction_price (all comps)\n  q10 = q50 * 0.85\n  q90 = q50 * 1.27\n  liquidity_score, expected_dts, confidence stay null — those are\n  live-recomputed in lib/valuation.js from the 24-month window, so\n  any seeded constant would be inert. The grid + asset detail pages\n  pick up live values automatically.\n  auth_risk = 0.06 (low default for tier-1 auction provenance)\n  breakdown = { source: 'auto-from-real-comps', generator: 'tick-14',\n                method: 'median q50 + 0.85/1.27 quantile heuristic' }\n\nNo 24-month filter on the snapshot-input window (intentionally broader\nthan the live liquidity calc), so configs whose only comps are 2+\nyears stale still surface — they show the historical median Q50 with\na low live liquidity score, which is the correct signal.\n\nEffect on Birkin family rollup:\n  asset_count   9   → 19\n  avg_q50      $24,367 → $85,658\n  total_comps  26   → 37 (24mo filter still applies in this metric)\n\n29 snapshots / 29 assets = zero orphans. 44/44 tests still green.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"d9aa2ad","date":"2026-05-10 00:40:16 -0700","author":"Steve Abrams","subject":"yolo tick #13: real Sotheby's auction-result data ingest","body":"Pivoted from feature polish to actual data acquisition. Per Steve's\n\"build and look anywhere for data\" + \"always stay legal for scraping\"\ninstructions, I went after Sotheby's editorial pages — public records\nof realized auction prices, robots-friendly, no auth bypass, no\nimages. NOT eBay sold-history (Marketplace Insights API gated, HTML\nscrape is TOS-prohibited). NOT third-party aggregators without\nlicense review.\n\nSources (cited in raw_observations.payload.source_url for every row):\n  1. /articles/the-10-most-expensive-hermes-bags-sold-at-auction-in-2024\n  2. /articles/the-11-most-expensive-hermes-bags-sold-at-auction-in-the-first-half-2024\n  3. /articles/the-newest-hermes-bag-collector-obsession-the-birkin-20\n  4. /articles/secondary-market-signals-after-hermes-impressive-2024-results\n\nSchema additions:\n  - 1 new model_family: Constance\n  - 14 new canonical_assets (mostly exotic Niloticus Himalaya configs +\n    Faubourg / Midas / Rainy Days / Vert d'Eau / Vanille / Havane —\n    the Sotheby's headline lots)\n  - 20 raw_observations with full source_url + lot description in\n    payload jsonb (idempotent via UNIQUE source+external_id+kind)\n  - 22 transactions linked back to their raw_observation. Net seller\n    proceeds computed as gross * 0.82 per METHODOLOGY § 4 Sotheby's\n    hammer-to-net haircut.\n\nRegion attribution: transactions.region carries the auction city\n(HK / FR), so per METHODOLOGY § 11 they're stored as cross-region\ncomps and excluded from US comp pools — the canonical assets retain\nregion='US' (buyer region we model).\n\nTop realized prices now in DB:\n  $330k Kelly 25 Sellier Niloticus Himalaya paved diamond, HK 2024\n  $284k Kelly 25 Sellier Niloticus Himalaya 14K + diamond, Paris 2024\n  $234k Kelly 28 Niloticus Himalaya 18K + diamond, HK 2024\n  $234k Birkin 20 Faubourg Snow, HK 2024\n  $227k Birkin 20 Faubourg Snow, Paris 2024\n  $203k Birkin 20 Faubourg Rainy Days, HK 2025\n  $187k Birkin 25 Niloticus Himalaya palladium, HK 2024\n  $181k Birkin 20 Faubourg Midnight, HK 2024\n  $157k Kelly 25 Sellier Midas (yellow gold), HK 2024\n  …\n\nCounts after: 29 canonical_assets (13 exotic), 61 transactions,\n20 raw_observations. 44/44 tests still green.\n\nNote: new exotic assets have comps but no valuation_snapshot rows —\ndetail page renders \"No snapshot yet for this asset\" honestly. Auto-\nsnapshot generation from comps is a future tick.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"41d8213","date":"2026-05-10 00:32:37 -0700","author":"Steve Abrams","subject":"yolo tick #12: family taxonomy pages (/family/:slug)","body":"Cohort-level discovery surface, symmetric to /indices/:slug. Each\nHermès model family becomes a first-class page with summary stats,\nasset grid, and related indices.\n\nlib/valuation.getFamily(slug) joins model_families + brands, reuses\nlistAssets() enrichment via post-filter, pulls related indices via\nslug-substring match (v0 heuristic), and computes a 4-key summary:\n  asset_count, avg_q50, total_comps (24mo), avg_liquidity\n\nAPI: GET /api/family/{slug} → FamilyResponse (Cache-Control: public,\nmax-age=300). 404s on unknown slugs.\nConsole: GET /family/{slug} → views/family.ejs with breadcrumb, KPI\ncohort block, related-indices list, 4-column asset grid.\n\nasset.ejs h1 links family name to /family/{slug} so navigation closes\nthe loop. Top-level \"Birkin\" and \"Kelly\" nav entries with active-\nstate highlighting. OpenAPI 3.1 spec gains the path + FamilyResponse\nschema.\n\nLive shape (Birkin): 9 assets, $24,367 avg Q50, 26 trailing-24mo\ncomps, 0.615 avg liquidity, 2 related indices.\n\n44/44 tests green: 3 new (api JSON, 404, HTML).\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"25a6082","date":"2026-05-10 00:21:49 -0700","author":"Steve Abrams","subject":"yolo tick #11: per-route Cache-Control discipline","body":"CDN + client cache hygiene matters for any enterprise API. Without\nexplicit headers, intermediaries either over-cache (serving stale\nvaluations) or under-cache (defeating CDN economics on the\nlist endpoints).\n\nDiscipline (lib/audit must record every valuation hit, so caching\nthose is non-negotiable):\n\n  no-store    /api/valuation/{slug}, /api/audit/recent, /api/portfolio*\n              — auditable + per-user; must hit live every time\n  no-cache    /api/health, /api/version\n              — conditional GETs OK, never serve stale to monitors\n  public 60s  /api/assets\n              — list + filter results; refresh quickly\n  public 300s /api/sources, /api/index/{slug}\n              — slow-moving reference data\n  public 1h   /api/openapi.json\n              — only changes on deploy\n\nPlus stale-while-revalidate on the public ones so a brief origin\nhiccup doesn't propagate to clients.\n\nHelper: setCache(value) mini-middleware factory inside routes/api.js\nso every route declares its own cache policy on the same line as the\nhandler — easy to audit at code-review time.\n\nSmoke test asserts five representative paths (one per cache class).\n41/41 tests green; live curl probe confirms all 8 documented routes\nemit the expected Cache-Control header.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"6d5a46c","date":"2026-05-10 00:16:56 -0700","author":"Steve Abrams","subject":"yolo tick #10: server-side /api/assets?q= filter","body":"Catalog discovery scaling. Until now the grid filter was DOM-only —\nlib/valuation.listAssets returned the entire catalog and the client-\nside JS hid non-matching cards. That works at 15 assets but breaks\nwhen a paying API consumer pulls /api/assets at 1,500+ assets.\n\nlistAssets now accepts opts.q — a single token matched\ncase-insensitively across slug, brand, family, size, material, color,\nhardware, and construction via parameterized SQL LIKE. Returns\n{ count, q, assets[] } so callers can confirm the filter was honoured.\n\nroutes/console.js GET / pulls req.query.q through to listAssets and\npasses it to the EJS view, which SSR-renders the filter into the\nsearch input plus a \"Clear &times;\" link when q is set. Pressing\nEnter in the search box now navigates to /?q=… for server-side\nscoping; typing keeps the existing DOM-side live filter for\nsub-second feedback.\n\n40/40 tests green; new tests assert ?q=birkin returns only Birkins\nand ?q=clemence filters by material.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"8639f38","date":"2026-05-10 00:16:56 -0700","author":"Steve Abrams","subject":"yolo tick #9: OpenAPI 3.1 spec at /api/openapi.json","body":"The enterprise-pitch artifact. Lenders, insurers, and marketplaces\nneed a machine-readable contract to generate clients in any language;\nwithout one, every customer integration is a custom interview.\n\nlib/openapi.js exports buildSpec({ version, gitSha }) which assembles\na hand-written 3.1 document covering all 9 current routes (health,\nversion, sources, assets, valuation/{slug}, index/{slug}, portfolio\nGET/POST/PATCH/DELETE, audit/recent) and 14 schemas (Source,\nCanonicalAsset, ValuationSnapshot, ConfidenceBreakdown, Comp,\nMsrpObservation, ValuationResponse, IndexPoint, IndexResponse,\nHolding, AuditEntry, ErrorResponse, HealthResponse, VersionResponse).\nHand-written rather than introspected so the source of truth is one\nfile the team reviews. Spec version is 'package_version+gitSha[7]'\nso deploys are pinpointed.\n\nCached at boot in routes/api.js; served as application/json. Spec is\n~12 KB, 5 tags (ops/catalog/valuation/indices/portfolio). Smoke test\nasserts every documented path is present and that the v0.2-introduced\nmsrp + premium_to_msrp fields surface in the ValuationResponse schema.\n\nNav adds an 'OpenAPI' link next to 'Sources' for one-click discovery.\n\n38/38 tests green.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"19cb967","date":"2026-05-10 00:11:21 -0700","author":"Steve Abrams","subject":"yolo tick #8: MSRP series + premium-to-retail ratio","body":"BLUEPRINT.md flagged MSRP backfill as the central v0 problem for\nHermès — they sell Birkin/Kelly/Constance \"exclusively in stores\",\nso there's no e-commerce feed to harvest. Migration 0005 lays in\nmsrp_observations with a CHECK-constrained source enum\n(boutique_receipt / specialist_tracker / auction_house_commentary /\nuser_upload / client_pdf_invoice) and a (asset, source, date) unique\nkey so the same observation can't be double-counted across sources.\n\nSeed: only the Birkin 30 Togo 2022–2026 series from BLUEPRINT.md's\nown table, attributed to 'auction_house_commentary' (Sotheby's\nreport). The other 14 canonical assets stay null until real\nobservations land — Steve's standing rule against synthesized data\napplies here too, so no fabricated MSRPs.\n\nvalueAsset() now joins the latest msrp_observation per asset and\ncomputes premium_to_msrp = q50 / msrp_usd to two decimals. Returns\nnull when no MSRP exists. Live values for seeded B30 Togo:\n  q50=22300, msrp_usd=14900 (2026 reading), premium=1.50×\nThe number is naturally lower than Sotheby's 2024-quoted 2.4× because\n2026 MSRP has caught up while 2025 secondary held — exactly the\ndrift signal METHODOLOGY § 1 calls out.\n\nAsset detail page renders a \"Primary-market reference\" KPI block\n(MSRP $ + observed year + source) with a teal-tinted \"Q50 ÷ MSRP\"\ncell when the ratio is computable, and a contextual blurb pointing\nto /methodology when MSRP is missing. CSS adds .msrp-block + the\naccent-color premium cell.\n\n37/37 tests green; smoke test now asserts the MSRP fields too.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"44cae3a","date":"2026-05-10 00:08:11 -0700","author":"Steve Abrams","subject":"yolo tick #7: immutable valuation_calls audit log + middleware","body":"Fulfill the BLUEPRINT and METHODOLOGY commitment to immutable audit\nlogging. Every paying customer (lender / insurer / marketplace) needs\nto point at a specific call, see what we returned, and reproduce it.\nWithout this, no enterprise contract closes.\n\nMigration 0004 lays in valuation_calls — INSERT-only by app convention.\nIndexes on (called_at DESC), (asset_slug, called_at DESC), and\n(caller_ip, called_at DESC) cover the standard \"last 100 calls\",\n\"calls for this asset\", and \"what's this IP doing\" queries. There is\ndeliberately no updated_at / deleted_at column; the v1.0 role split\nwill withhold UPDATE/DELETE grants from the app role.\n\nlib/audit.js wires three pieces:\n  hashToken — sha256 redaction of any Bearer token before storage. Raw\n              tokens never touch the DB.\n  auditMiddleware — Express middleware that hooks res.finish, computes\n              latency via process.hrtime.bigint, sets x-request-id\n              response header, and writes via setImmediate so the user\n              never waits on the audit insert. Errors swallowed so\n              logging can't break a request. /api/health excluded so\n              monitoring traffic doesn't pollute the table.\n  recentCalls — paged SELECT with optional asset_slug filter.\n\nEndpoint /api/audit/recent serves the last N calls for ops dashboards\nand customer-side reconciliation. Self-recording (asks itself appear\nin subsequent calls) — acceptable v0 behaviour.\n\n7 new audit tests covering: hashToken edge cases, end-to-end audit-row\ninsertion via the middleware (using a per-test unique user-agent so\nfiles running in parallel don't race), /api/health skip, /api/audit/recent\nordering + filtering, and a schema sanity check that the table stays\nINSERT-only (no updated_at/deleted_at columns). 37/37 green.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"0bd3a76","date":"2026-05-10 00:02:15 -0700","author":"Steve Abrams","subject":"yolo tick #6: /api/version introspection endpoint","body":"Add the standard ops-hygiene introspection endpoint paying customers\nexpect: package_version, methodology_version, git SHA + dirty flag (so\nops can answer \"is prod the same as my local?\"), full runtime block\n(node + platform + arch + pid + boot_time + uptime_seconds), schema\nstate (every applied migration with timestamp), and data snapshot\n(asset / transaction / snapshot / index / holding / image counts +\nlatest snapshot as_of date).\n\nGit SHA + dirty flag resolved at boot via execSync('git rev-parse'); if\ngit isn't installed (or the deploy is from a tarball not a checkout)\nthe field returns null without crashing the route.\n\nTightened /api/health to read PKG_VERSION from package.json (was\nhardcoded). Added a /api/version smoke test asserting all the top-level\nkeys are present and that ≥3 migrations have applied.\n\n30/30 tests green.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"63e0563","date":"2026-05-09 23:59:36 -0700","author":"Steve Abrams","subject":"yolo tick #5: METHODOLOGY.md + /methodology route","body":"Freeze the formal methodology document the BLUEPRINT calls for. Anchors\nevery rule the v0 stub and future ML iterations must respect: source\neligibility/weighting, comparable selection, four-tier price\nnormalization (gross→all-in→net→normalized), the 7-grade ordinal\ncondition system + facet jsonb, quantile interpretation, the live\nliquidity/dts formulas (with units and clamps), the 9-component\nconfidence breakdown table with v0 sources called out per row, the\nauthenticity-risk three-band escalation rule, current outlier-filtering\nposture (none — by design), region/currency scope, index rebasing\nrules, and an explicit \"what v0 does NOT model\" section so enterprise\nbuyers don't assume coverage that isn't shipped.\n\nmethodology_version field is the version pin every published snapshot\nand index point references; v0 stamps everything 'v0-stub'. Versioning\ndiscipline: any rule change moving a published quantile by >1% MUST\nbump.\n\nRender path: marked@18 (added) parses the .md at boot into HTML; cached\nacross requests; reload requires pm2 restart. /methodology added to\nnav and to console.js routes. New views/methodology.ejs is a thin\nwrapper that injects body_html through partials/head + foot. CSS\nrules added for typographic long-form: H2 underlines, pre/code\nbackgrounds, accent-color blockquote bar, link underline-offset.\n\n29/29 tests still green.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"660aa14","date":"2026-05-09 23:56:34 -0700","author":"Steve Abrams","subject":"yolo tick #4: image_assets + image_embeddings (prep for v0.3 CLIP intake)","body":"Lay in the schema for the image-intake pipeline BLUEPRINT.md calls for\nin v0.3 (image-first valuation flow):\n\nimage_assets:\n  Provenance-rich row per image. Tracks canonical_asset linkage,\n  origin (upload/auction_lot/marketplace/crawl/reference), source_url,\n  storage_url, sha256 for content-addressed dedupe, mime/width/height,\n  and an attribution string per Steve's standing public-domain-only\n  rule. UNIQUE (sha256) lets the connector idempotently store images\n  without re-uploading the same bytes.\n\nimage_embeddings:\n  One row per (image, model) pair so we can run multiple embedders\n  side-by-side (CLIP ViT-B/32 baseline + SigLIP fine re-rank, etc.).\n  Stored as real[] with explicit dim column — PG-native, no extension\n  required. When pgvector lands locally the migration to vector(dim) +\n  HNSW is a 2-line ALTER.\n\nNo data seeded; v0.3 connector lands in a future tick.\n\n29/29 tests still green. 3/3 migrations recorded.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"9b63e84","date":"2026-05-09 23:54:24 -0700","author":"Steve Abrams","subject":"yolo tick #3: live liquidity_score + expected_dts (no more constants)","body":"Replace the seed's constant 0.78 liquidity / 21 dts with per-asset live\ncalculations from the trailing 24-month transaction window.\n\ncomputeLiquidity(stats) — pure function combining:\n  0.4 * count_factor      log-saturating in n_comps over a 20-comp ceiling\n  0.4 * recency_factor    linear decay from newest comp's age over 365 days\n  0.2 * spread_factor     1 − (Q90−Q10)/avg_gross, gross-normalized so a\n                          $30k asset with $5k spread scores the same as a\n                          $3k asset with $500 spread\n\ncomputeExpectedDts(liquidity) — inverse of liquidity, clamped [7, 120]:\n  liquidity=1.0 → 10 days, =0.5 → 35 days, =0 → 60 days\n\nvalueAsset() and listAssets() both compute these live and override the\nsnapshot's stored constants before returning. listAssets does it in SQL\nvia a second LATERAL aggregating PERCENTILE_CONT(0.9/0.1) and AVG —\nmirrors the JS quantile() helper so server + DB agree on numbers.\n\nAcross the 15 seeded assets: liquidity ranges 0.349–0.741, dts 23–43.\nThe console grid's sort-by-liquidity now produces meaningful order; the\nasset detail page shows real, per-config days-to-sell.\n\n8 new pure-function tests in tests/valuation.test.js (29/29 total green).\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"ea1adfb","date":"2026-05-09 23:50:44 -0700","author":"Steve Abrams","subject":"yolo tick #2: portfolio nav link + view styling","body":"Add /portfolio to the hamburger nav (was previously only reachable via\ndirect URL) and an /api/sources/api/health pair as external-link buttons.\nActive-state highlighting works for the indices and portfolio sections,\nnot just the console root.\n\nWire CSS for the classes the portfolio.ejs view was already emitting but\nthat had no rules: pl-pos/pl-neg coloring (good/bad CSS vars), flash\nbanners (ok/err with color-mix tinting), owner-form + holding-form grid\nlayouts, inline-remove link button, and a thicker top border on the\ntable totals row. color-mix in srgb keeps the tints theme-aware in both\nlight and dark.\n\n21/21 tests still green. /portfolio renders with active nav state.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"1d24570","date":"2026-05-09 23:46:15 -0700","author":"Steve Abrams","subject":"yolo tick #1: enforce transactions natural-key + seed idempotency","body":"Migration 0002 dedupes transactions on the natural key\n(canonical_asset_id, source_id, transacted_at, gross_transaction_price)\nand adds a UNIQUE constraint so future re-seeds are no-ops. Seed.sql\nalready had ON CONFLICT DO NOTHING in place (commit 917d044) — those\nclauses were inert until this constraint existed.\n\nVerified: txns went 128 → 41 after dedupe, re-seed held at 41.\n\ntests/valuation.test.js: dotenv + PGHOST=/tmp guards so tests work in\nthe Mac peer-auth environment without needing PGUSER/PGPASSWORD set.\n21/21 green.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"917d044","date":"2026-05-09 23:44:35 -0700","author":"Steve Abrams","subject":"real confidence calc + sparklines + portfolio CRUD (Max It batch)","body":"Three parallel improvements landed via the Max It dispatcher:\n\n1. lib/valuation.js — replace constant-stub confidence_breakdown with a\n   compositional calculator (computeBreakdown). Source quality weights from\n   the joined sources.weight column (tier fallback {1:1.0, 2:0.7, 3:0.4,\n   4:0.2}); sample depth log-saturates over 20 comps; recency decays\n   linearly over a year on the newest comp; auth/condition/region risk\n   penalties are bounded. Latest_snapshot.confidence is recomputed from\n   the live breakdown sum, clamped [0,1]. computeBreakdown is exported as\n   a pure function so tests don't need to boot Express.\n\n2. SVG sparklines — new views/partials/sparkline.ejs renders inline path +\n   circles from {as_of, value} points. Integrated into asset.ejs (price\n   trend over comps' transacted_at → gross) and index_detail.ejs (time\n   series of index points). Pure SSR, no client-side chart library. CSS\n   adds .spark { color: var(--accent); } and .trend section spacing.\n\n3. Portfolio CRUD — routes/api.js gains GET/POST/PATCH/DELETE on\n   /api/portfolio with email-format validation, asset_slug existence check,\n   parameterized queries, and LATERAL-joined latest snapshots so each\n   holding ships with current Q50 + unrealized P&L. routes/console.js adds\n   /portfolio?owner= page using a shared lib/portfolio.js helper. New\n   views/portfolio.ejs renders the holdings table + add-holding form +\n   per-row remove form with totals row at bottom. Datalist of all asset\n   slugs powers the add-form autocomplete.\n\nTests: 21 total now (7 smoke + 5 valuation + 9 portfolio); all green.\npackage.json test script switched to glob tests/*.test.js so future test\nfiles auto-pick up. tests/valuation.test.js needed dotenv + PGHOST=/tmp\nguards to match smoke.test.js's local-socket pattern.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"0877b89","date":"2026-05-09 23:44:35 -0700","author":"Steve Abrams","subject":"catalog breadth: 14 more assets + 3 indices (Max It batch)","body":"Expand the seed catalog from 1 → 15 canonical Hermès assets covering Birkin\n25/30/35 and Kelly 25/28/32 across Togo/Epsom/Clemence/Swift in Black, Gold,\nEtoupe, Vert Verone, with both GHW and PHW and Sellier/Retourne. Each new\nasset gets 2-4 transactions across 2024-2026 with realistic gross→net\nspreads (Sotheby's hammer-to-net ~18%, FASHIONPHILE/Rebag ~22%) and a v0\nvaluation snapshot whose breakdown jsonb mirrors the compositional-\nconfidence formula.\n\nPricing anchored to the BLUEPRINT.md Sotheby's reference (Birkin 30 Togo\nneutral $22,300 Q50): Birkin 25 +30-40%, Birkin 35 -10-20%, Sellier +15-25%\nover Retourne, Pristine/store-fresh +25-40%, recent stamps +10-20%.\n\nThree new indices: birkin-25-neutral, kelly-sellier-premium, recent-stamp-\npremium, each with 4 historical points 2022-12-31 → 2025-12-31.\n\nKnown issue: transactions table has no natural unique key, so re-running\nseed.sql duplicates txn rows (87 → 128 on second run). Logged for v0.x —\nfix is to add UNIQUE (canonical_asset_id, source_id, transacted_at,\ngross_transaction_price) constraint.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"cfa428e","date":"2026-05-09 21:36:58 -0700","author":"Steve Abrams","subject":"server + console + valuation API + smoke tests","body":"Express bootstrap (server.js) follows NPH middleware order: helmet CSP →\ncompression → morgan → body parsers → static → locals → routes → 404 →\nerror. Three route surfaces:\n\n  GET /                          internal market-ops console (EJS)\n  GET /asset/:slug               detail view w/ value band + comps + Schema.org Product\n  GET /indices/:slug             benchmark detail\n  GET /api/health                liveness + db_ok\n  GET /api/sources               tiered source registry\n  GET /api/assets                catalog list\n  GET /api/valuation/:slug       blueprint-shaped { asset, latest_snapshot, comps[], confidence_breakdown }\n  GET /api/index/:slug           time-series points\n  GET /api/portfolio?owner=…     stub holdings list\n\nlib/valuation.js is a SQL-only stub — its file-level docstring locks down\nthat the response shape is the future-API contract; only its body changes\nwhen real ML lands in v0.5.\n\nConsole enforces every standing-rule grid control: 8-option sort <select>,\n3–8 column density slider, live grid-search input, sun/moon dark/light\ntoggle (anti-flash inline + CSS-var inversion), hamburger nav, and\nSchema.org JSON-LD (WebSite root + Product on detail). All persisted to\nlocalStorage. Footer renders an explicit TODO until BRAND_DOMAIN is set,\nso v0.1 (info@<domain> Purelymail provisioning) can't be silently skipped.\n\n7 smoke tests via node --test cover health, sources, valuation contract\nshape, index time-series, console controls, and asset-page schema.org.\nAll green against the seeded database.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"5afcf7c","date":"2026-05-09 21:36:40 -0700","author":"Steve Abrams","subject":"canonical schema + Birkin sample seed + pg pool wrapper","body":"Migration 0001 lays out the 10-table canonical model from BLUEPRINT.md:\nsources / brands / model_families / canonical_assets / raw_observations\n(bronze) / transactions (silver) / valuation_snapshots (gold) / indices /\nindex_points / portfolio_holdings, plus schema_migrations bookkeeping.\n\nSeed data is the blueprint's normalized 2022–2025 secondary-market series\nfor the Birkin 30 Togo Gold GHW (US), 8 source rows across tiers 1–3, the\nbirkin-30-togo-neutral index with 4 historical points, and a v0-stub\nvaluation snapshot whose breakdown jsonb mirrors the compositional\nconfidence formula in the blueprint.\n\nlib/db.js carries the pg-pool gotcha guards (empty PGPASSWORD strip,\nomit password key when blank) that NPH learned the hard way; macOS dev\ndefaults PGHOST=/tmp so unix-socket peer auth Just Works.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"18d04fe","date":"2026-05-09 21:36:26 -0700","author":"Steve Abrams","subject":"initial scaffold — package, env, pm2, gitignore, blueprint, roadmap","body":"v0 of lifestyle-asset-intel — enterprise valuation/liquidity/ownership-cost\nplatform per the pasted blueprint. This commit lays in the project shell:\npackage.json + lockfile, .env.example with the macOS-socket PG pattern, pm2\necosystem on port 9820, .gitignore per Steve's standing rule, full\nBLUEPRINT.md verbatim, and ROADMAP.md mapping the path from this skeleton\nto the v2 multi-vertical (handbags → watches → sneakers → jewelry) target.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>\n"}]}