Files
vyndr/src/routes/props.js
T

82 lines
2.2 KiB
JavaScript

const express = require('express');
const { requireAuth } = require('../middleware/auth');
const { getSupabaseServiceClient } = require('../utils/supabase');
const router = express.Router();
// GET /joint-history — joint outcome history and phi coefficient
router.get('/joint-history', requireAuth, async (req, res) => {
const { player_a, stat_a, player_b, stat_b } = req.query;
// Block free tier
if (!req.user.tier || req.user.tier === 'free') {
return res.status(403).json({
error: 'Joint history requires Analyst or Desk tier',
upgrade_url: '/pricing',
});
}
if (!player_a || !stat_a || !player_b || !stat_b) {
return res.status(400).json({
error: 'Required query params: player_a, stat_a, player_b, stat_b',
});
}
try {
const supabase = getSupabaseServiceClient();
const { data, error } = await supabase
.from('joint_outcomes')
.select('*')
.eq('player_a', player_a)
.eq('stat_a', stat_a)
.eq('player_b', player_b)
.eq('stat_b', stat_b);
if (error) throw error;
if (!data || data.length === 0) {
return res.json({
player_a,
stat_a,
player_b,
stat_b,
sample_size: 0,
phi_coefficient: null,
outcomes: [],
});
}
// Calculate phi coefficient from joint outcomes
let both_hit = 0, a_only = 0, b_only = 0, neither = 0;
for (const row of data) {
if (row.a_hit && row.b_hit) both_hit++;
else if (row.a_hit && !row.b_hit) a_only++;
else if (!row.a_hit && row.b_hit) b_only++;
else neither++;
}
const n = data.length;
const num = (both_hit * neither) - (a_only * b_only);
const denom = Math.sqrt(
(both_hit + a_only) * (b_only + neither) *
(both_hit + b_only) * (a_only + neither)
);
const phi = denom === 0 ? 0 : num / denom;
res.json({
player_a,
stat_a,
player_b,
stat_b,
sample_size: n,
phi_coefficient: Math.round(phi * 1000) / 1000,
outcomes: data,
});
} catch (err) {
console.error('[props/joint-history]', err.message);
res.status(503).json({ error: 'Service temporarily unavailable' });
}
});
module.exports = router;