Anti-Bunching Copilot COMPX523 · University of Waikato · SUNT / Salvador
Streaming machine learning · Data-stream mining · COMPX523-26A
§ Report §1 · Introduction

An online anti-bunching copilot for bus drivers

When one bus falls behind, it picks up more people and slows down even more, so the bus behind catches up. Our model watches the buses, sees when two are about to bunch, and tells that driver to slow down a little. It only gives advice and needs nothing new to work.
Scroll to begin
Why Salvador

Many people in Salvador, Brazil depend on public transport, and most of them travel by bus. The city also has heavy road traffic and is one of the more congested in the world. Large transport projects are hard to deliver here, because responsibility is split across many public and private bodies and the bus system is under serious financial strain. These conditions favour interventions that work with the buses and data that already exist, cost little, and do not rely on agencies coordinating with each other.

The vocabulary

Six words you need before you read the dashboard

The picture below contrasts the service we want with the failure we are fighting. Scroll, and we will name each part.

Fig. 1 — Even service vs. bunched service
Evenly-spaced service on top, with five buses 15 minutes apart; bunched service on the bottom, with four buses collapsed into a platoon one minute apart while the lead bus has pulled 57 minutes ahead.
Top: five buses, 15 min apart — the intended service. Bottom: four buses collapsed into a platoon one minute apart, while the lead bus has pulled 57 min ahead. Adapted from Enayatollahi et al. (2019).
Term 01

Headway

The time gap between successive buses arriving at the same stop. Even headways — here, 15 min in the top row — are the entire goal of high-frequency service.

Term 02

Bus bunching

The unstable state in which buses that should be evenly spaced clump into a platoon, leaving a large gap behind them — the bottom row, where four buses run one minute apart.

Term 03

Dwell time

The time a bus spends stopped to board and alight passengers. Heavy boarding lengthens dwell — and dwell is a primary driver of bunching: a late bus collects the passengers an on-time bus would have carried, which slows it further.

Term 04

Forward headway

The gap from a given bus to the bus ahead of it on the same route. This is the core signal the model predicts. In the bottom row it has collapsed to a minute inside the platoon while opening to 57 min behind the leader.

Term 05

Driver nudge

The system's advisory output to one driver: “ease off / let the gap open” or “hold briefly”. It is advisory only — the driver stays in control — and it never asks the driver to speed up.

Term 06

Bunching-risk classes

For the next window, the model predicts one of three labels. This traffic-light grammar runs through the entire page:

ok warning bunching

Reading the signal
§ Report §3.3 · Driver nudging as classification

How one bus drifts off headway, component by component

Before the dashboard makes sense, watch the three numbers the model actually computes — built up one layer at a time.

The headway ratio, assembled
Forward headway — the realised gap to the bus ahead.
Layer 01

Forward headway

Start with the raw gap: the minutes since the previous bus passed this same stop. As our trailing bus closes on the one ahead, this gap shrinks.

Layer 02

The local “normal”

A raw gap means nothing on its own — peak gaps are short, late-evening gaps are long. So we compare it to the local-normal headway: the rolling median of the last eight gaps at this stop.

Layer 03

The headway ratio

Divide one by the other: ratio = realised gap ÷ normal. A ratio of 1.0 is on-headway; below 1.0 the bus is running closer than usual — closing on the bus ahead.

Layer 04

Two thresholds

The ratio is graded with two cut-points the whole system shares:

ratio ≥ 0.60 0.40 – 0.60 < 0.40

Cross 0.60 and the light turns amber; cross 0.40 and it turns red. That is exactly what the dashboard's HUD shows next.

Interactive · live
§ Report §3.2 · A streaming driver-nudge system

Put it together: drive the copilot yourself

Three buses on a 60-minute loop. The HUD is the signal you just learned, live. Follow the steps — or grab the controls and explore.

🚌
t = 0 min
0 minevenwarningbunching60 min
Bus 2 — headway nominal. No action needed.
Bus 2 · Fwd headway
Headway9.8 min
Normal9.8 min
Ratio1.00
Dwelllow
Risk
OK
Headway normal
≥ 0.60
0.40–0.60
< 0.40
Headway gaps
1 → 29.8 min
2 → 310.6 min
behind 324.6 min
α = 0.40 · β = 0.60
Ease off only — never speed up.
Step 01

Press play, or scrub

