Shane Cann
Shane Cann
Shane Cann

Data migration & analytics · and the tooling to run it

Shane Cann

Eight years moving data between systems that were never meant to talk. Now I build the tools that do it — six applications here, from migration tooling to reporting boards to models that grade themselves.

Senior Technical Architect at Zendesk · 8+ yrs in data migration, CRM and automation

cannshan@gmail.com github.com/cannshan linkedin.com/in/shane-cann

Projects
6 / 5 in git
Tracked source
1.67 MB
Commits
173
Languages
6
Period
May 2025 — Sep 2026
Deployed
2 on Vercel
Stacks
Next 16 · Expo 54
Revised
7 Oct 2026

About

I've spent eight years as a Technical Architect watching good ideas die in the gap between "someone should build that" and someone actually building it. AI closed that gap for me, and I haven't stopped since.

Now when I hit a problem worth solving, I build the thing. A meal tracker that reads a photo. A betting model that scores its own predictions. A content engine that writes for three platforms' algorithms instead of one. A tour app for a boat with no signal. Days, not quarters — and they get used, not screenshotted and abandoned.

The day job is why they hold up. Running migrations for a living teaches you where systems break: the edge case nobody specified, the two clients that quietly disagree, the number that looks right and isn't. I build with that already in mind, because I've spent a career cleaning up after it.

None of it is a tutorial project. Each one started because something was costing me or somebody else real time, and the fastest route to not paying that cost again was to build the thing.

  • Real tools. Bulk data loading, migration tracking, tour operations, content production — things with a person on the other end and a job to finish.
  • Honest models. Probability and estimation that states its own confidence, gets graded against what actually happened, and is tuned on a sample that can bear the weight.
  • Built for the real conditions. A tour app that assumes a boat with no signal, and a migration board that assumes somebody will forget the retrospective.
  • Whatever the stack needs to be. Python, TypeScript, React Native, Postgres, Supabase — picked per problem, not per comfort zone.

Built fast is not the same as built carelessly.

Anyone can generate an app that demos well and falls over on contact with reality. The difference is in the decisions that never show up in a screenshot — and every one of these is a real line of code in a project on the Projects tab.

  • It grades its own homework

    A model that won't tell you when it was wrong isn't finished. The betting model logs every suggestion the moment it's shown and settles it automatically against the real result once the game is played.

    Evidence — tracking.py, NFL Bet Generator
  • It's allowed to say "I don't know"

    An empty result ships empty rather than padded with filler. A photo estimate stays a draft until you correct it. Correlated bets get flagged instead of quoted as clean independent math.

    Evidence — has_same_game_legs · evidence-or-nothing search rule
  • The architecture is a decision

    The betting model's backtests import the live pricing functions rather than reimplementing them, so a test can never quietly drift from the code it is testing. Migration HQ keeps every project's phase, owner and dates in one row rather than scattered across a board and a spreadsheet.

    Evidence — shared pricing module in backtest_prop_blend
  • It knows what it costs

    Every model call in the content engine writes its real billed cost to a spend table — cache reads and writes priced at their actual multipliers, web searches per use. The write is awaited rather than fired and forgotten, because a serverless function freezes the moment it responds.

    Evidence — logUsage in lib/claude.js · public repo
  • It's built for the bad day

    The tour app assumes a boat with no signal: offline-tolerant storage so the itinerary survives losing service mid-tour, and over-the-air updates so a fix reaches passengers without an App Store round trip.

    Evidence — NetInfo + AsyncStorage, Wander Guide App
  • It gets iterated, not one-shot

    The content engine took 131 commits in five days. That's not a prompt and a deploy — that's building it, using it, finding what's wrong, and going again.

    Evidence — 131 commits in 5 days, Wanderlust Content Engine
  • It solves a problem I actually have

    Every one of these started as something I wanted to exist — a bulk loader for the migrations I run, a macro tracker I use, a tour app for a real operator. None of them are portfolio filler.

    Evidence — 2 deployed, in use, not demos
