# FFH.Recalculate — Universal Layer Integration Guide

**For:** Pace (Composer build lead)
**Version:** 1.0.0
**Owner:** FFH Power Tools Engine
**Files in this package:**
- `recalculate-engine.js` — drift watcher, state machine, resource brain, event bus, UI controller
- `recalculate-ui.css` — banner + sheet styles (FFH brand)
- `recalculate-demo.html` — three-tracker demo host page (proof of pattern)
- `recalculator-rewards.js` — **NEW (2026-05-22)** Supabase coin-economy bridge; listens to `recalc.*` events, awards +25 LIVE It on commit, +10 SHARE It on share, +5 LEARN It per checked action, Recalibrated badge on first comeback per goal; falls back to localStorage when Supabase offline
- `phit-bridge.js` — **NEW (2026-05-24)** PHIT signal bridge; listens to `phit.pattern.signal`, anonymizes (no user_id ever), persists to `public.phit_signals`, derives state from zip prefix for AZ/PA/OH; buffers to localStorage during Supabase outages and flushes on reconnect
- `INTEGRATION-GUIDE.md` — this file

---

## What this is

A platform-wide drift detection and comeback layer. Any FFH page that has a goal (Bingo, tracker, Quest, Academy module) imports the layer, registers its goal(s) in one call, and gets:

- Continuous drift monitoring
- A non-shaming drift banner that surfaces inline on the host page
- A full Me / We / Ours diagnostic sheet (3-step branching)
- A Comeback Plan with Scenic / Direct / Detour routes
- A matched-resource list (24+ barrier combinations per profile, extensible)
- A Share It kit with copy-ready captions for FB / IG / text
- Coin + badge awards (+25 Comeback Coins, Recalibrated badge)
- Composer event emission for every step
- Anonymized PHIT signal for the community pattern library

The host page does **not** need to know how any of that works.

---

## Two-line install (engine only)

```html
<link rel="stylesheet" href="recalculate-ui.css">
<script src="recalculate-engine.js"></script>
```

## Full install (engine + Supabase coin economy + PHIT signal)

For FFH-grade deploys, ship the full bridge chain so every comeback awards real coins AND feeds the PHIT community-intelligence layer:

```html
<!-- Supabase chain — load FIRST so FFH_SUPA exists for the bridges -->
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
<script src="/_shared-assets/supabase-config.js"></script>
<script src="/_shared-assets/supabase-client.js"></script>

<!-- Recalculate layer -->
<link rel="stylesheet" href="/recalculators/layer/recalculate-ui.css">
<script src="/recalculators/layer/recalculate-engine.js"></script>

<!-- Bridges (order doesn't matter — both listen on FFH.bus) -->
<script src="/recalculators/layer/recalculator-rewards.js"></script>
<script src="/recalculators/layer/phit-bridge.js"></script>
```

Both bridges auto-wire to `window.FFH.bus`, listen for the events they care about, write to Supabase, and fall back to localStorage when offline. No host-side wiring required.

**Optional zip pickup for richer PHIT signals:** if your host page knows the user's zip (from profile, from a community-specific URL, etc.), expose it so PHIT can build neighborhood-level barrier maps:

```html
<body data-zip="17601">
```

…or pass `?zip=17601` in the URL. Zip is OPT-IN per page, NEVER required, and is the only quasi-locator we capture. No user_id, no email, no profile fk ever lands in `phit_signals`.

Then per goal:

```js
FFH.Recalculate.register({
  goalId:         'walk-30x5',
  label:          'Walk 30 min, 5 days/week',
  tracker:        'sports-health',
  thresholdDays:  7,
  barrierProfile: 'movement'
});
FFH.Recalculate.watch();
```

When the user logs progress on that goal anywhere on the page:

```js
FFH.Recalculate.logAction('walk-30x5');
```

That's it. The layer handles everything else.

---

## Goal spec reference

```js
FFH.Recalculate.register({
  goalId:         'unique-id',                  // required
  label:          'Human-readable goal',        // shown in banner + sheet
  tracker:        'tracker-namespace',          // e.g. 'sports-health' — used for PHIT routing
  thresholdDays:  7,                            // days of inactivity before drift fires
  barrierProfile: 'movement',                   // 'movement' | 'mind' | 'nutrition' | 'finance' | 'generic'
  unit:           'walks',                      // optional, used in route descriptions
  onCommit:       function(payload) { ... }     // optional callback when user commits a plan
});
```

`barrierProfile` selects the diagnostic preset. Each profile has 6 primary barriers (Me / We / Ours tagged) and 4 secondary options each, so every Recalculate session is a 3-step flow that adapts to the goal type.

---

## Composer event bus

The engine looks for `window.FFH.bus.emit(eventName, payload)` and falls back to `console.log` if no bus is wired. Wire it once:

```js
window.FFH = window.FFH || {};
window.FFH.bus = {
  emit: function(name, payload) {
    Composer.dispatch(name, payload);  // your Composer routing
  }
};
```

### Events fired

