Skip to content

Compare model profiles

windgram/compare compares validated profiles for one site. It analyzes every document with one timezone and threshold set, then compares the resulting findings.

Two models, one verdict, evidence attached

Two synthetic profiles can express the same daytime development at different teaching hours, making timing differences visible without treating either profile as correct.

Two controlled profiles with the same daytime development at different hours, rendered side by side, with the windowAgreement finding compareProfiles computed from them: 2 voters, unanimous true.

timing-earlierEarlier development

w* m/s 3 0 900m 2953ft 1599m 5245ft 2298m 7538ft 2996m 9831ft 3695m 12123ft 4394m 14416ft 12 13 14 15 16 17 launch 1050 m

window 12:00–17:00 · peak at 15:00 · start clipped · end clipped

timing-laterLater development

w* m/s 3 0 900m 2953ft 1599m 5245ft 2298m 7538ft 2996m 9831ft 3695m 12123ft 4394m 14416ft 12 13 14 15 16 17 launch 1050 m

window 14:00–17:00 · peak at 17:00 · end clipped

windowAgreement
2 of 2 voters report a window — unanimous; start spread null (clipped edges abstain)
heightSpread
0 m between peaks the models place at 15:00 and at 17:00 — the same height, two hours apart, stated without a consensus
Charts and verdict are computed at build time from the committed comparison pair (re-slugged so each document keeps its own analysis). An edge clipped by a document's horizon reads as “open since at least” and stays out of the timing spread — which is why this pair's start spread is reported as null rather than a number no model stated.Units time UTC · heights m MSL · W* m/s
compare-profiles.ts
import type { WindgramProfile } from "windgram/contract";
import { compareProfiles, type WindgramComparison } from "windgram/compare";
export function compareSite(
profiles: readonly WindgramProfile[],
timeZone: string,
): WindgramComparison {
return compareProfiles(profiles, {
timeZone,
thresholds: {
flyableWindow: { wstarMinMs: 1.0, depthMinM: 350 },
},
unavailable: [{ model: "nam", miss: "absent" }],
});
}

Every profile must carry the same site.id. The function throws for an empty input or mixed sites. The output records the resolved thresholds and retains each model’s source analysis under analyses.

Each ComparisonMemberLedger states the facts that affect comparability:

Field Meaning
kind Deterministic or ensemble document shape
referenceTime / runAgeHours Run identity and age relative to the newest member
stepHours / hours Forecast cadence and published horizon
modelElevationM / elevationDeltaM Grid terrain and its launch-relative difference
benched A terrain mismatch whose published lift never reaches launch

Transport misses belong in options.unavailable, preserving the expected model roster even when a document is absent or invalid.

comparison-findings.ts
import type { WindgramComparison } from "windgram/compare";
export function comparisonRows(comparison: WindgramComparison) {
return comparison.findings.map((finding) => {
if (finding.kind === "windowAgreement") {
return {
day: finding.day,
windows: finding.windows.map((vote) => vote.model),
quiet: finding.quiet.map((vote) => vote.model),
abstained: finding.abstained,
unanimous: finding.unanimous,
startSpreadHours: finding.timing.startSpreadHours,
endSpreadHours: finding.timing.endSpreadHours,
};
}
return {
day: finding.day,
spreadM: finding.spreadM,
peaks: finding.peaks,
};
});
}

windowAgreement counts qualifying windows and complete quiet days as votes. A truncated quiet day abstains. Window edges clipped by the document horizon stay out of the corresponding timing spread.

heightSpread lists each model’s launch-relative peak and the difference between the highest and lowest. It does not create a mean or consensus height.

COMPARE_VOCABULARY_VERSION — currently 1, imported here from the package — versions these finding kinds independently of the published profile schemaVersion. Downstream publishers choose weighting, display language, and operational thresholds.