Bulletin 17C · appendix 10 · example 1

The Example in the Rulebook Moved

The United States has one federal standard for computing the hundred-year flood. It contains a worked example, done end to end, with the answer printed. That answer is no longer the answer.

printed in the standard, 2019

ft3/s

1 percent annual exceedance probability, table 10-5, from 68 annual peaks, water years 1947 to 2014.

versus

the same code, the record now

ft3/s

annual peaks, water years . Difference: .

The gauge is USGS 01134500, the Moose River at Victory, Vermont: a 75 square mile forested basin with a bog upstream that flattens its floods. Nothing here generalises to any other river. What generalises is the shape of the thing. A number that a floodplain map, an insurance rate and a building code all treat as a property of the place is in fact an estimate from a finite record, and it moves when the record does.

Before this page tells you what the number is tonight, it has to earn the right to. So the first thing it does is reproduce the book.

Pass one: does our code land on the published answer?

Bulletin 17C is unusually generous to anyone who wants to check it. Appendix 10 prints the input as well as the output: table 10-2 lists all 68 annual peaks with their dates, and table 10-5 lists the fitted discharge at six exceedance probabilities. So this half needs no feed at all, and it did not change when we rerouted the live half around a robots.txt. It is 68 numbers from a PDF, run through the fit, compared to 6 numbers from the same PDF.

the moments of the base-10 logs

Fitted discharge at six annual exceedance probabilities, ft3/s
AEP17C printsoursverdictwith weighted skew

Worst discrepancy across all six: percent. Published quantiles carry four significant figures, so "reproduces" here means our values round to the printed integers, and that is the only sense in which we claim it.

The last column is a real finding rather than decoration, and it is stated here as the thing we can actually show. Bulletin 17C's prose on page 109 says the analysis used a weighted skew of 0.421, blending the station's own 0.397 with a regional 0.44 borrowed from Olson and Veilleux (2014), and it points at table 10-5 for the result in the same sentence: "The estimated peak flow for selected annual exceedance probabilities can be found in table 10–5… The final estimated moments were 3.3286 (mean), 0.1403 (standard deviation), and 0.421 (weighted skew)." Run it that way and you hit of the six printed numbers. Run it with the station skew and you hit all six. So table 10-5, as printed, is not what the skew its own text names produces — and that inconsistency, not a verdict about which run made the table, is what this column establishes.

Two readings fit, and we cannot choose between them from outside. The document runs the same station twice: figure 10-2, which the prose ties to table 10-5, carries the plot annotation "EMA using weighted skew option / 0.421 = skew (G)", and figure 10-3 on the facing page — presented as a demonstration of what the weighting does — carries "EMA using station skew option / 0.397 = skew (G)". Either the printed table came from that second run, or 17C's EMA under weighted skew departs from the moment fit that reproduces every other number in the example to four figures. What reproducing a published table tells you is that the text and the table disagree; it does not tell you which of them slipped, and this page is not going to guess.

and the frequency factor itself, against a table computed in 1982

The whole fit hangs on one function: K, the number of standard deviations above the mean of the logs at which a given exceedance probability sits, for a distribution of a given skew. Bulletin 17B's appendix 3 tabulated it by hand for the Interagency Advisory Committee on Water Data. Our K is the exact Pearson Type III quantile, obtained by inverting the regularised incomplete gamma function; it has never seen the table.

tabulated values, largest absolute disagreement , which is smaller than the rounding in the printed table.

What "hundred-year" actually promises you

It is not a schedule. It is an annual probability, and probabilities compound over the length of time you stand in one place.

chance of at least one

Pass two: the same code, on the record as it stands

Eleven water years have closed since Bulletin 17C's example ended. Getting them was the hard part of building this page, and the difficulty is worth stating because it shaped the answer. USGS publishes a certified annual-peak file, and it lives on two hosts whose robots.txt tells machines not to fetch those paths. This site respects that instruction, so we do not have the certified file. What we have instead is the raw 15-minute discharge record on waterservices.usgs.gov, which serves no robots.txt at all, and our own arithmetic over it.

So these eleven values are described here exactly as what they are: the maximum of the 15-minute instantaneous record for each water year, computed by us. Not "the USGS annual peak". For a future year the two could differ, because USGS certification can draw on high-water marks and field measurements the public instantaneous series does not carry.

the control that lets us use our own arithmetic at all

Nine water years, 2006 to 2014, appear in both table 10-2 and the permitted feed. So the same annual-maximum function that produces the eleven new values can be pointed at nine years whose right answer the rulebook already printed. It is the same unmodified code path, running on the real feed, over the real domain; the only thing that changes is that for these nine we already know the answer.

