This commit is contained in:
NotARoomba 2026-03-03 12:41:29 -05:00
parent 6890adb44c
commit 8af711ef7e
3 changed files with 208 additions and 3 deletions

View file

@ -39,5 +39,8 @@ export const config = {
airtableUsersTableId: process.env.AIRTABLE_USERS_TABLE_ID!, airtableUsersTableId: process.env.AIRTABLE_USERS_TABLE_ID!,
// YSWS // YSWS
fraudToken: process.env.FRAUD_TOKEN fraudToken: process.env.FRAUD_TOKEN,
// HCB
hcbOrgSlug: 'ysws-scraps',
} }

View file

@ -194,7 +194,7 @@ admin.get("/stats", async ({ headers, status }) => {
? Math.round((totalTierCost / roundedTotalHours) * 100) / 100 ? Math.round((totalTierCost / roundedTotalHours) * 100) / 100
: 0; : 0;
// Real dollar cost from shop fulfillment // Max dollar cost from shop fulfillment (all luck_win orders regardless of fulfillment)
const [luckWinOrders, consolationCount] = await Promise.all([ const [luckWinOrders, consolationCount] = await Promise.all([
db db
.select({ .select({
@ -223,6 +223,75 @@ admin.get("/stats", async ({ headers, status }) => {
? Math.round((totalRealCost / roundedTotalHours) * 100) / 100 ? Math.round((totalRealCost / roundedTotalHours) * 100) / 100
: 0; : 0;
// Actual fulfillment cost (only fulfilled orders + upgrades consumed on fulfilled wins)
const [fulfilledLuckWinOrders, fulfilledConsolationCount, fulfilledUpgrades] =
await Promise.all([
db
.select({
itemPrice: shopItemsTable.price,
})
.from(shopOrdersTable)
.innerJoin(
shopItemsTable,
eq(shopOrdersTable.shopItemId, shopItemsTable.id),
)
.where(
and(
eq(shopOrdersTable.orderType, "luck_win"),
eq(shopOrdersTable.isFulfilled, true),
),
),
db
.select({ count: sql<number>`count(*)` })
.from(shopOrdersTable)
.where(
and(
eq(shopOrdersTable.orderType, "consolation"),
eq(shopOrdersTable.isFulfilled, true),
),
),
db.execute(
sql`SELECT COALESCE(SUM(rsh.cost), 0) AS total_cost
FROM refinery_spending_history rsh
WHERE (rsh.user_id, rsh.shop_item_id) IN (
SELECT DISTINCT so.user_id, so.shop_item_id
FROM shop_orders so
WHERE so.order_type = 'luck_win' AND so.is_fulfilled = true
)`,
),
]);
const fulfilledLuckWinDollarCost = fulfilledLuckWinOrders.reduce(
(sum, o) => sum + o.itemPrice / SCRAPS_PER_DOLLAR,
0,
);
const fulfilledConsolationDollarCost =
Number(fulfilledConsolationCount[0]?.count || 0) * 2;
const fulfilledUpgradeDollarCost =
Number((fulfilledUpgrades.rows[0] as { total_cost: string })?.total_cost || 0) /
SCRAPS_PER_DOLLAR;
const totalActualCost =
fulfilledLuckWinDollarCost +
fulfilledConsolationDollarCost +
fulfilledUpgradeDollarCost;
// HCB bank balance
let hcbBalanceCents = 0;
if (config.hcbOrgSlug) {
try {
const hcbRes = await fetch(
`https://hcb.hackclub.com/api/v3/organizations/${config.hcbOrgSlug}`,
{ headers: { Accept: "application/json" } },
);
if (hcbRes.ok) {
const hcbData = await hcbRes.json() as { balances?: { balance_cents?: number } };
hcbBalanceCents = hcbData.balances?.balance_cents ?? 0;
}
} catch {
// HCB unavailable, leave balance at 0
}
}
return { return {
totalUsers, totalUsers,
totalProjects, totalProjects,
@ -251,6 +320,21 @@ admin.get("/stats", async ({ headers, status }) => {
totalRealCost: Math.round(totalRealCost * 100) / 100, totalRealCost: Math.round(totalRealCost * 100) / 100,
realCostPerHour, realCostPerHour,
}, },
shopActualCost: {
hcbBalanceCents,
hcbBalance: Math.round((hcbBalanceCents / 100) * 100) / 100,
fulfilledLuckWinCost: Math.round(fulfilledLuckWinDollarCost * 100) / 100,
fulfilledLuckWinCount: fulfilledLuckWinOrders.length,
fulfilledConsolationCost:
Math.round(fulfilledConsolationDollarCost * 100) / 100,
fulfilledConsolationCount: Number(
fulfilledConsolationCount[0]?.count || 0,
),
fulfilledUpgradeCost: Math.round(fulfilledUpgradeDollarCost * 100) / 100,
totalActualCost: Math.round(totalActualCost * 100) / 100,
remainingBudget:
Math.round((hcbBalanceCents / 100 - totalActualCost) * 100) / 100,
},
}; };
}); });

View file

@ -52,6 +52,18 @@
realCostPerHour: number; realCostPerHour: number;
} }
interface ShopActualCost {
hcbBalanceCents: number;
hcbBalance: number;
fulfilledLuckWinCost: number;
fulfilledLuckWinCount: number;
fulfilledConsolationCost: number;
fulfilledConsolationCount: number;
fulfilledUpgradeCost: number;
totalActualCost: number;
remainingBudget: number;
}
interface Stats { interface Stats {
totalUsers: number; totalUsers: number;
totalProjects: number; totalProjects: number;
@ -66,6 +78,7 @@
totalTierCost?: number; totalTierCost?: number;
avgCostPerHour?: number; avgCostPerHour?: number;
shopRealCost?: ShopRealCost; shopRealCost?: ShopRealCost;
shopActualCost?: ShopActualCost;
} }
interface PendingProject { interface PendingProject {
@ -533,7 +546,7 @@
{/if} {/if}
{#if stats.shopRealCost} {#if stats.shopRealCost}
<h2 class="mt-10 mb-6 text-2xl font-bold">real fulfillment cost</h2> <h2 class="mt-10 mb-6 text-2xl font-bold">max fulfillment cost</h2>
<div class="grid grid-cols-1 gap-6 md:grid-cols-2"> <div class="grid grid-cols-1 gap-6 md:grid-cols-2">
<div class="flex items-center gap-4 rounded-2xl border-4 border-red-500 bg-red-50 p-6"> <div class="flex items-center gap-4 rounded-2xl border-4 border-red-500 bg-red-50 p-6">
<div <div
@ -592,6 +605,111 @@
</div> </div>
</div> </div>
{/if} {/if}
{#if stats.shopActualCost}
<h2 class="mt-10 mb-6 text-2xl font-bold">fulfillment cost</h2>
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
<div class="flex items-center gap-4 rounded-2xl border-4 border-green-500 bg-green-50 p-6">
<div
class="flex h-16 w-16 items-center justify-center rounded-full bg-green-600 text-white"
>
<DollarSign size={32} />
</div>
<div>
<p class="text-sm font-bold text-gray-500">hcb balance</p>
<p class="text-4xl font-bold text-green-700">
${stats.shopActualCost.hcbBalance.toFixed(2)}
</p>
<p class="text-xs text-gray-400">hack club bank account</p>
</div>
</div>
<div
class="flex items-center gap-4 rounded-2xl border-4 p-6 {stats.shopActualCost
.remainingBudget >= 0
? 'border-green-500 bg-green-50'
: 'border-red-500 bg-red-50'}"
>
<div
class="flex h-16 w-16 items-center justify-center rounded-full text-white {stats
.shopActualCost.remainingBudget >= 0
? 'bg-green-600'
: 'bg-red-600'}"
>
<DollarSign size={32} />
</div>
<div>
<p class="text-sm font-bold text-gray-500">remaining budget</p>
<p
class="text-4xl font-bold {stats.shopActualCost.remainingBudget >= 0
? 'text-green-700'
: 'text-red-700'}"
>
${stats.shopActualCost.remainingBudget.toFixed(2)}
</p>
<p class="text-xs text-gray-400">hcb balance actual fulfillment cost</p>
</div>
</div>
<div class="flex items-center gap-4 rounded-2xl border-4 border-red-500 bg-red-50 p-6">
<div
class="flex h-16 w-16 items-center justify-center rounded-full bg-red-600 text-white"
>
<DollarSign size={32} />
</div>
<div>
<p class="text-sm font-bold text-gray-500">total actual cost</p>
<p class="text-4xl font-bold text-red-700">
${stats.shopActualCost.totalActualCost.toFixed(2)}
</p>
<p class="text-xs text-gray-400">fulfilled items + consolations + upgrades</p>
</div>
</div>
<div class="flex items-center gap-4 rounded-2xl border-4 border-black p-6">
<div class="flex h-16 w-16 items-center justify-center rounded-full bg-black text-white">
<Coins size={32} />
</div>
<div>
<p class="text-sm font-bold text-gray-500">fulfilled luck wins</p>
<p class="text-4xl font-bold">
${stats.shopActualCost.fulfilledLuckWinCost.toFixed(2)}
</p>
<p class="text-xs text-gray-400">
{stats.shopActualCost.fulfilledLuckWinCount} items fulfilled
</p>
</div>
</div>
<div class="flex items-center gap-4 rounded-2xl border-4 border-black p-6">
<div class="flex h-16 w-16 items-center justify-center rounded-full bg-black text-white">
<Coins size={32} />
</div>
<div>
<p class="text-sm font-bold text-gray-500">fulfilled consolations</p>
<p class="text-4xl font-bold">
${stats.shopActualCost.fulfilledConsolationCost.toFixed(2)}
</p>
<p class="text-xs text-gray-400">
{stats.shopActualCost.fulfilledConsolationCount} consolations × $2
</p>
</div>
</div>
<div class="flex items-center gap-4 rounded-2xl border-4 border-black p-6">
<div class="flex h-16 w-16 items-center justify-center rounded-full bg-black text-white">
<Coins size={32} />
</div>
<div>
<p class="text-sm font-bold text-gray-500">upgrades consumed</p>
<p class="text-4xl font-bold">
${stats.shopActualCost.fulfilledUpgradeCost.toFixed(2)}
</p>
<p class="text-xs text-gray-400">refinery spending on fulfilled wins</p>
</div>
</div>
</div>
{/if}
{/if} {/if}
</div> </div>