Hit to animate the 60-minute window, or drag the slider to any moment. Right now the three buses are evenly spaced and every reading is green.

Step 02

Watch Bus 2 drift

As time passes, Bus 2's forward headway shrinks — it is catching up to Bus 1 while a gap opens behind Bus 3. The ratio falls, and the risk light turns amber, then red.

Step 03

Apply the nudge

When the Ease off / Hold briefly button appears, click it. Bus 2 eases off, the gap reopens, and the light returns to green.

The action space is deliberately safe: ease off or briefly hold — never speed up.

Step 04

Compare the scenarios

Toggle Corrected scenario to replay the same timeline with the auto-nudge firing at the warning threshold — and see even spacing preserved throughout, before bunching ever forms.

The system is advisory only: it informs the driver, who remains in control. The action space is deliberately safe — ease off or briefly hold, never speed up. — Methodology, §3.3 “Driver nudging as online classification”
The before · descriptive evidence

First, is bunching even real here? And what does it cost?

Measured directly from reconstructed forward headways on the eight busiest routes — no machine learning yet. Every statistic is computed within hourly windows, so we never mistake the normal daily demand cycle for bunching.

Fig. — Headway irregularity by route
Bar chart of within-window headway coefficient of variation per route, peak versus off-peak. Higher bars mean more irregular spacing; the peak bars are consistently taller.
Within-window headway coefficient of variation per route, peak versus off-peak. Higher is more irregular; the peak is consistently worse.
Measurement 01

Headway irregularity (CV)

The standard measure of spacing irregularity is the coefficient of variation, CV = std(h) / mean(h). CV = 0 is perfectly even service; bunching inflates it.

Reading the chart

A median CV of 0.73

Across the eight routes the median within-window CV is 0.73 (mean 0.80). The irregularity is worse in the peak — where most passengers travel — rising from 0.79 off-peak to 0.86 in the peak.

Fig. — Distribution of the headway ratio
Histogram of the realised headway as a fraction of the local-normal headway. A dashed red line marks the bunching threshold at 0.40 and an orange dashed line marks the warning threshold at 0.60; a large mass of arrivals sits left of these lines.
Distribution of realised headway as a fraction of the local-normal headway. Mass left of the dashed lines is service in warning (< 0.60) or bunched (< 0.40).
Measurement 02

How close the buses run

For every arrival we compute the same headway ratio the model uses, and plot the whole distribution. The dashed lines are the warning and bunching thresholds.

Reading the chart

Nearly a fifth already bunched

By the model's own thresholds, 19.4% of arrivals are already bunched (ratio < 0.40), and a further 9.8% are in warning (ratio < 0.60). Almost three arrivals in ten are running closer than they should.

Fig. — Avoidable passenger waiting
Stacked bar chart of mean passenger wait per route, split into the even-headway ideal and the avoidable excess wait caused by irregularity. The avoidable portion is a large share of every bar.
Mean passenger wait per route, decomposed into the even-headway ideal (h̄/2) and the avoidable excess caused by irregularity.
Measurement 03

Avoidable passenger waiting

Irregular spacing converts straight into wasted time. Renewal theory splits a passenger's mean wait into an even-service floor plus an avoidable term driven purely by irregularity.

Reading the chart

Half a million minutes a day

On these eight routes, irregular spacing imposes on the order of 5.6 × 10⁵ avoidable passenger-waiting-minutes per day — about 9,400 passenger-hours daily, or roughly 17 avoidable minutes per waiting passenger.

Schematic — severity over the day
Schematic, not measured. Per-arrival severity max(0, 1 − ratio) averaged by hour peaks in the morning and evening commute — so bunching concentrates exactly when buses are fullest.
Measurement 04

Severity over the day

Following Enayatollahi et al. (2019), severity is max(0, 1 − headway ratio): zero on schedule, rising toward 1 as a bus closes up on the one ahead.

Reading the shape

It peaks with the commute

Averaged by hour, severity peaks in the morning and evening commute. Bunching is worst precisely when the most people are waiting — the two shaded humps above.

Schematic — string diagram
Schematic, not measured. Time runs along the horizontal axis, route position up the vertical, and each line is one bus. Where lines converge or cross, those buses are bunching.
Measurement 05

Seeing bunching directly

The per-bus string diagram shows the same phenomenon in the raw trajectories. Each line follows one vehicle; converging or crossing lines are bunching events.

Why the data matters

Only OD data keeps this signal

