|
| 1 | +# OpenSPP DCI Demo Flow |
| 2 | + |
| 3 | +--- |
| 4 | + |
| 5 | +## Pre-demo state (assumed before slide 1) |
| 6 | + |
| 7 | +| Side | State | |
| 8 | +| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | |
| 9 | +| SP | Fresh database. Modules installed (`spp_dci_openspp_dr` + `spp_dci_openg2p`). 0 registrants. DR data source URL set. | |
| 10 | +| DR | `openspp_dr` database with 8 approved disability assessments seeded for `IND-NSR-0001/0003/0005/0007/0009/0011/0013/0015`. DCI bypass flags set. | |
| 11 | +| OpenG2P SR | Live at `partner-nsr.play.openg2p.org`. 15 personas seeded by OpenG2P. No SP-side action needed. | |
| 12 | +| Demo program | "Disability Assistance" defined on SP. Eligibility rule: `has_disability == true && is_poor == "low"`. | |
| 13 | + |
| 14 | +Run `scripts/demo/reset_spdci_demo.py` to guarantee a clean |
| 15 | +cache + draft memberships if you ran a dry run earlier. |
| 16 | + |
| 17 | +--- |
| 18 | + |
| 19 | +## Scene 1 — Frame the problem (1 min, slide-only) |
| 20 | + |
| 21 | +**Slide content**: the narrative paragraph from the briefing. |
| 22 | + |
| 23 | +> A government runs a social-protection program — **Disability Assistance** — that |
| 24 | +> targets registrants who are **both** living with a disability **and** classified as |
| 25 | +> poor. The two facts live in two independent registries owned by two independent |
| 26 | +> agencies. OpenSPP composes the eligibility decision by querying both over the DCI |
| 27 | +> standard, in real time, with a single click. |
| 28 | +
|
| 29 | +**Talking points** |
| 30 | + |
| 31 | +- Two registries, two owners, one decision. |
| 32 | +- The hard problem isn't computing eligibility — it's federating data across independent |
| 33 | + agencies without replicating their records. |
| 34 | +- SPDCI is the standard we use to do it without per-vendor glue. |
| 35 | + |
| 36 | +**Transition**: "Let's see what the platform looks like before any of this is wired in." |
| 37 | + |
| 38 | +--- |
| 39 | + |
| 40 | +## Scene 2 — The empty SP (1 min, live) |
| 41 | + |
| 42 | +**On screen**: SP UI → Registry → Registrants list. Empty. |
| 43 | + |
| 44 | +**Presenter does**: nothing yet. Just lets the audience see "0 registrants". |
| 45 | + |
| 46 | +**Talking points** |
| 47 | + |
| 48 | +- This is a clean OpenSPP install. We haven't pre-loaded data; we'll populate the |
| 49 | + registry the way an operator would in production. |
| 50 | +- The SP doesn't know about the Social Registry's records yet. Names, identifiers, |
| 51 | + demographics — all live on the SR. |
| 52 | +- We're going to pull a list of registrants from the SR using DCI search-sync, let the |
| 53 | + operator review them, and import the chosen ones. |
| 54 | + |
| 55 | +**Transition**: "Watch how the operator pulls registrants from the Social Registry." |
| 56 | + |
| 57 | +--- |
| 58 | + |
| 59 | +## Scene 3 — Import from the Social Registry (2 min, live) |
| 60 | + |
| 61 | +**On screen**: SP UI → Registry → **Import from External Registry**. |
| 62 | + |
| 63 | +**Presenter does**: |
| 64 | + |
| 65 | +1. Click **Registry → Import from External Registry**. |
| 66 | +2. Form opens. Note **Source Registry** is pre-filled with **Social Registry**. |
| 67 | + - _One sentence aside_: "That's the OpenG2P SR endpoint. It's just a DCI data source |
| 68 | + — production deployments configure their own." |
| 69 | +3. Discovery mode: **Identifier range sweep** (the default). |
| 70 | +4. Range: `IND-NSR-` `0001` to `0015`, pad `4`. |
| 71 | +5. Auto-enroll: pick **Disability Assistance**. |
| 72 | +6. Click **Preview**. |
| 73 | + |
| 74 | +**On screen**: 15 preview rows fill in. Each row carries given_name, surname, sex, |
| 75 | +birth_date pulled fresh from the SR over DCI. |
| 76 | + |
| 77 | +**Talking points** |
| 78 | + |
| 79 | +- Every row on the preview is the result of a separate DCI search-sync request to the |
| 80 | + live SR. The SR returned name + demographics + identifier metadata in the DCI standard |
| 81 | + envelope. |
| 82 | +- The wizard captured **only the minimum**: name, sex, birth_date, UIN. The rich |
| 83 | + attributes (income_level, marital_status, employment_status) stay on the SR — we'll |
| 84 | + fetch them on demand at eligibility time. |
| 85 | +- All 15 rows are pre-selected because none of them exist on the SP yet. |
| 86 | +- If we re-ran the wizard, already-imported rows would show "already on SP" and be |
| 87 | + unchecked by default. |
| 88 | + |
| 89 | +**Presenter does**: Click **Import Selected**. |
| 90 | + |
| 91 | +**On screen**: "15 registrant(s) imported." Wizard closes; navigate back to Registrants |
| 92 | +list to show the 15 new partners with their UIN reg_ids. |
| 93 | + |
| 94 | +**Transition**: "Now the SP has identifiers. It doesn't yet have disability data or |
| 95 | +poverty data. Let's run eligibility and watch the federation kick in." |
| 96 | + |
| 97 | +--- |
| 98 | + |
| 99 | +## Scene 4 — The program and its CEL rule (1 min, live) |
| 100 | + |
| 101 | +**On screen**: SP UI → Programs → **Disability Assistance**. |
| 102 | + |
| 103 | +**Presenter does**: open the program form. Scroll to the eligibility rule. |
| 104 | + |
| 105 | +**Slide content** (recap): |
| 106 | + |
| 107 | +``` |
| 108 | +has_disability == true && is_poor == "low" |
| 109 | +``` |
| 110 | + |
| 111 | +**Talking points** |
| 112 | + |
| 113 | +- This is a **CEL** (Common Expression Language) rule. Google's open-source expression |
| 114 | + DSL. We use it because CEL is vendor-neutral and the operator can change a rule |
| 115 | + without touching code. |
| 116 | +- `has_disability` and `is_poor` are CEL **variables**. Each one is bound to a registry |
| 117 | + behind the scenes: |
| 118 | + - `has_disability` → OpenSPP-DR (Disability Registry) over DCI |
| 119 | + - `is_poor` → OpenG2P SR (Social Registry) over DCI, reading `income_level` |
| 120 | +- The operator doesn't see "Disability Registry" or "OpenG2P" in the rule. They see |
| 121 | + semantic names. Swapping the backing source is a configuration change, not a code |
| 122 | + change. |
| 123 | +- The rule will evaluate true for any registrant who is both flagged as disabled by the |
| 124 | + DR _and_ classified `income_level == "low"` by the SR. |
| 125 | + |
| 126 | +**Transition**: "One click." |
| 127 | + |
| 128 | +--- |
| 129 | + |
| 130 | +## Scene 5 — Enroll Eligible (2 min, live, the centerpiece) |
| 131 | + |
| 132 | +**On screen**: Disability Assistance program form, top bar showing membership counts (15 |
| 133 | +draft / 0 enrolled). |
| 134 | + |
| 135 | +**Presenter does**: click **Enroll Eligible**. |
| 136 | + |
| 137 | +**What audiences see**: a few seconds of work, then the count flips to **4 enrolled / 11 |
| 138 | +draft**. |
| 139 | + |
| 140 | +**Talking points** (while it runs): |
| 141 | + |
| 142 | +- Under the hood, the platform just fired **30 DCI requests** in parallel: 15 to the DR |
| 143 | + for `has_disability`, 15 to the SR for `is_poor`. |
| 144 | +- Every response was cached in `spp.data.value` with a TTL. |
| 145 | +- Every fetch was audited in `spp.dci.fetch.audit` for compliance. |
| 146 | +- The CEL executor compiled the rule into a SQL query that AND-joined the two cached |
| 147 | + datasets to produce the final eligibility set. |
| 148 | +- The whole thing took under five seconds in this demo. In production this is the |
| 149 | + bottleneck you'd tune with caching and async pre-fetch. |
| 150 | + |
| 151 | +**On screen after**: the 4 enrolled — Alex Rivera, Morgan Cole, Taylor Brooks, Sam |
| 152 | +Hayes. (Names match the briefing sheet's expected outcome.) |
| 153 | + |
| 154 | +**Slide content** (split view recommended): |
| 155 | + |
| 156 | +| Why these 4? | Each is in **both** registries with both predicates true | |
| 157 | +| ---------------------------- | -------------------------------------------------------- | |
| 158 | +| Alex Rivera (IND-NSR-0001) | DR: has_disability=true • SR: income_level=low | |
| 159 | +| Morgan Cole (IND-NSR-0004) | DR: has_disability=true • SR: income_level=low | |
| 160 | +| Taylor Brooks (IND-NSR-0010) | DR: has_disability=true • SR: income_level=low | |
| 161 | +| Sam Hayes (IND-NSR-0013) | DR: has_disability=true • SR: income_level=low | |
| 162 | + |
| 163 | +**Transition**: "Let's see why one of them got picked — by drilling into the audit |
| 164 | +trail." |
| 165 | + |
| 166 | +--- |
| 167 | + |
| 168 | +## Scene 6 — Drill into one decision (2 min, live) |
| 169 | + |
| 170 | +**On screen**: SP UI → click Alex Rivera in the enrolled list. |
| 171 | + |
| 172 | +**Presenter does**: open Alex's partner form. Navigate to the audit log / DCI fetch |
| 173 | +audit related list (depending on UI exposure, this may be via the developer menu or a |
| 174 | +dedicated tab). |
| 175 | + |
| 176 | +**Talking points** |
| 177 | + |
| 178 | +- Each row here is a DCI fetch that contributed to Alex's eligibility. We see two: one |
| 179 | + to the DR for has_disability, one to the SR for is_poor. |
| 180 | +- The audit row carries the message_id (the DCI envelope's identifier), the registry |
| 181 | + that answered, the timestamp, and the resolved value. We can reconstruct the exact |
| 182 | + wire conversation for compliance review months later. |
| 183 | +- This is the "show your work" property of SPDCI: a federated eligibility decision is |
| 184 | + reproducible, attributable, and audit-ready. |
| 185 | + |
| 186 | +**Optional**: open one row, show the envelope/message detail. |
| 187 | + |
| 188 | +**Transition**: "And to prove these aren't synthetic numbers, let's see the same data on |
| 189 | +the source registry." |
| 190 | + |
| 191 | +--- |
| 192 | + |
| 193 | +## Scene 7 — Same record on the live SR (1 min, live) |
| 194 | + |
| 195 | +**On screen**: terminal with a `curl` or browser tab to OpenG2P's playground. |
| 196 | + |
| 197 | +**Presenter does**: run a one-line `curl` (or open the UI) against |
| 198 | +`partner-nsr.play.openg2p.org`: |
| 199 | + |
| 200 | +```bash |
| 201 | +# Pre-canned with auth + envelope already filled in. |
| 202 | +./scripts/demo/probe_openg2p.sh IND-NSR-0001 |
| 203 | +``` |
| 204 | + |
| 205 | +(If you don't have a pre-canned probe, fall back to the wizard's preview of |
| 206 | +`IND-NSR-0001` — the same JSON travels over the wire.) |
| 207 | + |
| 208 | +**On screen**: the SR's response showing the same Alex Rivera record with |
| 209 | +`income_level: "low"` in `additional_attributes` or `reg_records[0]`. |
| 210 | + |
| 211 | +**Talking points** |
| 212 | + |
| 213 | +- Same identifier, same record, returned by an independent OpenG2P-hosted registry. |
| 214 | + We're not mocking anyone — this is a live, third-party endpoint. |
| 215 | +- The DR side is our own OpenSPP instance acting as a DCI server. Different agency, |
| 216 | + different platform, both speaking the same protocol. |
| 217 | + |
| 218 | +**Transition**: "Let's recap and look at what's interesting beyond the happy path." |
| 219 | + |
| 220 | +--- |
| 221 | + |
| 222 | +## Scene 8 — Recap and what each failure mode tells us (1 min, slide) |
| 223 | + |
| 224 | +**Slide content** (compact version of the briefing's verdict table): |
| 225 | + |
| 226 | +| Failure mode | Example | What it shows | |
| 227 | +| -------------- | ---------------- | ------------------------------------------------------------------------------------------ | |
| 228 | +| Income not low | Kim Lee (medium) | SR says "not poor enough" — DCI returned a value, rule rejected it | |
| 229 | +| No DR record | Priya Rivera | DR returned not_found — variable resolved to null, rule rejected | |
| 230 | +| Both fail | Noah Rivera | Two failed predicates — no enrollment, no PII pulled from either side beyond identity | |
| 231 | +| Empty income | (several rows) | SR returned a record but `income_level` was unset — treated as null, fails strict equality | |
| 232 | + |
| 233 | +**Talking points** |
| 234 | + |
| 235 | +- The interesting thing isn't the 4 enrolled — it's that the platform handled four |
| 236 | + different failure modes without bespoke code. Each is just "a CEL predicate evaluated |
| 237 | + false, in a specific way." |
| 238 | +- This is what "configuration over code" looks like in practice: an operator changes the |
| 239 | + rule to `has_disability == true || is_poor == "low"` and 11 of the 15 enroll instead |
| 240 | + of 4. No deployment, no Python. |
| 241 | + |
| 242 | +--- |
| 243 | + |
| 244 | +## Scene 9 — Architecture map (1 min, slide) |
| 245 | + |
| 246 | +**Slide content**: the topology diagram from the briefing. |
| 247 | + |
| 248 | +**Talking points** |
| 249 | + |
| 250 | +- Three independent processes, two HTTP boundaries, one decision. |
| 251 | +- The SP is the **client** for both registries — it doesn't host their data, it queries |
| 252 | + them on demand. |
| 253 | +- DCI is the protocol that lets us bring on a third or fourth registry later (CRVS, IBR, |
| 254 | + MOSIP eSignet for ID verification) without rewriting the eligibility logic. New |
| 255 | + registry = new data source + new CEL variable; the rule grammar is unchanged. |
| 256 | + |
| 257 | +--- |
| 258 | + |
| 259 | +## Scene 10 — Where this goes next (1 min, slide) |
| 260 | + |
| 261 | +**Slide content**: three forward-looking bullets. |
| 262 | + |
| 263 | +- **More registries**: CRVS for civil events, IBR for cross-program dedup, MOSIP eSignet |
| 264 | + for OIDC-based ID verification. All slot in via the same SPDCI bridge. |
| 265 | +- **Async pre-warm**: long-running cohort scans move off the request path and into queue |
| 266 | + jobs, with operator-visible progress. |
| 267 | +- **Configurable consent**: per-source consent purpose codes, signed by the SP's |
| 268 | + registered DCI sender key. |
| 269 | + |
| 270 | +--- |
| 271 | + |
| 272 | +## If you only have 5 minutes (lightning version) |
| 273 | + |
| 274 | +Drop scenes 2, 6, 7, 9, 10. Keep: |
| 275 | + |
| 276 | +1. Scene 1 — Frame the problem |
| 277 | +2. Scene 3 — Import from SR (skip the wizard internals, just show the final 15-row |
| 278 | + preview and import) |
| 279 | +3. Scene 4 — The rule |
| 280 | +4. Scene 5 — Enroll Eligible → 4 / 15 |
| 281 | +5. Scene 8 — The failure-mode table |
| 282 | + |
| 283 | +--- |
| 284 | + |
| 285 | + |
0 commit comments