/** * Tier-based response gating. * * Free-tier users see the grade letter + confidence + edge (the hook) * but the explanation stays locked. Paid tiers see everything. The * frontend already has a `tier-locked` CSS class and `BlurredText` * component — we just need to mark the locked fields with * `locked: true` so the UI knows what to blur. * * This module is import-once / pure-function — it never reaches into * Supabase or Redis. Callers pass the user's tier string; the function * returns a new object (does not mutate input). */ const { canAccess } = require('../config/tiers'); const LOCKED_REASONING_SUMMARY = 'Upgrade to see full analysis.'; const LOCKED_KILL_REASON = 'Upgrade to see details.'; function lockReasoning(reasoning) { if (!reasoning || typeof reasoning !== 'object') { return { summary: LOCKED_REASONING_SUMMARY, steps: null, locked: true }; } return { summary: LOCKED_REASONING_SUMMARY, steps: null, locked: true }; } function lockKillConditions(list) { if (!Array.isArray(list)) return []; return list.map((kc) => ({ code: kc?.code ?? 'LOCKED', reason: LOCKED_KILL_REASON, locked: true, })); } /** * applyTierGating — translate a fully-shaped analyzer result into the * gated shape appropriate for the caller's tier. The legacy fields * (grade, confidence, edge_pct, player, stat_type, line, direction, * book) survive untouched on every tier. Only the explanation surface * is redacted for free. * * @param {Object} result — legacy-shaped analyzer output * @param {string} tierName — 'free' | 'analyst' | 'desk' | undefined * @returns {Object} gated result (new object — input not mutated) */ function applyTierGating(result, tierName) { if (!result || typeof result !== 'object') return result; if (canAccess(tierName, 'reasoning_visible')) { // Paid tier — pass through unchanged. return result; } // Free tier: keep grade + confidence + edge_pct (the hook), redact // reasoning + kill condition explanations. return { ...result, reasoning: lockReasoning(result.reasoning), kill_conditions_triggered: lockKillConditions(result.kill_conditions_triggered), tier_gated: true, upgrade_hint: 'Upgrade to see full analysis + kill condition details.', }; } module.exports = { applyTierGating, __internals: { lockReasoning, lockKillConditions, LOCKED_REASONING_SUMMARY, LOCKED_KILL_REASON, }, };