water year17C table 10-2book's dateours, from the feed, 2026-08-15ours, to the quarter hour

Value and date both correct: . The verifier re-runs this against the live service on every run, so if the historical record is ever revised under us, this table is where it shows.

the eleven water years the book does not contain

water yearpeak, ft3/swhen, gauge local timeUSGS qualifier15-min readings

the fit, all six probabilities

AEP17C publishedours, 17C's 68 peaksours, tonightchange

The obvious story would be that the floods are getting bigger, and the obvious story is not supported by this record. Truncate the same fit at a run of past water years and the hundred-year flood does not climb; it wanders.

record endsn1 percent AEP, ft3/s

Six decades of data and the estimate moves a few percent in both directions. Bulletin 17C, like every standard of its kind, assumes the record is stationary: that each year's peak is drawn from the same distribution as every other year's. That assumption is doing enormous work and this page is not going to fit a trend line through eight wobbles and call it a climate signal. The honest statement is narrower and more useful: the number is an estimate with sampling noise, and one gauge cannot tell you which of those two things you are looking at.

And the old half of the record is not fixed either

One of the 68 peaks Bulletin 17C printed in 2019 is not the peak USGS serves now. We know that because of one fetch, made once, before this project had settled its own rule about which paths it may read. That fetch is quoted here and is never repeated: the route it used, api.waterdata.usgs.gov/ogcapi/v0/collections/peaks/items, is disallowed by that host's robots.txt, which is why the eleven modern peaks above had to be computed from 15-minute data instead. Nothing in any fit on this page depends on it.

two rows out of came back different, and only one of them is an edit

water year17C printed, 2019served, 2026-08-15what changed

The second row is almost certainly not an edit at all, and saying so costs us the more dramatic version of this section. Water year 2011's peak sits, in our own derivation from the 15-minute record, at 2010-10-01T23:00:00-04:00. That instant is 2010-10-02T03:00Z: the served date is the same moment rendered in UTC rather than in the gauge's local time, which is a serialisation difference and not a revision. The real edit is the 1995 value, and it stands on its own.

Every row in that file carried one and the same last-modified stamp, . That is the signature of a bulk reload of the whole table on one day in April 2026 — USGS was migrating this product to a new host in exactly that window — and it is worth being precise about what a last-modified column can and cannot establish. It says the file was rewritten wholesale. It does not say the values were: 66 of the 68 came back identical, and a stamp cannot tell a re-serialisation from a correction. What survives is still the point. A record of the past is a live document, the hundred-year flood is computed from something that is still being edited, and a page that reproduces a published number has to be able to say which edition it reproduced.

Move the record and watch the hundred-year flood move

This is the part that a printed table cannot do. Every peak in the record is a vote, and you can see how much each one is worth. The horizontal axis is a normal probability scale, so a perfectly log-normal record would plot as a straight line and the curvature you can see is the skew.

17C, 1947–2014 tonight, the closed record yours observed peaks 17C's published 95% band

Move across the plot to read any exceedance probability off all three curves.

The start slider stops at 2016 on purpose. Below about ten years the skew estimator goes wild and the 1 percent quantile can swing by a factor of several; a control that let you reach that would be showing you noise and calling it a result. The skew in use is printed below, always.

frequency factor

n

mean log

sd log

skew

1 percent AEP

Three things fall out of playing with that, and the third is the one worth carrying away.

One. An ordinary year lowers the hundred-year flood. Drag the hypothetical peak up from zero and the fitted 1 percent discharge falls for a while before it starts to rise. Adding a year of data is not neutral: a quiet year is evidence, and it pushes the tail in.

Two. One flood is worth a lot. Untick and retick the July 2024 peak: a single observation out of moves the hundred-year flood by about 300 ft3/s, which is six percent, from one afternoon in one summer.

Three, and this is the one. The estimate is times the largest flood this gauge has ever recorded ( ft3/s in years). The hundred-year flood is not something the river has done. It is an extrapolation past the entire record, and its width is not a footnote: Bulletin 17C prints a 95 percent confidence interval of to ft3/s for its own answer, a factor of from bottom to top. Our own nonparametric bootstrap on the current record, which is a different and narrower calculation, gives to . No floodplain map prints either band. The line on the map is drawn from the point estimate as if the point estimate were a fact.

The trap this page had to be built around

why the fit stops at the last closed water year

The current water year is not finished. Its running maximum is whatever the river has managed so far, and it is not an annual peak: it is a censored observation, and Bulletin 17C would handle it with perception thresholds this page does not implement. The tempting shortcut is to append it anyway so the headline moves more often. Here is what that does, computed live, right now.

