Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions skills/lookout/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Lookout

Personalized ambient health awareness for Tula.

Your health radar. Lookout reads where you are right now and tells you what about your surroundings matters to your health, given your actual conditions, medications, and recent labs. A weather app tells everyone the same AQI. Lookout tells you the part that matters for you, and stays quiet about the rest.

Trigger: "lookout for me".

## What it does

- A daily morning briefing on Telegram: a short, personalized health forecast for where you are.
- On-demand answers: "how's the air today?", "where can I work out right now?", "anything I should know?", "what's [ZIP] like for me?"
- Quietly builds an environmental exposure history in your longitudinal record, and captures the neighborhood layer your EHR never sees.

## Why it is more than a weather app

The environmental data is a commodity. The value is the fusion. Lookout reads your local FHIR record (conditions, medications, recent labs) and triages the ambient data against it, surfacing only the relevant signal with an option or a question. Examples:

- You have asthma and ozone is high: it says so, and offers an indoor option or a rescue-inhaler refill check.
- One of your medications increases sun sensitivity and UV is high: it flags it.
- You take a diuretic and the heat index is climbing: it raises hydration and points to cooling options.
- You have a migraine history and pressure is dropping sharply tonight: it gives you a heads-up.

It never diagnoses, never recommends treatment, and never adjusts a medication. It surfaces data and a question.

## The data Lookout pulls

Endpoints, field names, and key requirements below reflect provider behavior as of early 2026. Verify against current provider documentation at build time.

Air and atmosphere
- Air quality / AQI: AirNow (US, free, free API key). Optional hyperlocal and global: PurpleAir, OpenAQ. Optional granular: Google Air Quality (keyed, paid).
- UV index: EPA UV Index, data.gov (US, free).
- Pollen (later): Google Pollen or Ambee (keyed, species-level).
- Wildfire smoke (later): NOAA HMS smoke product.

Weather stress and alerts
- Weather and active alerts: National Weather Service, api.weather.gov (US, free, no key, requires User-Agent).
- Barometric pressure and trend, global fallback: Open-Meteo (free, no key).

Public-health surveillance (later)
- Respiratory virus activity (flu, COVID, RSV), including wastewater: CDC (free).

Resources around you
- Gyms, grocery, pharmacy, urgent care, parks: Google Maps Platform Places (keyed, paid). Farmers markets: USDA Local Food Directories (free). Walkability: Walk Score.

Place-as-health: the SDoH overlay
- CDC PLACES (local chronic-disease and risk estimates), CDC/ATSDR Social Vulnerability Index, Area Deprivation Index, USDA Food Access Atlas, EPA EJScreen and Walkability Index, EPA radon zones. All free and public.
- Captured as SDoH context and fed into Tula's SDoH pipeline. Coded with ICD-10 Z codes where one applies, as clinical context only.

Free, government-first sourcing keeps Lookout aligned with Tula's open core (Apache 2.0) and avoids per-call cost and vendor lock-in. The paid pieces (Places, optional Google pollen and AQI) are opt-in.

## Privacy

Lookout sends only a location (latitude/longitude or ZIP) to the public data providers. Your conditions, medications, and labs never leave your VM to reach those providers. The personalized triage runs through Tula's already-configured model path: a HIPAA-eligible hosted tier, or a local open-weight model for fully air-gapped operation. No environmental provider ever sees PHI.

## Setup

1. Initialize the local SQLite store: `python3 scripts/init_db.py`.
2. Configure at least one location (label plus latitude/longitude or ZIP) through onboarding or the user profile. The default location drives the daily briefing. Location is user data and is not stored in the repo.
3. Ensure the Python dependency is installed: `python3 -m pip install httpx` or the package-manager equivalent.
4. Set units (imperial by default for US), the briefing time, and quiet hours.
5. Provide API access:
- AirNow free API key (set `AIRNOW_API_KEY`).
- A descriptive User-Agent for NWS (set `LOOKOUT_NWS_UA`).
- A Google Maps Platform key for the resources layer (optional).
- A free CDC Socrata app token for higher SDoH rate limits (optional).
6. Run the fetch (`scripts/fetch_environment.py`) on a schedule, then let the agent triage and brief.

## Storage

