2026-09-22 09:15:22 -04:00
'use strict' ;
const assert = require ( 'assert' );
const { assignMembersToSlots , isAllowedTeamName , slotRoleText , scoreMatch } = require ( './matching' );
const SLOTS = [
'[LEADER, VOX 1, ACOUSTIC] MIX 1' , '[VOX 2] MIX 2' , '[VOX 3] MIX 3' , '[VOX 4] MIX 4' ,
'[GUITAR, LEAD] MIX 5' , '[BASS] MIX 6' , '[VOX 5] MIX 7' , '[PIANO]' , '[DRUMS]' ,
'[KEYS 2]' , '[CALL TO WORSHIP] MC2' , 'STAGE MANAGER' , '[MC] MC1' , '[SPEAKER] HEADSET' ,
];
const M = ( label ) => ({ label , name : label });
const members = [
M ( 'Vox 3' ), M ( 'Vox 2' ), M ( 'Vox 1' ), M ( 'Guitar / Lead' ), M ( 'Bass' ), M ( 'Piano' ), M ( 'Drums' ), M ( 'MC' ), M ( 'Keys 2' ),
];
const out = assignMembersToSlots ( SLOTS , members ). map (( m ) => ( m ? m . label : null ));
// Numbered vocals land in the RIGHT slot (this is the swap bug we fixed).
assert . strictEqual ( out [ 0 ], 'Vox 1' , 'slot 0 → Vox 1' );
assert . strictEqual ( out [ 1 ], 'Vox 2' , 'slot 1 → Vox 2' );
assert . strictEqual ( out [ 2 ], 'Vox 3' , 'slot 2 → Vox 3' );
assert . strictEqual ( out [ 3 ], null , 'slot 3 (Vox 4) unfilled — nobody scheduled' );
assert . strictEqual ( out [ 4 ], 'Guitar / Lead' , 'slot 4 → Guitar/Lead' );
assert . strictEqual ( out [ 5 ], 'Bass' , 'slot 5 → Bass' );
assert . strictEqual ( out [ 6 ], null , 'slot 6 (Vox 5) unfilled' );
assert . strictEqual ( out [ 7 ], 'Piano' , 'slot 7 → Piano' );
assert . strictEqual ( out [ 8 ], 'Drums' , 'slot 8 → Drums' );
assert . strictEqual ( out [ 9 ], 'Keys 2' , 'slot 9 → Keys 2' );
assert . strictEqual ( out [ 12 ], 'MC' , 'slot 12 → MC' );
// A different number never matches.
assert . strictEqual ( scoreMatch ( slotRoleText ( '[VOX 2] MIX 2' ), 'Vox 3' ), 0 , 'Vox 3 must not match Vox 2 slot' );
// Mic number no longer pollutes: Vox 7 must not grab "[VOX 5] MIX 7".
assert . strictEqual ( scoreMatch ( slotRoleText ( '[VOX 5] MIX 7' ), 'Vox 7' ), 0 , 'Vox 7 must not match the MIX 7 slot' );
// Team allow-list (singular stems match plurals/variants via substring).
assert . ok ( isAllowedTeamName ( 'Band' , [ 'band' , 'vocal' ]));
assert . ok ( isAllowedTeamName ( 'Vocals' , [ 'band' , 'vocal' ]));
assert . ok ( isAllowedTeamName ( 'Vocal Team' , [ 'band' , 'vocal' ]));
assert . ok ( ! isAllowedTeamName ( 'Video' , [ 'band' , 'vocal' , 'production' ]));
console . log ( 'matching.test.js: all assertions passed ✓' );
console . log ( 'assignment:' , JSON . stringify ( out ));
// --- bestLibraryMatch: folder-photo → PCO person by name ---
{
const { bestLibraryMatch } = require ( './matching' );
const name = ( n ) => ( e ) => ( e ? e . name : null );
const lib = [
{ id : 'a' , name : 'Alex' }, { id : 'g' , name : 'AlexG' }, { id : 'bm' , name : 'Brian M' },
{ id : 'c' , name : 'Courtney' }, { id : 'gn' , name : 'Gaitan' }, { id : 'wes' , name : 'Wes' },
];
const pick = ( n , list = lib ) => { const e = bestLibraryMatch ( n , list ); return e ? e . name : null ; };
assert . strictEqual ( pick ( 'Alex Gaitan' ), 'Alex' , 'first name matches "Alex"' );
assert . strictEqual ( pick ( 'Brian Moniz' ), 'Brian M' , '"Brian M" matches "Brian Moniz"' );
assert . strictEqual ( pick ( 'Courtney Maranda' ), 'Courtney' , 'first name matches' );
assert . strictEqual ( pick ( 'Alexander Zinni' ), null , '"Alex" must NOT grab "Alexander Zinni"' );
assert . strictEqual ( pick ( 'Wes Carter' ), 'Wes' , 'first name matches "Wes"' );
// surname-named photo
assert . strictEqual ( pick ( 'Someone Gaitan' ), 'Gaitan' , 'surname photo matches by last name' );
// two people share a first name + only a first-name photo → ambiguous only if two matches tie;
// here a single "Alex" photo still maps to whoever's first name is exactly Alex.
assert . strictEqual ( pick ( 'Alex Johnson' ), 'Alex' , 'first-name photo maps to any exact-first-name person' );
// exact full name beats a first-name photo
assert . strictEqual ( pick ( 'Alex Gaitan' , [{ id : 'x' , name : 'Alex Gaitan' }, { id : 'a' , name : 'Alex' }]), 'Alex Gaitan' , 'exact full name wins' );
2026-10-20 13:28:49 -04:00
// two equally good photos ("Mike" and "mike") → still one of the booth's photos, never PCO's
assert . strictEqual ( bestLibraryMatch ( 'Mike Powell' , [{ id : '1' , name : 'Mike' }, { id : '2' , name : 'mike' }]). id , '1' , 'tie → first library photo' );
assert . strictEqual ( bestLibraryMatch ( 'Joram Lopez' , lib ), null , 'no photo of them → PCO headshot' );
2026-09-22 09:15:22 -04:00
console . log ( 'bestLibraryMatch: all assertions passed ✓' );
}
2026-10-20 13:11:56 -04:00
// --- one spot per person: someone scheduled twice (Leader + Acoustic) fills one spot only ---
{
const P = ( label , name , personId ) => ({ label , name , personId });
const slots = [ '[LEADER, VOX 1, ACOUSTIC] MIX 1' , '[GUITAR, 2] MIX 7' ];
const got = assignMembersToSlots ( slots , [ P ( '[Leader]' , 'Mike' , 'mp' ), P ( 'Guitar [Acoustic]' , 'Mike' , 'mp' ), P ( 'Guitar [Rhythm]' , 'Craig' , 'cs' )]). map (( m ) => ( m ? m . name : null ));
assert . deepStrictEqual ( got , [ 'Mike' , 'Craig' ], 'Mike once, Craig gets the guitar spot' );
console . log ( 'one spot per person: all assertions passed ✓' );
}
2026-10-20 13:14:39 -04:00
// --- Planning Center notes place the leader and vocalists (real notes from Oct 25 / Nov 1, 2026) ---
{
const { noteScore } = require ( './matching' );
const NA_SLOTS = [
'[LEADER, VOX 1, ACOUSTIC] MIX 1' , '[VOX 2] MIX 2' , '[VOX 3] MIX 3' , '[VOX 4] MIX 4' , '[GUITAR, LEAD] MIX 5' ,
'[BASS] MIX 6' , '[GUITAR, 2] MIX 7' , '[VOX 5] MIX 8' , '[PIANO]' , '[DRUMS]' , '[KEYS 2]' ,
];
const P = ( label , name , personId , note ) => ({ label , name , personId , note });
// Listed in the order Planning Center returned them — vocalists alphabetical, not by mic.
const oct25 = [
P ( 'Vocalist' , 'DJ' , 'dj' , 'Mix 3, VOX 3' ), P ( '[Leader]' , 'Mike' , 'mp' , 'Mix 1, VOX 1' ), P ( 'Guitar [Lead]' , 'Yeremis' , 'yc' ),
P ( 'Piano/Pad' , 'Kevin' , 'km' ), P ( 'Vocalist' , 'Genesis' , 'gg' , 'Mix 4, VOX 4' ), P ( 'Guitar [Rhythm]' , 'Craig' , 'cs' ),
P ( 'Guitar [Acoustic]' , 'Mike' , 'mp' ), P ( 'Vocalist' , 'Rosaliz' , 'rg' , 'Mix 2, VOX 2' ), P ( 'Drums' , 'Luis' , 'ld' ),
P ( 'Vocalist' , 'Bianca' , 'bb' , 'Mix 8, VOX 5' ), P ( 'Guitar [Bass]' , 'Joram' , 'jl' ),
];
const got = assignMembersToSlots ( NA_SLOTS , oct25 ). map (( m ) => ( m ? m . name : null ));
assert . deepStrictEqual ( got , [ 'Mike' , 'Rosaliz' , 'DJ' , 'Genesis' , 'Yeremis' , 'Joram' , 'Craig' , 'Bianca' , 'Kevin' , 'Luis' , null ], 'Oct 25 by notes' );
// Nov 1: leader note in different case, a drum note with no mix, an "NA" note.
const nov1 = [ P ( 'Guitar [Acoustic]' , 'Mike' , 'mp' , 'NA' ), P ( 'Drums' , 'Alex' , 'ag' , 'DP48, Drums' ), P ( '[Leader]' , 'Mike' , 'mp' , 'Mix 1, Vox 1' )];
assert . deepStrictEqual ( assignMembersToSlots ( NA_SLOTS , nov1 ). map (( m ) => ( m ? m . name : null )). filter ( Boolean ), [ 'Mike' , 'Alex' ], 'Nov 1: Mike once, Alex on drums' );
assert . strictEqual ( assignMembersToSlots ( NA_SLOTS , nov1 )[ 9 ]. name , 'Alex' , 'DP48, Drums → [DRUMS]' );
// A note never pulls someone onto a different mix.
assert . strictEqual ( noteScore ( '[VOX 2] MIX 2' , 'Mix 8, VOX 5' ), 0 , 'Mix 8 note must not match MIX 2' );
assert . strictEqual ( noteScore ( '[VOX 5] MIX 8' , 'Mix 8, VOX 5' ) > 0 , true , 'Mix 8, VOX 5 matches [VOX 5] MIX 8' );
assert . strictEqual ( noteScore ( '[VOX 5] MIX 8' , 'NA' ), 0 , 'NA says nothing' );
assert . strictEqual ( noteScore ( '[VOX 1] MIX 10' , 'Mix 1, VOX 1' ) , 0 , 'Mix 1 must not match MIX 10' );
console . log ( 'notes: all assertions passed ✓' );
}
2026-10-20 13:28:49 -04:00
// --- samePerson: is a dropped photo of the person already in the spot? ---
{
const { samePerson } = require ( './matching' );
assert . ok ( samePerson ( 'Rosaliz Gaitan' , 'Rose' ), 'nickname: Rose → Rosaliz' );
assert . ok ( samePerson ( 'Mike Powell' , 'mike' ), 'first name, any case' );
assert . ok ( samePerson ( 'Mike Powell' , 'Mike Powell action' ), 'full name plus words' );
assert . ok ( samePerson ( 'DJ McLean' , 'DJ action' ), 'short first name plus words' );
assert . ok ( ! samePerson ( 'Mike Powell' , 'Craig' ), 'a different person is a replacement, not their photo' );
assert . ok ( ! samePerson ( 'Al Smith' , 'Alex' ), 'two-letter names never match by prefix' );
console . log ( 'samePerson: all assertions passed ✓' );
}