← back to Lifestyle Asset Intel
yolo tick #22: source-weight-aware comparable_similarity_weight + methodology v0-stub.1
69ece512bde664b872a08cb722973b7d9d68bdaa · 2026-05-13 17:15:43 -0700 · Steve Abrams
- comparable_similarity_weight scales the 0.18 cap by avg comp source weight
(tier-1 pools score full 0.18; tier-4 pools drop to ~0.04). Real CLIP cosine
similarity still scheduled for v0.3.
- METHODOLOGY_VERSION constant exported from lib/valuation.js; audit middleware,
/api/version, and tests now read from one source of truth.
- Bumps methodology_version to v0-stub.1 per METHODOLOGY § 7 (component
semantics changed).
- METHODOLOGY.md change log + table row updated.
- 52/52 tests green.
Files touched
M METHODOLOGY.mdM lib/audit.jsM lib/valuation.jsM routes/api.jsM tests/audit.test.jsM tests/smoke.test.js
Diff
commit 69ece512bde664b872a08cb722973b7d9d68bdaa
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed May 13 17:15:43 2026 -0700
yolo tick #22: source-weight-aware comparable_similarity_weight + methodology v0-stub.1
- comparable_similarity_weight scales the 0.18 cap by avg comp source weight
(tier-1 pools score full 0.18; tier-4 pools drop to ~0.04). Real CLIP cosine
similarity still scheduled for v0.3.
- METHODOLOGY_VERSION constant exported from lib/valuation.js; audit middleware,
/api/version, and tests now read from one source of truth.
- Bumps methodology_version to v0-stub.1 per METHODOLOGY § 7 (component
semantics changed).
- METHODOLOGY.md change log + table row updated.
- 52/52 tests green.
---
METHODOLOGY.md | 9 +++++----
lib/audit.js | 5 +++--
lib/valuation.js | 40 ++++++++++++++++++++++++++++++++++++----
routes/api.js | 6 ++++--
tests/audit.test.js | 3 ++-
tests/smoke.test.js | 3 ++-
6 files changed, 52 insertions(+), 14 deletions(-)
diff --git a/METHODOLOGY.md b/METHODOLOGY.md
index 003c17d..8257adc 100644
--- a/METHODOLOGY.md
+++ b/METHODOLOGY.md
@@ -158,7 +158,7 @@ clamped to `[0, 1]`.
| Component | Range | v0 source |
|------------------------------------|----------------|--------------------------------------------------|
| `source_quality_weight` | 0 to +0.30 | Comps' `sources.weight`-weighted average × 0.30 |
-| `comparable_similarity_weight` | 0 to +0.18 | Constant 0.18 (no embeddings until v0.3) |
+| `comparable_similarity_weight` | 0 to +0.18 | Avg comp source weight × 0.18 (full CLIP cosine arrives in v0.3) |
| `sample_depth_weight` | 0 to +0.20 | log-saturating in n_comps over 20-comp ceiling |
| `recency_weight` | 0 to +0.15 | Linear decay over 365 days from newest comp |
| `image_match_weight` | 0 to +0.09 | Constant 0.09 (no image intake until v0.3) |
@@ -234,6 +234,7 @@ imply coverage.
## 14. Change log
-| Version | Date | Change |
-|----------|------------|---------------------------------------------------------------------|
-| v0-stub | 2026-05-09 | Initial methodology — confidence, liquidity, dts all live-computed. |
+| Version | Date | Change |
+|------------|------------|---------------------------------------------------------------------|
+| v0-stub | 2026-05-09 | Initial methodology — confidence, liquidity, dts all live-computed. |
+| v0-stub.1 | 2026-05-13 | `comparable_similarity_weight` is now source-weight-aware (was constant 0.18). Tier-1 comp pools score the full 0.18; tier-4 pools drop to ~0.04. Full CLIP embeddings still scheduled for v0.3. |
diff --git a/lib/audit.js b/lib/audit.js
index d54b381..b4d28f9 100644
--- a/lib/audit.js
+++ b/lib/audit.js
@@ -10,6 +10,7 @@
const crypto = require('crypto');
const { randomUUID } = require('crypto');
const { pool } = require('./db');
+const { METHODOLOGY_VERSION } = require('./valuation');
const SKIP_PATHS = new Set(['/health']); // relative to /api mount
@@ -43,7 +44,7 @@ async function recordCall(row) {
row.method,
row.asset_slug || null,
row.response_status,
- row.methodology_version || 'v0-stub',
+ row.methodology_version || METHODOLOGY_VERSION,
row.latency_ms == null ? null : row.latency_ms,
row.user_agent || null,
row.request_id || null
@@ -74,7 +75,7 @@ function auditMiddleware(req, res, next) {
method: req.method,
asset_slug: extractAssetSlug(req),
response_status: res.statusCode,
- methodology_version: 'v0-stub',
+ methodology_version: METHODOLOGY_VERSION,
latency_ms: latencyMs,
user_agent: req.get('user-agent') || null,
request_id: requestId
diff --git a/lib/valuation.js b/lib/valuation.js
index 3673daa..6fe547d 100644
--- a/lib/valuation.js
+++ b/lib/valuation.js
@@ -14,6 +14,13 @@ const { many, one } = require('./db');
// Tier-tier fallback weights when a source.weight column is unreachable.
const TIER_FALLBACK = { 1: 1.0, 2: 0.7, 3: 0.4, 4: 0.2 };
+// Single source of truth for the methodology stamp emitted by /api/version,
+// the audit log middleware, and any newly written valuation snapshots. Bump
+// this whenever a confidence-breakdown component changes meaning, per
+// METHODOLOGY § 7. v0-stub.1: comparable_similarity_weight became
+// source-weight-aware (was constant 0.18).
+const METHODOLOGY_VERSION = 'v0-stub.1';
+
async function valueAsset(slug) {
const asset = await one(
`SELECT ca.*, mf.name AS family_name, mf.slug AS family_slug,
@@ -210,9 +217,33 @@ function computeBreakdown({ asset, comps, snapshot, sourceWeights }) {
source_quality_weight = clamp(avg * 0.30, 0, 0.30);
}
- // 2. comparable_similarity_weight — constant for v0; embeddings come later.
- // TODO v0.3: replace with cosine-sim against a reference vector.
- const comparable_similarity_weight = n > 0 ? 0.18 : 0;
+ // 2. comparable_similarity_weight — source-weight-aware proxy for v0.
+ // Until real CLIP embeddings land in v0.3, the best signal we have for
+ // "how similar are these comps to the target" is how trusted the comp
+ // SOURCES are: a tier-1 auction-house lot describing the exact
+ // configuration is a tighter similarity signal than a tier-3 aggregator
+ // listing. We scale the 0.18 cap by the avg source weight already
+ // computed for source_quality_weight (re-derived here for purity),
+ // so a comp pool of all tier-1 lots scores the full 0.18, a mix of
+ // tier-1/2 scores ~0.15, and a tier-4 pool drops to ~0.04. Bumps
+ // methodology_version per METHODOLOGY § 7.
+ let comparable_similarity_weight = 0;
+ if (n > 0) {
+ let acc = 0;
+ for (const c of compArr) {
+ let w;
+ if (c.source_weight != null) {
+ w = parseFloat(c.source_weight);
+ } else if (weights[c.source_tier] != null) {
+ w = weights[c.source_tier];
+ } else {
+ w = TIER_FALLBACK[c.source_tier] != null ? TIER_FALLBACK[c.source_tier] : 0.2;
+ }
+ acc += isFinite(w) ? w : 0.2;
+ }
+ const avg = clamp(acc / n, 0, 1);
+ comparable_similarity_weight = clamp(avg * 0.18, 0, 0.18);
+ }
// 3. sample_depth_weight — log-saturating in comp count, capped at 20 comps.
const sample_depth_weight = clamp(
@@ -615,5 +646,6 @@ function quantile(sorted, q) {
module.exports = {
valueAsset, listAssets, getIndex, getFamily, listFamilies,
- computeBreakdown, computeLiquidity, computeExpectedDts, quantile
+ computeBreakdown, computeLiquidity, computeExpectedDts, quantile,
+ METHODOLOGY_VERSION
};
diff --git a/routes/api.js b/routes/api.js
index 01ec3ad..c3d867a 100644
--- a/routes/api.js
+++ b/routes/api.js
@@ -3,7 +3,9 @@ const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const { pool } = require('../lib/db');
-const { valueAsset, listAssets, getIndex, getFamily, listFamilies } = require('../lib/valuation');
+const {
+ valueAsset, listAssets, getIndex, getFamily, listFamilies, METHODOLOGY_VERSION
+} = require('../lib/valuation');
const {
isValidEmail,
listHoldings,
@@ -96,7 +98,7 @@ router.get('/version', setCache(NO_CACHE), async (req, res, next) => {
res.json({
service: 'lifestyle-asset-intel',
package_version: PKG_VERSION,
- methodology_version: 'v0-stub',
+ methodology_version: METHODOLOGY_VERSION,
git: GIT_SHA ? { sha: GIT_SHA, dirty: GIT_DIRTY } : null,
runtime: {
node: process.version,
diff --git a/tests/audit.test.js b/tests/audit.test.js
index 7fb4d88..39e30b7 100644
--- a/tests/audit.test.js
+++ b/tests/audit.test.js
@@ -12,6 +12,7 @@ const assert = require('node:assert/strict');
const app = require('../server');
const { pool } = require('../lib/db');
const { hashToken } = require('../lib/audit');
+const { METHODOLOGY_VERSION } = require('../lib/valuation');
let server;
let baseUrl;
@@ -78,7 +79,7 @@ test('GET /api/valuation/:slug records a row in valuation_calls', async () => {
assert.equal(r0.method, 'GET');
assert.equal(r0.asset_slug, 'birkin-30-togo-gold-ghw-us');
assert.equal(r0.response_status, 200);
- assert.equal(r0.methodology_version, 'v0-stub');
+ assert.equal(r0.methodology_version, METHODOLOGY_VERSION);
assert.equal(r0.user_agent, TEST_UA);
assert.match(r0.caller_token_hash, /^[0-9a-f]{64}$/);
assert.ok(typeof r0.latency_ms === 'number' && r0.latency_ms >= 0);
diff --git a/tests/smoke.test.js b/tests/smoke.test.js
index 557cf54..c7dafd4 100644
--- a/tests/smoke.test.js
+++ b/tests/smoke.test.js
@@ -15,6 +15,7 @@ process.env.PGDATABASE = process.env.PGDATABASE || 'lifestyle_asset_intel';
const app = require('../server');
const { pool } = require('../lib/db');
+const { METHODOLOGY_VERSION } = require('../lib/valuation');
let server;
let baseUrl;
@@ -157,7 +158,7 @@ test('GET /api/version returns build + schema + data introspection', async () =>
const r = await get('/api/version');
assert.equal(r.status, 200);
assert.equal(r.json.service, 'lifestyle-asset-intel');
- assert.equal(r.json.methodology_version, 'v0-stub');
+ assert.equal(r.json.methodology_version, METHODOLOGY_VERSION);
assert.ok(r.json.runtime && r.json.runtime.node, 'runtime.node present');
assert.ok(typeof r.json.runtime.uptime_seconds === 'number');
assert.ok(Array.isArray(r.json.schema.migrations));
← 3a8e130 yolo tick #21: /family HTML index page (families list with c
·
back to Lifestyle Asset Intel
·
yolo tick #23: source citation links on comp rows 0e64230 →