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": "..." } ] }
- Metrics accepted:
heart_rate_bpm,resp_rate_bpm,hrv_ms,activity,temp_c,spo2_pct. Anything else comes back inrejectedby name — a metric we silently discarded would look like success from the device side. - Sources:
device,video,manual. A device key may only claimdevice. - Idempotent on device, instant and metric, so a retry after a dropped connection cannot double-count.
- A device key writes and reads nothing. It is hashed at rest and shown once.
GET /api/v1/ingest/pingconfirms a key and returns server time for clock sync.
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
- The daily summary is written from computed facts; any number in a draft that is not in those facts discards the whole draft for the deterministic text.
- No condition name, no treatment, no medication, no guarantee, no accuracy percentage. The verifier runs on the deterministic path too.
- With no readings the screen says not connected. There is no sample data in the product.
- With no model the report still works and is labelled as written without one.
- Reference ranges are published veterinary figures and carry their basis to the screen. A metric with no range MaraPet can stand behind (variability, activity) gets a trend and no verdict.
Not built yet
- No MaraPet device exists. ISTC owns that half.
- No clinical validation of anything.
- Web push is not wired; alerts are in-app.