tjvmt.com

Contributors: Alvan Caleb Arulandu

Posted On: 09/07/2022

TJVMT Homepage

Problem

"1+1 trivially equals 2" — when a logical conclusion is deemed "trivial" by a mathematician, it is dismissed as a simple exercise for the reader. But for the average misinformed mathlete, "trivial", paired with a complicated competitive math topic, is a sign of dominance.


"This problem is trivial by roots of unity filters!" — in my freshman year, nearly 35 students dropped out of Varsity Math Team (VMT) because of demeaning sentences like these.

Solution

When I was elected statistician, I was greeted by a complex network of spreadsheets that have underpinned club proceedings since 2003: competition rankings, t-shirt surveys, election polls—that's it, I'm making a website! After a week of development, the management tool was born, but now, VMT's culture has indirectly transformed. tjvmt.com is now an online ecosystem for the TJHSST competitive math community!

Development

Architecture

tjvmt.com is hosted on Vercel, with the help of Google Domains and Google Analytics. Powering the site is Next.js, a production, React.js framework with support for server-side rendering. Our frontend application communicates with a REST API powered by Vercel Serverless Edge Functions, housing application logic. These functions use Prisma to easily CRUD data in our MongoDB cluster, deployed in AWS through MongoDB Atlas. Here's a code snippet of a handler for our directory API.

const handler = async (req: NextApiRequest, res: NextApiResponse) => { const { authorized, profileBody, user } = await authorize(req, res) if (!authorized) return res.status(401).send(null) if (req.method == 'GET') { const where = req.query.competitor === 'true' ? {competitor: true} : {} let users = await db.user.findMany({where}) const players = (await (await fetch(`https://mee6.xyz/api/plugins/levels/leaderboard/${process.env.DISCORD_GUILD_ID}`, {method: "GET"})).json()).players const playerMap = {}; players.forEach(p => playerMap[p.id] = p) users.forEach((u) => { u['stats'] = u.discordId ? playerMap[u.discordId] : undefined }) users = users.sort((a,b) => b.solvedProblemIds.length - a.solvedProblemIds.length) return res.status(200).json({ users }) } }

Authentication

Given that tjvmt.com manages student data, security is one of our priorities. The site's login functionality uses OAuth, an industry-standard authentication protocol supported by ion.tjhsst.edu, TJHSST's in-house student intranet. Using ion, our student directory allows mathletes to meet friends and see their profile picture!

Directory Discord Sync

TJVMT's Discord Server uses MEE6 to motivate members to participate in our diverse community. So, with 3rd party APIs, tjvmt.com stores voluntary Discord identification information to then calculate and display experience points in our directory.

LaTeX Support

Posting, discussing, and solving problems is a really important part of VMT, so our Problem of The Day (PoTD) system helps members take their problem solving to the next level. tjvmt.com supports LaTeX posts through MathJax, a JavaScript display engine for mathematics.

Attendance

Replacing our physical attendance system, VMT's teacher sponsors are able to create password-protected polls to track attendance. This also allows the club to record our most active members for future elections!

Automated Rankings

Decades-old rankings formulas have been migrated into admin-triggered compute jobs running on Vercel Serverless. Our grading portal allows officers to quickly input team selection test scores, which then update a public facing rankings system. To combat imposter syndrome, our rankings protect score anonymity while motivating students to improve over the competition season. Checkout our ranking algorithm below!

let subs = tst.submissions.filter(s => !s.writer) let answers = subs.map(s => s.answers) let solves = answers.reduce((a, b) => a.map((e, i) => e + b[i])) if(tst.weighted){ let weights = solves.map(s => s == 0 ? 0 : 1+Math.log(answers.length / s)) // weight: 1 + Log(submissions/solves) answers = answers.map(s => s.map((x, i) => x*weights[i])) } // compute scores: sum(answers) let scores = answers.map(x => x.reduce((a, b) => a+b)) // compute top 12 average let top = 12 let topAvg = [...scores].sort((a, b) => b-a).slice(0, top).reduce((a, b) => a+b)/top let index = scores.map(s => s*2000/topAvg) // index = 2000*score/top12avg await db.tST.update({where: {id: tst.id}, data: { solves }}) // update submission documents await Promise.all(subs.map(async (s, i) => { await db.submission.update({ where: { id: s.id, }, data: { index: index[i], score: scores[i] } }) })) await Promise.all(tst.submissions.filter(s => s.writer).map(async (s) => await db.submission.update({where: {id: s.id}, data: {index: 2000}}) ))

Testimonials

This year, Sophia, a freshman, came to her first VMT meeting knowing almost half of the upperclassmen with the online directory. Calvin, another freshman, published over fifteen problems through the website's Problem of the Day system, and Luv, also a freshman, used the website's Discord-link feature to join and promote summer “group-solve” sessions. Since tjvmt.com's introduction, attendance is not only faster online but freshman VMT signups have rebounded, and on March 14th, VMT will set a record for the most pies eaten without hands! 🥧

What's Next?

With 241 users weekly users and over 28,000 weekly views, I think it's safe to consider tjvmt.com a success, but my dream is to expand the site to serve more than just VMT. Hopefully, one day, tjvmt.com will be a place for all mathletes, beyond those just competing for TJHSST.