Files
vyndr/src/utils/tierGating.js
T

73 lines
2.4 KiB
JavaScript

/**
* 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,
},
};