Skip to content

Hands-On Assignments

Practical tasks spanning the whole system. Each has Objective, Files to touch, Acceptance criteria, and Hints. Use these for Week 2's checkpoint (A1) and as Week-4 capstone options. See the Learning Path.

Ground rules for every assignment: branch off main; respect multi-tenancy (parent_business_id + child_business_id); use the right MySQL pool (webPool for requests, jobsPool for crons); log with trace_id; introduce no new hardcoded secrets; never point local crons at prod DBs.


Assignment 1 — Get it running & trace one order (foundational — do first)

Objective. Run uengage-crm locally and trace a single order from ingestion into a customer update, documenting every hop.

Files to touch (read, mostly) - .env.dev, config.js, index.js (boot + cron registration) - utilities/mongodb.js, config/mysqlPool.js, utilities/redis.js - injestionController.js (crmQueue()), the relevant ingestion cron (e.g. uengageOrderIngestion.js), commonFunctions/processCommonIngestion.js, handleWalletPointsFlow.js

Acceptance criteria - App boots; you can POST a sample order and see a crm_queue doc with status:0. - You produce a written trace: source → crm_queue → drain cron → customer upsert (LTV/LTO/AOV recompute) → crm_points_allocation_queuehandleWalletPointsFlow → wallet update. - You can state which source id maps to which POS (1/2/3/4/6/7).

Hints - Comment out heavy crons in index.js locally. Use the smoke test in Local-Setup §7. - Correlate logs by trace_id. Verify with db.crm_queue.find().sort({insertedAt:-1}).limit(1).


Assignment 2 — Add a new API endpoint

Objective. Add a read endpoint that returns a customer's loyalty points ledger for a business, e.g. GET /crm_api/v2/customer/points-ledger?mobile=&parent_business_id=.

Files to touch - A route file under the CRM app routes; wire the correct auth middleware (authMiddleware for staff, or appMiddleware if customer-facing). - controllers/customerController.js (add a handler) or a small new controller. - Read from MySQL wallet/wallet_history (+ Mongo wallet_transactions if needed) via jobsPool/webPool appropriately.

Acceptance criteria - Endpoint returns earned / used / expired / available and honors parent_business_id scoping. - Rejects unauthenticated calls (fail-closed 401/403). - Response is consistent with the integrity rule Total = Used + Available + Expired.

Hints - Follow an existing read handler in customerController.js for the middleware + response shape. - Do not compute across tenants; always filter by parent (and child where relevant). - Add a trace_id-aware log line at entry/exit.


Assignment 3 — Add a DB column / collection field

Objective. Add a last_channel field capturing the order's originating channel, threaded from ingestion to the customer profile.

Files to touch - MySQL migration/DDL for the target column (e.g. on customers_new/customer table) or the Mongo customers model (models/). - Ingestion parser (parseOrderData.js / processCommonIngestion.js) to populate it. - The read path/controller that exposes the profile.

Acceptance criteria - New orders populate last_channel; existing rows default gracefully (nullable / backfill plan). - No breakage to LTV/LTO/AOV recompute. - Field visible via an existing customer read endpoint.

Hints - Mind the business-id type inconsistency (String vs Number) when writing to Mongo. - If MySQL, add the column nullable first (safe deploy), backfill separately. - Document the field in ../04-Database/.


Assignment 4 — Fix a real bug: E1.9 (AOV shows ₹0)

Objective. Fix "Your Effective Offers" so AOV = Revenue / Redemptions instead of rendering ₹0.

Files to touch - The offers/effective-offers computation (dashboard/campaign MIS path — start from dashboardControllerNew.js / processLibrary/aggregationProcess.js / the offers widget builder).

Acceptance criteria - AOV computes Revenue / Redemptions; when Redemptions == 0, show /N/A (never divide-by-zero → 0). - A test/validation asserts the metric per the Product Logic Spec (A4.1) and holds cross-widget integrity. - Manual verification on sample data shows a non-zero AOV where revenue+redemptions exist.

Hints - Confirm the denominator field name and that it's populated at compute time (the usual cause). - See the full triage in Level-5 Incident 4 and Known-Bugs.


Assignment 5 — Optimize a query (add an index)

Objective. Add one index from the gaps list and measure the improvement.

Files to touch - MySQL: add wallet (parentBusinessId, validTillDate, id) (speeds campaign segment extraction) or addo_orders (businessId, orderDate) (speeds daily aggregations). Or MongoDB: index parent_business_id/business_id (+ created_at) on a hot aggregation collection. - Migration/index-creation script; note it in ../04-Database/.

Acceptance criteria - EXPLAIN (MySQL) / explain() (Mongo) shows an index scan instead of a full scan for the target query. - Before/after timing recorded on representative data volume. - Index built safely (online / off-peak; large collections need care).

Hints - Match the index column order to the query's filter+sort. Confirm the exact query in the segment-extraction path (WHERE parentBusinessId=? AND validTillDate>=TODAY()). - Watch write-amplification on very hot write paths.


Assignment 6 — Implement a feature flag

Objective. Gate a new behavior behind a per-business flag stored in business_configs (e.g. enable_points_ledger_endpoint).

Files to touch - business_configs model/config read; a small helper to fetch the flag by parent_business_id. - The controller from Assignment 2 (or any target) to check the flag before executing.

Acceptance criteria - Behavior is ON only when the flag is truthy for that business; OFF (or default) otherwise. - Flag lookup is tenant-scoped and cached/efficient (avoid a DB hit per request where possible — consider Redis). - Safe default when the flag is absent.

Hints - business_configs already holds per-business campaign/wallet config — follow that pattern. - Don't hardcode business ids; read the flag dynamically. Log which path was taken with trace_id.


Assignment 7 — Add a cron with overlap protection

Objective. Add a small periodic cron (e.g. a daily loyalty reconciliation that flags customers where Total ≠ Used + Available + Expired) using the project's overlap-protection pattern.

Files to touch - A new file under crons/; register it in index.js (guarded for local dev). - Use jobsPool for MySQL; write findings to a report collection (e.g. loyalty_reconciliation_log).

Acceptance criteria - Cron uses a lock document (cron_stats/*_cron_status) and skips if the previous run isn't complete. - On crash it doesn't permanently wedge (release the lock in a finally; ideally an atomic claim + lease/TTL). - Produces a report of discrepancy customers; emits a trace_id-aware log with counts.

Hints - Copy the lock pattern from an existing cron (e.g. deleteCrmQueue.js / an ingestion cron). See Level-4 Q8 on why a boolean lock is fragile under scaling — do better if you can. - Reuse the reconciliation logic from Level-5 Incident 2.


Capstone rubric (Week 4)

Criterion Weight
Correctness (meets acceptance criteria) 40%
Respects conventions (tenancy, pools, no new secrets, trace_id) 25%
Verification (before/after, trace, or test) 20%
Code quality & PR hygiene (small, reviewed, documented) 15%

Graduation: a merged capstone PR + ability to whiteboard the order → WhatsApp win-back flow unaided.