appended running maxresulting skew1% AEP, exact1% AEP, Wilson-Hilfertyfitted upper bound

On 1 October, when the new water year is a few hours old and the river is running at 8 ft3/s, an unguarded build would publish a hundred-year flood of ft3/s if it used the Wilson-Hilferty frequency factor that most textbook implementations use, or ft3/s if it used the exact Pearson III quantile. Those differ by a factor of about . Both are nonsense, and they are nonsense in opposite directions, which is the part worth pausing on: a single low outlier drives the skew of the logs to about −6, and a Pearson III with a large negative skew is bounded above. The exact fit stops being an overestimate and becomes a fitted ceiling lower than a flood the river has already had. The approximation, having no such bound, goes to the moon instead. The choice of frequency factor is normally a rounding detail. Here it is the difference between two wrong answers twenty times apart.

So this page fits on closed water years only, and says so on its face. The open year appears below, labelled as what it is.

The river tonight

live from the gauge, water year , window

loading…

Asking this site's own worker for the feed. The gauge never sees you.

newest reading in payload

its age

readings returned

HTTP 200 is not freshness. This page does not trust the status code or the headers; it reads the newest timestamp inside the data and compares it to your clock. There are two thresholds and the reason there are two was measured, not assumed. The first version of this file refused anything older than three hours, on the grounds that the service posts every fifteen minutes. Then we counted the gaps between consecutive readings in this gauge's own water year, and the count is redone from tonight's payload every time this page loads: 25,241 intervals, of which 120 are eight hours long and the longest is 16.3 hours; a three-hour rule would have blanked this panel for 9.0 percent of the water year so far. (Counted on 15 August 2026; it is recounted live when the feed below arrives.) That is a refusal path firing on data that is entirely normal for this river, which is its own kind of lie. So the page flags at three hours and keeps showing the reading with its own timestamp, and refuses at twenty-four, which is longer than any gap in the current water year.

It is not longer than this gauge has ever gone quiet, and the first draft of this paragraph said it was, on a maximum measured in one water year and generalised to "ever". The older instantaneous record carries winter outages of months: across the twenty water years this page re-derives, the longest single gap is 131 days, in water year 2008, and 45- and 70-day gaps appear as recently as water years 2006 and 2015. Those are the months each winter when discharge at an ice-affected gauge is not published at all. If one opens again this panel will refuse for its whole duration, which is the behaviour we want — a gauge silent for a week is precisely what a freshness test is for — but the honest description of the threshold is "longer than anything this water year has done", not "longer than anything the gauge has ever done". The verifier measures both halves of that on every run: the gap histogram of the current water year, from the same payload this page reads, and the largest gap in each of the twenty historical water years it was already fetching for the derivation control, which is where these numbers come from.

Two honest notes on those two lines. This service sends no Last-Modified header, so the "has it moved since your last visit" question genuinely cannot be answered from the envelope, and this page will not fake an answer by substituting its own fetch time. And the headline of this page is not a number that moves hourly: fitted on closed water years, it changes once a year, at the end of September. Come back next week and the big number at the top will be the same. That is the truth about this quantity and it is printed here rather than dressed up.

A claim you can score, on 1 October 2026

The claim. Water year 2026 closes on 30 September 2026. Take its annual peak to be the maximum of the 15-minute record, as everywhere else on this page. Then one, that peak comes in below the break-even printed below, and two, the 1 percent AEP recomputed on water years 1947 to 2026 comes in below ft3/s, the value this page prints tonight.

Both halves are scored separately, because the second does not follow from the first and an earlier draft of this claim said "therefore" as though it did. The difference curve is not monotone: a peak anywhere between about and ft3/s is below the break-even and still raises the estimate, by up to ft3/s. The band is narrow, and the panel below prints this water year's peak to date against it, but it is a real way for half two to fail while half one holds, so it is named here rather than explained afterwards.

Waiting for the feed to score it.

Scoreable by anyone with the same public data and this repository. The verifier scores it on every run after the water year closes and appends the outcome, held or failed, to research/the-example-in-the-rulebook-moved/record.jsonl, which is append-only and in the repository. The panel above counts the risk from the record rather than asserting it, and the count narrows on its own as September runs out.

The check

every number above, and every free choice behind it

17C table 10-5 reproduced

from the book's own table 10-2

17B appendix 3 K values

to five decimals

derivation control

water years 2006-2014

our bootstrap, 1% AEP

4,000 resamples, seed

What we implement, stated before the number and not after

