fix(arena): show phone format validity from published audits

This commit is contained in:
Jason Zhou
2026-09-10 16:10:06 +10:00
parent 70ab6b994f
commit 86249b7bcb
5 changed files with 45 additions and 10 deletions
+8 -3
View File
@@ -670,10 +670,15 @@ table to avoid clipping. No sample-status sublabels are shown.
Row hover details identify the verifier providers and check date; the chart source shows the sample period. Fewer than 20 completed checks
are withheld; small-sample labels are hidden. Risky, unknown and conflicting verdicts stay
in the denominator but do not count as valid; unfinished checks are excluded. A genuine zero is
displayed and missing values have no chart bars. Phone format checks never produce a verified rate:
reachability and ownership remain unverified. The publication preserves the legacy `rate` projection
displayed and missing values have no chart bars. Phone lookup uses a separate **Phone format validity**
metric: the percentage of sampled returned numbers passing format checks among completed checks.
The header tooltip explains that this does not confirm a live line, deliverability or ownership.
The API publishes this only as `format_validity_rate` for `phone_format` rows with at least 20
completed checks; it never promotes phone format checks into email validity or verified hit rate.
The publication preserves the legacy `rate` projection
for older consumers and adds explicit `checked_n` and `validity_rate` fields; the UI only consumes
`validity_rate`, so older snapshots cannot accidentally display the lookup projection as validity.
`validity_rate` for email and `format_validity_rate` for phone, so older snapshots cannot
accidentally display the lookup projection as validity.
Publication data stays outside the checkout. To populate a migrated deployment, pass the private
aggregate JSON to `scripts/import_arena_verification.py` with its configured `TREG_DATABASE_URL`;
@@ -83,6 +83,8 @@ def build_snapshot(data):
row["checked_n"] = row["sample_n"] - row["unresolved_n"]
eligible_validity = row["method"] == "email_verifier_consensus" and row["checked_n"] >= 20
row["validity_rate"] = round(100 * row["passed_n"] / row["checked_n"], 2) if eligible_validity else None
if row["method"] == "phone_format":
row["format_validity_rate"] = round(100 * row["passed_n"] / row["checked_n"], 2) if row["checked_n"] >= 20 else None
return payload
+7 -4
View File
@@ -235,8 +235,8 @@
},
supportsVerifiedRate(){return ['people.email.find','people.phone.find'].includes(this.taskId);},
vendorListingPrompt(){return 'Read https://treg.to/vendor-listing.md and add our API to the treg catalog, then open a PR.';},
verifiedRateLabel(){return this.taskId==='people.email.find'?'Email validity rate':'Verified hit rate';},
verifiedExplanation(){return this.taskId==='people.phone.find'?'Phone format checks do not establish reachability or ownership. Verified hit rate is not available yet.':'Percentage of sampled returned emails with completed checks that both verifiers marked valid. Risky, unknown and conflicting verdicts do not count as valid.';},
verifiedRateLabel(){return this.taskId==='people.email.find'?'Email validity rate':'Phone format validity';},
verifiedExplanation(){return this.taskId==='people.phone.find'?'Percentage of sampled returned phone numbers with completed checks that passed the verifier’s number-format check. This does not confirm a live line, deliverability or ownership.':'Percentage of sampled returned emails with completed checks that both verifiers marked valid. Risky, unknown and conflicting verdicts do not count as valid.';},
verificationWindow(){const v=this.insights?.verification;if(!v)return 'No published pilot';const date=x=>new Date(x).toLocaleDateString('en-US',{timeZone:'UTC'});return 'Sample '+date(v.sample_since)+'–'+date(v.sample_until)+' · Checked '+date(v.checked_at)+' · UTC';},
insightVerification(){return ['people.email.verify','people.phone.verify'].includes(this.taskId);},
insightWindow(){if(!this.insights)return '';const date=s=>new Intl.DateTimeFormat('en-US',{month:'short',day:'numeric',year:'numeric',timeZone:'UTC'}).format(new Date(s));const observed=this.insights.observed_since&&this.insights.observed_until;return (observed?'Recorded ':'Window ')+date(observed?this.insights.observed_since:this.insights.since)+' – '+date(observed?this.insights.observed_until:this.insights.until)+' · UTC';},
@@ -331,15 +331,18 @@
showMetricTooltip(event){const r=event.currentTarget.getBoundingClientRect(),width=Math.min(300,window.innerWidth-24);this.metricTooltip={width,left:Math.max(12,Math.min(r.left+(r.width-width)/2,window.innerWidth-width-12)),top:Math.max(12,Math.min(r.bottom+8,window.innerHeight-140))};},
verifiedRateValue(row){
const a=row.audit;
return this.taskId==='people.email.find'&&a?.method==='email_verifier_consensus'&&a.checked_n>=20&&Number.isFinite(a.validity_rate)&&a.validity_rate>=0&&a.validity_rate<=100?a.validity_rate:null;
const email=this.taskId==='people.email.find',phone=this.taskId==='people.phone.find';
if(!a||!(a.checked_n>=20)||!(email&&a.method==='email_verifier_consensus'||phone&&a.method==='phone_format'))return null;
const rate=email?a.validity_rate:a.format_validity_rate;
return Number.isFinite(rate)&&rate>=0&&rate<=100?rate:null;
},
verifiedRate(row){const rate=this.verifiedRateValue(row);return rate===null?'—':rate.toFixed(1)+'%';},
verifiedRateNote(row){
if(this.taskId==='people.phone.find')return 'Format checked only; reachability and ownership are not verified.';
const a=row.audit;
if(!a)return 'No published verification sample for this endpoint and input.';
if(!(a.checked_n>=20))return 'Insufficient completed verification sample.';
const v=this.insights?.verification,date=x=>x?new Date(x).toLocaleDateString('en-US',{timeZone:'UTC'}):'';
if(this.taskId==='people.phone.find')return 'Share of sampled returned phone numbers that passed '+a.verifiers.map(p=>this.providerName(p)).join(' + ')+' number-format checks. Checks completed '+date(v?.checked_at)+'. Unresolved inputs are excluded; reachability and ownership are not verified.';
return 'Share of sampled returned emails marked valid by '+a.verifiers.map(p=>this.providerName(p)).join(' + ')+'. Checks completed '+date(v?.checked_at)+'. '+'Risky, unknown and conflicting verdicts are not counted as valid; unfinished checks are excluded. Verifier agreement does not confirm delivery or ownership.';
},
insightRate(row){
+12 -3
View File
@@ -730,10 +730,19 @@ test('Email validity matches exact endpoint and input and is independent of look
audit.checked_n=40;audit.validity_rate=NaN;assert.equal(app.chartBars.length,0);
});
test('Phone format-only evidence never becomes verified hit rate',()=>{
test('Phone format validity uses its own metric and never implies verified hit rate',()=>{
const {app}=setup();app.taskId='people.phone.find';
const row={audit:{method:'phone_format',sample_n:100,checked_n:100,validity_rate:100,baseline_n:200,rate:100,estimate:true}};
assert.equal(app.verifiedRate(row),'—');assert.match(app.verifiedRateNote(row),/ownership/);assert.ok(app.chartViews.some(v=>v.id==='verified'));
const row={audit:{method:'phone_format',sample_n:100,checked_n:100,validity_rate:100,baseline_n:200,rate:100,verifiers:['tomba']}};
assert.equal(app.verifiedRate(row),'—','Legacy email and projected rates must not leak into phone metrics');
row.audit.format_validity_rate=82.5;assert.equal(app.verifiedRate(row),'82.5%');
assert.equal(app.verifiedRateLabel,'Phone format validity');assert.match(app.verifiedExplanation,/does not confirm a live line/);
assert.match(app.verifiedRateNote(row),/ownership are not verified/);assert.ok(app.chartViews.some(v=>v.id==='verified'));
row.audit.format_validity_rate=0;assert.equal(app.verifiedRate(row),'0.0%');
row.audit.checked_n=19;assert.equal(app.verifiedRate(row),'—');
delete row.audit.checked_n;assert.equal(app.verifiedRate(row),'—');
row.audit.checked_n=100;row.audit.format_validity_rate=101;assert.equal(app.verifiedRate(row),'—');
row.audit.format_validity_rate=82.5;row.audit.method='email_verifier_consensus';assert.equal(app.verifiedRate(row),'—');
app.taskId='people.email.find';assert.equal(app.verifiedRate(row),'100.0%','Email still uses email validity only');
app.taskId='people.email.verify';assert.ok(!app.chartViews.some(v=>v.id==='verified'));
});
+16
View File
@@ -87,3 +87,19 @@ def test_validity_uses_completed_returned_emails_not_lookup_baseline():
data["rows"][0].update(task="people.phone.find", endpoint="tomba.people.phone.find",
method="phone_format", verifiers=["tomba"], unresolved_n=0)
assert build_snapshot(data)["rows"][0]["validity_rate"] is None
def test_phone_format_validity_has_a_separate_completed_check_denominator():
data = aggregate()
data["rows"][0].update(task="people.phone.find", endpoint="tomba.people.phone.find",
method="phone_format", verifiers=["tomba"], unresolved_n=5)
row = build_snapshot(data)["rows"][0]
assert row["format_validity_rate"] == 85.71
assert row["validity_rate"] is None and row["rate"] is None
data["rows"][0].update(passed_n=0, unresolved_n=21)
assert build_snapshot(data)["rows"][0]["format_validity_rate"] is None
data["rows"][0]["unresolved_n"] = 40
assert build_snapshot(data)["rows"][0]["format_validity_rate"] is None
data["rows"][0]["unresolved_n"] = 0
assert build_snapshot(data)["rows"][0]["format_validity_rate"] == 0
assert "format_validity_rate" not in build_snapshot(aggregate())["rows"][0]