Runoff and green infrastructure reduction, for small sites
The curve number is not the hard part.
Defending it is. You draw a boundary, and CurveNumber reads the soils, the land cover, the slope and the design rainfall out of the public datasets, crosses them, and computes the reduction between the existing and the proposed condition. Then it prints, beside every figure, either a citation with an edition, an assumption with what it is worth in curve number units, or the name of the person who changed it and the reason they gave. Where nothing can honestly be derived, it says so and withholds the number rather than filling the gap.
It is built for the civil engineer or site planner who will seal the drawing and then sit across from a reviewer who is paid to doubt it.
Two sites free, then USD 100 per site, charged the first time a site is computed. No subscription. The quick check needs no account at all.
app/src/cnapp/report.py. The identifier is a hash
of the run and excludes the generation time, so two printouts of the same run carry the
same identifier and a reviewer holding both can tell at a glance.What is actually being sold
Every quantity is one of three things, and it says which
Nothing in this engine is a bare number. A quantity carries its magnitude, its unit and how it came to be, and construction fails if that is missing. A reviewer's question is always some version of "where did this come from", so the answer is attached to the figure rather than reconstructed afterwards.
Retrieved from a published source
The citation carries an edition, because TR-55 1986 and TR-55 1975 disagree and a citation without an edition cannot be checked. A derived value with no edition on its source raises rather than saving.
Tables 2-2a to 2-2d
An assumption, priced in the answer
Each default names a registry entry, and the entry states what the assumption is worth in curve number units or in inches of runoff, and what evidence would displace it. Adjectives are not accepted there.
worth 5 to 12 CN units
Changed by a person, with a reason
Who, when, the prior value and the reason given, written to an append-only record that cannot be edited afterwards. The published value stays visible next to the attested one, because the arrow between them is the record.
soil boring log, 2 borings, tested
An override is not a text box
The interface states what the choice is worth before it is made, asks for a reason in proportion to that figure, and names the account the sentence will be filed against. An engineer who has to write "because the software said B" tends to go and look instead, which is the entire intent.
The soil survey rates no hydrologic group for a large share of urban land, which is the ordinary case on the sites this product is for. That is not a blank to fill with B. It is a question, and it goes to the person who can answer it.
The part that is unusual
It refuses, in writing, rather than filling the gap
Any tool can put a number in every cell. The failure mode that costs an engineer their afternoon in a review is the number that looks entirely reasonable and is quietly wrong, and it is nearly always produced by something being filled in silently: an unrated soil map unit read as B, a pond given CN 98, a dual soil group collapsed to one letter.
So the engine separates the states that have different remedies and keeps them separate all the way to the screen. A service being down and a site being outside the survey are not the same problem and are never the same message. Where a material share of a site is unresolved, no headline number is produced at all.
The threshold is a tenth of the site, and it is recorded as a judgement rather than a finding. At a twentieth unresolved, the compounded error on a site whose gap is really group D is already 10.6 percent of the design volume, so a tenth is not the point where the gap becomes harmless. It is the point where warning stops being a proportionate response.
segmentation.py, MATERIAL_GAP_FRACTION = 0.10, with the arithmetic in the comment above it
What a refusal reads like
TR-55 has no open water row and open water is not a rainfall to runoff transformation. A pond is a storage element: what leaves it during a storm is set by its stage, its surface area and its outlet, none of which a curve number represents. The common assignment of 98 or 100 asserts that the whole surface rainfall leaves instantly, which on a site otherwise at CN 69 on group B with a pond over a tenth of the area moves the answer from 0.670 to 0.880 in at P = 3 in, and from 0.019 to 0.116 in at P = 1.2 in.
Supply instead: the water body as a routing element with a stage, area and discharge relationship, or its area excluded from the curve number computation with the exclusion stated in the report.
crosswalk.py, REFUSED[11], quoted in full. Eight land cover classes are refused this way, each with its own reason and its own remedy.
The defaults registry
An assumption that states its own price
Ninety-eight values in this engine are defaults rather than anything read off your site. Every one of them is written down in a registry, classified by what would have to happen for it to change, and carries two fields that turn a lookup into guidance: what it is worth in the answer, in computed figures rather than adjectives, and what evidence would displace it.
Fifty-four of them say no external source exists on their face, because nobody publishes the figure at all. Seven more say the source exists and was not checked, which is a different admission with a different remedy, and the two are never merged. The registry is meant to be published free and citable.
That is also how the interface can be quiet. A user shown thirty defaults confirms none of them, so the engine ranks its own assumptions by what they are worth on this site and surfaces the two or three that move the answer.
Runoff at P = 3 in between reading a B/D soil as drained and as undrained. 0.365 in against 1.250 in. The engine reports B/D as B/D and records the resolution as its own assumption, because collapsing the two would make "the map says D" indistinguishable from "the map says B/D and we assumed no drainage".
How high a composite curve number runs on NLCD class 22 if the TR-55 residential row is used as the pervious cover and the measured impervious share is added on top of it. That row already contains 38 percent impervious, so the share is counted twice. Nothing about the answer looks wrong.
Initial abstraction at CN 98, against 1.279 in at CN 61. Which is why the depth criterion in this engine is the storm over the segment's own initial abstraction, and not an absolute number of inches, which would be wrong in both directions at once.
registry.py and crosswalk.py. Every runoff figure in those files is computed with cn.runoff_depth at lambda = 0.20 on the tabulated curve numbers, and pinned against the code in tests/test_registry_arithmetic.py
Where the inputs come from
Public data, named, dated and cached
You supply the boundary and the design storm. Everything below is retrieved, and each retrieval is recorded with its URL, its status, the timestamp and a hash of the response body, so a figure in a report traces to a specific server response on a specific day.
The boundary is drawn on the map, or edited vertex by vertex, or cut with a hole where a building is excluded. The enclosed area is computed on the ellipsoid and shown while you draw, because an acreage that appears only after you commit is an acreage nobody checks.
engine/src/curvenumber/sources/. These are unfunded public services, so every response is cached to disk by request, and the test suites run offline against a recorded fixture set rather than reaching them.
Read this before you buy anything
Where this actually is
This is pre-launch software. It has never had a paying customer, and the version number is 0.8.0. If a page like this one is going to argue that provenance is the product, it has to be candid about its own, so here is what is not finished.
The published curve number tables and the rainfall distributions in this engine were
transcribed by one person and checked by nobody. The code records that as
CHECKED_BY = None and a test asserts it stays honest, so every report
that uses them prints "checked by NOBODY" beside the citation rather than leaving a
reviewer to assume otherwise.
Two specifics. The cover table shipped is a 22 row seed subset of TR-55 Tables 2-2a to 2-2d, not the whole of them. And one row of the antecedent runoff condition table is under active suspicion: its local gradient does not match its neighbours in a way no smooth published relation would produce. It is left exactly as transcribed, with the doubt recorded in an open test, because the remedy for a transcription doubt is a person with the published document in front of them and not a guess from here.
engine/src/curvenumber/tables.py and distributions.py, and tests/test_published.py::TestTheArcTableIsAnOpenItem
An unchecked transcription is a real defect, and the reason to say so on the front page rather than in a footnote is that it is exactly the sort of thing that gets discovered by a reviewer instead. Every figure this product prints requires independent verification in any case. What the product is for is making that verification cheap: the citation, the edition and the assumption are already next to the number.
Also not finished
- Peak discharge is not implemented. Before it is, the lag to time of concentration relation has to be verified against the primary source. The engine computes volumes and depths, routes a hydrograph through a practice, and does not give you a peak flow for a pipe sizing.
- The coverage is the conterminous United States, because the datasets are. Land cover classes that only occur in Alaska are refused by name rather than mapped to something that looks plausible.
- One metre land cover is resolved but not read. The Chesapeake Conservancy assets are located and identified; each is a whole county at 143 MB and reading them needs a range reader that is not written.
- Payment is not wired. There is no checkout yet. How you pay today is on the pricing page, and it involves a human being.
- The assistant ships switched off. There is an optional assistant that answers questions about a site you have computed, and it is off by default on every deployment. It may never produce a number: every figure it states has to be one the record already holds, every citation has to be something it retrieved in the same turn, and a response that breaks either rule is blocked before you see it, by a deterministic check rather than by asking the model nicely. What it has not had is the expert review panel that would let anyone claim it is correct, and its own evaluation report says so in its own words. Where it is switched on it sends your site's record to a commercial model vendor, which the privacy page describes item by item.
README.md, "Honest gaps", which the codebase keeps current
Price
Two sites free, then USD 100 per site
A site is one boundary with its existing and proposed conditions, and it is charged the first time it is computed rather than when it is created, so you can set one up, look at what the public data says about it, change your mind and pay nothing. Deriving the public data is free and does not consume anything. There is no subscription, no seat count and no annual commitment.
The stateless quick check is free and needs no account. Nothing about it is stored, which also means it carries no audit trail, and the audit trail is the part a reviewer relies on.
A report, in HTML and as a JSON companion generated from the same build so the two cannot disagree, holding the reduction and its composition, the segment table with the citation and origin on every value, the ranked assumptions with their worth and what would displace them, every departure from a default with who made it and why, the state of each public service at the time of the run, and the identifiers needed to reproduce it.
Retention and filtration are reported separately and are never added together anywhere in it, because many jurisdictions credit the first and not the second.