Skip to content
DA DataAcuity by The Geek Network

DataAcuity — BI Pipeline Implementation Runbook

Status: 🔵 DESIGNED — implementation tracking starts at Phase 0 Owner: Tinashe Bhengu Companion docs: DataAcuity_BI_Pipeline.md (architecture/design), DataAcuity_BI_Anonymisation_Standard.md (controls), DataAcuity_BI_Compliance_Map.md (regulator mapping), DataAcuity_Security_Posture.md (security pre-reqs)

The execution-side counterpart to the BI Pipeline design doc. Phase-by-phase: what to build, exact commands, acceptance tests, rollback procedures, monitoring queries.

Security pre-requisite: Security Phases A+B+C+D from DataAcuity_Security_Posture.md MUST be complete before BI Pipeline Phase 0 starts carrying customer data. Phase 0 itself (foundation audit) can happen in parallel.


1. Implementation status — at a glance

Phase Status Started Completed
Security A: Port lockdown ✅ COMPLETE 2026-05-28 2026-05-28
Security B: Traefik + auth 🔵 PENDING
Security C: Backup DR 🔵 PENDING
Security D: IR + alerting 🔵 PENDING
BI Phase 0: Foundation audit 🔵 PENDING
BI Phase 1: Extract framework 🔵 PENDING
BI Phase 2: Anonymisation layer 🔵 PENDING
BI Phase 3: First marts + push-back 🔵 PENDING
BI Phase 4: Source coverage 🔵 PENDING
BI Phase 5: Enrichment 🔵 PENDING
BI Phase 6: BI surfaces 🔵 PENDING
BI Phase 7: ML feature stores 🟢 NOT STARTED (open-ended)

Update this table as work progresses.


2. BI Phase 0 — Foundation audit (1 week)

Goal: Honest accounting of what exists today so subsequent phases build on truth, not assumptions.

Tasks

# Task Owner Verification
0.1 Verify postgres roles + connection strings for data_warehouse, markets_db, superset_db DBA psql -U <role> -d <db> -c "select current_user" succeeds for each documented role
0.2 Document the existing dbt models (5 marts + 2 staging) — owner, source, freshness, current row counts Data engineer New section added to DataAcuity_BI_Pipeline.md §8.4 with concrete numbers
0.3 Set up Grafana "BI Pipeline Health" dashboard scraping existing metrics Ops Dashboard live in Grafana, link added to DataAcuity_BI_Pipeline.md §11.2
0.4 Audit n8n's existing 55 MB sqlite — what workflows are running? Ops Documented list of workflows + decision per workflow (keep / retire / document) added to a new DataAcuity_n8n_Audit.md
0.5 Create compliance DB on .118 (empty, just the schema for §9 below) DBA psql -h 118 -U compliance_admin -d compliance -c "\dn" shows compliance schema
0.6 Set up the named compliance_admin role + dbt_runner role + etl_user role DBA All three roles exist, with permissions per DataAcuity_Security_Posture.md §8.4
0.7 Confirm .105 is an actual read-replica and what user the ETL should use DBA psql -h 105 -U replicator -d auth_db -c "select pg_is_in_recovery()" returns t

Acceptance

  • Updated docs reflect current state accurately
  • Grafana dashboard is live
  • All roles required for Phase 1 exist
  • n8n audit is committed to the repo

Rollback

Phase 0 is read-only. No rollback needed.


3. BI Phase 1 — Extract framework (1 week)

Goal: Reliable, idempotent, restartable extraction from .105 into data_warehouse.raw.* for the 3 revenue-critical source DBs.

Tasks

# Task Owner Verification
1.1 Decide: build custom Python extract, or wire up Airbyte (already at /home/geektrading/airbyte/) Tech lead Decision recorded in this doc + ADR file
1.2 Implement extract for ledger_db.transactions, ledger_db.accounts, ledger_db.holds, ledger_db.reversals Data engineer After job runs, SELECT COUNT(*) FROM raw.ledger_db__transactions matches expected row count from .105
1.3 Implement extract for sdpkt_db.wallets, sdpkt_db.payments, sdpkt_db.holds, sdpkt_db.merchant_settlements Data engineer Same verification pattern
1.4 Implement extract for bidbaas_db.auctions, bidbaas_db.bids, bidbaas_db.winners, bidbaas_db.payouts Data engineer Same verification pattern
1.5 Configure schedule: every 5 min for revenue-critical event tables, hourly for dimension tables Ops Cron / Airbyte schedule shows correct cadence
1.6 Add freshness alert: any source table with event_time > 2× expected interval old triggers warning Ops Alertmanager rule live; verified by stopping extract and confirming alert fires
1.7 Document the extract config (source registry) as a single YAML/JSON the extract reads Data engineer /home/geektrading/etl-config/sources.yml exists with all 3 source DBs

