Analyze a profile
windgram/analyze compresses one profile into a small, versioned vocabulary
of findings. Each finding is a typed statement about magnitudes, timing,
published absences, or arithmetic relationships in that document. Findings
that depend on thresholds carry the thresholds that produced them; findings
that cite hours carry the underlying values and UTC validAt instants in an
evidence block.
Findings, drawn on the document they cite
Each finding carries the thresholds that produced it and the instants it cites, so it can be drawn back onto its source hours.
A teaching windgram with the flyableWindow finding computed by analyzeProfile overlaid as a highlighted band from 14:00 to 16:00, and the day's other findings listed with their evidence values.
flyableWindow- 14:00–16:00 local, peak 2,896 m above launch at 15:00; cites 3 source hours
capTiming- verdict
capBreaks; peak CAPE 780 J/kg at 15:00 windSummary- max gust 9 m/s at 16:00 (declared hourMax)
dataCaveats- absentQuantities · derivedNullHours
Use windgram/derive when you need quantities. Use windgram/analyze when you
need a statement that remains inspectable after the full profile is no longer
in the immediate view or prompt.
windgram/compare applies one analysis threshold
set across multiple models and compares their findings.
Analyze a validated document
Section titled “Analyze a validated document”Validate a profile before passing it to the analysis API:
import { analyzeProfile, type WindgramAnalysis,} from "windgram/analyze";import { parseWindgramProfileJson } from "windgram/contract";
export function analyzeProfileJson(text: string): WindgramAnalysis { const profile = parseWindgramProfileJson(text); if (!profile) throw new Error("invalid profile");
return analyzeProfile(profile, { thresholds: { flyableWindow: { wstarMinMs: 1.0, depthMinM: 350 }, }, });}thresholds is optional. Overrides are merged by finding kind over
DEFAULT_ANALYZE_THRESHOLDS. They are conventions chosen by the caller, not
new physics, and the effective values are copied into every finding they
shape. The defaults, imported from the package at build time:
| Kind | Default thresholds |
|---|---|
flyableWindow |
W* ≥ 0.9 m/s and depth ≥ 300 m above launch |
liftCeiling |
cloud-cap margin 50 m |
capTiming |
instability from 100 J/kg; broken cap at ≤ 25 J/kg CIN with ≥ 200 J/kg CAPE; precipitation from 0.2 mm/h |
terrainMismatch |
reported from 250 m absolute delta |
windSummary |
climb band padded 200 m; persistence within 0.8 of the peak |
ensembleMembership |
band widening from ratio 1.5 |
Keep the evidence with the statement
Section titled “Keep the evidence with the statement”Narrow findings by their kind discriminant. This example prepares rows for
a teaching table while retaining the exact series and instants behind every
window.
import type { WindgramAnalysis } from "windgram/analyze";
export function windowRows(analysis: WindgramAnalysis) { return analysis.findings.flatMap((finding) => { if (finding.kind !== "flyableWindow") return [];
return [{ day: finding.day, localStart: finding.start.local, localEnd: finding.end.local, peakAboveLaunchM: finding.peakLiftTopAboveLaunchM, thresholds: finding.thresholds, evidence: { validAt: finding.evidence.hours, usableLiftTopM: finding.evidence.usableLiftTopM, thermalVelocityMs: finding.evidence.thermalVelocityMs, liftTopBandP10P90: finding.evidence.liftTopBandP10P90, }, }]; });}Do not reduce a finding to a prose label before storing its thresholds and evidence. Those fields are what make the compressed statement auditable.
The finding vocabulary
Section titled “The finding vocabulary”ANALYZE_VOCABULARY_VERSION is 3
(imported here from the package, so this page cannot lag it). Adding,
renaming, or removing a kind is an analysis-contract event even when the
profile schemaVersion remains 1.
| Finding kind | What it states | Evidence and limits |
|---|---|---|
flyableWindow |
Consecutive hours meeting the embedded W* and launch-relative depth thresholds | Carries clippedAtStart and clippedAtEnd when the window touches the document horizon |
quietDay |
A local day produced no qualifying window and which threshold floors its best hours missed | Carries the day’s peaks, failed criteria, effective thresholds, and a coverage.truncated verdict |
liftCeiling |
Whether each segment’s arithmetic ceiling is cloud-capped or sink-limited | Carries usable-lift top, cloud base, and boundary-layer top for each segment |
capTiming |
CAPE build, CIN erosion, and precipitation timing relative to a window | Emitted only for hourly deterministic documents with CIN; no ensemble-median or three-hour interpolation claim |
windSummary |
Maximum gust and climb-band wind magnitudes, timing, altitude, and persistence | Carries the document’s gust semantics and reports magnitudes without policy labels |
terrainMismatch |
Grid terrain delta and whether published lift ever arithmetically reaches surveyed launch altitude | Emitted only when launch altitude is known and the embedded mismatch threshold is met |
ensembleMembership |
Contributor-count loss and p10–p90 band-width magnitude or trend | Spread and membership are not a confidence interval or confidence score |
dataCaveats |
Absent quantity families, derived-null hours, coarse cadence, or UTC fallback | Threshold-free; absence remains “not published,” never zero |
Local time and cadence stay visible
Section titled “Local time and cadence stay visible”analyzeProfile chooses its timezone in this order:
options.timeZone, when supplied;- the profile’s optional
site.timeZone; then - UTC for an older document, with
timeZoneSource: "utcFallback"and atimesAreUtcdata caveat.
Every CitedInstant keeps both its local label and the document’s UTC
validAt, so a finding can join back to the source hour. The analysis envelope
also reports stepHours; cadence-sensitive findings gate themselves rather
than inventing timing between published instants.
Every finding day uses the exported LocalDayKey string type. Compute scene
day windows and analysis with the same timezone so midnight does not split one
local day across two keys.
resolveAnalyzeThresholds(overrides) returns the complete threshold set used
by analyzeProfile and compareProfiles.
Choose a payload
Section titled “Choose a payload”Findings serialize three ways: the full array (every finding with evidence), a filtered subset (only the kinds a surface presents), or a single finding’s evidence object. Measure the serialized result against the consuming surface’s actual input budget — a chat context, a webhook body, a UI panel — rather than assuming the full array fits; evidence dominates the byte count, and filtering by kind before serializing is usually the right first cut.