| Event | Payload | When |
|---|---|---|
| `recalc.drift.detected` | `{ goalId, daysSinceLastAction, threshold }` | Watch loop finds inactivity ≥ threshold |
| `recalc.session.started` | `{ goalId, sessionId }` | User opens Recalculate sheet |
| `recalc.barrier.tagged` | `{ goalId, tier, value }` | Step 1 or 2 answered (tier = `'primary'` or `'secondary'`) |
| `recalc.pace.selected` | `{ goalId, pace }` | Step 3 answered (`'tiny'` / `'moderate'` / `'full'` / `'different'`) |
| `recalc.route.selected` | `{ goalId, route }` | User picks Scenic / Direct / Detour |
| `recalc.action.completed` | `{ goalId, index, done }` | User checks/unchecks a tiny action |
| `recalc.shared` | `{ goalId, channel }` | User taps a Share button |
| `recalc.plan.committed` | `{ sessionId, goalId, tracker, route, pace, barrierTags, timestamp }` | User locks in plan |
| `recalc.session.dismissed` | `{ goalId, reason }` | User dismisses banner |
| `phit.pattern.signal` | `{ tracker, barrierTags, route, pace, timestamp }` | Always fires alongside `plan.committed` — anonymized |

### Awards (suggested, not hard-coded)

| Trigger | Reward |
|---|---|
| `recalc.plan.committed` | +25 Comeback Coins, Recalibrated badge (once per goalId) |
| `recalc.shared` | +10 Coins, +1 Ripple credit |
| `recalc.action.completed` (done=true) | +5 Coins per action |

These are currently `alert()` stubs in the engine. Wire to the LMS economy through Composer.

---

## Public API

```js
FFH.Recalculate.register(spec)              // register a goal
FFH.Recalculate.logAction(goalId)           // reset drift clock
FFH.Recalculate.watch(opts)                 // start monitoring (opts: intervalMs, autoBanner)
FFH.Recalculate.unwatch()                   // stop monitoring
FFH.Recalculate.checkDrift(goalId)          // returns drift object or null
FFH.Recalculate.open(goalId)                // open the sheet manually
FFH.Recalculate.close()                     // close the sheet
FFH.Recalculate.dismissBanner(goalId)       // dismiss the inline banner
FFH.Recalculate.addResources({ ... })       // extend resource catalog
```

### Testing helpers (remove in prod or guard behind a flag)

```js
FFH.Recalculate._simulateDrift(goalId, days)
FFH.Recalculate._clearDismissed(goalId)
FFH.Recalculate._reset()
```

---

## Storage

The engine uses `localStorage` under the key `ffh.recalc.v1` and `sessionStorage` for banner dismissals. Schema:

```json
{
  "walk-30x5": {
    "lastActionISO": "2026-05-08T14:22:11.000Z",
    "drifts": 2,
    "comebacks": 1,
    "lastSession": { ...plan.committed payload... }
  }
}
```

When Pace wires the backend: replace the four `readStore` / `writeStore` / `getGoalState` / `setGoalState` helpers in `recalculate-engine.js` with API calls. The rest of the engine does not need to change.

---

## Resource catalog

Resources are keyed by `barrier.subBarrier` (e.g. `place.safety`, `motivation.lowmood`). The engine ships with ~80 resource entries across the four named profiles. Trackers can extend the catalog without touching the engine:

```js
FFH.Recalculate.addResources({
  'body.pain': ['Coach Lucy: Knee-pain micro-lesson', 'PT Buddy Referral'],
  'place.safety': ['Geisinger walking-route map', 'YMCA indoor track program']
});
```

This is where to inject locality-specific resources (Geisinger, AZ rural health, PA rural health) once those partnerships go live.

---

## PHIT integration

The `phit.pattern.signal` event is anonymized by design — no userId, no PII. Suggested PHIT pipeline:

1. Composer receives `phit.pattern.signal`
2. Enriches with user's zip code (already in the PHIT data governance scope)
3. Pushes to PHIT aggregate store
4. PHIT's pattern library surfaces queries like:
   - "Top 5 barriers in zip 17601 for movement goals, last 30 days"
   - "Recalibration rate by tracker, by quarter"
   - "Communities where `place.safety` is the dominant barrier" → civic planning signal

This is how individual drift becomes community intelligence — exactly the Force Index × Ripple Coefficient loop, but with the *recovery* layer captured for the first time.

---

## What's left for production

1. **Backend swap** — replace localStorage with API calls in the four storage helpers
2. **Award wire-up** — replace `alert()` calls with Composer coin/badge dispatches
3. **Locality resources** — feed Geisinger / AZ / PA / county-specific resource keys
4. **Banner display rules** — currently shows once per session; production may want per-day or quieter rules
5. **Multi-goal banner** — if 3 goals drift simultaneously, only one banner shows at a time (stacking rule TBD with Lucy)
6. **Coach Lucy AI hand-off** — the diagnostic could optionally hand to a live Coach Lucy chat instead of the canned flow, for premium tiers
7. **A11y pass** — add ARIA roles to the sheet and banner

---

## Try it locally

Open `recalculate-demo.html` in a browser. Three tracker cards, each registered with a different goal and profile. Click any "Simulate drift" button to see the banner fire, click Recalculate, walk through the diagnostic, watch the event log stream every event live.

Reset with `resetDemo()` in the console.

---

## Questions or changes

The engine is intentionally small (~700 lines including the embedded resource catalog) and dependency-free. Nothing in it requires a build step. Pace can refactor the storage layer and the bus emitter without touching the UI or the resource brain. Lucy and Dr. Rob can edit barrier copy and resource lists without touching code — they live in two clearly-marked objects at the top of the engine file.
