Projects

Five systems, and what I did on each

One research instrument I built alone, three production systems at Amazon Health, and an independent product I still operate. Each entry states what the system does, what I was responsible for, how it is put together, and which engineering problem was actually hard.

PhoneMood

A behavioral research instrument built from a measurement limitation in a study I worked on: repeated, in-context mood sampling paired with recorded phone use.

Role: Sole authorContext: IndependentSince: 2026Status: Available on Google PlayStack: Kotlin · Jetpack Compose · Room · on-device analysis

What I built

PhoneMood records active phone use and app-level foreground time, requests a 1–10 mood rating once a configurable amount of active use has accumulated, and analyzes the resulting records on the device. It exists because of a specific gap: in the PISA 2022 study I contributed to, pre-sleep digital self-regulation is measured by a single retrospective self-report item, and the paper’s own limitations section proposes adding behavioral measures such as smartphone logs. PhoneMood is my attempt to build one.

My ownership

Everything, and the repository shows it: the research questions and literature review, the sampling protocol, the Android application, the session engine, the statistical engine, the export schema, and the privacy model — one person, no framework doing the analysis for me. Where the design follows a published finding, the review says which one.

PhoneMood instrument architectureAs implemented in the public repository.

Engineering problems worth naming

Time alignment. A mood rating is only interpretable if you know what it is anchored to. The session engine separates active use from elapsed time — a locked screen interrupts active use — and the database records when a prompt was issued separately from when it was answered, so a late answer is analyzed as a late answer.

Missing observations are observations. Unanswered prompts, dismissals, and gaps in usage monitoring are written to the database and carried into the analysis sample. Dropping them would quietly select the sample toward the moments when someone felt like replying, which is exactly the bias the instrument exists to avoid.

Reconstructable analysis. Dataset construction applies the inclusion rules in one place and exports them alongside the data, so the observations, the decisions that produced the sample, the model inputs, and the estimates travel together.

Methodology

Methodological choiceResearch rationale
Usage-triggered momentary assessmentPair app-level behavioral records with mood reports anchored to actual response times, preserving the sequence of use and experience.
Within-session centering and weighted regressionEstimate mood change as active use accumulates within a session, separating that relationship from differences in average mood between sessions. Weight observations so each session contributes equal total weight.
Conditional comparisons of app useModel the ending mood rating with adjustment for starting mood, total active use, elapsed time, and time of day. Interpret app time relative to time allocated to other apps, using comparisons supported by the observed data.
Uncertainty and stability checksEstimate uncertainty with HC3 or day-clustered covariance as specified by the analysis policy. Refit models while leaving out days or sessions to examine whether a pattern depends on a particular block of observations.
Traceable sample construction and analysisRetain unanswered prompts, response delays, and monitoring gaps. Export observations, inclusion decisions, model inputs, and estimates together so the analysis can be reconstructed.
PhoneMood Today screen showing average mood, phone-use duration, recorded check-ins, and issued reminders together
Recorded phone use, mood ratings, and answered versus issued prompts on one screen. Synthetic demonstration data.
PhoneMood check-in prompt asking for a mood rating from one to ten after a period of active phone use
A mood rating requested after 30 minutes of active use. Synthetic demonstration data.
PhoneMood Trends screen plotting daily mood across a seven-day period
Daily mood across a selected period, with the underlying records available. Synthetic demonstration data.

Where it stands

Implemented
  • Active-use and app-level recording, session boundaries, and prompts triggered at a configurable use threshold
  • Notification and floating-card check-ins with snooze, dismiss, and pause controls
  • Within-session models, conditional app comparisons, HC3 and day-clustered uncertainty, and deletion-based stability checks
  • Daily and period review, exploratory associations, and JSON export with quality metadata
  • Fully local operation — no account, no server, no internet permission — with English and Chinese interfaces
Current testing
  • An engineering validation record covering build, unit, device, lint, and emulator checks for the 1.0.0 build
  • Analysis unit tests and schema checks run against clearly labeled synthetic exports
  • Small-scale user testing underway; participant counts and procedures are not documented, and no participant findings are reported
Planned evaluation
  • Compare usage estimates against recorded behavior, and check device coverage, prompt timing, and the mood item itself
  • Examine within-person use and mood associations with explicit handling of prior mood, time of day, and missing responses
  • Test whether reviewing one’s own records helps a person recognize patterns, keeping that separate from any claim about outcomes

These are three different kinds of claim and the project keeps them apart. Tests show that the software behaves as specified. Any association the app can estimate is observational and exploratory. Whether the mood item is a valid measure of anything is a study to run, not a result to report — and no participant findings, psychometric validation, or health claims are made here.

Why it matters for research