Schema for raw.*

Tables are named raw.{source_db}__{table}. Schema mirrors source exactly except:

  • Add _extracted_at timestamp column
  • Add _extract_batch_id UUID column (so we can correlate rows extracted together)
  • Keep all source columns (no filtering yet — that happens at staging)

Acceptance

  • Every 5 min, fresh rows from the 3 revenue-critical sources land in raw.*
  • Freshness alert fires correctly when extract is paused
  • Extract is idempotent — running it twice on the same window doesn't create duplicates

Rollback

Stop the extract scheduler. Optionally TRUNCATE raw.* for the affected tables. No app impact since this is read-only against .105.


4. BI Phase 2 — Anonymisation layer (1 week)

Goal: Move data from raw.*staging.*intermediate.* with all PII removed at the intermediate.* boundary per DataAcuity_BI_Anonymisation_Standard.md.

Pre-requisites

  • Security Phase A complete (port lockdown) ✅
  • compliance.salts table exists on .118 (Phase 0 task 0.5)
  • A named compliance reviewer is identified

Tasks

# Task Owner Verification
2.1 Implement hash helper functions in Postgres (compliance.hash_user, compliance.hash_device, etc.) DBA SELECT compliance.hash_user('test') returns 64-char hex
2.2 Implement token vault on .118 with tokenise_* functions per Anonymisation Standard §4 DBA + compliance Functions exist; access controls verified
2.3 Write the PII-absence dbt test macros from Anonymisation Standard §7 Data engineer Tests fail on a deliberately-leaky test model
2.4 Write intermediate.int_users_anon joining auth_db.users across sources with deterministic hashing Data engineer Output rows have user_hash (64-char hex), no email/phone/name_* columns
2.5 Write intermediate.int_ledger_transactions_anon Data engineer Same pattern, applied to ledger
2.6 Write intermediate.int_sdpkt_payments_anon Data engineer
2.7 Write intermediate.int_bidbaas_auctions_anon Data engineer
2.8 Compliance review of sample anonymised output (10 rows from each intermediate.* model) Compliance reviewer Sign-off recorded in PR + compliance log
2.9 Wire dbt test suite into nightly run; alert on failure Ops Test failure triggers Alertmanager P1

Acceptance

  • An independent reviewer (not the model author) verifies a sample of intermediate.* rows and confirms no PII leaks
  • dbt test suite passes on every intermediate.* model
  • Salt rotation procedure documented and tested in staging environment

Rollback

If a PII leak is found post-deployment:

  1. Immediately REVOKE ALL ON ALL TABLES IN SCHEMA intermediate FROM PUBLIC and other roles
  2. Drop the leaky model
  3. Fix the model, re-run anonymisation tests, re-deploy
  4. Log the incident in compliance.incident_log

5. BI Phase 3 — First 5 marts + push-back (2 weeks)

Goal: Five highest-value marts built; postgres_fdw push-back wired; BidBaas reads its summary from .104 analytics_db.

Marts to build (in order)

  1. marts.fct_ledger_transactions_anon — every ledger event with hashed user, FX-normalised
  2. marts.fct_bidbaas_auctions — auction × winner-hash × revenue
  3. marts.fct_sdpkt_payments — payments × merchant-hash × FX-normalised
  4. marts.dim_user_360_anon — anonymised user × 30-day rollup across all apps in scope
  5. marts.agg_revenue_daily_app_country — daily revenue rolled up to (app, country, day)

Each mart:

  • Has a description: block stating purpose (POPIA Sec 13 / GDPR Art 5(1)(b))
  • Has meta.owner identifying the named owner
  • Has meta.compliance_class from {operational, ops_analytics, marketing, financial}
  • Has dbt tests: not_null on PK, unique on natural key, k-anonymity ≥ 5 for user-aggregating marts

Push-back tasks

# Task Owner Verification
3.1 Create analytics_db on .104 + replicate to .105 DBA psql -h 104 -d analytics_db succeeds
3.2 Create per-app schemas: analytics_db.bidbaas, analytics_db.tagme, etc. DBA \dn in analytics_db shows expected schemas
3.3 Create per-app read role: bidbaas_app_reader with SELECT on analytics_db.bidbaas.* DBA Role can SELECT, cannot INSERT/UPDATE
3.4 Set up postgres_fdw from data_warehouse on .106 to analytics_db on .104 DBA CREATE FOREIGN TABLE succeeds; can SELECT over FDW
3.5 Implement push-back job for analytics_db.bidbaas.user_summary Data engineer After run, SELECT * FROM analytics_db.bidbaas.user_summary LIMIT 5 returns expected rows
3.6 BidBaas API consumes the summary view App developer BidBaas in-app shows "your bids this week" sourced from analytics_db
3.7 Document the push-back pattern for the next 5 apps to follow Data engineer New section in DataAcuity_BI_Pipeline.md §9

