I turn operational data into systems people actually run the business on.
I work where ticketing data, revenue strategy, and software meet. Most of what I build starts the same way. Someone is making a real decision off a spreadsheet that is stale, wrong, or three systems away from the truth. I go get the truth, model it, and then ship the thing that puts it in front of them every morning.
Six things I built, and what each one was actually for.
Every figure below is a count, not a dollar. Client financials stay where they belong; the engineering is what carries the work.
01
Live
Gulls Command Center
Operations dashboard·2026
The daily revenue dashboard for a professional hockey club. Eleven analytical modules over a live feed pipeline, replacing a stack of hand-maintained spreadsheets.
Ticket revenue for the season lived across a warehouse, a ticketing platform, and a set of workbooks that were rebuilt by hand every morning. Everyone was reading a slightly different number, and nobody could say how far ahead or behind the season actually was without an afternoon of reconciliation.
What I did
01Built an authenticated Next.js application that reads daily feeds out of SharePoint through the Microsoft Graph API, caching the data rather than the render so freshness stamps stay honest.
02Modeled the season as eleven distinct questions: pacing to budget, renewals, revenue quality, per-game performance, rep activity, league context. Each one got its own module instead of one undifferentiated report.
03Wrote a demand model that scores every game on the schedule and predicts attendance, with a what-if tool for testing opponent, weekday, and date-window combinations.
04Ran a twelve-pass design audit that collapsed thirty ad-hoc font sizes into a nine-step scale, unified three competing color systems, and reduced nine breakpoints to three.
Outcome
One source of truth, refreshed on its own, that leadership opens instead of asking for. The morning reconciliation is gone.
02
Ongoing
CRM Identity Remediation
Data forensics·2026
A full-population audit of 1.6 million Salesforce person accounts inherited from a migration, establishing what the data actually contained before anyone wrote to it.
Key figures
records audited
1.64M
population, not sampled
100%
ID namespaces untangled
3
bad write caught pre-flight
1
Stack
Salesforce / SOQL
SQL Server
T-SQL
Bulk API
Node.js
Internal work · details on request
Read the case study
CRM Identity Remediation case study
The problem
A CRM migration left 1,637,188 person accounts under a single owner. The plan was to classify them and correct identity where possible. The plan assumed the migration had carried its source fields across.
What I did
01Profiled every custom field across all 1.6M rows rather than a sample. Six fields that the classification plan depended on came back exactly 0% populated. The migration had created empty shells.
02Traced load batches through record creation timestamps and tested the hypothesis that they mapped to source systems. Joined against the warehouse identity table and refuted it: every batch was 84–100% present in both candidate sources. Source system could not partition the file; only purchase recency could.
03Identified three separate ticketing-ID namespaces being treated as one, and established that a 0% match across them was the correct expected result rather than a broken join.
04Retracted a draft update file built on the wrong namespace before it reached production, and replaced the join strategy with cross-source corroboration.
Outcome
A migration cleanup that would have written surrogate keys into a production CRM was stopped by the audit that preceded it. Deliverables ship as self-contained handoff files with verification steps built in.
03
Delivered
Season Pricing & Offer Build
Systems + automation·2026
216 ticketing offers across six sales channels and a full season of games, generated from a single source of truth instead of typed by hand.
Key figures
offers generated
216
sales channels
6
price levels mapped
19
hand-typed fields left
0
Stack
AXS Back Office
Node.js
TypeScript
Excel / CSV pipelines
Internal work · details on request
Read the case study
Season Pricing & Offer Build case study
The problem
New-season events are copied forward from the prior season, so their prices are never blank. They are stale, which is far more dangerous. Offers were built by hand, and the one hand-typed column in the source workbook had at one point been sorted independently of its key, silently reversing all 216 rows.
What I did
01Mapped the real structure of the pricing platform: where the price chart actually lives, which of the nineteen price levels the pricing sheet names, and which accessible-seating codes have to move together or strand a fan segment a season behind.
02Established a single verification cell, the one price level that changes across all four game ratings, so any event could be proven updated in one glance.
03Replaced the hand-typed workbook with a generator that builds all 216 offers directly from the schedule, making the transcription error class structurally impossible.
04Wrote a copy-template playbook that front-loads the risky edits onto one game and leaves the remaining 210 copies purely mechanical.
Outcome
A season's offers built correctly and verifiably, with the manual step that caused the original defect designed out rather than double-checked.
04
Live
SD Music
Public web app·2026
Every concert and DJ set within 100 miles of San Diego, on one page, synced automatically and filterable by night, region, genre, and price.
Finding live music in a metro area means checking a dozen venue sites and three ticketing platforms, each with its own calendar. The information exists; it is just scattered past the point of being useful.
What I did
01Built an auto-syncing feed that aggregates events across venues and primary sellers, with no manual upkeep once running.
02Designed the filtering around how people actually decide, not around how the source data is shaped: what night am I free, how far will I drive, what am I willing to pay.
03Linked every listing straight to the primary seller, so the app routes demand rather than intercepting it.
Outcome
A live, public utility that stays current on its own.
05
Live
Salt to Summit
Itinerary tool·2026
A day-by-day planning tool for a multi-country trip, built because spreadsheets and group chats lose the thread.
Complex travel degrades fast in a shared document: several countries, moving parts, other people depending on the plan. Dates drift out of sync with bookings and nobody trusts the latest version.
What I did
01Structured the trip as a sequence of legs with their own logistics, so a change in one place propagates visibly instead of quietly.
02Designed for reading on a phone mid-transit, where the plan actually gets consulted.
Outcome
One authoritative plan, legible at a glance, that survives contact with reality.
06
Live
Holt Photography
Independent business·Ongoing
A San Diego photography practice covering couples, portraits, events, athletes, and brands. Candid work, no staged smiles.
Separate from the analytics work, and run like its own business: booking, delivery timelines, pricing, and client communication all sit with me.
What I did
01Built and run the brand end to end, from the site through to a 7–14 day delivery commitment.
02Specialized in on-base access and community rates for military families.
Outcome
An operating business that sharpens the commercial instincts the analytics work depends on.
How I work
Four rules, each one learned the expensive way.
These are not values on a wall. Each came out of a specific thing that went wrong, or nearly did.
01
Audit before you write
The most valuable thing I have shipped is a file I retracted. Profiling 1.6M records at full population, not a sample, is what caught six fields the plan depended on being entirely empty, and a surrogate key that was one approval away from a production CRM.
02
Design the error class out
A defect you double-check is still a defect you will meet again. When a hand-typed column reversed 216 rows, the fix was not a better proofreading step. It was generating the whole workbook from the schedule so the column stopped existing.
03
Stale is worse than blank
A missing number gets noticed. A number that quietly belongs to last season does not. Most of what I check for is data that looks completely reasonable and is a year out of date.
04
Ship it where they already look
Analysis that lives in an attachment gets read once. The same analysis on a page that refreshes itself becomes the thing the room argues from.
Capabilities
What I reach for, grouped by the job it does.
Data & Warehouse
Getting to the real number, at full population, without trusting the label on the column.
SQL Server / T-SQL
SOQL & Salesforce Bulk API
Large-file ETL (500MB+ exports)
Identity resolution & fuzzy joins
Data profiling and full-population audits
Warehouse freshness diagnostics
Analytics & Modeling
Turning history into a defensible forecast, and forecasts into decisions with dollars attached.
Demand modeling & attendance forecasting
Pacing to budget
Revenue quality analysis
Cohort & renewal analysis
Scenario / what-if tooling
Benchmarking against league context
Software & Delivery
Shipping the tool, not the recommendation that someone else has to build.
TypeScript
React 19 & Next.js App Router
Node.js automation
Tailwind CSS
Recharts / data visualization
Vercel deployment & custom domains
Platforms & Integration
The systems the revenue actually flows through.
AXS Back Office & Archtics
Salesforce CRM
Microsoft Graph & SharePoint
Eloqua
Excel / workbook automation
Git & code review workflows
Contact
Got a number nobody trusts?
I take on analytics and build work: audits, demand models, internal tools, and the dashboards that replace the morning spreadsheet. Tell me what decision you are trying to make and I will tell you whether I am the right person for it.