This structure is visible only because the Origin–Destination records keep each bus individually, with exact arrival times. Aggregating into 5- or 30-minute per-interval counts destroys it — you can no longer tell a 30-second gap from a four-minute one.

The data & the model

From a raw stream to an online predictor — and the metric that decides

Before any model can learn, the data itself has to be understood: it arrives as a stream, it comes from a specific dataset, and it has to be reshaped into features. We walk that path — from raw arrivals to an online predictor — and then to the metric that actually decides which model to trust.

The problem is a data stream
Bus arrivals form an unbounded, ordered stream — each is predicted before it is seen.
The setting

An unbounded, shifting stream

Each bus arrival is one event in an unbounded, non-stationary stream. The arrivals never stop, and the patterns that govern them shift with time of day, demand, traffic and weather. A model trained once on a fixed sample would slowly fall out of step with the world it is meant to watch.

Learning online

Test, then train — in order

This setting calls for prequential evaluation: for each arriving bus the model first predicts, the bus then arrives, and the model finally learns from the realised outcome before moving to the next. Processed in strict chronological order, this reproduces the score the model would have earned running live.

Why order matters

A random split would leak the future

Shuffling the arrivals into a random train/test split would let the model learn from events that, in real time, have not happened yet. Because bunching is defined by what a bus does next, that leakage would inflate the score. Keeping the stream ordered is what makes the evaluation honest.

The SUNT dataset — one row per arrival
Each raw record is one bus arriving at one stop, with its own vehicle id and exact time.
The source

Salvador's raw arrival records

The data is the Salvador Urban Network Transportation (SUNT) dataset — public Origin–Destination records in which every row is one bus arriving at one stop. Crucially, each row carries an individual vehicle id, a trip id, the route and direction, an exact arrival time, the stop position, and the onboard load.

The scope

Eight routes, three days, 109,029 arrivals

From this release we take the eight busiest routes over 1–3 March 2024. After cleaning, this yields 109,029 arrivals, ordered by time. It is enough to expose the daily demand cycle and the drift that comes with it, while staying small enough to reason about.

Why the aggregated tables cannot be used
A 30-second gap and a 4-minute gap collapse to the same aggregated count.
The alternative

The aggregated tables lose the identities

The assignment also supplies SUNT pre-aggregated to 5- and 30-minute intervals. Those tables are keyed by (stop, interval) and store only per-interval counts — boardings, loading, vehicle counts. They carry no vehicle id and no exact arrival time.

The consequence

Aggregation destroys the signal

If two buses pass a stop in the same window, the aggregate cannot tell whether they were 30 seconds apart (bunched) or four minutes apart (normal). Aggregation therefore destroys the very signal bunching is defined on, and no post-processing can recover it. That is why the pipeline works from the raw records.

From a raw row to a labelled instance
Each arrival becomes six causal features and one future-looking label.
Reconstruct

Rebuild the headway from arrivals

The pipeline first drops trips with out-of-order timestamps (about 23% of the raw trips), then reconstructs, for each arrival, the forward headway — the gap to the previous distinct vehicle at that stop — and the local-normal headway, the rolling median of the last eight gaps there.

Features

Six features, all causal

From these it derives six features that use only information available at or before the current arrival: headway_min, headway_ratio, headway_trend, since_prev_stop_min, pt_sequence, and is_peak. Restricting to the present prevents the future from leaking into the input.

Label

A future-looking, three-class target

The label looks three stops ahead on the same trip. If the minimum future headway drops below 0.40 of the local normal it is bunching; below 0.60, warning; otherwise ok. The classes are imbalanced — roughly 70% ok, 10% warning, 20% bunching.

Test-then-train, 109,029 times
Predict, reveal, learn, advance — repeated in order over every arrival.
Predict

First, guess the arriving bus

With only its causal features in hand, the model predicts a risk class for the bus about to arrive — ok, warning, or bunching. This is the prediction it would issue live, before the outcome is known.

Then learn

The bus arrives; learn from it

The arrival then reveals the true label, and the model updates on that single instance before touching the next one. Learning one arrival at a time is what lets the streaming learners track drift instead of freezing on a stale snapshot of the network.

Repeat

Repeat, in order, 109,029 times

Predict, reveal, learn, advance — repeated over every arrival in chronological order. The running score this produces is exactly the prequential accuracy charted next, and the basis for the model bake-off that follows.

