Existing duplicate-account reconciliation: detect + merge users with >1 account already in prod (Elazar still finding dupes) — mars+venus+pluto
Elazar 2026-07-19: still comes across users with more than one account. EVO-45 (BLOCKED) covers PREVENTION at the data layer (Gmail dot/plus canonical unique index) — this item is the RECONCILE of dupes that ALREADY exist / slipped through. Investigate WHY dupes still appear despite the canonical unique index: pre-index rows, non-gmail-domain dots (index only folds gmail), multi-provider identities, or different-email same-person. Then per app: detection query (canonical-email collisions + likely same-person heuristics) -> human-reviewed merge (keep one, re-point practicas/roles, soft-archive the other with archive context). Design merge WITH Elazar. Links EVO-45 (prevention, blocked — flag its blocker to Elazar). See PLUTO-202 (admin change-email dedup path).
Sub-tickets — 3/5 done
Questions
Activity
-
Elazar 2026-07-19 concrete case + detection approach. The dupes that slip through are same-person with DIFFERENT emails AND differently-written names, e.g. 'Lopez, Luis M. <lopezlm@gmail.com>' vs 'Luis Martín López <lopez82@gmail.com>'. Neither email-canonical (real different addresses) nor exact-name match catches these. DETECTION ANCHOR = DNI (documentoNumber): same documento number across two live user rows = near-certain dup regardless of email/name spelling. EVO-74 STEP 1 (per app mars/pluto/venus): confirm whether the USER/student accounts store documentoNumber at all — if yes, dedup query = duplicate documentoNumber among deletedAt IS NULL rows; if NO, that missing anchor is WHY dupes fly under the radar → capturing DNI on user accounts becomes part of the fix (ties to (f) patient DNI centralization EVO-72/MARS-331/PLUTO-437, and to pre-created-from-client-data which usually includes DNI). STEP 2 for rows lacking DNI: fuzzy same-person heuristic — canonicalized name (sort tokens, strip accents, normalize 'Apellido, Nombre' vs 'Nombre Apellido', expand initials) + comisión/practicas overlap. STEP 3: present candidate pairs to Elazar for human confirm → merge (keep one, re-point practicas/roles, soft-archive other with archive context). NO auto-merge — a wrong merge is worse than a dup.
-
Elazar 2026-07-19 BROADENED scope: dedup is multi-signal, recall-first (not a single DNI anchor). Available student data beyond email: legajo (institutional unique student ID - STRONGEST anchor, stronger than DNI for a student system), DNI/documentoNumber, secondary email, name heuristics. Because candidates are HUMAN-REVIEWED downstream, optimize the generator for RECALL not precision - false positives are cheap (human filters), misses are the real cost. CANDIDATE-FLAG SIGNALS (any one flags a live-row pair for review): (1) legajo match; (2) DNI match; (3) secondary-email match (either account's secondary == other's primary OR secondary); (4) loose fuzzy name (token-sort, accent-strip, initial-expansion, nickname - deliberately over-inclusive); (5) comisión/practicas activity overlap as confidence booster/tiebreaker. Output = candidate PAIRS ranked by signal strength -> Elazar reviews -> merge (keep one, re-point practicas/roles, soft-archive other w/ archive context). NO auto-merge, NO precision gate on generator. STEP-1 schema check per app still applies: confirm which of {legajo, documentoNumber, secondaryEmail} the student/user tables actually store - the stored anchors determine which signals are runnable now vs need a field added (ties to (f) patient-DNI centralization + inactive-user cleanup EVO-73).
-
Elazar go 2026-07-19; dispatching read-only detection pass to db-mars/pluto/venus
-
db-venus MERGE PLANS (dry-run, no writes): #2 Martina - martirubinoiriarte@gmail (roster_import, 6 participations, logged-in) SURVIVES over rubinoiriartemartina@gmail (access_request, 1 participation, never logged in/authId null); re-point 1 practicaParticipations + archive loser role+account. CLEAN. #3 Eugenia - od.eugeniamendez@gmail (roster, 33 participations) SURVIVES over mariaeugeniamendez96@gmail (access_request, 1, never logged in); re-point 1 participation + archive. CLEAN. #4 Sebastian Puia - HELD FOR ELAZAR: both hold ACTIVE titular role; institutional email bc9a082a (461 events, sede-scoped) vs personal email 2f189ead (5 events, but seesAllSedes=TRUE global). Naive most-used-survives would DOWNGRADE his visibility. Needs Elazar call on canonical email + visibility scope before re-point.
-
pluto merge plans (dry-run, no execution): PAIR 1 - Rodriguez, Sebastian -> RECOMMEND MERGE. Survivor: a1f8a01b-79bd-44ce-944e-c8e77ee339a7 (access_request origin, has authId/real Google login, legajo 212/15, 12 appEvents rows, 1 practica as operador + 2 as asistente, 1 practicas.createdBy, 2 notifications, live studentAssignments row 8a286d1f-2b0c-4341-bb9c-556a22ec3c9f in comision c...006/periodo a...001, role alumno). Loser: f3c0776e-560c-4133-9ba6-770d87e78aa0 (migration-origin stub, isActive=false, no authId, legajo OD-0212/15 -- same person, formatted differently, confirms match). Zero appEvents, zero practica participation. Owns exactly 2 rows anywhere: its own duplicate studentAssignments row f34fad68-0301-4fb3-b0a3-8cea4900f80a (same comision+periodo as survivor's) and its own userRoles row for alumno. Surveyed all 25+ FK-to-users.id columns -- loser owns nothing else. FK re-points needed: NONE (loser has no owned rows outside the 2 duplicate rows below). Soft-archive: (1) loser users row, archiveReason admin:user:deactivate:duplicate-account-merged-into-a1f8a01b-79bd-44ce-944e-c8e77ee339a7 (matches existing pluto precedent, seen 2026-06-25); (2) loser studentAssignments row f34fad68 (redundant dup of survivor's live assignment -- soft-delete, do not repoint, repointing would create a second live assignment for a comision the survivor is already in); (3) loser userRoles alumno row (soft-delete, survivor has its own). PAIR 2 - Biglieri, Desiree -> RECOMMEND DO NOT MERGE. 39367b91-4dd4-408c-a826-950e64922eb8 (active, access_request, legajo 064/19, role alumno, live studentAssignments in c...005) vs 9c923179-a198-4aad-ba45-b6c92fcb4fa4 (migration-origin, isActive=false, no authId, NO legajo, role docente_ayudante_primera -- a teaching-assistant role, not alumno -- holding a LIVE jtpAyudantes row ed7d3373 as ayudanteId in the SAME comision c...005). Finding: the two accounts hold different, both-live, both-real role types (student vs teaching assistant) in the same comision, zero corroborating identity anchor (no legajo on the loser, no documento/contactEmail match). More consistent with two distinct real people sharing a name than a duplicate. No merge plan proposed -- flagging as a name-only false positive; only action needed is Elazar confirming they are in fact 2 different people. Elazar confirms each before any execution -- plan only, nothing applied.
-
db-mars MERGE PLANS (doc mars/pm/reports/evo74-mars-merge-plans-2026-07-20.md, dry-run). EXECUTE-READY per-pair GO: #1 Portigliatti survivor institutional 7880dfd2 (CAVEAT: survivor never signed in - move the working supabaseAuthId from the gmail twin); #2 Boetto survivor 306f4585, re-point 8 jtp practicas + 2 reviews; #3 Velazquez survivor = admin_manual 045f5bd2 (INVERTS prefer-Google: the access_request twin bonzaibacteriano NEVER signed in). HOLD - need human identity confirm: #4 Pablo - two institutional accounts, surnames RODRIGUEZ vs Perez Rodriguez, BOTH signed-in + independently active => almost certainly TWO DISTINCT Pablos (likely NOT a dupe); #5 Camila - two admin_manual accounts BOTH concurrently entering practicas (15 vs 11) => active/active hazard, do not auto-merge. Full FK re-point checklist in doc.
-
CORRECTION: venus DNI anchor available after all (alumnos.documentoNumber, 100% of 33 alumnos) - the venus name-only dedup pass may have missed DNI-colliding pairs. DNI-exact re-run dispatched to db-venus (full pass + DNI-agreement check on the 3 existing venus candidates to firm merge confidence).
-
CORRECTION: venus DNI anchor available after all (alumnos.documentoNumber via join, 100% of 33 alumnos) - the venus name-only dedup pass may have missed DNI-colliding pairs. DNI-exact re-run authorized on db-venus: full pass for new pairs + DNI-agreement check on the 3 existing venus candidates to firm merge confidence.