This is not an implementation of Bulletin 17C. It is the subset 17C itself says collapses to Bulletin 17B for this station, and the book says so in these words: "Because the annual peak-flow record contains only systematic peaks with no historic information, no censored peaks, and no PILFs identified by the MGBT, the fitted frequency curve using these flood frequency Guidelines is the same as that from Bulletin 17B." Systematic peaks only, no historical information, no censored values, no low outliers flagged by the Multiple Grubbs-Beck Test. Point that same code at a station where any of those applies and it will be wrong.

Everything uncertain or chosen, in one list

Run it yourself: node research/the-example-in-the-rulebook-moved/verify-the-example-in-the-rulebook-moved.mjs. It imports the same three modules this page does, recomputes every number above, re-runs the nine-year derivation control against the live service, feeds a deliberately stale payload to the freshness test to watch it fail, and appends a row to the record file.

Where the data came from, and one path we did not take

Bulletin 17C is at pubs.usgs.gov, whose robots.txt disallows only /archive/. Read 2026-08-15T00:40Z. The anchor document is at /tm/04/b05/tm4b5.pdf and is permitted.

The live half is waterservices.usgs.gov, which returns HTTP 404 for /robots.txt: no file, nothing disallowed, no crawl delay to honour. Read the same minute. Long date ranges 301-redirect to nwis.waterservices.usgs.gov, which also serves no robots.txt, so the redirect stays inside permitted ground.

The path we do not take, because this is the honest part: USGS's certified annual peak-flow product is far better suited to this page than our own arithmetic over 15-minute data. It lives at nwis.waterdata.usgs.gov, whose robots.txt is Disallow: /, and at api.waterdata.usgs.gov, whose robots.txt disallows the collections-items paths that serve it. That second file is worth quoting because it makes the decision harder rather than easier: it also contains the comment "Our data is public and you are welcome to scrape it!" and explains the rules exist "to prevent bots from unintentionally making huge numbers of inefficient requests." We think they probably would not have minded. We took the reading we would not have to argue our way out of, on a site whose whole subject is machines that read without asking. The cost was real and it is on the page: it is why the eleven modern peaks are labelled "computed here" instead of "certified", and it is why nine overlap years had to be checked before they could be used.

And the cost of our own courtesy, stated because this section quotes theirs. Re-deriving twenty water years from 15-minute data is not free: one full verifier run makes 21 requests to waterservices.usgs.gov and pulls about 46 MB, and both figures are written into record.jsonl on every run so the bill is on the record rather than in a footnote. That is a real load on a service USGS is permitted to degrade from this month, incurred to avoid a single small request to the disallowed path that would have answered the same question. We think the trade is right and we are not going to pretend it is costless.

And the part that would be easy to leave out. That decision was taken after a scouting pass had already fetched the certified file once, on 15 August 2026, while the rule was still being argued. We did not delete what it showed, because deleting an inconvenient observation is worse than having made it: it is the two-row table above, and it is the reason we can say the historical record gets edited at all. It has not been fetched since, it is not a runtime source, no fit on this page uses it, and this paragraph exists so that the sentence "we do not fetch disallowed paths" is true in the present tense rather than true-sounding.

The consequence for one row in particular: table 10-2 prints water year 1995 as 4,536 ft3/s, a four-significant-figure value in a column otherwise rounded to three, which always looked like a number that had been refined. The single fetch says it now reads 4,540. We cannot re-check that, because the instantaneous service does not reach back to 1995 and the certified file is on a disallowed host. So every fit here uses the book's 4,536, and the panel above shows what the other value would do.

The arithmetic, if you want to do it on paper

Take the base-10 logarithm of every annual peak. Compute the mean, the sample standard deviation, and the sample skew with the (n−1)(n−2) correction that Bulletin 17B specifies. Then the discharge with annual exceedance probability p is 10^(mean + K(p, skew) × sd).

K is the Pearson Type III frequency factor. For a positive skew g, a Pearson III variate is a gamma with shape a = 4/g², shifted and scaled, so K = -2/g + (g/2) × Q(a, 1-p) where Q is the inverse regularised incomplete gamma. For negative g the tail flips and the distribution acquires a hard upper bound at -2/g standard deviations above the mean, which is the mechanism behind the trap above.

Wilson-Hilferty replaces that inverse with a cube-root approximation, K ≈ (2/g)[1 + (z - g/6)(g/6)]³ - 2/g, where z is the standard normal deviate. It is excellent near zero skew and it is what most spreadsheets do. Toggle between them in the instrument above and watch how little it matters until, suddenly, it is everything.