Fig. — Prequential accuracy over the stream
Line chart of prequential rolling accuracy over the chronological stream for the Hoeffding Adaptive Tree and the Adaptive Random Forest. Both rise quickly to about 0.95 and stay there; faint grey vertical lines mark ADWIN-detected drift points, with brief dips that recover.
Prequential accuracy over the chronological stream for HAT and ARF; grey lines mark ADWIN-detected drift points.
Streaming setup

Learn online, adapt to drift

A model trained once would grow stale. These learners update with every new arrival, and ADWIN monitors the stream for distribution change, letting the trees self-replace when the world shifts.

Reading the chart

It recovers after every drift

Accuracy climbs fast and holds near 0.95. The grey lines are ADWIN drift points; the brief dips around them — and the prompt recovery — are the adaptive learners doing their job.

The bake-off

Six learners, one stream

On 109,029 instances, an Adaptive Random Forest takes the top bunching-class F1 and balanced accuracy — but accuracy alone, you'll see, is not the point.

ModelF1Bal. acc.κ
ARF0.9160.9150.892
static threshold0.9090.9040.883
HAT0.9070.8990.879
Hoeffding Tree0.8860.7910.792
EFDT0.8770.7890.785
KNN0.7610.7280.676
Naive Bayes0.0000.3330.000
Fig. — Nudge lead time (ARF)
Histogram of nudge lead time for the Adaptive Random Forest: minutes between a fired warning and the observed bunching. A red vertical line marks the median at about 28 minutes; the distribution has a long tail out past 100 minutes.
Nudge lead time for ARF: minutes between a fired warning and the observed bunching. Median ≈ 28 min.
The deciding metric

Skill alone is not enough

Notice the static threshold was competitive on F1. On a conventional accuracy metric the model looks only marginally better. So what actually separates them?

Lead time

≈ 28 minutes of warning

ARF's nudges fire a median of ≈ 28 minutes before the observed bunching — early enough for a driver to ease off. The static threshold and persistence baselines have no meaningful lead: they flag bunching only as it happens.

The takeaway

A warning is only useful if it's early

For an intervention that depends on a driver having time to act, a predictor that fires early is worth more than one that scores a fraction of an F1 point higher but fires late. Lead time, not F1, is what decides whether a model can support a nudge.

The after · counterfactual simulation

If drivers act on the warning, does service improve?

The true effect can't be observed — SUNT has no period when the copilot was deployed. So we replay the recorded arrivals and, whenever ARF fires a nudge on a bus genuinely closing on the one ahead, ease that driver off by a few seconds (capped at 1.5 min per trip), then recompute downstream headways.

⚠ Read this honestly
This is a simulation under stated assumptions, not a measured field outcome. The most consequential assumption is driver compliance. The nudge never asks a driver to speed up, and the local “normal” headway is held fixed so the comparison is against a stable reference.
How the replay works

Before the numbers — exactly what the simulation does

The result rests on a specific, mechanical procedure: replay each recorded arrival, decide where a driver would have been nudged, add a small bounded ease-off, and recompute the spacing. We walk that procedure — and follow one bus through it — before reading the outcome.

The simulation replays a saved run
Each recorded arrival already carries the prediction it earned live — the simulation replays it.
The problem

There is nothing to measure directly

SUNT has no period when the copilot was deployed, so the effect of the nudge cannot be observed. Instead we take the recorded arrivals as they happened and ask, arrival by arrival, what would have changed had drivers acted on the warnings.

Fed by replay

Read the predictions back, don't re-run

The model is not invoked again. The simulation reads the predictions the streaming run already produced — one pred_ARF per arrival, saved alongside the recorded headway and the local-normal reference — and replays the arrivals in trip order. This keeps the counterfactual honest: it uses exactly the live-earned predictions, not hindsight-tuned ones.

A nudge needs both conditions
A driver is eased off only when the model warns AND the bus is genuinely closing.
Condition one

The model predicts a warning

The first requirement is a live prediction of at least warning — that is, pred_ARF ≥ 1 (warning or bunching). A prediction of ok never triggers anything.

Condition two

…and the bus is genuinely closing

The prediction alone is not enough. The bus must also be actually closing on the one ahead: headway ratio < 0.60, using the same ratio the whole system speaks. This guard stops the ease-off from firing on a bus that is merely flagged but still well-spaced.

The rule

Both, together, at that stop

