mirror of
https://github.com/superdesigndev/treg.git
synced 2026-10-02 03:24:35 +08:00
fix(arena): show phone format validity from published audits
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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'));
|
||||
});
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user