Sessions 5-7a: 955 tests, deployment ready

This commit is contained in:
Kev
2026-06-08 18:35:13 -04:00
parent 06b82624a2
commit 1fa04dc776
371 changed files with 49366 additions and 955 deletions
+91
View File
@@ -0,0 +1,91 @@
/**
* Coach system + pace signal.
*
* Two signals exposed:
* coach_pace_delta: coach's career pace MINUS current team's pace,
* scaled by tenure (longer tenure = stronger
* adjustment).
* coach_player_interaction: magnitude of system shift when the primary
* player is OUT vs IN. Drives suppression for
* role players when the star sits.
*
* Profiles live in `coach_profiles` (migration 017). On first read for a
* team we check the table; if empty, fall back to the seed file at
* src/config/coaches.json so launch isn't blocked on a fully populated
* table.
*/
const path = require('path');
const fs = require('fs');
const { getSupabaseServiceClient } = require('../../utils/supabase');
let seedCache = null;
function loadSeed() {
if (seedCache !== null) return seedCache;
try {
const raw = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'config', 'coaches.json'), 'utf8'));
seedCache = { coaches: raw.coaches || [] };
} catch {
seedCache = { coaches: [] };
}
return seedCache;
}
function tenureAdjustment(games) {
// Linear ramp to 1.0 over ~40 games — a coach inheriting a roster needs
// time before the system actually drifts toward their preference.
const g = Number(games) || 0;
return Math.min(1.0, Math.max(0, g / 40));
}
async function getCoachProfile(sport, teamAbbr) {
const supabase = getSupabaseServiceClient();
const { data, error } = await supabase
.from('coach_profiles')
.select('coach_name, team, sport, career_avg_pace, current_team_pace, tenure_games, primary_player, system_style, without_primary_style, without_primary_pace_delta')
.eq('team', teamAbbr)
.eq('sport', sport)
.maybeSingle();
if (error) {
console.warn('[coachSignals] profile lookup failed:', error.message);
}
if (data) return data;
// Fall back to the seed file — same shape, different home.
const seed = loadSeed();
return seed.coaches.find((c) => c.team === teamAbbr && c.sport === sport) || null;
}
async function getCoachImpact(sport, teamAbbr, gameContext = {}) {
const profile = await getCoachProfile(sport, teamAbbr);
if (!profile) return null;
const career = Number(profile.career_avg_pace);
const team = Number(profile.current_team_pace);
const paceDelta = Number.isFinite(career) && Number.isFinite(team) ? career - team : null;
const tenureAdj = tenureAdjustment(profile.tenure_games);
const adjustedPaceDelta = paceDelta != null ? paceDelta * tenureAdj : null;
// Primary-player status comes from the caller — usually injuryParser told
// them whether the star is OUT/DOUBTFUL.
const primaryStatus = gameContext.primary_player_status ?? 'unknown';
const systemOverride = primaryStatus === 'out' || primaryStatus === 'doubtful'
? profile.without_primary_style
: null;
const withoutPrimaryShift = primaryStatus === 'out'
? Number(profile.without_primary_pace_delta) || 0
: 0;
return {
coach_name: profile.coach_name,
system_style: profile.system_style ?? null,
primary_player: profile.primary_player ?? null,
pace_delta: paceDelta,
tenure_adjustment: tenureAdj,
adjusted_pace_delta: adjustedPaceDelta,
primary_player_status: primaryStatus,
system_override: systemOverride,
without_primary_pace_shift: withoutPrimaryShift,
};
}
module.exports = { getCoachImpact, getCoachProfile, tenureAdjustment, loadSeed };