Only when both hold — warned and closing — is that arrival marked to act. Everywhere else the bus is left exactly as recorded, so the intervention is deliberately conservative.

The ease-off: +8 s per stop, capped
Each acting stop adds 8 s of dwell; the per-trip total is capped at 90 s (1.5 min).
The action

Add a few seconds of dwell

When a stop acts, the driver eases off by a small, fixed amount — 8 seconds of extra dwell in the default setting. Nothing dramatic; just enough to let the gap ahead begin to open.

It accumulates

8, 16, 24 … up to a 90-second ceiling

If later stops on the same trip also act, the delay accumulates — 8 s, 16 s, 24 s, and so on. To keep the intervention realistic it is capped at 90 seconds (1.5 minutes) per trip: once a trip has been eased off by a minute and a half in total, no further delay is added.

Direction

It only ever slows a bus

The delay is always non-negative: the nudge can ease a bus off, never speed it up. This matters for realism — a driver can dwell a little longer, but cannot be asked to make up time by driving faster.

One bus through the ease-off (illustrative)
A single trip: the nudge fires, arrivals shift forward, and the gap re-opens.
Walkthrough · 1

A bus closing on the one ahead

Follow one trip along its stops. On the recorded baseline the trailing bus is closing — the gap to the bus ahead is shrinking stop by stop, and the ratio is sliding toward the warning band. (The diagram is idealised for clarity; the real trip has more stops and smaller shifts.)

Walkthrough · 2

The nudge fires — arrivals shift forward

At the first stop where the bus is warned and closing, the ease-off begins. The added dwell shifts this bus's arrival at that stop and every later stop forward in time — the accumulated delay travels with the bus for the rest of the trip.

Walkthrough · 3

The gap re-opens, headways recomputed

Because the bus now arrives a little later at each downstream stop, the gap to the bus ahead re-opens. Forward headways are then recomputed on the shifted arrival times, and it is these recomputed spacings — across every trip — that the results measure.

Kept fair by design
The reference is held fixed; two checks vary the assumptions to test robustness.
Held constant

A stable yardstick

The local-normal headway — the "normal" each ratio is measured against — is not recomputed after the ease-off. Holding it fixed means the before and after are judged against the same reference, so the comparison reflects the intervention rather than a moving baseline.

Two honesty checks

Is the benefit real, and is it fragile?

Two guards frame the result. A true-positive-only run eases off only where the model was correct, testing whether the benefit comes from genuine prediction rather than hindsight. A sensitivity sweep varies the ease-off from 4 to 16 seconds, testing whether the result balances on a knife's edge. The next figures put numbers to both.

Fig. — Counterfactual effect of the nudge
Three grouped bar charts comparing baseline against the nudge: bunching events fall about 13 percent, mean headway CV falls about 3 percent, and excess wait falls about 10 percent. Results are shown for the nudge applied on all fired warnings and on true-positive warnings only.
Counterfactual effect (simulation): bunching events, headway CV, and avoidable wait — baseline vs. the nudge on all fired warnings and on true-positives only.
The headline

About one event in eight, gone

The share of arrivals that are bunched falls from 19.4% to 17.2%, and the raw count of bunching events from 21,123 to 18,463 (−13%) — because a closing bus eased off and let the gap reopen.

The cost

≈ 10% less avoidable waiting

Avoidable wait drops from 201,978 to 182,196 passenger-minutes (−9.8%), and mean headway CV from 0.796 to 0.768.

Robustness

It's the prediction, not hindsight

Gating the ease-off to only the model's correct (true-positive) nudges gives virtually the same reduction (−12.7% bunching / −9.8% wait) — so the benefit is driven by real-time prediction, not by knowing the answer in advance.

−13%
bunching events
21,123 → 18,463
−9.8%
avoidable wait
201,978 → 182,196 pax-min
19.4→17.2%
bunching rate
(% of arrivals)
0.796→0.768
mean headway CV
(−3.5%)
Fig. — Sensitivity to the ease-off magnitude
Line chart of the simulated reduction in bunching and in excess wait against the assumed per-stop ease-off magnitude from 4 to 16 seconds. Both lines stay roughly flat — bunching reduction near 12 percent, excess-wait reduction near 10 percent.
Sensitivity of the simulated reduction to the assumed per-stop ease-off. The effect is stable across 4–16 s.
Robustness check

Not balanced on a knife's edge

Does the result hinge on one lucky choice of ease-off size? We sweep the per-stop delay from 4 to 16 seconds.