Show
Note Three repositories are public — Wanderlust Content Engine, NFL Bet Generator and MacroSnap. Their code is there to read, and the links below go straight to the files worth reading. The remaining four are private.

Data Migration Tooling

2 projects · the day job, productised

Migration HQ

Supabase · Vercel Internal team tool

The board a migration team actually runs on — and it will not let you close a project without writing down what went wrong.

Migration HQ kanban board with eight projects spread across Kickoff, Mapping, Test Migration, Closed Migration, Go Live and Post Go Live
Reporting view with total, active and completed project counts above a bar chart of projects by data source
Project Retrospective Required modal with required data-source issues field, root cause analysis and takeaways, and a Complete Retrospective and Archive button
Fig. 1 / 3

Screens captured against a local demo database — every company, person and date shown is invented. The production board holds live customer migrations.

A single-file static client on Supabase, deployed to Vercel with no build step. Every migration is a card on a board: engagement manager, developer and owner, the data source and objects in scope, and the four dates that actually matter — mapping signoff, test migration, closed migration, go live. Cards drag between groups, and the board filters by source, object, created-date range and whether it's yours or the team's.

The feature it exists for is the one at the end. Archiving a project opens a retrospective you cannot skip: the data-source issues and anomalies field is required, with root cause analysis and recommendations beside it. The button says "Complete Retrospective & Archive" because that is the only way a project leaves the board.

Its placeholder text is the giveaway that a practitioner wrote it — formatting errors, missing fields, API limitations, bad CSV exports. Those are the four things that cost you a week, every time.

  • Institutional memory as structured data. Every closed migration leaves behind a searchable record of what broke and why, so the same anomaly stops costing a different engineer the same week.
  • Phases with owners and dates — "where is it?" has an answer without a status call.
  • Security by policy, not by secrecy. The anon key is deliberately client-visible; every access rule lives in Postgres row-level security, with magic-link auth and no password to leak.
  • No build step. One HTML file, a CDN client library and a schema — the right weight for a tool a small team maintains itself.
Client59 KB, single file
StackSupabase · Postgres RLS
Updated16 Sep 2026
StatusIn use by the team

HTML, CSS & JS in one file 100%

Zendesk Data Loader

Python · pywebview desktop app Private repo

Import, update, delete and export Zendesk data without writing a line against the API.

Zendesk Data Loader sign-in screen with subdomain, email and API token fields
Select Action screen offering Import, Update, Delete and Export
Select an object dropdown with Continue and Back buttons
Field Mapping screen with Choose Ticket CSV, Auto-Map Fields, Save Mapping and Load Mapping
Fig. 1 / 4

The oldest project in the set and still the largest single body of Python here — a full CRUD-and-export tool that puts the Zendesk API behind a desktop interface for people who are never going to script against it themselves.

Built because bulk data work is what I do all day, for the admin who has a spreadsheet and a deadline and no intention of learning an API. A personal project, not a vendor product — but built by someone who knows exactly where these jobs go wrong.

  • All four operations — import, update, delete, export — rather than the import-only script most teams settle for.
  • Mappings are reusable. Auto-map guesses the columns, then save it and load it on the next job.
  • 595 KB of Python, HTML and CSS: close to a third of all the tracked source on this sheet.
Commits2
Source595 KB
Updated23 May 2025

Python 79.9% HTML 17.8% CSS 2.3%

Modeling & Inference

2 projects · Python, TypeScript

NFL Bet Generator

Python · Live odds Public repo — readable

A transparent probability model for NFL bets that refuses to quote correlated legs as independent math.

NFL Bet Generator dashboard showing a Broncos at Chiefs same-game parlay with eight annotated player-prop legs
Track Record tab showing 0 settled bets, 44 pending, and a warning that the sample is too small to be meaningful
Fig. 1 / 2

