Task 1
Buber Fuel
Buber, a cab company, has hired a team of vibe coders to train a model that predicts the fuel a ride burns from the car, the trip distance and a bunch of other parameters. Buber is large, so its ride history is huge, and the vibe coders, being vibe coders, tried pd.read_csv() on it and failed miserably. You are the adult in the room. Build the service they query instead. There is no frontend: package it as a Docker container, push it to GitHub and submit the repository here. Our server builds it, runs it in a sandbox with the real dataset, hits it with a burst of queries and gives you a verdict.
1. What to build
A query holds the quantitative description of a ride, nothing else: no ride id, no pickup time, no driver, no zones. Your service returns the fuel burned (litres) by a ride in the dataset that matches the query, within the error the query allows. It is guaranteed that at least one ride in the dataset matches every query exactly.
POST /fuel HTTP/1.1
Content-Type: application/json
{"distance_km": 12.35, "engine_cc": 1600, "fuel_type": 0,
"vehicle_weight_kg": 1250, "avg_speed_kmh": 41.5, "idle_min": 3.2,
"ambient_temp_c": 27, "ac_on": 1, "passengers": 2, "gradient_pct": 0.4,
"max_error": 0.05}
HTTP/1.1 200 OK
Content-Type: application/json
{"fuel_l": 1.874}- All ten features are numbers. Missing or non-numeric feature, or a body that is not JSON:
400. max_erroris optional and is a fraction:0.05means the answer may be off by 5%. Without it, assume 5%.- Ignore fields you do not recognise.
- Your answer is correct if it is within
max_errorof the fuel of any ride that matches the query. You may answer from a similar ride, from your own estimate, or from a formula you worked out. We do not care how.
The features
| Field | Meaning | Query precision |
|---|---|---|
distance_km | trip distance | 2 decimals below 10 km, 1 decimal below 100 km, whole km from 100 |
engine_cc | engine size | multiples of 100, as in the file |
fuel_type | 0 petrol, 1 diesel, 2 CNG, 3 hybrid | as in the file |
vehicle_weight_kg | kerb weight | nearest 10 kg |
avg_speed_kmh | average speed | nearest 0.5 km/h |
idle_min | minutes spent idling | nearest 0.1 min |
ambient_temp_c | outside temperature | nearest 1 °C |
ac_on | air conditioning, 0 or 1 | as in the file |
passengers | 1 to 4 | as in the file |
gradient_pct | average road gradient | nearest 0.1 % |
The dataset keeps more digits than a query does. A query is the ride rounded to the precision above (halves round up), so a ride in the file is matched by a query when its features round to the query's. Several rides can share one query and then any of their fuel values is acceptable.
The dataset
A fixed ride log of roughly 30 million rows (about 2.5 GB of CSV). It is not in your repository and you cannot download it. A random sample of about 300,000 rows comes from the same log: rides_sample.csv (24 MB). Columns:
ride_id,pickup_ts,driver_id,start_zone,end_zone,distance_km,engine_cc,fuel_type,vehicle_weight_kg,
avg_speed_kmh,idle_min,ambient_temp_c,ac_on,passengers,gradient_pct,fuel_l- Sample rows are never used as queries when you are judged, so a service that only knows the sample will not get far. The sample is for experimenting, not for answering.
- Queries are drawn uniformly over the whole log, so caching answers does not help.
- Your code must read the dataset from the path in the environment variable
DATASET_PATH. Never hard-code a file name. In your experiments it points at the sample; at judging time we mount the full file and setDATASET_PATHto it. The same image runs in both cases. Mind the 512 MB: the full file is several times larger than your RAM. - The starter kit has a Dockerfile, a tiny working server, a local accuracy checker and
docker-example.sh, which shows exactly how the sample (dev) and the full dataset (judge) are mounted.
How the judge talks to you
After a few single-request checks, the judge sends 2,000 POST /fuel queries inside 1 second. It does not wait for answers before sending the next query, so a slow server simply falls behind. Each query carries its own max_error:
| Share of queries | max_error |
|---|---|
| 70% | 5% |
| 20% | 2% |
| 10% | 1% |
Accuracy and speed pull against each other: scanning for the closest rides is exact but slow, guessing is instant but sloppy. Pick your balance. Answers are only verified once the burst is over. You have 2 CPUs and 512 MB.
2. Container contract
| Dockerfile | A file named Dockerfile at the repository root. The whole service runs from this one image. |
|---|---|
| Port | You choose the port. Declare it with exactly one EXPOSE <port> line in your Dockerfile and listen on 0.0.0.0 at that port. localhost-only binds are unreachable. |
| Dataset | Read-only file at the path in $DATASET_PATH. Set a default for it in your Dockerfile (ENV DATASET_PATH=/dataset/rides_sample.csv) so your experiments work; the judge overrides it. In the final no-dataset run the file is missing: do not crash on that. |
| Health | GET /healthz must return 200 within 4 minutes of the container starting. That is your budget for reading or indexing the data. Traffic only starts once it is healthy, and a fast start earns a bonus. |
| Network | The build step has internet access (install your dependencies there). At runtime there is no outbound network. |
| Filesystem | Read-only root. /tmp and /data are writable (64 MB each, wiped on exit). The full dataset is not baked into your image and is the only thing mounted at $DATASET_PATH. |
| Resources | 512 MB RAM, 2 CPUs, 256 processes. Linux capabilities are dropped, so run as a non-root user if you can. |
| Limits | Repository up to 300 MB, build up to 10 minutes, image up to 1 GB. |
Minimal Dockerfile shape
# Any base image works: node, python, go, java...
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir -r requirements.txt # the build has internet access
USER nobody
EXPOSE 8080
# Experiments: the sample. The judge replaces this with -e DATASET_PATH=<full dataset>.
ENV DATASET_PATH=/dataset/rides_sample.csv
CMD ["python", "-u", "server.py"]3. How to submit
- Push your project to a GitHub repository.
- Register your team (once) and log in.
- Open Submit, paste the repository URL, optionally a branch or tag (default branch if empty).
- Private repository? Create a fine-grained personal access token limited to that one repository with "Contents: Read-only" and paste it in the token field. It is encrypted at rest and deleted as soon as judging finishes.
- Watch the live status page. You can resubmit after each result (short cooldown applies, one submission at a time per team).
4. How you are judged
Every submission goes through the same automated pipeline:
- Clone your repository at its latest commit (the commit hash is recorded with your result).
- Build the Docker image.
- Run it in an isolated sandbox with no outbound network and the full dataset mounted.
- Check the contract with a handful of single requests: a valid query, a missing feature, a bad body.
- Hit it with a burst of 2,000
POST /fuelqueries in one second, record every answer, then check each one against the rides that match its query. - Run it again, with no dataset. The same image is restarted with
DATASET_PATHpointing at a file that does not exist, and gets 1,000 more queries (half of them withmax_error1%). Whatever you can answer then, you can answer without reading the log. - Score the checks below and issue a verdict.
Verdicts
| PASSED | Every required check passed. Your score shows how many optional and bonus checks passed too. |
| FAILED | Your app ran, but at least one required check failed. |
| START_FAILED | The container exited, crashed, ran out of memory, or never answered /healthz. |
| BUILD_FAILED | No Dockerfile at the root, docker build failed, or a limit was exceeded. |
| CLONE_FAILED | We couldn't fetch the repository (bad URL or branch, or missing token for a private repo). |
| ERROR | A problem on our side. Resubmit, and tell an organizer if it repeats. |
Scoring rubric
Total 148 points. Checks marked required must all pass for a PASSED verdict. The Model track is the hard part and is worth the most: a service that only looks answers up in the dataset cannot earn those points, so a perfect score is only reachable by another route. The sample is a window into how the whole log behaves.
| Check | What we verify | Points |
|---|---|---|
| Container | ||
| Health endpoint required | GET /healthz returns 200 within 4 minutes of the container starting. That is your time budget for reading or indexing the dataset. | 4 |
| Service | ||
| POST /fuel works required | Before the burst, sequential calls are checked: a valid query returns 200 and application/json with a positive numeric "fuel_l" within 15 s, a query with a missing or non-numeric feature returns 400, malformed JSON returns 400. If this fails the burst is skipped. | 8 |
| Survives the burst required | At least 99% of the 2,000 burst requests get a well-formed answer, and the last answer is back within 3 s of the first request. | 10 |
| Meets the 5% error budget required | At least 95% of the queries that allow 5% error are answered within 5% of the fuel of a ride that matches the query. | 20 |
| Zero failed requests | Not a single request errored, timed out, was refused or came back malformed. | 5 |
| Bounded tail latency | 99th percentile latency, measured from each request's scheduled send time, is under 2000 ms. | 5 |
| Accuracy bonus | ||
| Meets the 2% error budget | At least 95% of the queries that allow 2% error are answered within 2%. | 12 |
| Tight-error bonus (1%) | At least 95% of the queries that allow only 1% error are answered within 1%. | 10 |
| Speed bonus | ||
| Quick start | /healthz is 200 within 15 s of the container starting (needs the 5% budget met). | 10 |
| Fast | Every answer is back within 1.5 s of the first request (needs the 5% budget met). | 8 |
| Near real time | Every answer is back within 1.2 s of the first request, barely after the last one was sent (needs the 5% budget met). | 8 |
| Snappy | 99th percentile latency is under 100 ms (needs the 5% budget met). | 8 |
| Model track | ||
| No dataset: 5% and 2% budgets | The judge restarts your image with no dataset at all (DATASET_PATH points at a file that does not exist) and sends 1,000 more queries. At least 95% of both the 5% and the 2% queries must still be answered within their budget. | 15 |
| No dataset: 1% budget | In the same no-dataset run, at least 95% of the queries that allow only 1% error are answered within 1%. Half of that run's queries are 1% queries, and nothing you can look up in the original data gets you there. | 25 |
5. Common mistakes
- Server listens on
127.0.0.1, or on a different port than the one in yourEXPOSEline. - Hard-coding
rides_sample.csvinstead of reading$DATASET_PATH. - Loading the whole file into memory. It works on the sample and gets your container killed on the real thing (exit code 137).
- Starting to answer before you are ready, or taking so long to start that you miss the health budget.
- Rounding queries differently from the rules above, so your lookups miss.
- Writing files outside
/tmpor/data(the root filesystem is read-only). - Crashing or refusing to start when
DATASET_PATHdoes not exist. The last run has no dataset. - Trying to fetch data over the network at startup. There is no network at runtime.
Fair play. Submissions run in a sandbox and are judged by their observable behavior. Attempts to detect or tamper with the judge, escape the sandbox or attack the portal lead to disqualification.