v1.15.1 — Roster polish + sub-account merge in fault contributors
[analysis/roster_discovery.py](analysis/roster_discovery.py). Before: a member with ≥2 aliases had ALL of them classified as `sub` (so P1 marked core + P2 attached as sub of P1 ma…
Fixed — classifier bug: both halves of a main+sub pair showed as each other's sub
analysis/roster_discovery.py. Before: a member with ≥2 aliases had ALL of them classified as sub (so P1 marked core + P2 attached as sub of P1 made BOTH P1 and P2 render as each other's sub). After: pick the PRIMARY alias per member (min alias.id — the one created first, by convention the member's main character) and only non-primary aliases get sub. Primary keeps member.kind ('core' or 'substitute'). The fix preserves the existing test (Bob → sub) and adds a regression assertion that the primary (Alice → core) stays correctly classified.
Fixed — copy clarification + "sub-stitute" hyphen
web/src/Members.jsx. The page header now spells out the four roles explicitly: core = a real person's main account; sub = a secondary character of the SAME person (alt / sub-account); substitute = a backup member (their own person, their own characters); ignore = pugs, loot trades, anyone you don't want in analytics. The character-checklist intro got the same treatment. Replaced the visually-hyphenated <button>sub-stitute</button> with plain <button>substitute</button> (the dash was an unintentional line-wrap artifact).
Added — sub-account merge in "Who's contributing to wipes"
When a roster member owns multiple character aliases (main + subs), the fault contributors table now collapses them into one row.
- Backend (analysis/fault_attribution.py):
fault_aggregate_for_encounterjoins each FFLogsplayer_idto its roster member via static-scopedcharacter_aliases(prefer(name, server)exact match; fall back to name-only when exactly one member claims the name — same lookup asresolve_members.py/roster_discovery.py). Per-player rows now carrymember_id,member_name, andserver. - Frontend (web/src/Home.jsx):
FaultSectiongroupedPlayers regroups bymember_idwhen present (m:${id}key), falls back to character name (n:${name}). Each grouped row tracks its constituent characters in acharacters_listwith per-characterfights/score/jobs. The Player cell renders a+N alt/+N altsaccent pill whencharacter_count > 1(tooltip lists each character + server + jobs). The row expansion grows a third sub-table — "Characters merged into Alice (3 accounts)" — between the per-job breakdown and the per-mechanic top-killers.
Tests
- 1 new in tests/test_fault_attribution.py: seed Alice owning P1, Bob owning P2 + P3 (main + sub), compute faults, assert the aggregate response gives all three rows a
member_id/member_nameand that Bob's rows share the same member id. - 1 regression assertion in tests/test_roster_discovery_v1_15.py: in
test_discovery_marks_subs_when_owner_has_multiple_aliases, also assert that "Alice Tankerton" (the primary) stays asclassification: "core"after Bob is attached. - 494 tests passing (480 → 494, +14: 13 from v1.15.0 + 1 today).