Acceptance

  • BidBaas in-app shows "your bids this week" with data sourced from analytics_db
  • End-to-end latency (event captured → visible in app) < 1 hour
  • No PII leaks past the intermediate.* boundary (verified by dbt tests)
  • Push-back job is idempotent (re-running doesn't duplicate or corrupt)

Rollback

If the BidBaas integration breaks:

  1. App falls back to direct query against bidbaas_db (the existing path) — verify this fallback works before shipping
  2. Drop the FDW connection
  3. Diagnose, fix, re-deploy

6. BI Phase 4 — Coverage expansion (2 weeks)

Goal: All source DBs in scope are being extracted, anonymised, and the per-app analytics.*_user_summary views are live.

Tasks

# Task Owner Verification
4.1 Add 18 source DBs to extract registry per DataAcuity_BI_Pipeline.md §5 Data engineer All 18 sources have rows in raw.* within 24 h
4.2 Write intermediate.* models for each source Data engineer All have PII-absence tests passing
4.3 Build the remaining marts listed in DataAcuity_BI_Pipeline.md §8.4 Data engineer All marts populated with current data
4.4 Wire per-app push-back: TagMe, TxTMe!, Takemehome, WhatWeWant, SDPKT, SleptOn, etc. Data engineer + app teams Each app's analytics tab shows real data
4.5 Compliance review of each new intermediate.* model Compliance reviewer Sign-off recorded per model

Acceptance

  • Every TGN app with an analytics surface is reading from analytics_db
  • Source registry is exhaustive (no app data missing)
  • Per-app team has signed off on their summary's shape

7. BI Phase 5 — Enrichment expansion (1 week)

Goal: Geographic, temporal, FX, and cross-app context attached to relevant marts.

Tasks

# Task Owner Verification
5.1 Geo enrichment: integrate geo_mcp.reverse_geocode calls into dbt transformation for any location-bearing fact Data engineer Sample row has city, country, nearest_poi_category populated
5.2 Temporal enrichment: add day_of_week, hour_of_day, is_weekend, is_payday_week, is_public_holiday, season to all timestamped facts Data engineer Sample row has all 6 columns populated
5.3 FX enrichment: ingest daily ZAR/USD/EUR/GBP/BTC into dim_fx_rates_daily from exchangerate.host (until Markets API repaired) Data engineer Daily rows present; sample monetary fact has amount_zar AND amount_usd AND amount_btc_sats
5.4 User segment enrichment: KYC tier, lifecycle stage, mesh node class Data engineer Sample user has all 3 columns populated
5.5 Cross-app session derivation: cross_app_session_id via 30-min sliding window Data engineer A test user with events in 2 apps within 30 min shares a session_id
5.6 Build marts.agg_funnel_app_to_app Data engineer Shows real funnel rates between app pairs

Acceptance

  • marts.fct_* tables have enriched columns populated
  • A test "user journey" trace across 3+ apps shows correct session linking
  • FX conversion is correct (spot check: 100 ZAR transaction shows ~5.5 USD at current rate)

8. BI Phase 6 — BI surfaces (1-2 weeks)

Goal: BigBruh! Analytics tab is live with Superset embeds; per-app dashboards published.

Tasks

# Task Owner Verification
6.1 Build BigBruh! "Analytics" tab — Superset embed via SSO token Frontend + ops Tab loads dashboards; user identity flows through
6.2 Build per-app Superset dashboards: TagMe, BidBaas, SDPKT, Takemehome, WhatWeWant, TxTMe!, SleptOn BI author Each dashboard is published, has owner, has refresh schedule
6.3 Public-facing aggregate dashboards (if approved in open question §13 of BI Pipeline doc) BI author If yes, lives at dataacuity_portal
6.4 Retire BigBruh!'s "live counters only" view Frontend View removed from navigation

Acceptance

  • BigBruh!'s Overview includes both live ops counters AND historical analytics
  • Each per-app dashboard has a named owner and a refresh schedule
  • Performance: dashboards load < 3 s for typical queries

9. BI Phase 7 — ML feature stores (open-ended)

Goal: First two ML feature stores defined, tested for leakage, wired to a training pipeline.

Will be specced out when an ML team is in place. For now:

  • ml.* schema reserved
  • Naming convention: ml.feat_<entity>_v<N>
  • Strict time-aligned features (no future-bleed)
  • Versioned tables (never DROP TABLE; add v2, v3 alongside v1)

10. Monitoring queries

Useful one-liners for the on-call to run.

10.1 Extract freshness

-- Show last extract per source table
SELECT table_name, max(_extracted_at) AS last_extract_at, now() - max(_extracted_at) AS lag
FROM (
  SELECT 'ledger_db__transactions' AS table_name, _extracted_at FROM raw.ledger_db__transactions
  UNION ALL
  SELECT 'sdpkt_db__payments', _extracted_at FROM raw.sdpkt_db__payments
  -- ... etc
) t
GROUP BY table_name
ORDER BY lag DESC;

10.2 dbt test failure summary

-- After a dbt run, what's failing?
SELECT model_name, test_name, status, num_failing_rows
FROM dbt_artifacts.test_results
WHERE run_id = (SELECT max(run_id) FROM dbt_artifacts.test_results)
  AND status != 'pass'
ORDER BY num_failing_rows DESC;

10.3 K-anonymity health check (ad-hoc)

-- For a given mart, find groups below k=5
SELECT city, age_band, count(DISTINCT user_hash) AS n
FROM marts.fct_some_user_event
GROUP BY city, age_band
HAVING count(DISTINCT user_hash) < 5
ORDER BY n;

10.4 Push-back lag

-- On .104 analytics_db
SELECT 'bidbaas.user_summary' AS view, max(last_updated_at) AS last_push, now() - max(last_updated_at) AS lag
FROM bidbaas.user_summary
UNION ALL
SELECT 'tagme.user_summary', max(last_updated_at), now() - max(last_updated_at) FROM tagme.user_summary
-- ... etc
ORDER BY lag DESC;

10.5 Token vault access in last 24h

-- On .118 compliance_db
SELECT accessed_by, count(*) AS access_count, array_agg(DISTINCT token_family) AS families
FROM compliance.token_access_audit
WHERE accessed_at > now() - interval '24 hours'
GROUP BY accessed_by;

11. Deploy procedures

11.1 Deploying a new dbt model

  1. PR includes: model SQL, description: block, meta.owner and meta.compliance_class, dbt tests, sample output (10 rows of post-anonymisation result)
  2. CI runs dbt build --select <new_model>+ on staging warehouse
  3. Compliance reviewer signs off if it touches intermediate.* or any user data
  4. Merge → next dbt run on .106 picks up the new model
  5. Verify: query the new model, check row count + sample output

11.2 Deploying a new push-back view

  1. Create the marts.agg_* source mart first
  2. Create the analytics.{app}_* view definition
  3. Run the push-back job manually once: docker exec dbt_transform dbt run --select <push_back_model>
  4. Verify psql -h 104 analytics_db shows the new view
  5. App team queries the view, integrates into UI
  6. Schedule push-back in cron

11.3 Salt rotation (annual)

  1. Schedule a change window (low traffic period)
  2. Generate new salt: openssl rand -base64 32 > /tmp/new_salt.txt
  3. Update compliance.salts SET is_retired = true, then INSERT new row
  4. Update orchestrator's env var with new salt
  5. Run full intermediate.* rebuild (will use new salt)
  6. Cascade through marts.*, analytics.*
  7. Verify analytics still work (joins succeed)
  8. Document rotation in compliance.salts audit columns

11.4 Right-to-be-forgotten request

  1. Request lands at AuthAPI → users.deleted_at set on .104
  2. Within 24 h: extract job picks up the soft-delete
  3. Within 7 days: retention job cascades deletes through all warehouse layers
  4. Within 30 days: compliance ticket closed with compliance.deletion_completion row + email confirmation to user

Manual escalation if SLA at risk: data engineer runs retention job ad-hoc.

12. Cross-references

  • DataAcuity_BI_Pipeline.md — the design
  • DataAcuity_BI_Anonymisation_Standard.md — the controls
  • DataAcuity_BI_Compliance_Map.md — the regulator mapping
  • DataAcuity_Security_Posture.md — security pre-reqs
  • Deployment/deployment-credentials.ps1replicator credentials for ETL

13. Change log

Date Change By
2026-05-28 Initial document — execution runbook split out from BI Pipeline design Tinashe Bhengu
2026-05-28 Phase A (Security port lockdown) executed Tinashe Bhengu
Something went wrong on this page. Reload