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 (webPoolfor requests,jobsPoolfor crons); log withtrace_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_queue → handleWalletPointsFlow → 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.