Reading the chart

Stable across the range

The bunching reduction stays in a narrow 12–13% band and the excess-wait reduction near 10% throughout. The estimate doesn't depend on a single tuned value.

Why the gain takes this shape

The benefit is variance-driven, not a faster average

𝔼[W] = h̄ / 2 · ( 1 + CV² )
Mean wait = the even-service floor h̄/2 plus a variance term (h̄/2)·CV². The nudge cuts the variance, not the floor.

Easing a closing bus off slightly delays it, so the median wait barely moves (≈ 10.7 min before and after) and ride time is untouched. What falls is the avoidable, variance-driven portion of the wait. A flat median alongside a falling avoidable wait is exactly what the renewal model predicts — internal evidence the simulation is behaving correctly, not against it.

The nudge evens spacing, not driving speed. We claim no crowding benefit and no congestion benefit — in SUNT, occupancy barely tracks the headway (r ≈ 0.09) and boardings do not rise with the gap ahead (r ≈ 0.00), so there is nothing measurable there to improve. — Discussion, “What we deliberately do not claim”
Your turn
§ Report §7 · Conclusion

A copilot, not a control room

A decentralised, advisory, zero-infrastructure copilot that predicts bunching early enough for one driver to act — reconstructing per-bus forward headways from SUNT OD data, learning online under prequential evaluation, and warning with actionable lead time. An Adaptive Random Forest gives the best imbalance-aware skill; a clearly-labelled counterfactual estimates ≈13% fewer bunching events and ≈10% less avoidable waiting.

Scroll back up and explore the simulation freely: scrub the 60-minute window, fire the nudge, toggle the corrected scenario, switch the theme. The whole page speaks the same traffic-light grammar — ok, warning, bunching.

↑ Back to the simulation
Tools & environment

The experiment — preprocessing, the streaming models, and the counterfactual — runs entirely in Python; the streaming learning itself is done in CapyMOA (a Python interface to the JVM-based MOA engine). The report toolchain is excluded here.

Streaming ML CapyMOA 0.13MOA · JVM ARF · HAT · HT · EFDT · kNN · NBADWIN drift
Data pandas 2.3NumPy 2.4PyArrow 24
Metrics scikit-learn 1.9 (metrics only)
Config PyYAML 6
Figures Matplotlib 3.10Jupyter
Runtime Python 3.12Java 11+ / JVMApache Parquet
From the author

A personal note

The author by the lake on the University of Waikato campus, holding a coffee, with a university building reflected in the water behind.
Hi, I'm Ademar 👋

It was a great opportunity to study Machine Learning for Data Streams at the University of Waikato, one of the pioneering institutions in the field and a global centre for stream-learning research.

Waikato researchers helped develop MOA (Massive Online Analysis), an influential open-source framework introduced in 2010 for building and evaluating machine-learning models on continuously arriving data. That work continues through newer tools such as the Python-based CapyMOA library.

Learning directly from researchers behind methods we were studying, including ADWIN, Hoeffding Adaptive Tree, Leveraging Bagging, and Adaptive Random Forest, was both challenging and rewarding. These were not simply algorithms presented in a textbook. They were ideas developed and refined by researchers connected to the university and the course itself.

What I valued most, however, was the opportunity to apply machine learning for data streams to a real-world dataset such as SUNT.

For our project, we built a model that continuously learned from public-transport data to predict when buses were at risk of bunching together. We then explored how those predictions could support an early-warning system that gives drivers enough time to respond.

The course was difficult, but it demonstrated what makes this area of machine learning so exciting. The goal is not simply to train a model once and improve a metric. It is to build systems that can keep learning as the world changes and potentially help solve problems that affect people every day.

100 out of 100 · full marks 🎉
Graffiti-styled grade card reading 'WE COOKED' and '100/100'. Grade 100.00 / 100.00, graded Saturday 27 June 2026, with the marker's feedback for Group 4: a very interesting and somewhat actionable topic that scored the best overall.
“Very interesting topic … this group scored the best overall.” — Marker's feedback, Assessment 3
Graffiti-styled instructor note reading 'WE COOKED / BIG W'. The Assessment 3 conclusion notes that this year's cohort produced some of the strongest submissions, that solutions will be forwarded to Profs. Ricardo and Tatiana (UFBA, who donated the dataset), and that they may get in touch to discuss potential paper publications.