Live lines come from The Odds API, completed-game scores from ESPN, and team efficiency plus weekly player stat lines from nflverse's free CSV releases. Team side: an opponent-adjusted power rating in the spirit of a Massey rating, blended with EPA per game and pushed through a normal-distribution margin model to get win, cover and total probabilities.

Player props are each player's own weighted recent-game mean and standard deviation, adjusted for opponent run/pass defense, a ~10%-per-week recency decay, target-share trend over the last three games, weather, and the opposing defense's fresh injuries.

  • Two parlay builders toward a target payout — same-game, and unrestricted best-odds.
  • Honest about correlation. Any combo with multiple legs from one game is flagged has_same_game_legs and its probability labelled illustrative. parlay_builder.py
  • Track Record tab logs every suggestion and auto-grades it against the real result once the game finishes. tracking.py
  • Backtested against an unselected sample. The obvious way to tune the model is on its own settled bets — and it's wrong, because those are only the legs the model already liked. The backtests rebuild a clean sample from every cached pre-kickoff board instead, and import the production pricing functions so the test can't drift from the app. Read the reasoning in backtest_prop_blend.py — it's in the docstring.
Commits1
Source122 KB
Updated14 Sep 2026

Python 82.3% HTML 11.5% CSS 6.2%

MacroSnap

Next.js · Claude vision Public repo — readable

Photograph a meal, get its macros — but nothing is logged until you have corrected the estimate.

MacroSnap History screen showing Monday September 14 at 1156 kcal with three logged meals and their photos
MacroSnap Daily goals screen with calorie, protein, carb, fat and fiber targets
Fig. 1 / 2

A mobile-first web app that runs on your own machine. The camera button opens the real camera on a phone; Claude identifies each food in the frame, estimates its portion, and returns macros per item. You review and adjust the breakdown first — the estimate is a draft, not a record.

The dev server binds to 0.0.0.0, so the phone on the same Wi-Fi can install it to the home screen and run it full-screen with no browser chrome.

  • Today — a calorie ring, macro bars, and every logged meal expandable to its per-item breakdown.
  • History — the last fourteen days grouped by day, with a running daily average.
  • Goals that argue back. It flags the case where your macro split and your calorie target disagree by more than 5%.
Commits1
Source71 KB
Updated14 Sep 2026

TypeScript 97.0% CSS 2.2% JS 0.8%

Products & Tools

2 projects · JavaScript, TypeScript

Wanderlust Content Engine

Live · Vercel Public repo — readable

One idea in, three genuinely different social packages out — not the same caption with a swapped hashtag suffix.

Wanderlust Content Engine Content tab with an idea, location, story beat, three platform toggles and filming extras
Discovery tab with location and category pickers beside a sidebar of saved searches and their place counts
Fig. 1 / 2

Enter an idea and a location and the app fires an independent request per platform, in parallel. Each writes to that platform's own ranking signals — TikTok optimises for completion rate and comments/saves, Instagram for DM shares first — following a caption and hashtag formula derived from fourteen of the creator's real posts, and runs its own live web search for currently-trending tags and sounds before finalising.

Now nine tabs and forty-odd API routes across 15,500 lines: Discovery sorts real places into popular / interesting / hidden gem, Planning and two calendars turn those into a shoot schedule, and Reel Voiceover, Stories, Collab Search and a Profile of voice rules sit around the Content tab everything feeds.

  • Evidence or nothing. No place is labelled "proven" or "hidden gem" without real search evidence, and an empty list ships as an honest result rather than padded filler.
  • It watches its own bill, to the cent. Every model call logs real billed cost — input, output, cache-write at 1.25×, cache-read at 0.1×, web search per use — to a spend table, so "what does clicking this actually cost" has an answer instead of a guess from token ceilings. A running meter sits in the header. See logUsage.
  • Awaited on purpose. That cost write is deliberately not fire-and-forget: a serverless function freezes once it responds, so an un-awaited insert is a coin flip on whether it ever lands. The reasoning is written into the file, not just the behaviour.
  • Format decided, not asked. The tool judges whether a topic reads better as narrative or an itinerary list, so that never becomes a form field.
  • 131 commits in five days — the most heavily iterated project on this sheet, by an order of magnitude.

