To test course-ranking methods against an answer I already knew, I built a simulated elite field for the six World Marathon Majors, 2015 to 2024, and planted a time penalty on each course. The within-runner paired comparison found them best: it read Boston as 101 seconds slower than Berlin against a planted 105, and missed no course by more than 7.3 seconds.
From outputs/analysis_results.csv (estimates) and src/build_data.py (planted penalties).
Bootstrap 95% intervals are about ±2 s on every course. The estimate falls short of the planted value on all five courses.
Minetti et al. (2002) energy cost of running, integrated over each course's elevation profile. It uses no race results. It puts Boston 89 s faster than Berlin, because of the 136 m net descent.
42,567 same-athlete pairs of Majors run within 18 months, divided by a weather multiplier, then least squares for one offset per course with Berlin fixed at zero; 2,000 bootstrap resamples.
Each edition's top-10 average divided by a Maughan / El Helou style heat, humidity and wind multiplier, averaged over years. Boston +100.1 s, New York +80.3 s; worst miss 11.1 s (Tokyo).
A weighted composite (0.15, 0.50, 0.35) gives a Course Difficulty Index with Berlin at 1.000. Boston comes out at 1.0092 and New York at 1.0089: the elevation model's negative Boston number pulls the two almost level.
The race results and athletes are simulated, so none of these numbers measures the courses themselves. The test is also easier than race data would be, because the weather correction uses the same curve that generated the times. Whether Boston is 105 s slower than Berlin, or faster as the elevation model says, this data can't tell.