What the face-comparison distance number actually means, which thresholds turn it into a verdict on the vuer_oss side, and what vuer_cv itself recommends — the set of facts that keeps being re-derived from scratch on every threshold ticket (FKITDEV-8827, FKITDEV-8655 / ASSRAFIPI-110).
For Agents
Durable, cross-ticket reference. All
file:linecitations are first-hand source reads in the localvuer_ossandvuer_cvworking copies (verified 2026-07-22). Companion notes: face-comparison-data-verdict-threshold-model (the canonical data-model note — which FK column links which flow, videochat vs self-service, nocreatedAtindex) and face-comparison-different-face-db-query (read-only SQL recipe). This note is the numbers-and-semantics half; go there for the plumbing.
TL;DR — the numbers, in one table
| Number | Where it lives | What it actually means |
|---|---|---|
| 0–2 | faceComparisons.euclideanDistance | cosine distance range (misnamed column) |
| 0.5 | perfect (shipped default) | SUCCESS / PROBABLE boundary |
| 0.6 | probable (shipped default) | PROBABLE / FAILURE boundary = the real accept/reject line |
| 0.55 | Raiffeisen perfect override | still only the SUCCESS/PROBABLE boundary — not accept/reject |
| 0.62 | CV’s own recommended matching threshold (CV 4.0.0) | what the model authors say “match” means |
Lower distance = more similar. We run stricter than CV’s own recommendation at every tier.
1. The stored number is a cosine distance, despite the name
faceComparisons.euclideanDistance is a cosine distance in the range 0–2, not a Euclidean distance — the column name is legacy.
vuer_oss/server/db/model/faceComparison.js:31
It is written unconditionally for every computed comparison — the row is persisted regardless of whether the comparison passed or failed any threshold. There is no “only store failures” filter, so the table is a complete population for statistical work.
Do not reason about it as a Euclidean metric
Any intuition imported from Euclidean face-embedding distances (typical dlib-style 0.6 cutoffs on a 0–~1.4 scale) is coincidentally close but conceptually wrong. It is
cosine, range 0–2. Also see Face Comparison for the CV-internal computation.
2. The verdict is a read-time three-tier ladder, not a stored flag
Nothing in the DB stores “match” / “different_face”. faceComparisons.status is only {created, failed, success} (i.e. did the comparison run). The human-meaningful verdict is computed at read time:
vuer_oss/server/service/SelfServiceCheckerService.js:142-151
distance <= perfect → SUCCESS
distance <= match → PROBABLE
distance <= probable → PROBABLE
otherwise → FAILURE
Both the match and probable tiers map to PROBABLE; only the top tier is SUCCESS and only the fall-through is FAILURE.
Threshold resolution order — settings come from settings.faceComparison.euclideanDistances, resolved:
- per-self-service-room config-state activity (the room’s own recorded config), then
- the global
settingsServicevalue.
See 5 Threshold sourcing differs by link type for the exact activity-log lookup and the REPLACE-not-MERGE caveat, and for the fact that videochat/operator-linked rows are not verdict-classified at runtime at all.
3. Shipped defaults, and the per-partner override pattern
Shipped defaults: perfect 0.5 / probable 0.6.
The established per-partner override pattern overrides only perfect — the worked example is:
vuer_oss/customization/listeners/self-service-v2.js:77-89
Raiffeisen sets perfect = 0.55. probable is left at the default, so Raiffeisen’s effective ladder is ≤0.55 SUCCESS · ≤0.6 PROBABLE · >0.6 FAILURE.
When a partner says "our threshold is X"
Ask which key they mean. In practice partners have only ever been given a
perfectvalue, because that is the only key the override pattern touches.
4. ⚠️ The 0.55 trap — perfect is NOT the accept/reject boundary
This has already misframed a customer ticket
A partner-visible “threshold” of 0.55 is the SUCCESS/PROBABLE boundary, not the accept/reject boundary. Rejection happens above
probable(0.6 by default). A comparison at distance 0.58 is PROBABLE, not rejected — whether the customer is stopped depends on what the partner’s process does with a non-SUCCESS verdict, which is a business decision outside the ladder.Conflating “the 0.55 threshold” with “the pass/fail threshold” already misframed ASSRAFIPI-110 (Raiffeisen asking whether 0.55 should be relaxed for 14–18-year-olds). Before tuning anything for a partner, establish which boundary they are actually experiencing — if their process auto-rejects everything that isn’t SUCCESS, then 0.55 is their effective cutoff, but that is their choice, not ours.
Checklist for any incoming “please change our threshold” request:
- Which key —
perfect,match, orprobable? - Does their process auto-reject non-SUCCESS, or does PROBABLE route to manual review?
- Are the thresholds global or overridden per self-service room (config-state activity)?
- What distances are the affected customers actually producing? (see §7 — we can measure this)
5. CV side: 0.62 is CV’s own recommendation, and we are stricter
From vuer_cv/changelog.md (local shallow clone @ 4.9.1):
- CV’s recommended matching threshold =
0.62, lower distance = match, distance range 0–2 — set in CV 4.0.0 —vuer_cv/changelog.md:194-195 - CV 4.0.0 is also the last change to the face detection / landmark / encoding models —
vuer_cv/changelog.md:202
Nothing since 4.0.0 retrains the face model. In particular, CV 4.9.0’s only face-comparison-relevant entry is:
“improve input preprocessing of face encoder, face landmark detector” —
vuer_cv/changelog.md:29
Two consequences worth carrying into every threshold discussion:
We run stricter than CV's own recommendation on every tier
CV says “match” at ≤ 0.62. Our SUCCESS tier is 0.5 (0.55 for Raiffeisen) and even our most permissive non-FAILURE tier tops out at 0.6. Every tier of our ladder is tighter than the model authors’ recommendation. Any conversation about “loosening” thresholds starts from a position that is already conservative relative to CV.
Preprocessing changes can silently invalidate calibrated thresholds
A change to the input preprocessing of the face encoder (exactly what 4.9.0 shipped) can shift the distance distribution without any model retrain and without any changelog claim about accuracy. Thresholds calibrated against a pre-upgrade distribution are not guaranteed to hold post-upgrade — and nothing in the release notes will tell you. Treat a CV upgrade that touches encoder/landmark preprocessing as a threshold-recalibration trigger, and ask CV for before/after distance distributions rather than an accuracy adjective.
Related precedent: FKITDEV-8788 — a CV 4.9.0 fix was marked “Solved” prematurely on a full-image-only test. Asking CV for evidence rather than assurances is warranted.
6. ⚠️ Access gotcha — vuer_cv source cannot be checked
No GitHub access, no CV git history
TechTeamer/vuer_cvis not accessible to our account — aswowjeeezwithreposcope, GitHub returns HTTP 404.The only local copy is a shallow (
depth-1) clone at:/Users/levander/coding/facekom-v2-clones/vuer_cv— on branchdevel@ 4.9.1.Consequences: there is no CV git history to inspect (no blame, no diff between releases, no commit archaeology), and CV-team claims cannot be verified from source on our side. The
changelog.mdin the shallow clone is the only artefact available. Evidence about CV behaviour must be requested from the CV team — that is a hard constraint, not an unwillingness to look.
7. Age-stratified analysis is feasible on our side — no CV involvement needed
The blocking question in ASSRAFIPI-110 (“does the threshold need to differ for 14–18 / 18–26?”) is answerable with our own data:
emrtdInfocarries the chip-readdateOfBirthand shares join keys (customerId/roomId/selfServiceRoomId) withfaceComparisons—vuer_oss/server/db/model/emrtdInfo.js:124-126,279-288- → exact age at comparison time is computable, from the passport/ID chip, not an estimate.
The tooling largely exists: FKITDEV-8827’s vuer_oss/customization/bin/raiffeisen-facecomparison-export.js already handles verdicts, threshold resolution, decryption, and CSV/XLSX output — adding an age column and an emrtdInfo join is a small delta on top.
Don't reach for CV's age model
CV does expose
POST /api/v1/face-gender-age(predicted age), but it is unnecessary here: the chip DOB is exact and already in our database. Predicted age would add model error to an analysis whose whole point is to isolate an age effect.
8. Reframing for age-related threshold questions
For 14–18-year-olds the chip portrait can be years old, so the observed effect is plausibly an age-progression (reference-photo age gap) problem rather than an age-group problem. An analysis stratified only by current age will conflate the two; the useful stratification is (age at comparison, age of the reference photo). Both are derivable from emrtdInfo + faceComparisons timestamps.
Ticket context (do not duplicate here)
| Where | What |
|---|---|
| FKITDEV-8655 | Full analysis, changelog findings, and the CV-evidence framing |
/Users/levander/coding/facekom/FKITDEV-8655-cv-questions.md | CV briefing + ready-to-send HU question set (external, not in the vault) |
| ASSRAFIPI-110 | The customer-facing parent ticket (Raiffeisen, 14–18 threshold) |
| FKITDEV-8827 | The face-comparison export bin this analysis would extend |
Related
- FKITDEV-8655 — Raiffeisen PION 14–18 threshold, CV evidence request (the ticket this note was extracted from)
- ASSRAFIPI-110 — customer-facing parent; the 0.55 conflation happened here
- FKITDEV-8827 — face-comparison export bin (
raiffeisen-facecomparison-export.js); the vehicle for age-stratified stats - FKITDEV-8788 — precedent for CV fixes marked “Solved” on insufficient test evidence
- face-comparison-data-verdict-threshold-model — canonical data-model note: linkage columns, videochat vs self-service, threshold sourcing mechanics, no
createdAtindex - face-comparison-different-face-db-query — read-only SQL recipe for pulling comparison rows
- vuer_cv — the CV service producing the encodings and distances
- vuer_oss — host of
faceComparisons,SelfServiceCheckerService,SettingsService, the customization listeners - database-schema — Postgres schema reference
- FaceKom — platform overview