- Working store: SQLite (`schema.sql`). Every value carries its source, timestamp, unit, reference range or category band, and a raw source id, so everything Lookout surfaces is traceable.
- Longitudinal record: durable readings are written as FHIR R4 `Observation` resources (environmental exposure, and SDoH as social-history with a Z code where applicable). Patient-generated health data per the ONC definition.

## Roadmap

v1: AirNow AQI, NWS weather and alerts, EPA UV, Open-Meteo pressure, Places resources, CDC PLACES and SVI overlay, daily briefing, on-demand queries, FHIR Observation logging.

Later: pollen species, respiratory-virus surveillance, wildfire smoke, water advisories, radon, polished travel and relocation mode, correlation insight (for example flare days against PM2.5), wearable cross-reference.

## Safety and scope

Lookout is not a medical device. It surfaces data and questions. It is health-data only: it never produces billing, payor, prior-auth, EOB, or medical-coding content. SDoH Z codes are the only codes used, and only as clinical context. For potentially urgent conditions, Lookout surfaces the item prominently and points to the care team or appropriate action, the open-core counterpart to Aria's configurable escalation policy.

Part of Tula. https://github.com/realactivity/tula | https://realactivity.ai
95 changes: 95 additions & 0 deletions skills/lookout/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
---
name: lookout
description: "Personalized ambient health awareness. Pulls environmental, public-health, and place-based data for the user's location and triages it against their longitudinal record, surfacing only what matters. USE FOR: 'lookout for me' briefings, ambient/air/UV/weather/pressure checks, place-based resource queries, travel-mode 'what's [ZIP] like for me'. DO NOT USE FOR: diagnosing, recommending or changing treatment, billing/payor/coding content, or sending PHI to any environmental provider."
metadata:
{
"openclaw":
{
"emoji": "🛰️",
"requires": { "bins": ["python3"] }
}
}
---

# Lookout

Ambient health radar. Lookout reads where the user is right now, fuses environmental + place + neighborhood SDoH data with their FHIR record, and surfaces only the items that matter for *them*. A weather app says "AQI 142." Lookout says "ozone is high, you have asthma on file, want an indoor option?"

## When to Use

✅ Use when:

- The user says "lookout for me" or any natural variant ("how's the air today?", "where can I work out right now?", "anything I should know today?", "what's [ZIP] like for me?")
- The configured daily briefing time fires
- The user asks about open-now resources (gym, pharmacy, urgent care, grocery, park) tied to their health
- The user asks about a neighborhood's SDoH overlay for travel or relocation

## When NOT to Use

❌ Don't use when:

- The user wants a clinical interpretation of a PDF → use `med-pdf`
- The user wants to connect/refresh their patient portal → use `health-records`
- The user wants a drafted clinician message → use `epic-note`
- A request would diagnose, recommend treatment, or adjust a medication
- A request would produce billing, payor, prior-auth, EOB, or coding content (CPT/HCPCS/HCC). SDoH Z codes are the only codes used, and only as clinical context.

## Setup

1. **Init the store.** `python3 {baseDir}/scripts/init_db.py`
- Creates `~/.openclaw/workspace/.lookout-cache/lookout.db` from `schema.sql`.
- Seeds only tenant-neutral preferences (imperial, 07:30 briefing, 22:00–07:00 quiet hours). Re-running is safe.
2. **Configure location.** Onboarding or the user profile must create one `location` row with `is_default = 1` and latitude/longitude before fetch runs. Location is user data; don't hard-code it in the skill.
3. **Install Python dependency.** Ensure `httpx` is available to Python (`python3 -m pip install httpx` or the distro/package-manager equivalent).
4. **Optional keys.** Lookout runs end-to-end with no keys. For richer data:
- `AIRNOW_API_KEY` — US AQI by lat/lon (free key from AirNow).
- `GOOGLE_PLACES_API_KEY` — open-now resources layer (paid).
- `LOOKOUT_NWS_UA` — descriptive User-Agent for api.weather.gov.

## Workflow

1. **Fetch** — `python3 {baseDir}/scripts/fetch_environment.py`
- Reads the default location row, calls NWS (alerts + current weather), Open-Meteo (pressure + trend, temp, humidity), and EPA UV (data.gov). No keys required.
- Inserts rows into `environment_reading` and `alert` with `raw_source_id` for traceability. AirNow + Google Places stay stubbed until keyed.
- Cache dir: `~/.openclaw/workspace/.lookout-cache/`.

