Permission-Based Relevance Engine

How it works.

Six flows that replace surveillance with consent — exactly as implemented in the MiFamilias backend, mobile app, and admin portal. Every diagram below maps to real handlers, entities, and routes.

The Moment You've been posting about your dog. Want relevant offers — for how long?
1 week 1 month 3 months 1 year
01The Opt-In Journey

From signal to switched-on — with the user in charge

One end-to-end pass through the system: a detected moment becomes a question, a question becomes a time-boxed window, and the window expires on its own.

flowchart LR
    A(["📱 User posts about
their dog (3rd time
in 30 days)"]) --> B{{"🔔 Just-in-time prompt
"We noticed you're sharing
a lot about your pet""}} B -->|"Yes — ☑ vets ☑ supplies
⏱ 1 month"| C["🟢 Interest windows open
AD_INTEREST#pets"] B -->|"No thanks"| D["🚫 Dismissed
nothing stored,
nothing inferred"] B -->|"Maybe later"| E["😴 Snoozed 7 days
then asked once more"] C --> F["📺 Relevant ads served
labeled 'Because you asked'"] F --> G{"User feedback
on every ad"} G -->|"👍 Relevant"| F G -->|"👎 Not relevant"| F G -->|"🔕 Stop these"| H["⛔ Window ends early
ad hidden forever"] C -.->|"⏰ time's up"| I["🌙 Auto-expires —
silence unless renewed"] style A fill:#14262a,stroke:#34d399,color:#e8f4f0 style B fill:#0e3b2e,stroke:#4ade80,stroke-width:2px,color:#e8f4f0 style C fill:#0e3b2e,stroke:#34d399,color:#e8f4f0 style D fill:#14262a,stroke:#8aa6a0,color:#8aa6a0 style E fill:#14262a,stroke:#8aa6a0,color:#8aa6a0 style F fill:#14262a,stroke:#34d399,color:#e8f4f0 style G fill:#14262a,stroke:#34d399,color:#e8f4f0 style H fill:#2a1416,stroke:#f87171,color:#fecaca style I fill:#14262a,stroke:#8aa6a0,color:#8aa6a0

Saying no is free: dismissed prompts store no preference, build no profile, and trigger a 60-day cooldown before that topic can ever be asked about again.

02Signal Detection

Detection creates a question — never an ad

Hooked into createPost (fire-and-forget). Five guards stand between a topic mention and a prompt.

