Module 5 Β· Lecture 17

End-to-End Multi-Source Surge Pricing

Demand + driver supply + weather, traffic, and events β†’ explainable pricing decision

⏱ 90 minutesπŸ“ Prerequisites: Kafka, Python consumers, MySQL, Grafana🎯 Outcome: Live zone-pricing dashboard

Educational model: This project demonstrates the decision architecture behind dynamic pricing. It is not Uber's proprietary algorithm, does not claim to reproduce Uber prices, and deliberately uses transparent weights so students can debate them.

Contents
  1. Learning outcomes and timeline
  2. Business case and data sources
  3. Architecture and files
  4. Pricing logic and guardrails
  5. Run the end-to-end pipeline
  6. Build the live Grafana dashboard
  7. Management decisions, fairness, and troubleshooting

Learning Outcomes and Timeline

TimeSegmentOutput
0–10Business problemWhy demand-only pricing fails
10–25Architecture + formulaSource map and pricing policy
25–40Start the pipelineThree live Kafka sources
40–65Grafana buildFive-panel pricing control room
65–78Scenario observationNormal β†’ surge β†’ recovery evidence
78–88Policy challengeRevised weights or guardrails
88–90Briefing60-second management recommendation

Section 1: The Business Case

A ride platform must balance two competing outcomes: riders need cars quickly, while drivers need a reason to move toward high-demand zones. A multiplier can help rebalance the marketβ€”but a weak rule can create unfair prices, false scarcity, or reputational risk.

Demand sourceRide requests by zone. Answers: how many riders currently want a trip?
Supply sourceAvailable and occupied drivers. Answers: how much capacity can respond?
Context sourceRain, traffic, and event intensity. Answers: why might matching be difficult?

The 60-Second Scenario

PhaseSecondsWhat ChangesExpected Decision
Normal0–19Balanced requests and drivers; light trafficMultiplier stays near 1.0Γ—
Surge20–44Concert ends during heavy rain; CBD and station demand rises while available drivers fallCBD rises fastest, but never above 2.5Γ—
Recovery45–59Rain reduces and drivers returnMultiplier declines gradually, not instantly

Section 2: Architecture and Files

ride_requests_17.csv      driver_supply_17.csv      zone_context_17.csv
          |                         |                         |
          v                         v                         v
ride_request_producer   driver_supply_producer     context_producer
          |                         |                         |
ride-requests-l17       driver-supply-l17          zone-context-l17
          \_________________________|_________________________/
                                    |
                                    v
                      surge_pricing_consumer_17.py
                       10-second zone state + formula
                                    |
                                    v
                 MySQL: sda_course_l17 (4 audit tables)
                                    |
                                    v
                         Grafana pricing dashboard

Why separate topics? Demand, supply, and context have different owners, update rates, and failure modes. Kafka lets the pricing engine consume them together without forcing the source teams into one application.

Download Project Files

generate_surge_data_17.pyCreates all three source datasets ride_request_producer_17.pyDemand producer driver_supply_producer_17.pySupply producer context_producer_17.pyWeather, traffic, and event producer surge_pricing_logic_17.pyPure explainable pricing formula surge_pricing_consumer_17.pyMulti-topic state, MySQL, and decisions

Section 3: Pricing Logic and Guardrails

For each zone, the consumer counts requests received in the last 10 seconds, combines the latest driver supply and context, then calculates:

ratio = requests_in_last_10_seconds / max(available_drivers, 1) demand adjustment = max(ratio - 1, 0) Γ— 0.35 weather adjustment = rain intensity Γ— 0.20 traffic adjustment = max(traffic index - 0.50, 0) Γ— 0.30 event adjustment = event intensity Γ— 0.25 raw multiplier = 1 + all adjustments capped target = clamp(raw, 1.0, 2.5) final multiplier moves toward the target by at most Β±0.20
GuardrailManagement Purpose
Minimum 1.0Γ—This classroom model does not apply discounts.
Maximum 2.5Γ—Limits extreme customer impact during shocks.
Β±0.20 per decisionAvoids abrupt jumps caused by one update.
Visible componentsEvery decision stores the uncapped proposal, capped/rate-limited result, factor contributions, and a human-readable reason.
Isolated databasesda_course_l17 cannot overwrite Lecture 10 or 16 data.

Decision question: Should rain increase price when demand remains below supply? The current formula says β€œslightly.” A team could instead use context only to explain demand pressure, not directly change price. There is no neutral choiceβ€”only a policy with consequences.

Section 4: Run the End-to-End Pipeline

1. Start Existing Infrastructure

docker start sda-mysql grafana

# Kafka and ZooKeeper should also be running from earlier lectures.
pip install kafka-python mysql-connector-python

If MySQL or Grafana has not been created, use the Docker setup from Lecture 16. The consumer creates the new database and grants the existing grafana user read-only access.

2. Generate the Three Sources

python generate_surge_data_17.py

# Expected:
# Ride requests : 735
# Supply updates: 300
# Context rows  : 300

Every run receives a timestamp-based run ID, so event primary keys do not conflict with previous classroom runs.

Scenario checkpoint: With the default policy, the simulated peak is 2.5Γ— in the Central Business District and Railway Station, about 1.41Γ— at the Airport, and about 1.21Γ— in the Tech Park and Residential zone.

3. Optional Clean Reset

docker exec -it sda-mysql mysql -uroot -padmin \
  -e "DROP DATABASE IF EXISTS sda_course_l17;"

