Document restructuring and the beginnings of Filter 2 changes

Split the pipeline documentation by purpose so each fact has one home:
- docs/pipeline-plan.md keeps the plan, checklist, tracker, and guardrails
- docs/decisions.md holds open decisions and the dated decision log
- docs/reviews/ holds findings and tasks: one file per filter, plus
  00-cross-filter.md for findings that span filters
- scripts/common/README.md holds the shared-helper rules (formerly Phase 2)
- filter-calculations.md now describes calculations only

Filed findings 12-22 from a consistency audit of the app, docs, and scripts.

Filter 1 (Köppen-Geiger): use "Köppen" with the umlaut in all prose, labels,
docstrings, help text, and checker messages (finding 21), and correct the
base build's "majority" docstring (finding 22).

Filter 2 (annual avg temperature): record the adopted definition in
filter-calculations.md §2: equally weighted 1991-2020 monthly normals, per
WMO-No. 1203 and NOAA's 2020 methodology; area-weighted county means; blank
unless all 12 months exist. Code changes for this filter are still pending.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 16:13:11 -04:00
co-authored by Claude Opus 5
parent a9e722791d
commit e855d583e3
27 changed files with 807 additions and 242 deletions
+12 -12
View File
@@ -106,7 +106,7 @@ const KOPPEN_CLASS_META = {
Mixed: { label: "Mixed Climate", color: "#9aa3b2" }
};
// Counties with no predominant Koppen-Geiger class; see docs/koppen-mixed-display-plan.md.
// Counties with no predominant Köppen-Geiger class; see docs/koppen-mixed-display-plan.md.
const KOPPEN_MIXED_CLASS = "Mixed";
// Standard style: share of each stripe pair given to the primary (top) class; the secondary class
// gets the rest.
@@ -147,17 +147,17 @@ const MONTH_CATEGORY_META = {
const METRICS = {
koppenZone: {
label: "Koppen-Geiger Climate Class",
label: "Köppen-Geiger Climate Class",
type: "categorical",
unit: "class",
categoryMeta: KOPPEN_CLASS_META,
allOptionLabel: "All Koppen Classes",
allOptionLabel: "All Köppen Classes",
source: {
description:
"Beck et al. 2023 Koppen-Geiger maps (1991-2020, 1 km). County class shares are area-weighted. A county shows its top class when that class covers at least 50% of its land and leads the runner-up by at least 5 percentage points; otherwise it is Mixed Climate, striped in the colors of its top two classes.",
"Beck et al. 2023 Köppen-Geiger maps (1991-2020, 1 km). County class shares are area-weighted. A county shows its top class when that class covers at least 50% of its land and leads the runner-up by at least 5 percentage points; otherwise it is Mixed Climate, striped in the colors of its top two classes.",
links: [
{
label: "Beck et al. 2023 Koppen-Geiger",
label: "Beck et al. 2023 Köppen-Geiger",
url: "https://www.nature.com/articles/s41597-023-02549-6"
}
]
@@ -389,7 +389,7 @@ const COUNTY_DETAIL_METRIC_KEYS = [...DATA_METRIC_KEYS];
const METRIC_GROUPS = [
{
key: "koppenGeigerClassification",
label: "Koppen-Geiger Classification",
label: "Köppen-Geiger Classification",
metricKeys: ["koppenZone"]
},
{
@@ -733,7 +733,7 @@ function normalizeAntimeridianFeature(feature) {
});
}
// Clean and validate a Koppen-Geiger climate class code.
// Clean and validate a Köppen-Geiger climate class code.
function normalizeKoppenCode(code) {
if (typeof code !== "string") {
return null;
@@ -923,7 +923,7 @@ function sanitizeOverrideRecord(rawRecord) {
}
});
// Stripe classes for Mixed Koppen counties; blank for predominant counties.
// Stripe classes for Mixed Köppen counties; blank for predominant counties.
sanitizedRecord.koppenPrimaryClass = normalizeKoppenCode(rawRecord.koppenPrimaryClass);
sanitizedRecord.koppenSecondaryClass = normalizeKoppenCode(rawRecord.koppenSecondaryClass);
@@ -1166,7 +1166,7 @@ function getKoppenMixedStripePairWidthPx() {
return pixelsPer100Miles / KOPPEN_MIXED_LINE_PAIRS_PER_100_MILES;
}
// Return a Mixed county's primary and secondary Koppen classes, or null when it is not striped.
// Return a Mixed county's primary and secondary Köppen classes, or null when it is not striped.
function getKoppenStripeClasses(stats) {
if (stats?.koppenZone !== KOPPEN_MIXED_CLASS) {
return null;
@@ -1421,7 +1421,7 @@ function createKoppenStripePatterns() {
applyKoppenStripeStyle(getKoppenStripeStyle(map.getZoom()));
}
// Pick a county's map fill: a stripe pattern for Mixed Koppen counties, otherwise the value color.
// Pick a county's map fill: a stripe pattern for Mixed Köppen counties, otherwise the value color.
function fillForCounty(stats) {
if (currentMetricKey === "koppenZone") {
const classes = getKoppenStripeClasses(stats);
@@ -1442,7 +1442,7 @@ function buildKoppenMixedLegendSwatchBackground() {
);
}
// Format a county's metric value, naming the stripe classes for Mixed Koppen counties.
// Format a county's metric value, naming the stripe classes for Mixed Köppen counties.
function formatCountyMetricValue(metricKey, stats) {
const text = formatMetricValue(metricKey, stats[metricKey]);
const classes = metricKey === "koppenZone" ? getKoppenStripeClasses(stats) : null;
@@ -1459,7 +1459,7 @@ function shouldFeaturePassFilter(stats) {
if (currentCategoricalFilter === "all" || categoryValue === currentCategoricalFilter) {
return true;
}
// A Mixed Koppen county also matches its primary and secondary classes.
// A Mixed Köppen county also matches its primary and secondary classes.
const stripeClasses = currentMetricKey === "koppenZone" ? getKoppenStripeClasses(stats) : null;
return Boolean(stripeClasses) && [stripeClasses.primary, stripeClasses.secondary].includes(currentCategoricalFilter);
}