-
-
Notifications
You must be signed in to change notification settings - Fork 166
feat(speakers): let one person own several clusters, correctly #474
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
467281f
97e778f
da98bd3
bf93380
1158873
9f197d3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import { describe, expect, it } from 'vitest'; | ||
|
|
||
| import { orderProfilesForRow } from './SpeakerReviewPanel'; | ||
|
|
||
| const p = (display_name: string) => ({ display_name, person_id: `id-${display_name.toLowerCase()}` }); | ||
|
|
||
| describe('orderProfilesForRow', () => { | ||
| it('puts people already assigned in this meeting first', () => { | ||
| const ordered = orderProfilesForRow( | ||
| [p('Zoe'), p('Alice'), p('Max')], | ||
| new Set(['id-max']), | ||
| ); | ||
| expect(ordered.map((x) => x.display_name)).toEqual(['Max', 'Alice', 'Zoe']); | ||
| }); | ||
|
|
||
| it('keeps each group alphabetical', () => { | ||
| const ordered = orderProfilesForRow( | ||
| [p('Zoe'), p('Alice'), p('Max'), p('Bea')], | ||
| new Set(['id-max', 'id-zoe']), | ||
| ); | ||
| expect(ordered.map((x) => x.display_name)).toEqual(['Max', 'Zoe', 'Alice', 'Bea']); | ||
| }); | ||
|
|
||
| it('leaves the order alone when nobody is assigned yet', () => { | ||
| const ordered = orderProfilesForRow([p('Zoe'), p('Alice')], new Set()); | ||
| expect(ordered.map((x) => x.display_name)).toEqual(['Alice', 'Zoe']); | ||
| }); | ||
|
|
||
| it('does not mutate the list it was given', () => { | ||
| const input = [p('Zoe'), p('Alice')]; | ||
| orderProfilesForRow(input, new Set(['id-alice'])); | ||
| expect(input.map((x) => x.display_name)).toEqual(['Zoe', 'Alice']); | ||
| }); | ||
| }); | ||
|
|
||
| describe('orderProfilesForRow identity', () => { | ||
| it('matches on person_id, not on the display name', () => { | ||
| // Two profiles can read alike after a rename. Marking the never-assigned | ||
| // one as present in this meeting would invite the exact misassignment | ||
| // the "here" hint exists to prevent. | ||
| const assigned = { display_name: 'Alex', person_id: 'id-a' }; | ||
| const other = { display_name: 'Alex', person_id: 'id-b' }; | ||
| const ordered = orderProfilesForRow([other, assigned], new Set(['id-a'])); | ||
| expect(ordered.map((x) => x.person_id)).toEqual(['id-a', 'id-b']); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -396,3 +396,54 @@ test('the panel says how many people spoke when a cluster is known to hold more | |
| await expect(note).toBeVisible(); | ||
| await expect(note).toContainText('At least 6 people spoke, but only 5 could be told apart'); | ||
| }); | ||
|
|
||
| test('rows are ordered by speaking time, so the first decisions cover the most transcript', async ({ | ||
| launchApp, | ||
| }) => { | ||
| const { page } = await launchApp({ | ||
| mockIpc: true, | ||
| env: { STENOAI_E2E_SEED_SPEAKER_SUGGESTIONS: '1' }, | ||
| }); | ||
| await openDetail(page); | ||
|
|
||
| // The seed's durations are 245 / 80 / 30 s (plus two filtered rows), and | ||
| // the cluster ids run the other way for SPEAKER_2, so channel+id order -- | ||
| // what the panel used before -- would be a different sequence. Reviewing | ||
| // is voluntary and abandonable, so whoever stops after one row should | ||
| // have covered the biggest speaker, not the lowest-numbered slot. | ||
| const keys = await page | ||
| .getByTestId('speaker-review-panel') | ||
| .locator('[data-testid^="speaker-row-"]') | ||
| .evaluateAll((els) => els.map((el) => el.getAttribute('data-testid'))); | ||
|
|
||
| expect(keys).toEqual([ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: This ordering test can't catch a regression to cluster-id order: the seeded durations (245/80/30) are already monotonically decreasing with speaker id, so both the old id order and the new duration-descending order render [SPEAKER_0, SPEAKER_1, SPEAKER_2]. The test passes regardless of which ordering the panel implements, giving false confidence that the feature is covered. Consider giving at least one cluster a duration that breaks the id↔duration alignment (e.g. make SPEAKER_1 or SPEAKER_2 the longest) so the expected sequence actually differs from ascending id order. Prompt for AI agents |
||
| 'speaker-row-mic:SPEAKER_0', | ||
| 'speaker-row-mic:SPEAKER_1', | ||
| 'speaker-row-mic:SPEAKER_2', | ||
| ]); | ||
| }); | ||
|
|
||
| test('the Change picker offers people already assigned in this meeting first', async ({ | ||
| launchApp, | ||
| }) => { | ||
| const { page } = await launchApp({ | ||
| mockIpc: true, | ||
| env: { STENOAI_E2E_SEED_SPEAKER_SUGGESTIONS: '1' }, | ||
| }); | ||
| await openDetail(page); | ||
|
|
||
| // One person owning several clusters is the normal case once the diarizer | ||
| // splits a voice, and it has to be at least as easy to say as "New | ||
| // person" -- naming the same voice twice makes it a hard negative against | ||
| // itself, which suppresses that speaker's future suggestions for good. | ||
| await page.getByTestId('speaker-approve-mic:SPEAKER_0').click(); | ||
| await expect(page.getByTestId('speaker-row-mic:SPEAKER_0')).toContainText('Confirmed as Julian'); | ||
|
|
||
| await page.getByTestId('speaker-change-mic:SPEAKER_1').click(); | ||
| const names = await page | ||
| .locator('[data-testid^="speaker-pick-person-"]') | ||
| .evaluateAll((els) => els.map((el) => el.textContent?.trim() ?? '')); | ||
|
|
||
| expect(names[0]).toContain('Julian'); | ||
| expect(names[0]).toContain('here'); | ||
| }); | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P3: The Change picker ordering is recomputed per row render, so larger meetings repeatedly sort the same profiles list and can make the panel feel slower. Reusing one precomputed/memoized ordered profile list for all rows would keep behavior the same with less render work.
Prompt for AI agents