Senior Full-Stack — Scopic Software (US) Apr 2025 — present trophyscan.com

TrophyScan

A 3D antler scoring app with a social feed, competitions and subscriptions. My contribution ran across five surfaces — iOS, Flutter, the Node backend, the React admin, and a Python/FastAPI platform service.

Swift · SwiftUI Flutter React · TypeScript Node.js · Express Drizzle · PostgreSQL StoreKit 2 Python · FastAPI Amplitude Branch.io

Over a year here I worked on iOS, the Node backend, the React admin dashboard, the Flutter rewrite, and lately a Python/FastAPI platform service — which changes what you can see. Instrumentation, API shape and client behaviour stop being three teams’ problems and start being one.

Employment work at Scopic Software. TrophyScan is the client’s product, built by a ten-person team — everything described below is my own contribution within it, and the interface shown is an illustration of the concepts, not a screenshot.

5Surfaces worked
~55Tracked events
4Languages shipped
2Toolchains, same app
01

Five surfaces, one product

TrophyScan scores whitetail and mule deer antlers from a 3D scan, then wraps that in the things that make people come back — a social feed, certification, competitions with leaderboards and a subscription.

I joined on mobile and did not stay there.

iOS First public release — certification, competitions, subscription paywall Swift · SwiftUI · Core Data
Flutter The same product rebuilt when the team changed toolchains Dart · Flutter
Node backend Participant exclusion pushed into the query, not applied to results Express · Drizzle · PostgreSQL
Admin web Internal dashboard the operations staff work in daily React · TypeScript
Platform service Schema constraints, job reconciliation, API contract guarantees Python · FastAPI · S3 · SQS

02 — Surfaces

Five surfaces, four languages.

Instrumentation, API shape and client behaviour stop being three teams’ problems when one person has written all three. The backend rule I kept to is the clearest example: if a row must not be visible, it should not come out of the database — filtering it out afterwards means it has already crossed a boundary, and every new caller has to remember the same rule.

  • Exclusion enforced in the query, so no caller can forget it
  • One schema for an event, whichever of three codebases fires it
  • The same product shipped on two mobile toolchains
  • Swift 6 strict-concurrency migration
Trophy room Certification & transfer — illustrative
iOS · Flutter
trophy_certified
one definition · fired from Swift, TypeScript and Node
One schema
Without the wrapper
three spellings of the same action, and a funnel nobody trusts
Drift
Concurrency
Swift 6 strict
Subscription
Pro — StoreKit 2
Deep links
Branch.io

03 — Mobile

Shipped it in Swift. Then shipped it again.

I was a key contributor to the first public release on iOS — the social layer (publish, like, share), trophy certification, competitions with leaderboards, ownership transfer over Branch.io deep links, and the StoreKit 2 subscription paywall with its tiers, trials and receipt validation.

Later the team moved the app to Flutter and I moved with it, so I have built the same product twice on two toolchains — which is the fastest way to learn which of your decisions were really about the platform and which were about the product.

  • First public release — social, certification, competitions
  • Ownership transfer via Branch.io deep links, survives install
  • StoreKit 2 paywall: tiers, trials, receipt validation
  • Contributed to the Swift 6 strict-concurrency migration
  • Then rebuilt on Flutter with the team
Analytics Amplitude · three surfaces
schema-first
trophy_certified — iOS
Swift · via the tracking wrapper
Same schema
trophy_certified — Web
TypeScript · via the tracking wrapper
Same schema
trophy_certified — Backend
Node · via the tracking wrapper
Same schema
One user action
three possible origins, one well-formed event
~55 events total
Tracked events
~55
Surfaces
iOS · Web · API
Naming
wrapper-enforced

04 — Instrumentation

The SDK was easy. Agreement was not.

Around 55 tracked events across iOS, web and the backend. The difficulty was never the Amplitude SDK — it was making the same user action produce one well-formed event regardless of which surface it originated on.

So the fix was a tracking wrapper that standardises names and properties on every platform. Without it you get three spellings of the same event and a funnel nobody trusts, which is worse than no analytics at all because people act on it.

  • ~55 events instrumented across three codebases
  • One wrapper per platform, enforcing names and properties
  • Same action → same event, whichever surface fires it
  • Schema agreed before instrumenting, not after
Platform service Python · FastAPI · PostgreSQL — illustrative
reviewed & CI-gated
Constraint migration, split in two
two lock modes on a hot table — read pg_locks before writing it
Shipped
Spec-vs-implementation guard
documented errors checked against raised ones, both directions
Guarded
Revert the fix — the test must fail
caught a test that asserted the bug was correct behaviour
Verified
Migrations
lock-aware
Tests
real PostgreSQL
Storage & queue
S3 · SQS

05 — Platform

Hired for mobile. Shipped into the API.

The last stretch has been backend: a production Python / FastAPI service over PostgreSQL — access-control hardening, schema constraints and migrations, background-job reconciliation, and API contract guarantees.

The one worth telling: a status enum had to be mirrored onto the database as a CHECK constraint, and doing it in one transaction would have held two different lock modes on a hot table at once. I read pg_locks to see which, then split the migration in two. Every fix was then mutation-tested — revert it, and a named test has to fail.

  • Migrations written against measured lock behaviour, not assumption
  • A bidirectional guard so the API spec and the code cannot drift apart
  • Regression tests run against real PostgreSQL, not a mock
  • Each branch measured against a pristine base on a fresh database
06

Stack

Mobile

  • Swift · SwiftUI
  • Flutter · Dart
  • Core Data
  • Combine
  • StoreKit 2
  • Branch.io

Backend

  • Python · FastAPI
  • Node.js · Express
  • Drizzle ORM
  • PostgreSQL
  • AWS S3 · SQS
  • REST APIs

Web

  • React
  • TypeScript
  • Data grids
  • Modal systems

Practice

  • Amplitude
  • Cross-language schema design
  • Mutation testing
  • Swift 6 concurrency
07

Outcome

Key contributor to the first public release, then to the layers behind it, then to the rebuild — and most recently into the platform service itself. Working across every surface means the instrumentation, the API shape and the client can be reasoned about together rather than negotiated across teams.

I go where the problem is, not where my title is.