This portfolio is private

Enter the password to view Ryan Vandertol's work.

Selected work · 2026

Ryan Vandertol

Developer & automation engineer at Fibernetics — I build real tools that ship: revenue dashboards, data-reconciliation pipelines, and the servers behind them.

Python Node.js SQL Flask 3 projects

The Work

ordered by business impact

Business intelligence · Python · live

NEWT Customer Dashboard

● Live

A Flask business-intelligence dashboard over Fibernetics' billing database that turns raw invoice line-items into revenue and growth insight. Backend engineering: every query is parameterized against injection, results are paginated, and the CSV export uses a server-side cursor so it streams in constant memory no matter how large the result set. Login-gated and deployed on Mosaic.

Python Flask PyMySQL MySQL Mosaic auth
Flow Browser Flask · PyMySQL MySQL · FSFEN streamed CSV
app.py — streaming CSV export
# Exports can be huge, so stream them: a server-side cursor keeps the
# result set on MySQL's side and we yield one CSV row at a time.
@app.get('/api/invoices/download')
def download():
    where_sql, params = build_where(request.args)   # parameterized · injection-safe

    def generate():
        buf = io.StringIO(); writer = csv.writer(buf)
        writer.writerow(COLUMNS); yield buf.getvalue()

        conn = get_connection(cursorclass=pymysql.cursors.SSCursor)
        with conn.cursor() as cur:
            cur.execute(f'SELECT {COLUMN_LIST_SQL} FROM FSFEN{where_sql}', params)
            for row in cur:                 # streamed from the server
                buf.seek(0); buf.truncate(0)
                writer.writerow(row); yield buf.getvalue()

    return Response(generate(), mimetype='text/csv')
View live requires login

Data tooling · Python

Bell DSL Invoice Reconciliation

Cost control

A Python pipeline that reconciles a monthly Bell GAS_MRC invoice against the Worldline active-customer list to catch lines Fibernetics shouldn't be paying for. It normalizes messy real-world addresses and matches on a strict key — postal code + civic number + unit — then writes a multi-tab Excel workbook (summary, review list, exceptions) with conditional formatting.

Python openpyxl regex normalization Excel automation
Flow Bell GAS_MRC.xlsxWorldline.xlsx Python matcher Excel report
final_compare.py — the match rule
# Match rule: postal code + civic number + unit (unit-level strict).
def norm_postal(p): return re.sub(r'[^A-Za-z0-9]', '', str(p or '')).upper()

def norm_unit(u):
    u = re.sub(r'[^0-9A-Za-z]', '', str(u or '')).upper()
    return str(int(u)) if u.isdigit() else u   # 007 and 7 are one unit

def key(row):
    return (norm_postal(row.postal), civic_of(row.addr), norm_unit(row.unit))

# Anything Bell billed with no matching key in the active list → review.
review = [r for r in gas_rows if key(r) not in worldline_keys]

Game · full-stack · live leaderboard

Newt Dominates the Canadian Telecom Industry

● Live

A branded HTML5 Canvas arcade game — no engine, no framework — backed by a Node/Express server and a shared PostgreSQL leaderboard. The clever part is the server-side anti-cheat: every run opens with a single-use session token minted on the server, and scores are rejected if they're implausible for the time actually played.

Dominating Bell Rogers Telus
HTML5 Canvas Node.js Express PostgreSQL anti-cheat
Flow Canvas client Express · anti-cheat Postgres leaderboard
Top 5 · high scores from live DB
  1. 1PETE 273,77072% acc
  2. 2RYAN72,65073% acc
  3. 3WEEDEN65,26077% acc
  4. 4JMANZIEL33,40081% acc
  5. 5RYANBOYFRIEN8,16053% acc
snapshot · alpha-lxp Postgres · Sep 2026
server.js — score validation
// A run must open with a single-use token minted here — the start
// time lives on the server so the client can't backdate it.
app.post('/api/scores', async (req, res) => {
  const startedAt = sessions.get(req.body.token);
  sessions.delete(req.body.token);            // single-use
  if (!startedAt) return res.status(403).json({ error: 'invalid session' });

  // The score has to be achievable in the time actually played.
  const maxForElapsed = ((Date.now() - startedAt) / 1000) * MAX_POINTS_PER_SEC;
  if (score > maxForElapsed)
    return res.status(403).json({ error: 'score implausible' });

  await sql('INSERT INTO scores (name, score) VALUES ($1, $2)', [name, score]);
});