JavaScript 86.6% HTML 7.6% CSS 5.8%

Wander Guide App

Expo 54 · React Native Private repo

The same tour, in the passenger's hand, on a boat with no signal.

An Expo Router app on React Native 19 backed by Supabase. Location-aware via expo-location, with audio narration through expo-av, secure credential storage, haptics, and over-the-air updates so a fix reaches passengers without an App Store round trip.

Built for a working environment with intermittent connectivity: NetInfo plus AsyncStorage means the itinerary survives losing signal mid-tour.

  • A real map, not a web view. Tour stops render through native map components per platform — which is also why this one genuinely has to ship as an app rather than a mobile site.
  • PLpgSQL in the repo. Real Supabase migrations and functions are versioned alongside the client, not clicked into a dashboard.
  • OTA updates via expo-updates and EAS — the practical choice for software running on a seasonal tour schedule.
Commits3
Source268 KB
Updated8 Sep 2026

TypeScript 95.2% JS 3.0% PLpgSQL 1.8%

Data migration & analytics

A migration is only half a data problem. The other half is knowing, before you start, exactly how the source is going to betray you.

I've spent eight years on that half — profiling source systems, writing the mapping specs, running the cutovers, and explaining to a customer why their export has 4,000 records with no owner. Enterprise CRM and support platforms, at volumes up to twenty concurrent projects, with a standardised process behind them that took a quarter off delivery time.

The method is deliberately platform-agnostic. A helpdesk, a CRM, a project tracker, a pile of spreadsheets or a legacy database nobody has credentials for — the work is the same: understand the shape of the source, agree what must not break, move it, prove it landed.

The analytics side is the same discipline pointed forward instead of backward: reporting and dashboards for the teams I've supported, and statistical modelling where the question is genuinely probabilistic — opponent-adjusted ratings, recency-weighted distributions, and the discipline to score those predictions against what actually happened.

Both halves are why the tooling below exists. When the same anomaly costs you a week for the third time, you stop writing it in a doc and start building something that catches it.

  • 01Source profiling

    Encoding, nulls, duplicates, orphaned relations, rate limits and the fields the customer swore were populated. Found before the plan, not during the cutover.

  • 02Mapping & transformation

    Field-level mapping specs agreed with the customer, auto-mapped where the names allow, saved and reused across jobs rather than rebuilt each time.

  • 03Phased cutover

    Mapping signoff, test migration, closed migration, go-live — each a date somebody owns, so "where is it?" has an answer without a status call.

  • 04Validation

    Reconciled counts, spot checks on the relations most likely to have broken, and a clear statement of what did not come across and why.

  • 05Retrospective

    Mandatory, not optional. Issues, root cause, and what to do differently — captured as structured data so the pattern is searchable across projects.

  • 06Analysis

    Reporting and dashboards on the result, and statistical modelling where the answer is a probability rather than a number — scored against the outcome, on a sample chosen so the scoring actually means something.

Tooling CRM & helpdesk platforms Postgres / Supabase SQL Python / pandas CSV & API pipelines Excel at scale

The background behind it

Zendesk Senior Technical Architect, and Technical Architect before that 2021 — present · 5 yrs 6 mos
Apto Data Team Lead, up from Project Manager, Consultant and Support 2017 — 2021 · Denver, CO
Xero Customer Experience Technical Specialist 2014 — 2017 · Denver, CO
Michigan State Student Technician Supervisor 2011 — 2014
Along the way
  • 20 concurrent migrations run
  • 25% faster migrations
  • 50% throughput lift
  • 5 engineers led

Michigan State University · B.S. Media & Information, B.A. Advertising · 2009–2014