Skip to content

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.

Pressure kPa 90.3 90 Precip mm/h 0.5 0 Cloud % 100 0 H M L Layers % w* m/s 3 0 CAPE J/kg 1500 0 900m 2953ft 1668m 5472ft 2436m 7991ft 3203m 10510ft 3971m 13029ft 4739m 15548ft 10 11 12 13 14 15 16 17 18 19 11° 16° 23° 26° 24° 17° 13° launch 1050 m G7 G9 G11 G14 G22 G29 G32 G22 G16 G13 10° 20°
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
The band and every number below are computed at build time by analyzeProfile on this committed profile with the default thresholds (W* ≥ 0.9 m/s, depth ≥ 300 m above launch). Nothing in this figure is hand-written.Units time UTC · heights m MSL · W* m/s

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.

Validate a profile before passing it to the analysis API:

analyze-profile.ts
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

Narrow findings by their kind discriminant. This example prepares rows for a teaching table while retaining the exact series and instants behind every window.

window-rows.ts
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.

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

analyzeProfile chooses its timezone in this order:

  1. options.timeZone, when supplied;
  2. the profile’s optional site.timeZone; then
  3. UTC for an older document, with timeZoneSource: "utcFallback" and a timesAreUtc data 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.

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.