The instrument makes a measurement question answerable: how closely does recorded phone and app use agree with what people report, and which experiences do usage-triggered prompts miss? The pattern — a question, a sampling rule, an instrument, and an analysis path a reader can reconstruct — is what I would bring to a group’s data collection.

Pharmacy verification copilot

A multimodal verification pipeline that compares four kinds of evidence about one prescription and reports the specific disagreements to the pharmacist who decides.

Role: Software Development EngineerContext: Amazon Health, team systemYears: 2022–2026Stack: Python · AWS Bedrock / Claude · Lambda · DynamoDB

What I built

Before a prescription ships, someone has to reconcile the prescription image, the text extracted from the label, the order record, and the database entry for that medication. The copilot performs those comparisons and presents what disagrees, with the evidence attached. It is decision support inside an existing pharmacist workflow; the pharmacist retains final authority over every outcome.

My ownership

I built pipeline components and comparison logic, and developed the checking and escalation controls together with pharmacists and applied scientists. This was a team system: the parts I owned are the evidence comparison path and the controls that decide what the system may pass without human attention, not the whole architecture on my own.

Human-in-the-loop verification architectureProduction architecture; pharmacists retain final authority.

Engineering problems worth naming

From one model to coordinated specialists. The architecture moved from a single model to a single agent, and then to specialist agents for prescription rationality, image-to-record comparison, and label-to-record comparison. Narrowing what each agent judges makes its output checkable against one specific piece of evidence, and structured cross-checks between them reduce hallucination risk before pharmacist review. The cost is more calls and more orchestration per prescription, inside a latency budget the workflow sets.

“I cannot tell” has to survive to the screen. In the output below, the system lists the mismatched fields and then states that the available image does not support a definitive comparison. A disagreement and an inconclusive comparison call for different actions, so the output preserves that distinction instead of collapsing everything into one score.

Evaluation at two levels. Agents were evaluated individually and as a combined system against pharmacist-reviewed cases, because components that each look acceptable can still fail together. False-positive handling, gating, and escalation rules were designed with the pharmacists who would be interrupted by them.

Prescription verification screen listing mismatches in drug strength and directions, and stating that the available image does not support a definitive comparison
The system reports specific mismatches and, in the last item, states that the available image does not support a definitive comparison. Demonstration data.

Scale and results

3,000+ images / dayPrescription images processed by the verification pipeline in production.
Under 800 msAverage latency of the verification pipeline, not of the full pharmacist task.
92% pre-screening accuracyReported for the system against pharmacist-reviewed cases, before pharmacist review — it says nothing about the quality of the pharmacist’s decision.
95% less manual verification, 4× throughputWorkflow effects reported for the deployment. These are operational measures, not evidence of clinical outcomes.

Why it matters for research

This is where one of my research questions comes from: what does a person need to see in order to judge model-generated evidence well, and when should a system withhold a recommendation rather than produce one? Building an answer and testing it empirically are different tasks. I have done the first and would like to do the second.

PharmacyLite platform

The full-stack application pharmacists and operations staff actually work in: scan, read the record, verify, handle the exception, complete — with every step auditable.

Role: Architected the review applicationContext: Amazon Health, team systemYears: 2022–2026Stack: React · Java · AWS ECS · DynamoDB

What I built

Staff scan a prescription label or vial barcode, read the prescription and shipment record attached to it, record a pass or fail with a reason, and route anything unusual into an exception queue. PharmacyLite is where all of that happens. It is a separate product from the verification copilot: the copilot produces evidence, the platform is where people act on it and where the record of that action lives.

My ownership

I architected the review application across the React interface, the Java APIs, and the backend workflow orchestration, including the review-state model and the APIs other pharmacy services call. I also ran iterative workflow sessions with pharmacists and operations staff and folded what they reported back into the flow.

Review workflow architectureOne surface over fill, shipment, prescription, and review state.

Engineering problems worth naming

A review is a state machine, not a form submission. A verification can be in progress, passed, failed, cancelled, or complete, and it can be interrupted — a shift ends, a vial goes back on the shelf, a scan is repeated. Each transition is written with its history, so a review that stopped halfway can be resumed or cancelled explicitly instead of being left in a state nobody can interpret a week later.

Exceptions are the normal path for the hard cases. A failed verification has to carry a reason and a route to someone who can resolve it. Structuring review outcomes and problem information this way is what makes operational monitoring and after-the-fact investigation possible at all.

State the platform owns, records it does not. The application connects fill, shipment, and prescription records owned by other pharmacy services with the review state it owns, so the review surface stays consistent with systems it cannot control.

Pharmacy workflow platform home screen with entry points for fill, verify, pack, problem tracking, and pick lists
The workflow platform pharmacists and operations staff work in. Demonstration data.