Classroom only: This deletes previous Lecture 17 evidence. Never use a destructive reset on a production database.

4. Start Four Terminals

TerminalCommandRole
1python surge_pricing_consumer_17.pyStart first; creates schema and listens to all topics
2python ride_request_producer_17.pyDemand stream
3python driver_supply_producer_17.pySupply stream
4python context_producer_17.pyWeather, traffic, and event stream

Launch the three producers close together. Each groups records by scenario second, publishes that second's events, then waits one real second. The scenario therefore unfolds live for approximately one minute.

5. Verify MySQL

docker exec sda-mysql mysql -uroot -padmin -e "
SELECT COUNT(*) AS requests FROM sda_course_l17.ride_requests;
SELECT COUNT(*) AS decisions FROM sda_course_l17.pricing_decisions;
SELECT zone, MAX(final_multiplier) AS peak
FROM sda_course_l17.pricing_decisions
GROUP BY zone ORDER BY peak DESC;"

Section 5: Grafana Pricing Control Room

  1. Add a MySQL datasource named MySQL β€” Lecture 17 Surge.
  2. Host: sda-mysql:3306 | Database: sda_course_l17.
  3. User/password: grafana / grafana; disable TLS for the local lab.
  4. Create dashboard Surge Pricing Control Room β€” Lecture 17.
  5. Set range to Last 5 minutes and refresh to 5s.

Variable β€” Zone

SELECT DISTINCT zone AS __text, zone AS __value
FROM pricing_decisions
ORDER BY zone;

Enable Multi-value and Include All. Use custom All value .*.

Panel A β€” Current Multiplier by Zone (Bar Gauge)

SELECT p.zone, p.final_multiplier AS value
FROM pricing_decisions p
JOIN (
  SELECT zone, MAX(decision_id) AS latest_id
  FROM pricing_decisions
  GROUP BY zone
) latest ON p.decision_id = latest.latest_id
WHERE p.zone REGEXP '${zone:regex}'
ORDER BY value DESC;

Panel B β€” Multiplier over Time (Time Series)

SELECT
  decision_time AS time,
  final_multiplier AS value,
  zone AS metric
FROM pricing_decisions
WHERE $__timeFilter(decision_time)
  AND zone REGEXP '${zone:regex}'
ORDER BY decision_time;

Panel C β€” Demand versus Supply (Time Series)

SELECT
  decision_time AS time,
  demand_window AS `Requests in 10s`,
  available_drivers AS `Available Drivers`
FROM pricing_decisions
WHERE $__timeFilter(decision_time)
  AND zone = '${zone}'
ORDER BY decision_time;

For this panel, select one zone rather than All.

Panel D β€” Latest Factor Breakdown (Table)

SELECT
  zone,
  demand_supply_ratio,
  demand_adjustment,
  weather_adjustment,
  traffic_adjustment,
  event_adjustment,
  final_multiplier,
  reason
FROM pricing_decisions
WHERE zone REGEXP '${zone:regex}'
ORDER BY decision_id DESC
LIMIT 20;

Panel E β€” Pricing Decision Audit (Table)

SELECT
  decision_time,
  phase,
  zone,
  demand_window,
  available_drivers,
  raw_multiplier,
  final_multiplier,
  reason
FROM pricing_decisions
WHERE $__timeFilter(decision_time)
  AND zone REGEXP '${zone:regex}'
ORDER BY decision_time DESC
LIMIT 50;

Section 6: Interpret, Challenge, and Govern

What Students Should Observe

  1. Normal phase multipliers remain close to 1.0Γ—.
  2. The CBD and Railway Station respond more strongly than other zones.
  3. The final multiplier moves gradually because each decision is rate-limited.
  4. Recovery lags the source change; smoothing trades responsiveness for stability.
  5. The audit table explains whether demand, weather, traffic, or an event contributed.

Policy Challenge β€” Change One Rule

Each group chooses one change, predicts the effect, edits surge_pricing_logic_17.py, reruns the scenario, and compares the dashboard:

Fairness and Governance Discussion

RiskManagement QuestionPossible Control
Emergency price shockShould surge apply during floods or evacuations?Emergency-mode cap or disable rule
Geographic proxy biasDo some neighbourhoods face systematically higher prices?Zone fairness audit and outcome monitoring
False scarcityWhat if driver telemetry is delayed?Freshness checks; freeze multiplier on stale supply
Customer transparencyCan a rider understand why price increased?Reason codes and pre-booking disclosure
Driver gamingCan coordinated sign-offs create artificial shortage?Anomaly detection and minimum evidence window

60-Second Management Briefing

  1. Situation: Which zone changed, and when?
  2. Evidence: What happened to demand, supply, and context?
  3. Decision: Keep, cap, or suspend the multiplier?
  4. Guardrail: What customer or driver risk must be monitored?

Troubleshooting

SymptomLikely CauseFix
No decisionsSupply or context producer has not startedRun all three producers; the engine needs complete zone state
Only one topic has rowsProducer started in the wrong folderKeep all generated CSVs and scripts together
Dashboard is emptyWrong database or time rangeUse sda_course_l17 and Last 5 minutes
Multiplier changes too quicklyProducers were not launched togetherReset the database and restart the four terminals
Duplicate run dataDatabase was not resetFilter by time or use the optional clean reset

Key takeaway: Dynamic pricing is not a formula alone. It is a governed decision system: multiple sources, stateful processing, explainable rules, operational monitoring, and explicit limits on harm.