Product offer engine¶
| ID | MOD-108 |
| System | SD08 |
| Repo | bank-app |
| Build status | Not started |
| Deployed | No |
What it does¶
MOD-108 generates bank-initiated personalised product offers. It operates on the eligible and NBP-ranked product set and derives specific offer terms (interest rate, credit limit, fee structure, promotional bonus) personalised to each customer. Offers are stored with full lifecycle tracking: GENERATED → PRESENTED → ACCEPTED / REJECTED / EXPIRED.
Distinguish clearly from MOD-109 (product deal engine): MOD-108 is systemic and bank-initiated — offers are generated by the engine without a triggering agent interaction. MOD-109 is agent-initiated — a specific deal is proposed by an agent during a customer interaction and requires authorisation.
Why it exists¶
Personalised offers convert at significantly higher rates than generic rate-card offers. The commercial case (BG-003, BG-005) is conversion improvement through personalisation. The compliance case is that every offer must be traceable — what was offered, to whom, on what basis, and what happened to it — satisfying CON-004 and CON-006.
Offer derivation¶
| Input | Source | Effect on offer terms |
|---|---|---|
| NBP rank | MOD-107 | Rank 1 product generates the primary offer; ranks 2–3 generate secondary offers |
| Customer ROTE | MOD-106 | High-ROTE customers eligible for premium rates; low-ROTE customers offered standard terms |
| Behavioural consent | MOD-049 | If marketing consent present: personalised rate. If not: standard rate-card offer only |
| Pre-approval limit | MOD-029 | For credit products: offer limit derived from pre-approval result; never exceeds affordability max |
| Product rate card | Product configuration | Floor and ceiling for offer rates; offer cannot exceed ceiling or fall below floor |
Financial advice licensing gate¶
Before generating an offer for any product flagged requires_advice_review = true in the product register, MOD-108 checks that an authorised adviser has reviewed the customer profile within the past 12 months (read from product_eligibility.advice_reviews). If no review exists, the offer is suppressed and an offer.advice_review_required event is raised for the back-office queue. Currently no products in the bank's register require this gate, but the gate is built and active — it will be triggered when investment or KiwiSaver distribution products are added.
Offer lifecycle¶
GENERATED
→ PRESENTED (displayed in app or surfaced to agent)
→ ACCEPTED (customer accepted offer terms; triggers application workflow)
→ REJECTED (customer declined)
→ EXPIRED (offer validity period elapsed — default 30 days for credit; 14 days for rate offers)
→ SUPPRESSED (advice review required; or eligibility re-evaluation failed before presentation)
Data model¶
-- app.product_offers (Postgres — bank_app)
CREATE TABLE app.product_offers (
offer_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
party_id uuid NOT NULL,
product_id text NOT NULL,
jurisdiction text NOT NULL CHECK (jurisdiction IN ('NZ','AU')),
offer_terms jsonb NOT NULL, -- rate, limit, fee waiver, promotional bonus
derivation_basis jsonb NOT NULL, -- snapshot of inputs used to derive terms
status text NOT NULL CHECK (status IN ('GENERATED','PRESENTED','ACCEPTED','REJECTED','EXPIRED','SUPPRESSED')),
nbp_rank int,
offer_type text NOT NULL CHECK (offer_type IN ('RATE','LIMIT','FEE_WAIVER','PROMOTIONAL')),
valid_from timestamptz NOT NULL DEFAULT now(),
valid_to timestamptz NOT NULL,
presented_at timestamptz,
responded_at timestamptz,
response text, -- ACCEPTED / REJECTED / null
created_at timestamptz NOT NULL DEFAULT now()
-- append-only: no UPDATE/DELETE; status changes are new rows via offer_events
);
-- app.offer_events (append-only lifecycle log)
CREATE TABLE app.offer_events (
event_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
offer_id uuid NOT NULL REFERENCES app.product_offers(offer_id),
event_type text NOT NULL, -- GENERATED, PRESENTED, ACCEPTED, REJECTED, EXPIRED, SUPPRESSED
actor text NOT NULL, -- 'system' or staff_id
event_at timestamptz NOT NULL DEFAULT now(),
detail jsonb
);
Events¶
product.offer_presented— on every app presentation; carriesparty_id,product_id,offer_id,jurisdiction.product.offer_accepted— triggers application or account-open workflow in the relevant system domain.product.offer_expired— nightly sweep; triggers NBP re-evaluation for the customer.
Module dependencies¶
Depends on¶
| Module | Title | Required? | Contract | Reason |
|---|---|---|---|---|
| MOD-105 | Product eligibility engine | Required | — | Eligibility gate — MOD-108 cannot generate an offer for a product that MOD-105 has not confirmed eligible. |
| MOD-107 | Next best product engine | Required | — | NBP rankings from MOD-107 determine which eligible products progress to offer generation. |
| MOD-049 | Open banking consent management | Required | — | Consent record for data-driven marketing must be present before behavioural offer terms are derived. |
| MOD-104 | AWS shared infrastructure bootstrap | Required | — | AWS shared infrastructure provisioned by MOD-104 is required before this module can be deployed. |
| MOD-103 | Neon database platform bootstrap | Required | — | Neon database provisioned by MOD-103 must exist before this module can read or write Postgres. |
Required by¶
(No modules in this wiki currently declare a dependency on this module.)
Policies satisfied¶
| Policy | Title | Mode | How |
|---|---|---|---|
| CON-006 | Product suitability and governance | GATE |
No offer is generated for a product that is not in the customer's eligible set from MOD-105 — eligibility check is a hard pre-condition for offer generation. |
| CON-004 | Product Disclosure & Sales Practice Policy | LOG |
Every generated offer is logged with its terms, derivation basis, and lifecycle events (presented, accepted, rejected, expired) in an immutable offer audit trail. |
| CON-001 | Customer Fairness & Conduct Policy | AUTO |
Offer generation rate and acceptance outcomes are monitored by MOD-107 fairness reporting — systematic disparities trigger a compliance alert. |
| PRI-001 | Privacy Policy | GATE |
Behavioural personalisation of offer terms requires the customer's active consent record for data-driven marketing — no behavioural offer is generated without a valid consent. |
Capabilities satisfied¶
(No capabilities mapped)
Part of SD08 — Customer App & Back Office Platform
Compiled 2026-05-22 from source/entities/modules/MOD-108.yaml