flowchart TD
    P["📝 New post text"] --> K{"Topic keywords?
pets · baby · wedding
home · travel · kids' sports"} K -->|no| X1(["stop — nothing happens"]) K -->|yes| G1{"Ads enabled
for this user?"} G1 -->|no| X2(["stop"]) G1 -->|yes| G2{"Guardian requires
approval for opt-ins?"} G2 -->|"yes (protected minor)"| X3(["stop — never prompt"]) G2 -->|no| G3{"Pending or snoozed
prompt already open?"} G3 -->|yes| X4(["stop — one question
at a time"]) G3 -->|no| G4{"Active window for
this category?"} G4 -->|yes| X5(["stop — already opted in"]) G4 -->|no| G5{"Prompted about this
topic in last 60 days?"} G5 -->|yes| X6(["stop — cooldown"]) G5 -->|no| CNT["AD_SIGNAL counter +1
(rolling 30-day window)"] CNT --> TH{"3rd mention?"} TH -->|no| W(["wait for more signal"]) TH -->|yes| PR["🔔 Create InterestPrompt
with plain-language reason
· counter discarded ·"] style P fill:#14262a,stroke:#34d399,color:#e8f4f0 style PR fill:#0e3b2e,stroke:#4ade80,stroke-width:2px,color:#e8f4f0 style CNT fill:#14262a,stroke:#34d399,color:#e8f4f0 style K fill:#14262a,stroke:#34d399,color:#e8f4f0 style G1 fill:#14262a,stroke:#34d399,color:#e8f4f0 style G2 fill:#14262a,stroke:#34d399,color:#e8f4f0 style G3 fill:#14262a,stroke:#34d399,color:#e8f4f0 style G4 fill:#14262a,stroke:#34d399,color:#e8f4f0 style G5 fill:#14262a,stroke:#34d399,color:#e8f4f0 style TH fill:#14262a,stroke:#34d399,color:#e8f4f0 style X1 fill:#0e1b1e,stroke:#8aa6a0,color:#8aa6a0 style X2 fill:#0e1b1e,stroke:#8aa6a0,color:#8aa6a0 style X3 fill:#0e1b1e,stroke:#8aa6a0,color:#8aa6a0 style X4 fill:#0e1b1e,stroke:#8aa6a0,color:#8aa6a0 style X5 fill:#0e1b1e,stroke:#8aa6a0,color:#8aa6a0 style X6 fill:#0e1b1e,stroke:#8aa6a0,color:#8aa6a0 style W fill:#0e1b1e,stroke:#8aa6a0,color:#8aa6a0

Once a prompt is created the topic counter resets to zero — no profile accumulates beyond the rolling 30-day count. Service: interestSignalService.ts

03Window Lifecycle

Every opt-in carries its own off-switch

An InterestWindow is keyed USER#id / AD_INTEREST#category and lazily expired at read time — no cron job needed.

stateDiagram-v2
    direction LR
    [*] --> Active : opt-in
(prompt · manual · guardian)
1w / 1mo / 3mo / 1yr Active --> Active : 👍 relevant / 👎 not relevant
(feedback counters) Active --> OptedOut : 🔕 "Stop these" on an ad
or End in Settings Active --> Expired : ⏰ expiresAt passes
(lazy expiry at read) Expired --> Active : 🔁 renew (new opt-in) OptedOut --> Active : 🔁 user re-opts in Expired --> [*] OptedOut --> [*]

Routes: GET/POST /ads/interests · DELETE /ads/interests/:category — ended windows are kept for the user's own history, never for profiling.

04Ad Serving

Explicit opt-in always wins — guardian rules always win more

The serving order inside GET /ads/active: guardian restrictions first, then opt-outs, then the user's declared interests outrank everything.

sequenceDiagram
    autonumber
    participant App as 📱 Mobile App
    participant API as ⚙️ getActiveAds
    participant DB as 🗄️ DynamoDB

    App->>API: GET /ads/active?placement=feed
    API->>DB: subscription tier?
    alt premium subscriber
        API-->>App: no ads, ever 🏆
    else free tier
        API->>DB: active InterestWindows (lazy-expire)
        API->>DB: AdPreferences (hidden ads, guardian rules)
        API->>DB: active campaigns (GSI3 STATUS#active)
        Note over API: 🛡️ drop guardian-restricted categories
(server-side, cannot be overridden) Note over API: 🙈 drop ads the user opted out of Note over API: ⭐ interest categories bypass targeting
scores AND client excludes — ranked first API-->>App: ads — interest matches labeled
“Because you asked” App->>API: POST /ads/:id/events (impression) end

Smoke-verified: a user with no pets on their profile who opts into the pets category still receives pets ads — declared intent beats inferred targeting.

05The Feedback Loop

Every ad answers to the user

Three controls on every served ad feed three different systems — and feedback events cost the advertiser nothing.

flowchart LR
    AD["📺 Served ad"] --> R["👍 Relevant"]
    AD --> NR["👎 Not relevant"]
    AD --> OO["🔕 Stop these"]

    R --> C1["Ad relevantCount +1
Window relevantCount +1"] NR --> C2["Ad notRelevantCount +1
Window notRelevantCount +1"] OO --> C3["⛔ Window → opted_out
adId → hiddenAdIds
(never served again)"] C1 --> IQ[("📊 IntentStats
STATS / INTENT")] C2 --> IQ C3 --> IQ IQ --> DASH["Admin · Intent Quality tab
relevance scores per campaign"] style AD fill:#14262a,stroke:#34d399,color:#e8f4f0 style R fill:#0e3b2e,stroke:#4ade80,color:#e8f4f0 style NR fill:#14262a,stroke:#fbbf24,color:#fde68a style OO fill:#2a1416,stroke:#f87171,color:#fecaca style C1 fill:#14262a,stroke:#34d399,color:#e8f4f0 style C2 fill:#14262a,stroke:#34d399,color:#e8f4f0 style C3 fill:#14262a,stroke:#f87171,color:#e8f4f0 style IQ fill:#0e3b2e,stroke:#4ade80,stroke-width:2px,color:#e8f4f0 style DASH fill:#14262a,stroke:#34d399,color:#e8f4f0

Extended event types on POST /ads/:adId/events: relevant · not_relevant · opt_out (alongside impression/click).

06Family Governance

Families decide, not algorithms

A verified guardian sets the rules for a minor once; the backend enforces them in every path — serving, prompting, and opting in.

flowchart TD
    G["👨‍👧 Verified guardian"] -->|"PUT /ads/preferences/
guardian/:minorUserId"| RULES["🛡️ GuardianAdControls
✅ approved · ⛔ restricted
· require approval flag ·"] RULES --> E1["Ad serving
restricted categories
never reach the minor"] RULES --> E2["Signal detection
protected minors are
never prompted"] RULES --> E3["Opt-in handlers
restricted category →
403 Forbidden"] M["🧒 Minor tries to opt in
to a restricted category"] --> E3 E3 --> DENY(["⛔ Blocked server-side —
no client can override"]) style G fill:#14262a,stroke:#34d399,color:#e8f4f0 style RULES fill:#0e3b2e,stroke:#4ade80,stroke-width:2px,color:#e8f4f0 style E1 fill:#14262a,stroke:#34d399,color:#e8f4f0 style E2 fill:#14262a,stroke:#34d399,color:#e8f4f0 style E3 fill:#14262a,stroke:#34d399,color:#e8f4f0 style M fill:#14262a,stroke:#fbbf24,color:#fde68a style DENY fill:#2a1416,stroke:#f87171,color:#fecaca

Guardianship is verified via family membership + adult check (verifyGuardianRelationship), the same trust machinery used by child-safety consent.

New North-Star Metric Intent Quality — not how many people saw the ad, but how many asked to see it. Live at GET /admin/marketing/intent.