2. **Read the patient's record** from the local FHIR store:
- Active `Condition` (clinical-status active)
- Active `MedicationStatement`
- Recent `Observation` (category laboratory) — most recent values

3. **Triage.** Map record signals to ambient signals (asthma/COPD → ozone, PM2.5, smoke; photosensitizing meds → UV; HF or diuretic → heat index; migraine → pressure drop; recent low vitamin D → moderate UV; depression/SAD → daylight; OA → cold/damp/falling pressure). If nothing matches a signal, **stay silent about it**. Silence about clean air is a feature.

4. **Brief.** Lead with what matters for *this* patient. Plain language first, clinical term in parens where it adds clarity. Every health-relevant item ends in an option or a question, never a directive. Tag as informational, not medical advice. Urgent items (severe-asthma + air quality emergency, HF + extreme heat) go first and prominently, with a pointer to the care team.

5. **Persist.**
- Durable, health-relevant readings → FHIR `Observation` (environmental-exposure category, UCUM unit, `derivedFrom`/`note` back to source).
- SDoH context → `Observation` social-history with the relevant ICD-10 Z code where one applies. Feeds Tula's SDoH pipeline.
- Path: `~/.openclaw/workspace/tula/fhir/Observation/environmental/` and the existing social-history location.
- Briefing summary → `briefing` table with `source_reading_ids` for traceability.

## Scripts

- `scripts/init_db.py` — apply `schema.sql` and seed tenant-neutral preferences. Idempotent. It does not seed a location.
- `scripts/fetch_environment.py` — deterministic fetch. No PHI ever leaves the VM — only lat/lng or ZIP reach the providers. Run on a schedule or before a briefing.

## Privacy

Lookout sends **only a location** (lat/lng or ZIP) to public data providers. Conditions, medications, labs never leave the VM. The personalized triage runs through Tula's configured model path (HIPAA-eligible hosted tier, or local open-weight model for air-gapped operation).

- Cache stays under `~/.openclaw/workspace/.lookout-cache/`. Don't copy out.
- No environmental provider ever sees PHI.
- Location is sensitive — stored locally only.

## Safety

- Not a medical device. Surfaces data and questions; never names a diagnosis, recommends treatment, or adjusts a medication.
- No hallucinated values. Every value surfaced maps to a row with a `raw_source_id`. If a fetch failed or a value is stale, say so — don't guess.
- Show the full informational-only notice once at onboarding; carry a short standing tag on health-relevant outputs.
- Escalation: urgent items first and prominently, with a pointer to the care team. Open-core counterpart to Aria's configurable escalation policy.

## Notes

- v1 scope: NWS, Open-Meteo, EPA UV, AirNow (keyed), Places (keyed), CDC PLACES + SVI overlay, daily briefing, on-demand queries, FHIR Observation logging.
- Later: pollen species, respiratory-virus surveillance, wildfire smoke, water advisories, radon, polished travel/relocation mode, correlation insight, wearable cross-reference.
- Endpoints and field names reflect provider behavior as of early 2026 — verify against current provider docs before depending on a field name.
108 changes: 108 additions & 0 deletions skills/lookout/schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
-- Lookout skill: SQLite working store
-- Tula health-skill conventions: store units and reference ranges with every value;
-- keep a raw source identifier on every reading for traceability (no hallucinated values).
-- All data is local to the patient's VM.

PRAGMA foreign_keys = ON;

-- Configured location(s). Location is sensitive; stored locally only.
CREATE TABLE IF NOT EXISTS location (
id INTEGER PRIMARY KEY,
label TEXT NOT NULL, -- e.g. 'home', 'office'
latitude REAL,
longitude REAL,
zip TEXT,
fips_tract TEXT, -- census tract for SDoH joins
is_default INTEGER NOT NULL DEFAULT 0, -- 1 = used for the daily briefing
source TEXT NOT NULL DEFAULT 'manual', -- 'manual' | 'device'
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);

-- Patient preferences for the skill.
CREATE TABLE IF NOT EXISTS preference (
id INTEGER PRIMARY KEY,
key TEXT NOT NULL UNIQUE, -- 'units' | 'briefing_time' | 'quiet_hours' | 'prioritize_signals' | 'suppress_signals'
value TEXT NOT NULL,
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);

