ADR-019: Intelligent customer home screen and financial intelligence layer¶
| Status | Accepted |
| Date | 2026-04-10 |
| Deciders | CTO, Head of Product, Head of Data |
| Affects repos | bank-app, bank-risk-platform |
Status¶
Accepted — 2026-04-10
Context¶
Legacy banks show customers a balance and a transaction list. This bank's architecture — real-time ledger, CDC pipeline, Snowflake intelligence models — makes something fundamentally different possible: a home screen that shows what the customer should do next, not just what they have done.
This ADR documents the decision to invest in a proactive intelligence layer that drives the customer home screen, and the architectural approach for doing it without adding latency to the customer-facing API.
Previous drafts of this ADR proposed writing Snowflake-computed insight signals back to Postgres for the home screen to read. That approach has been superseded by ADR-038, which establishes that display and insight data belongs in Tier 3 (Snowflake) and is never replicated into Postgres for rendering purposes. This ADR reflects that revised data path.
Decision¶
The home screen shows a net worth summary, an account mini-view, and a ranked stack of insight cards driven by Snowflake intelligence models. All intelligence is pre-computed in Snowflake dynamic tables and served via the Snowflake read API with an API-layer cache (Redis / ElastiCache). The home screen API call reads from the cache — it never calls Snowflake live on the request path and never reads insight signals from Postgres.
The one exception is the pre-approved credit decision. A credit pre-approval is an operational decision (not a display signal) — it is published to Neon via the ADR-036 decision inbox. The home screen reads the existence of a decision from Postgres (a single boolean + amount field on the customer record) but reads the detail (rate, term, rationale) from the Snowflake read API.
The five intelligence capabilities¶
Each capability is a Snowflake dynamic table refreshed on the cadence shown. The Snowflake read API serves the current signal per customer on demand; the API-layer cache absorbs the per-customer load.
1. Smart balance / idle cash detection¶
Snowflake table: intelligence.customer_signals — fields idle_cash_amount, idle_cash_days
How it works: Snowflake compares the customer's everyday account balance against their 90-day median spending pattern. If the balance has been materially above the typical spending buffer for 7+ days, the idle cash signal fires.
What the customer sees: "NZD 1,200 is earning nothing — Move to savings and earn NZD 4.80/month at 4.8% p.a." One-tap action executes the sweep.
Why it matters: Customers who move idle cash to savings earn more and stay more engaged. The bank builds a more stable deposit base. Both parties benefit.
2. Proactive pre-approved credit¶
Snowflake table: intelligence.credit_signals — fields pre_approved_amount, pre_approved_rate, pre_approved_term
How it works: MOD-029 runs the full affordability model nightly. Customers who meet the criteria have a decision published to Neon via ADR-036. The home screen reads the existence of a live offer from Postgres (fast boolean check); the offer detail is fetched from the Snowflake read API and cached.
What the customer sees: "You're pre-approved for a NZD 15,000 personal loan at 9.9% p.a. No impact on your credit score to check terms." One-tap to the full offer flow.
Why it matters: Pre-approved offers convert significantly better than cold applications because the credit decision has already been made. The customer receives and accepts rather than applying and waiting.
3. Spend anomaly detection¶
Snowflake table: intelligence.customer_signals — fields anomalous_merchants[], anomaly_description
How it works: Snowflake compares each merchant category's current month total against the customer's 6-month average. A spike of >30% in any category triggers the anomaly signal. The description is generated from the data — "Contact Energy charged NZD 284 vs your 6-month average of NZD 198."
What the customer sees: "Power bill up 43% this month — This is the 2nd increase in a row. Want to review your utility spend?" One-tap to a filtered transaction view.
Why it matters: Customers value being told something they didn't notice. This is the moment where the bank earns trust.
4. FX rate opportunity alert¶
Snowflake table: intelligence.fx_signals — fields nzd_aud_rate, rate_vs_3m_percentile, customer_typical_transfer_amount
How it works: Snowflake monitors the live NZD/AUD rate against a 3-month rolling percentile and the customer's own historical transfer rates. When the rate is above the 80th percentile of the last 3 months and the customer typically transfers at this level, the insight fires.
What the customer sees: "Good time to top up your AU account — NZD/AUD is at a 3-month high of 0.9311. Transfer now and get ~1.2% more AUD than last month." One-tap to the FX transfer screen with the current rate pre-loaded.
Why it matters: Customers with dual accounts miss opportunities by checking rates manually. This removes that friction entirely.
5. Payday automation suggestion¶
Snowflake table: intelligence.customer_signals — fields salary_detected, manual_sweep_count, suggested_rule_amount
How it works: Snowflake detects regular large credits matching a salary pattern. If the customer has manually transferred money to savings 2+ times after salary receipt, the automation suggestion fires with the inferred sweep amount.
What the customer sees: "Automate your payday savings — You've manually moved money to savings 3 times after your salary landed. Set a rule: sweep 20% of income to savings automatically." One-tap to the automation rules engine.
Why it matters: Customers who use automation rules have significantly higher retention and deposit growth.
Architecture: Snowflake presentation layer → read API → home screen¶
The home screen has two distinct data sources with different latency characteristics:
- Operational data (balances, account list, recent transactions) — served from Neon via domain APIs. Sub-10ms. Always current.
- Insight signals (idle cash, anomaly, FX opportunity, payday suggestion, credit offer) — served from Snowflake via the read API. 150–400ms on a warm warehouse.
These are separate API calls. The home screen renders operational data immediately and populates insight cards as the Snowflake response arrives — the insight signals are not on the critical render path.
Neon (operational data)
→ domain APIs → home screen (balance, accounts, transactions)
Snowflake presentation tables (PLATFORM.reporting.home_screen_signals)
→ refreshed on signal cadence (see table below)
→ Snowflake read API (Lambda → dedicated XS warehouse)
→ home screen insight cards
Insight signals never pass through Postgres. Postgres holds only the operational fact that a credit decision exists (set by the ADR-036 decision inbox on the customer record). All signal content — amounts, descriptions, recommendations — lives in Snowflake presentation tables and is queried directly.
No external cache layer. The Snowflake presentation table is pre-shaped to the API response payload — one row per customer. The read API runs a trivial indexed point lookup. The Dynamic Table refresh cadence controls freshness; there is no TTL management, no cache invalidation, and no Redis to operate. The dedicated XS warehouse (ADR-038) stays warm to ensure consistent query latency.
The home screen total load time target is ≤2 seconds TTI on 4G mobile (NFR-017). Operational data from Neon loads in <50ms; insight signals from Snowflake arrive within 150–400ms on a warm warehouse — both well within budget.
Presentation table refresh cadences¶
Each signal is a column in PLATFORM.reporting.home_screen_signals, refreshed by the relevant Snowflake Dynamic Table on the cadence below. Staleness is bounded by the refresh cadence — no TTL management required.
| Signal | Refresh cadence | Notes |
|---|---|---|
| Idle cash detection | Nightly | Stale-tolerant; underlying balance changes slowly |
| Pre-approved credit | Nightly (MOD-029) | Decision existence flag in Postgres; offer detail in this table |
| Spend anomaly | Daily + triggered on significant transaction event | EventBridge event triggers an incremental Dynamic Table refresh |
| FX rate opportunity | Every 15 minutes | Presentation table updated on each FX feed refresh |
| Payday automation | Triggered on salary credit event | EventBridge salary event triggers incremental refresh |
Net worth summary¶
The net worth view reads live account balances from Postgres (Tier 1 — these are operational values, not insights). Net worth = sum of all account balances in NZD equivalent. The FX conversion factor for AU balances is taken from the Snowflake FX signal cache (15-minute TTL).
This hybrid is intentional: balance accuracy requires Tier 1 Postgres; FX rate freshness is adequately served by the 15-minute cache.
Customer experience principles¶
Cards are actionable, not informational. Every insight card has a one-tap primary action that takes the customer directly to the relevant screen, pre-populated. The card is not an article — it is a gateway to an action.
Cards are dismissible. Every card has a "Not now" or "Dismiss" option. Dismissed cards do not reappear until the underlying signal changes materially. Dismissal is logged — it feeds the signal confidence model over time.
Cards are honest. The pre-approval shows exactly why it was made. The FX insight shows the data behind the suggestion. Customers should be able to verify every claim.
Cards are not marketing. The intelligence layer is funded by the customer's own data to serve the customer's own interests. Pre-approval only fires when the affordability model says the customer can comfortably afford the repayments. FX alerts only fire when the rate is genuinely better than the customer's historical rate.
Phase 2: AI natural language layer¶
Phase 2 (ADR-009, 3–6 months post-launch) adds a natural language interaction layer on top of the pre-computed intelligence:
- Customer asks: "Why did my spending go up last month?" — AI generates an explanation from the categorised transaction data in Snowflake
- Customer asks: "How much have I saved this year?" — AI queries Snowflake directly and produces a natural language answer
- Back office internal: "Show me customers with credit score above 650 who haven't been offered a loan" — AI generates a Snowflake query and returns results
This is deferred to Phase 2 to keep the initial build focused on pre-computed intelligence rather than generative AI complexity.
Principles alignment¶
| Principle | Assessment | Notes |
|---|---|---|
| AP-001 KISS | ✓ | Pre-computed signals in Snowflake; cache absorbs load; home screen reads cache — no Postgres insight tables |
| AP-005 Customer driven | ✓ | Every card is an action that benefits the customer |
| AP-007 Evolution | ✓ | New signals added as new Snowflake models are built; no API change required |
| AP-008 Real time | ~ | Intelligence is daily batch; FX rate is near-real-time — appropriate for each signal type |
Perspectives¶
| Perspective | Assessment | Notes |
|---|---|---|
| Performance & Scalability | ✓ | Pre-computed; cache absorbs morning burst; ≤200ms achievable on cache hit |
| Strategy | ✓ | Primary competitive differentiator — legacy banks cannot replicate without platform rebuild |
| Usability | ✓ | Actionable cards with one-tap flows; dismissible; honest about data behind the insight |
| Evolution | ✓ | Phase 2 AI layer extensible on same architecture; new Snowflake signals require no Postgres schema changes |
| Capability | ✓ | Delivers BG-003 (intelligent financial assistant) from day one |
See perspectives.md for how to use these evaluation lenses.
Relevant viewpoints¶
- Functional viewpoint — Home screen card set; insight signal map; one-tap action flows
- System viewpoint — Snowflake dynamic tables → Snowflake read API → Redis cache → home screen API
- Information viewpoint —
intelligence.customer_signalsschema;intelligence.fx_signalsschema; cache key design - Operational viewpoint — Signal staleness monitoring; cache hit rate; card dismissal analytics; model performance tracking
See viewpoints.md for guidance on producing these viewpoints.
Signoff record¶
| Date | Name | Role | Status |
|---|---|---|---|
| 2026-04-10 | Ross Millen | CTO | Approved |
| 2026-04-10 | Ross Millen | Head of Architecture | Approved |
| 2026-04-10 | Ross Millen | Head of Data | Approved |
Capabilities¶
| Capability | Description | Relationship |
|---|---|---|
| CAP-006 | Foreign exchange — live rates, rate lock | enabled — FX rate opportunity alert card with one-tap to transfer |
| CAP-021 | Low balance alert | enabled — balance threshold signals surfaced as home screen cards |
| CAP-022 | Unusual transaction alert | enabled — spend anomaly detection signals displayed as actionable cards |
| CAP-063 | Proactive financial insight engine | enabled — five intelligence signals and delivery architecture defined here |
| CAP-064 | Customer automation rules (sweep, round-up, rate alert, safety net) | enabled — payday automation suggestion card links to rules engine |
| CAP-065 | Pre-approval engine (nightly eligibility scoring) | enabled — pre-approval card on home screen; credit offer detail from Snowflake |
| CAP-067 | Idle cash detection | enabled — idle cash signal computed in Snowflake, surfaced via home screen |
Related decisions¶
| ADR | Title | Relationship |
|---|---|---|
| ADR-002 | Snowflake as the analytics and risk compute platform | Snowflake Dynamic Tables are the source for all intelligence signals |
| ADR-009 | Insights and data visualisation approach | ADR-009 establishes the embedded chart and read API pattern this ADR uses |
| ADR-020 | Customer financial automation rules engine | payday automation insight card links to rules engine |
| ADR-036 | Decision result publication — governed Snowflake → Neon write-back contract | credit decision existence flag written to Postgres via decision inbox |
| ADR-038 | Data access tier policy — Snowflake as the reporting and insight layer | all insight signals are Tier 3 — served from Snowflake only |
All ADRs
Compiled 2026-05-22 from source/entities/adrs/ADR-019.yaml