ISTC × Digit2AI

How MaraPet works

Written for whoever has to maintain this, and for ISTC's engineering lead building the device.

Who owns which half

ISTC owns the device: board, sensors, firmware, enclosure, battery, certification and manufacture or sourcing. Digit2AI owns the app, the cloud, the database, the ingest contract and the AI. The alliance is named ISTC × Digit2AI, in that order, on every surface.

The ingest contract

Every reading enters by one door, so the board can change without the platform changing.

POST /api/v1/readings
Authorization: Bearer mp_dev_...

{ "recorded_at": "2026-10-17T03:12:00Z",
  "firmware": "1.0.3",
  "metrics": { "heart_rate_bpm": 78, "resp_rate_bpm": 22 } }

Or the explicit form, one object per sample, which is what a buffered collar sends when it reconnects:

{ "samples": [
    { "metric": "heart_rate_bpm", "value": 78, "recorded_at": "..." },
    { "metric": "resp_rate_bpm",  "value": 22, "recorded_at": "..." } ] }

Blood pressure is deliberately absent

Canine blood pressure is an oscillometric cuff measurement on a limb or the tail base. It is not a continuous wearable reading, so it is not in the data model at all rather than present and empty. If ISTC ships a cuff it enters as a timestamped manual reading, never as a trend line.

Oxygen needs a different site, not a better collar

Transmittance pulse oximetry needs thin, sparsely furred tissue, which is why clinical veterinary oximeters clip to an ear pinna, lip or tongue. At the neck, under fur, it does not work. spo2_pct is in the allow-list so a future ear-site device is a config change, and until a row exists the app draws no oxygen card at all.

The breathing estimate, and what it is not

The browser samples the brightness of the chest region frame by frame, detrends it, and finds the dominant period in the 6–120 breaths-per-minute band. The video never leaves the phone; only the brightness series and the resulting figure are sent.

The detection threshold is derived from a false-alarm bound rather than picked, because the first version of it returned a confident 102 breaths a minute from pure noise. scripts/calibrate-breathing.js measures the real false-alarm and recovery rates. Below the floor it returns no number and the reason.

It has not been validated against a vet with a stopwatch. It is labelled an estimate from video, with its confidence, everywhere it appears, and that validation is the first external dependency.

What is enforced in code, not promised

Not built yet

Live service status