-- One row per fetched ambient value.
CREATE TABLE IF NOT EXISTS environment_reading (
id INTEGER PRIMARY KEY,
location_id INTEGER NOT NULL REFERENCES location(id),
captured_at TEXT NOT NULL DEFAULT (datetime('now')),
source TEXT NOT NULL, -- 'airnow' | 'nws' | 'open-meteo' | 'epa-uv' | ...
metric TEXT NOT NULL, -- 'aqi' | 'pm25' | 'ozone' | 'uv_index' | 'temp' | 'heat_index' | 'humidity' | 'pressure' | 'pressure_trend' | ...
value REAL,
value_text TEXT, -- for non-numeric values (e.g. 'falling')
unit TEXT, -- UCUM where applicable
reference_low REAL,
reference_high REAL,
category_band TEXT, -- 'good' | 'moderate' | 'unhealthy_sensitive' | 'unhealthy' | ...
raw_source_id TEXT, -- provider response id / record key for traceability
notes TEXT
);
CREATE INDEX IF NOT EXISTS idx_reading_loc_time ON environment_reading(location_id, captured_at);
CREATE INDEX IF NOT EXISTS idx_reading_metric ON environment_reading(metric);

-- Active public alerts (weather, air-quality action days, etc.).
CREATE TABLE IF NOT EXISTS alert (
id INTEGER PRIMARY KEY,
location_id INTEGER NOT NULL REFERENCES location(id),
captured_at TEXT NOT NULL DEFAULT (datetime('now')),
source TEXT NOT NULL, -- 'nws' | ...
alert_type TEXT, -- e.g. 'heat_advisory', 'air_quality'
severity TEXT,
headline TEXT,
effective_at TEXT,
expires_at TEXT,
raw_source_id TEXT
);
CREATE INDEX IF NOT EXISTS idx_alert_loc ON alert(location_id, expires_at);

-- Nearby resources (the resources layer; from Places).
CREATE TABLE IF NOT EXISTS place (
id INTEGER PRIMARY KEY,
location_id INTEGER NOT NULL REFERENCES location(id),
captured_at TEXT NOT NULL DEFAULT (datetime('now')),
place_type TEXT NOT NULL, -- 'gym' | 'grocery' | 'pharmacy' | 'urgent_care' | 'park' | 'farmers_market'
name TEXT NOT NULL,
address TEXT,
latitude REAL,
longitude REAL,
open_now INTEGER, -- 1 | 0 | NULL unknown
hours_json TEXT,
rating REAL,
distance_m INTEGER,
source TEXT NOT NULL DEFAULT 'google_places',
place_ref TEXT -- provider place id
);
CREATE INDEX IF NOT EXISTS idx_place_loc_type ON place(location_id, place_type);

-- Neighborhood SDoH overlay. Feeds Tula's SDoH pipeline. Z codes permitted as context.
CREATE TABLE IF NOT EXISTS sdoh_context (
id INTEGER PRIMARY KEY,
location_id INTEGER NOT NULL REFERENCES location(id),
captured_at TEXT NOT NULL DEFAULT (datetime('now')),
indicator TEXT NOT NULL, -- 'svi_overall' | 'food_access_low' | 'adi_national' | 'walkability' | 'places_*'
value REAL,
value_text TEXT,
unit TEXT,
geography_level TEXT, -- 'tract' | 'zip' | 'county'
geography_id TEXT,
z_code TEXT, -- ICD-10 Z code where one applies (context only)
source TEXT NOT NULL -- 'cdc_places' | 'cdc_svi' | 'usda_fara' | ...
);
CREATE INDEX IF NOT EXISTS idx_sdoh_loc ON sdoh_context(location_id, indicator);

-- Generated daily briefings, with traceability back to the readings used.
CREATE TABLE IF NOT EXISTS briefing (
id INTEGER PRIMARY KEY,
location_id INTEGER NOT NULL REFERENCES location(id),
generated_at TEXT NOT NULL DEFAULT (datetime('now')),
summary_text TEXT,
items_json TEXT, -- the personalized items surfaced
model_used TEXT,
source_reading_ids TEXT -- comma-separated environment_reading.id values used
);
CREATE INDEX IF NOT EXISTS idx_briefing_loc ON briefing(location_id, generated_at);
Loading
Loading