Pet activity monitor
A photo-based activity tally with explicit gaps, plus an offline control card.
Documentation revision 1 · Contract reference API 54 · Installed readiness unknown
Preview offline card · Complete HTML interaction body · Machine-readable recipe
What runs where
- Timer → bounded camera snapshot → free local change filter → Clef choice → persistent watch tally → HTML card reads tally and state.
- The phone continues watching with the controller offline; remote model inference still needs internet. Pausing preserves the tally and does not extend expiry.
Controller integration
- Create the watch, persist returned watch_id, and generate a card with that resource_id.
- Changes to the watch definition require snapshotting the old tally, stopping it, and recreating; the new watch has a new tally.
Only explicit decision-model/weather requests leave the phone; direct or relay transport still requires a running reachable phone.
Boundaries
- Public composition is a generic watch, not the native Pet Monitor. It does not inherit native camera selection, dual-model comparisons, pet history/portal, microphone classifier, camera search or feeding.
- Samples show visible activity only. A missed pet is unknown coverage; cached classifications are reused evidence, not new model observations.
Lifecycle
start
POST /v1/watches with the start example; retain the returned watch_id. Then render/post the card with resource_id.
update
GET state/tally first. To change the definition, export required history, DELETE old watch, POST revised definition. No watch PATCH exists.
pause
POST /v1/watches/{id}/pause with {"paused":true}; resume with false. Confirm user_paused from the returned metadata. Expiry is unchanged.
stop
DELETE /v1/watches/{id}; confirm deleted equals the saved id and watch.ended reason deleted. Archive needed tally BEFORE deletion. Cancel the interaction separately.
Failures and costs
- Camera locked/busy: record an error gap and retry at a later slot; never classify it as resting.
- Budget/quota/model failure: inspect recent_checks and watch.ended. The template never turns an unavailable reading into zero episodes.
- At most 20 paid calls/hour for this watch, plus shared phone/team limits; reused answers are free. These are model costs covered only within the current published budget.
- Check usage.paid_cost_usd and skipped_over_paid_cap; a quiet scene still refreshes within 300 seconds.
Complete synthetic contract examples
These fixtures demonstrate contracts. They are not observations from a phone. Replace image/resource IDs with returned authorized IDs. Load request.json as the complete body of POST /v1/interactions; it contains the full offline HTML.
start
{
"id": "start",
"request": {
"method": "POST",
"path": "/v1/watches",
"body": {
"label": "Pet activity samples",
"data": [
"camera"
],
"camera": "rear",
"questions": {
"activity": {
"type": "choice",
"instructions": "Classify only the visible animal in this photo. If absent or ambiguous choose unclear. Do not infer identity or health.",
"criteria": {
"resting": "An animal is lying still.",
"eating": "An animal visibly eats at a bowl.",
"drinking": "An animal visibly drinks at a bowl.",
"other": "Visible activity fits none of those.",
"unclear": "Absent, occluded or uncertain."
}
}
},
"model": "clef",
"interval_s": 30,
"window": 1,
"min_readings": 1,
"expires_in_s": 7200,
"repeat": true,
"local": {
"change_check": true,
"sensitivity": "high",
"learn_noise": true,
"max_reuse_s": 300
},
"max_paid_per_hour": 20,
"tally": {
"track": "activity",
"labels": [
"resting",
"eating",
"drinking",
"other"
],
"min_confidence": 0.7,
"gap_s": 90,
"keep_days": 7
}
}
},
"response": {
"status": 201,
"body": {
"watch_id": "w0123456789ab",
"label": "Pet activity samples",
"user_paused": false,
"created_at": "2026-11-04T10:00:00Z",
"expires_at": "2026-11-04T12:00:00Z",
"interval_s": 30,
"data": [
"camera"
],
"checks": 0,
"matches": 0,
"last_match_at": null,
"readings_held": 0,
"last_check": null,
"usage": {
"model": "clef",
"answered_locally": 0,
"paid_calls": 0,
"paid_cost_usd": 0,
"paid_calls_last_hour": 0,
"max_paid_per_hour": 20,
"skipped_over_paid_cap": 0,
"change_check": true
},
"spec": {
"label": "Pet activity samples",
"data": [
"camera"
],
"camera": "rear",
"questions": {
"activity": {
"type": "choice",
"instructions": "Classify only the visible animal in this photo. If absent or ambiguous choose unclear. Do not infer identity or health.",
"criteria": {
"resting": "An animal is lying still.",
"eating": "An animal visibly eats at a bowl.",
"drinking": "An animal visibly drinks at a bowl.",
"other": "Visible activity fits none of those.",
"unclear": "Absent, occluded or uncertain."
}
}
},
"model": "clef",
"interval_s": 30,
"window": 1,
"min_readings": 1,
"expires_in_s": 7200,
"repeat": true,
"local": {
"change_check": true,
"sensitivity": "high",
"learn_noise": true,
"max_reuse_s": 300
},
"max_paid_per_hour": 20,
"tally": {
"track": "activity",
"labels": [
"resting",
"eating",
"drinking",
"other"
],
"min_confidence": 0.7,
"gap_s": 90,
"keep_days": 7
}
},
"callback_secret": null,
"first_check_in_s": 0,
"delivery": "watch.matched and watch.ended go to your registered callback if you have one, and to GET /v1/events (both need the interactions permission)"
}
},
"evidence": "Synthetic contract fixture, not a device observation."
}
stop
{
"id": "stop",
"request": {
"method": "DELETE",
"path": "/v1/watches/w0123456789ab"
},
"response": {
"status": 200,
"body": {
"deleted": "w0123456789ab"
}
},
"evidence": "Synthetic contract fixture, not a device observation."
}
Extensions
- Use an enrolled lan:<id> camera after grant checks.
- Add a seen condition using a separate pet-present noul question to count watched time only while visible.
- Tune episode gaps and confidence with labeled samples before interpreting totals.
Source evidence
- app/src/main/java/cc/bridgit/app/api/Endpoints.kt
- app/src/main/java/cc/bridgit/app/interact/CardRules.kt
- app/src/main/java/cc/bridgit/app/interact/CardActions.kt
- app/src/main/java/cc/bridgit/app/dog/DogMonitor.kt
- app/src/main/java/cc/bridgit/app/dog/DogChecks.kt
- app/src/main/java/cc/bridgit/app/dog/DogAudio.kt
- app/src/main/java/cc/bridgit/app/dog/DogCamera.kt
- app/src/main/java/cc/bridgit/app/dog/PetCameraSearch.kt
- app/src/main/java/cc/bridgit/app/watch/WatchModule.kt
- app/src/main/java/cc/bridgit/app/watch/WatchTally.kt
- app/src/main/java/cc/bridgit/app/remoteview/RemoteViewDog.kt
- app/src/main/java/cc/bridgit/app/remoteview/RemoteView.kt