Interview & Self-Check Question Bank¶
A bank of interview and self-check questions for uEngage Prism (the F&B CRM:
uengage-crmmonolith +prism-servicesserverless). Questions mix conceptual ("why is it built this way?") with code-specific ("which cron drains which queue?"). Every question has an Answer you can reveal.Use this for: interviewing candidates, onboarding self-tests, or as a companion to the New-Joiner Learning Path. For graded, level-structured exams see 15-Assessments.
How to use this bank¶
- Interviewers: pick 2–3 conceptual + 2–3 code-specific per area. Follow-ups matter more than the first answer.
- New joiners: cover the answer, attempt aloud, then reveal. Track which areas you miss and re-read the linked KB page.
- Difficulty tags: 🟢 foundational · 🟡 working knowledge · 🔴 deep/senior.
Index¶
| Area | Focus |
|---|---|
| 1. Product & Business | What Prism does, revenue model, RFM, journeys |
| 2. Architecture | Monolith vs serverless, Kafka, crm_queue seam, dual stores |
| 3. Data & Datastores | MongoDB/MySQL split, keys, indexes, tech debt |
| 4. Queues & Crons | Queue collections, cron catalog, overlap protection |
| 5. Security & Auth | 6 middlewares, hardcoded secrets, CORS |
| 6. Per-Module | Order Ingestion, CRM, Campaigns, Loyalty, RFM, WhatsApp, Dashboard, Feedback |
| 7. Rapid-fire code-specific | Name-the-file / name-the-cron drills |
1. Product & Business¶
Q1.1 🟢 In one sentence, what does Prism do and who buys it?
Answer
Prism is uEngage's B2B SaaS **restaurant/F&B customer-engagement & retention platform**, sold to restaurant *brands* (the client); the brand's diners are the *customers/guests* whose data it manages. It ingests every order/customer across channels, builds Customer 360 / Order 360, segments with RFM, and drives repeat business via loyalty, memberships, referrals, journeys and campaigns. See [Product-Overview](../01-Business/Product-Overview.md).Q1.2 🟢 Name the three layers of the Prism value proposition.
Answer
(1) **Ingest & Unify** — pull every order+customer into one profile; (2) **Understand** — Customer 360, Order 360, RFM segments, 37 filters; (3) **Act & Retain** — campaigns, journeys, loyalty, membership, referral, feedback. Motto: *"filter to action in seconds."*Q1.3 🟡 What are Prism's revenue streams?
Answer
SaaS subscription (Basic Free / Premium ₹12,000/yr / Business custom), Digital Bills (per-bill), Feedback comms, WhatsApp (WABA setup fee + per-message markup), SMS (per-SMS). See [Product-Overview §2](../01-Business/Product-Overview.md).Q1.4 🟡 Explain RFM. What does "relative to the brand's own base" mean, and what score does a never-ordered customer get?
Answer
RFM = **R**ecency, **F**requency, **M**onetary, each scored 1–5 **by quintile of that brand's own customer base** (not an absolute threshold) — top 20% most-recent get R=5, etc. A never-ordered customer is **R=1**. `FM = Ceil((F+M)/2)` collapses the grid; an R×FM lookup assigns one of **11 segment labels**. See [Terminologies](../01-Business/Terminologies.md).Q1.5 🔴 Name at least 5 of the 8 automated journeys and the general shape a journey follows.
Answer
Shape: **EVENT → CONDITION → ACTION**. The 8: Welcome (sign-up), Birthday, Anniversary, Wallet-points-near-expiry, Lost Customers (no order for X days), Abandoned Cart, Low-Rating Rewards, Referral Boost. On activation up to **10,000** past customers are backtracked. See [Business-Flow §4](../01-Business/Business-Flow.md).Q1.6 🟡 What are the cross-widget data-integrity rules that must always hold, and why do they matter?
Answer
`Total Customers = Transacting + Non-Transacting`; `Total Orders = Delivery + Pick-Up + Dine-In`; `Total Loyalty Points = Used + Available + Expired` (never negative); revenue-by-channel sums to 100%; `Unique Customers ≤ Redemptions`. They are the **P0 acceptance bar** for any dashboard work (Data Validation Protocol A4). Known violations are the "E" bugs (e.g. E1.10). See [Roadmap §E](../01-Business/Roadmap.md).Q1.7 🔴 What happens to a customer's unredeemed points on tier downgrade, and why is that worth knowing?
Answer
On downgrade, **all unredeemed points are immediately forfeited** (Loyalty rule 2.3, strict). It produces "the system stole my points" tickets and interacts with the E1.10 points-conservation bug. Downgrade runs nightly via `crons/loyalty_milestone_downgrade.js`. See [Business-Flow](../01-Business/Business-Flow.md).2. Architecture¶
Q2.1 🟢 Prism is "two deployables, one database." Name them and what each does.
Answer
**uengage-crm** — long-running Express monolith (PM2): APIs, dashboards, campaigns, loyalty, and 91 in-process crons. **prism-services** — AWS Lambda + Kafka (MSK) serverless tier absorbing high-volume POS ingestion. Both write to the same MongoDB `uengage` DB; the **`crm_queue` collection is the seam** between them. See [System-Overview](../00-Getting-Started/System-Overview.md).Q2.2 🟡 Why was the serverless tier added, and what does Kafka buy you?
Answer
Prism began as a single monolith doing everything. As order volume grew, a serverless ingestion tier was placed in front to **absorb spiky, high-throughput POS traffic without risking the monolith**. Kafka (MSK topic `prism-ingest-orders`) **decouples** the burst of incoming orders from slower downstream processing. The system is deliberately **mid-migration**. See [High-Level-Architecture §2](../02-Architecture/High-Level-Architecture.md).Q2.3 🟡 What is the "dual-writer" problem on crm_queue?
Answer
Two independent code paths write to `crm_queue`: the `orders-consumer` Lambda **and** the monolith's own ingestion controllers (`uengageOrderIngestion.js`, `petpoojaOrderIngestion.js`, etc.). There is no dedup/idempotency between them → risk of **duplicate orders and races**; unclear source-of-truth. See [High-Level-Architecture §9](../02-Architecture/High-Level-Architecture.md) and map-05.Q2.4 🟡 Name the 3 Kafka topics and their consumers. Which are stubbed?
Answer
`prism-ingest-orders` → `orders-consumer` λ (writes `crm_queue`, batch 10). `whatsapp-messages` → `consumer-send` λ (**stubbed, log-only**). `whatsapp-receipts` → `consumer-receipts` λ (**stubbed, log-only**). The monolith's own WhatsApp crons currently do the real send/receipt work; reconciling this is a roadmap item. See [High-Level-Architecture §4](../02-Architecture/High-Level-Architecture.md).Q2.5 🔴 Data plane vs control plane: walk through the five planes.
Answer
**Ingestion** (orders/customers → `crm_queue`, both Lambda + monolith); **Processing** (drain queue → Customer 360/Order data, points, journeys — monolith crons + commonFunctions); **Intelligence** (RFM, segments, MIS — crons + processLibrary); **Activation** (campaigns/journeys → WhatsApp/SMS/Push — channel queues + crons); **Presentation** (dashboards, reports, `/stats`). See [High-Level-Architecture §3](../02-Architecture/High-Level-Architecture.md).Q2.6 🔴 Why do "two dashboards" and "three campaign pipelines" both exist? What does that tell you?
Answer
It's the visible surface of an in-progress Next.js migration. Dashboards: `dashboardController.js` (legacy) + `dashboardControllerNew.js` (unified, Redis-cached, Socket.io). Campaigns: `campaignController.js` (traditional SMS/FCM/Email/WhatsApp) + `campaignControllerDynamic.js` (custom-code) + `automatedCampaignController.js` (journeys). Lesson: when in doubt, check which pipeline a feature actually routes through before editing. See map-02 and [Module-Architecture](../02-Architecture/Module-Architecture.md).Q2.7 🔴 Where are the non-atomic points in the system, and what failure mode do they create?
Answer
Loyalty points span **MySQL** (`wallet`, `wallet_history`) and **MongoDB** (`orders`, `customers`, `wallet_transactions`) with **no distributed transaction**. A crash mid-flow can update the wallet but not mark the order, or vice-versa → **balance drift** (the E1.10 2-point discrepancy class of bug). Mitigation is idempotency (`order.points_allocated` flag) + a reconciliation job, not true atomicity. See map-04 §5.7.Q2.8 🟡 Why do crons "in the web process" matter operationally?
Answer
All **91 crons are `require()`d at startup in `index.js`** and run inside the same Node process as the web server, so a heavy cron can **couple cron load to API latency**. That's precisely why MySQL has two pools — `webPool` (15) for requests and `jobsPool` (10) for crons — so background jobs never starve web traffic. See [System-Overview §3](../00-Getting-Started/System-Overview.md).3. Data & Datastores¶
Q3.1 🟢 What lives in MongoDB vs MySQL? Give the rule of thumb.
Answer
**MongoDB `uengage`** (maxPool 400, SECONDARY_PREFERRED): high-write queues (`crm_queue`, ingestion dumps), campaign MIS, journeys, ratings/NPS, dashboard MIS, socket chat, config. **MySQL `addo_*`** (webPool 15 + jobsPool 10): transactional/joined data — orders, order items, users/roles/auth, customers, `wallet`/`wallet_history`/`wallet_rules`, `crm_points_allocation_queue`, promo engine, menus. Rule of thumb: **Mongo = fast-changing/analytical buffers, MySQL = money & identity.** See [High-Level-Architecture §5](../02-Architecture/High-Level-Architecture.md).Q3.2 🟢 What is the universal customer join key, and the tenant scope?
Answer
Customer key = the **10-digit Indian mobile number** (no country code) — the join key across MongoDB *and* MySQL. Tenant scope = **`parent_business_id` (brand/HQ) + `child_business_id` (outlet)** — nearly every query filters on these. See [System-Overview §6](../00-Getting-Started/System-Overview.md).Q3.3 🟡 Trace the linking chain from a mobile number to a wallet balance.
Answer
`mobile → addo_contacts.mobileNo → addo_contacts_mapping.contactId → wallet.contactMappingId → loyalty/transaction tables`. Business isolation via `parent_business_id`/`child_business_id`. See map-03.Q3.4 🔴 Name two concrete indexing gaps and the query each would speed up.
Answer
MySQL: `wallet (parentBusinessId, validTillDate, id)` — speeds **campaign audience/segment extraction** (`WHERE parentBusinessId=? AND validTillDate>=TODAY()`); `addo_orders (businessId, orderDate)` — speeds **daily aggregations**. MongoDB is missing indexes on `parent_business_id`/`business_id` (every aggregation) and `created_at` (MIS time-range). See map-03 "Indexing Gaps".Q3.5 🔴 Give two data-consistency tech-debt items in the data layer.
Answer
(1) **No TTL cleanup** on `crm_queue` and `*_dump` collections → unbounded growth. (2) **Business-ID type inconsistency** — sometimes String, sometimes Number across MongoDB, which breaks naive equality filters. Also: soft-deletes only (no `deleted_at`), undocumented denormalized `mobileNo` sync. See map-03 "Tech Debt Notes".4. Queues & Crons¶
Q4.1 🟢 Name the four channel/work queues and their status fields.
Answer
SMS `sms_queue_YYYYMMDD` (`processed` bool); Push `push_noti_queue_YYYYMMDD` (`processed` bool); WhatsApp `whatsapp_queue_YYYYMMDD` (`process_status` int: 0 pending, 2 picked, 3 sent/pending-webhook, 4 skipped); Points `crm_points_allocation_queue` (MySQL, `status` string: pending/done/failed). The inconsistent status conventions are themselves tech debt. See [Queues](../06-Queues/README.md), map-04 §4.1.Q4.2 🟡 Which cron drains crm_points_allocation_queue, how often, and what does it do on failure?
Answer
`crmPointsAllocationQueueCron.js`, **every 2 min**, batch ~5000 (`status='pending'`). Types: `uen_order`, `order_reward`, `welcome_point`, `refer_earn`. Calls `handleWalletPointsFlow` or `helpers.handleOrderRewards`. On failure it **retries max 5×** with ~15-min backoff (`next_retry_at`). See map-04 §1.Q4.3 🟡 Why are there two WhatsApp send crons with different cadences?
Answer
Traffic is split by the `type` field on the queue doc. **Marketing** (no `type`) → `sendWhatsappMessageCron.js` (~every 3 s). **Transactional/order-related** (`type` present) → `sendWhatsappTransactionalCron.js` (~every 1 s) so time-sensitive order messages jump the queue. Both poll `process_status=0` and delegate to `whatsappMsgProcess.js` → Gupshup/Meta. See map-04 §1.Q4.4 🟡 Which cron computes RFM, when, and where does it write?
Answer
`crons/rfmSegments.js`, **3 AM daily** (`0 3 * * *`), over a **90-day window** per parent business. Assigns segments (new_customers, promising, regular, champions, need_attention, at_risk, lost) and writes to `rfm_metrics_tmp` + `rfm_job_log`. See map-04 §1.Q4.5 🟡 Which cron deletes old crm_queue records and on what rule?
Answer
`deleteCrmQueue.js`, every 2 days (`0 2 */2 * *`). Deletes `status=1` (completed) records **and** all `source=6` non-terminal events (keeping only "Closed"). This is the workaround for the missing TTL. See map-04 §1.Q4.6 🔴 How does cron overlap protection work today, and where does it fall short?
Answer
Crons check a `status:false` lock in a `*_cron_status` / `cron_stats` document before running; if the previous run isn't marked `status:true`, the new run **skips**. Stuck detection is reactive (~20 min → alert email), with a force-close fallback (~4 h, seen in `dedupCustomers`). It falls short: **no automatic recovery / circuit breaker**, and locks are per-document not distributed. See map-04 §5.6, [Request-LifeCycle §4](../02-Architecture/Request-LifeCycle.md).Q4.7 🔴 Two loyalty-points queues coexist. Which is legacy and why does it matter?
Answer
**Legacy:** MongoDB `loyalty_points_allocate` drained by `pointAllocation.js` (~every 1 min, status 0/1/-1). **Current:** MySQL `crm_points_allocation_queue` drained by `crmPointsAllocationQueueCron.js` (~2 min, with retry/backoff). Matters because points can be produced/consumed by *either* path — a debugging trap if you check only one. See map-04 §5.2.Q4.8 🟡 Roughly how many crons exist, and how are they categorized?
Answer
~91 crons (map-01/map-04 count ~93 files incl. subdirs: autoCampaignRoiCrons, rewardsCrons, updationCrons). ~14 are "critical" (ingestion, points, channel sends, RFM, downgrade, dedup, cleanup); the rest are reporting/cleanup/auxiliary. See map-04 §7.5. Security & Auth¶
Q5.1 🟢 Name the six auth middlewares and who each is for.
Answer
`authMiddleware` (business/staff, MySQL token/`auth_tokens`/bcrypt); `adminMiddleware` (admins, `business_id==1`); `appMiddleware` (customer loyalty app, Mongo `addo_contacts_mapping` token); `jwtMiddleware`/`jwtWhatsappMiddleware` (internal/WhatsApp, JWT HS256 with hardcoded secrets ⚠️); `crm_offer_middleware` (POS/3rd-party, Mongo `authorization_token`); `injestion_middleware`/`thirdPartyMiddleware` (ingestion, hardcoded static token ⚠️). See [System-Overview §7](../00-Getting-Started/System-Overview.md), [Authentication](../08-Security/Authentication.md).Q5.2 🟡 What order do the global middlewares run in, and what does the traceId middleware give you?
Answer
`bodyParser` (urlencoded+json) → `cors()` (**wildcard** ⚠️) → `traceIdMiddleware` → `requestLogger` → route + route-level auth. `traceIdMiddleware` sets a request-scoped `trace_id` (from `x-trace-id` or new UUIDv4) via AsyncLocalStorage, so you can **grep logs by `trace_id`** to reconstruct a request. See [Request-LifeCycle §1](../02-Architecture/Request-LifeCycle.md).Q5.3 🔴 List the headline security debts a new joiner must not repeat.
Answer
Hardcoded JWT secrets and static ingestion tokens; **wildcard CORS**; **no rate limiting**; committed `.env.dev`; plaintext MongoDB Atlas URI + hardcoded MSK brokers in the `serverless-*.yml` files; hardcoded Redis ElastiCache host in `utilities/redis.js`. Auth failures are at least **fail-closed** (403/500). See [Security](../08-Security/README.md), map-05 §10.Q5.4 🟡 How does requestLogger handle a request carrying a plaintext password?
Answer
It **flags/logs the metadata but never logs the value** — a security monitor for accidental plaintext credentials. See [Request-LifeCycle §1](../02-Architecture/Request-LifeCycle.md).6. Per-Module¶
Order Ingestion¶
Q6.1 🟡 Trace an order from POST to processed. Which collection, which status values?
Answer
`POST /crm_api/injestion/{pos}` (Lambda `orders-api`) **or** monolith `/injestion/{pos}` → insert into **`crm_queue`** `{data, source:Q6.2 🟡 Which controllers/crons ingest orders, and what's the smell?
Answer
`commonOrderIngestion.js` (~2 min), `petpoojaOrderIngestion.js` / `ristaOrderIngestion.js` / `uengageOrderIngestion.js` (~1 min), `shoutloOrderIngestion.js` (~2 min); HTTP gateway `injestionController.js` (`crmQueue()`, `petPooja()`, `rista()`, `posist()`, `ls_center()`). Smell: 5 near-duplicate crons for 5 POS types + a parallel Lambda path — duplicated error/email logic. See map-02.Customer / CRM¶
Q6.3 🟡 What's the "largest and most central" controller, and name two responsibilities.
Answer
`customerController.js` (40+ functions): customer CRUD, segmentation, RFM lookups, loyalty/rewards, chat integration, occasional rewards (`checkOccasionalRewards`), feedback loyalty points (`feedbackLoyaltyPoints`). App-side lightweight creation is split into `appCustomerController.js` (writes dumps for async ingestion). See map-02.Q6.4 🔴 How is customer identity unified across channels, and which cron cleans duplicates?
Answer
By **phone number**. `crons/dedupCustomers.js` (1 AM daily) dedups the `customers` collection by `mobileNo`, picks canonical by (LTO desc, LTV desc, `_id`), re-points non-canonical outlets to canonical, deletes duplicates. Dedup correctness is called out as critical (Dashboard 3.3). See map-04, [Business-Flow §2](../01-Business/Business-Flow.md).Campaigns¶
Q6.5 🟡 Walk the campaign flow and say where the business wallet is debited.
Answer
Select channel → audience (segment/upload/cohort) → template → sender ID + ROI window → schedule → send to channel queue → delivery/read/click tracking → ROI attribution (revenue in the attribution window) → MIS. The **business wallet is debited per message** (in `sendSms.js` / campaign builders) and sends are **blocked if balance is insufficient**. See [Business-Flow §5](../01-Business/Business-Flow.md), [Campaigns](../03-Modules/Campaigns/README.md).Q6.6 🟡 What is ROI attribution and where is it stored?
Answer
Revenue credited to a campaign for orders placed within a **configurable attribution window (hours)** after the send. Tracked in `auto_campaign_roi` / `campaign_mis` via `processLibrary/campaignMis.js` and `uengageProcess*ROI*` helpers. See [Terminologies](../01-Business/Terminologies.md).Loyalty¶
Q6.7 🟡 What is the core points-allocation function and what does it touch?
Answer
`commonFunctions/handleWalletPointsFlow.js` — checks fraud rules + eligibility, inserts wallet transactions, updates MySQL wallet balance, and triggers milestone upgrade (`handleMilestoneUpgrade.js`). Reward amount comes from `rewardHelper.js`. Logic is spread across these + `helpers.js` (a consolidation target). See map-04 §2, §5.8.Q6.8 🔴 A brand configures different earn % for Dine-in vs Delivery. Where does that live and what rule complicates the ledger?
Answer
Per-order-type earn/burn % lives in loyalty config (`wallet_rules`); applied during allocation. Complication: `Total = Used + Available + Expired` must always hold and never go negative, but tier **downgrade forfeits unredeemed points** and non-atomic Mongo/MySQL writes can drift it (E1.10). See [Loyalty](../03-Modules/Loyalty/README.md), [Roadmap §E](../01-Business/Roadmap.md).RFM / Intelligence¶
Q6.9 🔴 Distinguish rfmSegments.js from customerInsightsCron.js / segments_count.js.
Answer
`rfmSegments.js` (3 AM) = pure R/F/M scoring → 11 segments → `rfm_metrics_tmp`. `customerInsightsCron.js` = advanced multi-filter segmentation (mobile validity, visited_days, time_of_day, order_amount) → `segment_counts`. `segments_count.js` (4:45 PM) = simple reachability counts (SMS dnd=0, WhatsApp optin=1, Push has FCM). See map-04 §1.WhatsApp¶
Q6.10 🔴 Where does the "WhatsApp volume mismatch" (E1.3) come from architecturally?
Answer
Sends and receipts flow through **two half-migrated paths**: the monolith's WhatsApp queue/crons do the real work, while the Kafka `whatsapp-messages`/`whatsapp-receipts` consumers are **stubbed (log-only)** — so delivery receipts (`process_status=3` "pending webhook") aren't fully reconciled and there's no visible delivery-receipt processor cron. Counting from an incomplete pipeline yields inaccurate volume reports. See [High-Level-Architecture §7](../02-Architecture/High-Level-Architecture.md), map-04 §5.11.Dashboard / Analytics¶
Q6.11 🟡 Which module builds the daily aggregates, and name three collections it writes.
Answer
`processLibrary/aggregationProcess.js` (ETL, IST date math) builds ~14 collections, e.g. `orders_by_day_business`, `fulfillment_breakdown_by_parent`, `daily_summary`. `dashboardMIS.js` writes `dashboard_mis` (orders, revenue, AOV, customer counts, sentiment). See map-04 §3.Feedback / NPS¶
Q6.12 🟡 How does a low rating get handled differently from a high one?
Answer
High rating → routed to Google/Zomato public reviews. Low rating → triggers the **low-rating recovery journey + promo** (`lowRatingTrigger.js`, FCM/JWT). NPS buckets responses Promoter (9–10)/Passive (7–8)/Detractor (0–6). See [Business-Flow §7](../01-Business/Business-Flow.md), map-02.7. Rapid-fire code-specific¶
Cover the answers; aim for instant recall.
| # | Question | Answer |
|---|---|---|
| R1 | Cron that drains crm_points_allocation_queue? cadence? |
crmPointsAllocationQueueCron.js, ~2 min |
| R2 | Cron that computes RFM? when? | rfmSegments.js, 3 AM |
| R3 | Cron that downgrades loyalty tiers? | loyalty_milestone_downgrade.js, 2 AM |
| R4 | Cron that dedups customers? | dedupCustomers.js, 1 AM |
| R5 | Cron that cleans crm_queue? |
deleteCrmQueue.js, every 2 days |
| R6 | Marketing vs transactional WhatsApp cron? | sendWhatsappMessageCron.js (3 s) / sendWhatsappTransactionalCron.js (1 s) |
| R7 | SMS / Push send crons? | smsQueueCron.js (3 min) / pushQueueCron.js (5 min) |
| R8 | Core points-allocation function? | commonFunctions/handleWalletPointsFlow.js |
| R9 | Main journey/campaign orchestrator (processLibrary)? | uengageProcess.js (~89KB) |
| R10 | Collection that is the monolith↔serverless seam? | crm_queue |
| R11 | Kafka topic for orders? | prism-ingest-orders |
| R12 | Source ids 1/2/6/7? | uengage / petpooja / rista / posist |
| R13 | MongoDB pool size + read pref? | maxPool 400, SECONDARY_PREFERRED |
| R14 | MySQL two pools + sizes? | webPool 15, jobsPool 10 |
| R15 | Legacy vs new dashboard controllers? | dashboardController.js / dashboardControllerNew.js |
| R16 | AOV formula (and E1.9 bug)? | Revenue / Orders; E1.9 shows ₹0 in "Your Effective Offers" |
| R17 | FM score formula? | Ceil((F+M)/2) |
| R18 | Aggregation ETL module? | processLibrary/aggregationProcess.js |