Scale and results

200+ internal usersPharmacists and operations staff using the platform.
10,000+ reviews / dayPrescription review requests handled by the platform, at 99.9% uptime.
~30% fewer manual stepsReduction in manual verification steps after iterative workflow sessions with pharmacists and operations staff.
Separate from the copilotTwo different products in the same workflow; the figures above describe this platform only.

Why it matters for research

Human-in-the-loop research needs the loop to exist. Building the surface where a person sees evidence, decides, and leaves a durable record is the same instrumentation problem as behavioral data collection, with a different subject and higher stakes.

Pharmacy fulfillment cost estimation

A high-throughput service that answers, for a medication at a given facility, which processing path it takes and what dispensing it costs — fast enough to sit inside a routing decision.

Role: Built the API, data flow, and serving pathContext: Amazon HealthYears: 2022–2026Stack: Java · AWS ECS · DynamoDB / DAX · Lambda + SQS · CDK

What I built

The same medication can be dispensed through different processing paths at different fulfillment facilities, and those paths cost different amounts. A request names a set of medications and one facility; the response carries a per-medication dispensing cost and the processing path behind it. The estimate covers dispensing, not a complete shipping and packing cost model.

My ownership

I built the cost API, the facility- and medication-level data flow behind it, the asynchronous update path, and the low-latency serving path, with the ECS API and the queue-driven ingestion defined as separate CDK stacks.

Two paths through the serviceUpdates run off the request path so answers stay fast.

Event path — keeping each facility’s processing path current

Request path — answering a cost query

Engineering problems worth naming

Two reads, in parallel, behind a cache. A cost answer needs both the processing path for that medication at that facility and the cost configuration for that path. Issuing those reads in parallel and serving them through DAX brought DynamoDB read latency from roughly 400 ms to roughly 10 ms — the difference between a service you call inside a routing decision and one you call ahead of it.

Unknown stays unknown. When a facility has no recent dispensing history for a medication, the service returns an explicit unknown-path result with a fallback estimate instead of silently substituting a default. Downstream routing can then treat “we do not know” differently from “we know it is cheap,” which is the whole point of making the gap visible.

Say what the estimate actually is. Processing paths are updated from recent dispensing history through a rule applied off the request path. That is a rule-based selection over operational history, not a trained forecasting model, and the service is described that way here because it is what the code does.

Scale and results

2,000+ requests / secondSustained request rate served by the cost API.
20+ fulfillment facilitiesFacilities covered, each with its own processing-path records.
~400 ms → ~10 msDynamoDB read latency on the serving path after parallel reads and DAX. This is the database read, not end-to-end service latency.
$9M+ annual savingsReported for the cost-aware routing decisions this service feeds. The service is one input to that routing, not the sole cause.

Why it matters for research

Estimates become decisions here. What a downstream system does when an estimate is missing, stale, or uncertain is a design question with a direct research analogue: how uncertainty should be represented to whoever — or whatever — acts on it.

Douxingma

A bilingual VPN product I designed, built, deployed, and still operate — the whole thing, from the signup form to the certificate renewals.

Role: Sole developer and operatorContext: Independent product, in productionStack: React · TypeScript · Python / FastAPI · DynamoDB · AWS Lightsail · CDK

What I built

Douxingma brings account registration and recovery, subscription redemption, connection setup, and traffic usage into one bilingual website. A React frontend talks to a FastAPI backend for email verification, account access, subscription redemption, and administration; DynamoDB holds the application records; payment callbacks record order status and apply subscription upgrades.

My ownership

All of it, and it is the part of my portfolio with no one else to blame: product decisions, frontend, backend, data model, infrastructure in AWS CDK on Lightsail, deployment, and the ongoing operations. The source repository is private, so the link below goes to the running product.

Douxingma architectureOne application path, plus a scheduled reconciliation against the running service.

Application path

Reconciliation path

Engineering problems worth naming

Two systems, one subscriber. The VPN management system and the product’s own records each know something different about an account. A scheduled job reconciles traffic usage from the management database into the application records, so what a user sees matches what the service is enforcing. That makes the display eventually consistent — a tradeoff I took deliberately rather than coupling every page load to the management system.

Payment callbacks arrive whenever they arrive. Order status and subscription upgrades are applied when the payment provider calls back, which may be late or repeated relative to what the user is doing in the browser. The order record is the authority for what was paid for, and account state is derived from it.

Operations are part of the product. Account recovery, bilingual onboarding, deploys, and certificate renewals are all mine too. The failure mode I care about most is the one that locks a paying user out of their own account.

Where it stands

In production and actively maintained. It is not research, and I do not present it as such: it is the clearest evidence I have that I can take a system from an idea to something strangers rely on, and keep it running.