AI agent examples

Sample prompts and parsing recipes for the /near/<city>/snapshot.json endpoint. Each city snapshot exposes a bestTonight.tieBreak object and a closestSites[].tieBreak array — the exact metrics our 7-level ranking ladder uses, so agents can re-rank, filter, or explain decisions to users.

Endpoints:

1. The tieBreak shape

Every entry in closestSites[] and the top-level bestTonight contains the same tieBreak block:

{
  "verdict": "Excellent" | "Good" | "Fair" | "Poor" | "No-go",
  "verdictRank": 5 | 4 | 3 | 2 | 1,
  "windowHours": 0,            // hours in the longest sustained clear window tonight
  "windowMeanCloudPct": 12,    // mean cloud % inside that window (null if no window)
  "tonightMinCloudPct": 8,     // min cloud % across the whole night
  "tonightMeanCloudPct": 35,   // mean cloud % across the whole night
  "bortle": 2,                 // site Bortle scale (1 = darkest, 9 = inner city)
  "distanceKm": 66.5
}

Sort priority (descending importance) — also published as tieBreakLadder:

#FieldDirectionWhy
1verdictRankdescExcellent skies always beat Poor.
2windowHoursdescLonger clear windows = more usable time.
3windowMeanCloudPctascCleaner skies inside the window.
4tonightMinCloudPctascBest moment of the night.
5tonightMeanCloudPctascOverall sky quality.
6bortleascDarker sites win at parity.
7distanceKmascCloser wins last.

2. Sample prompts

Prompt "Where should I go stargazing near Toronto tonight?"

User: Where should I go stargazing near Toronto tonight?

Recipe: Fetch /near/toronto/snapshot.json, read bestTonight, narrate tonight.verdictReason and tieBreak.windowHours.

const snap = await fetch("https://ontarionightsky.com/near/toronto/snapshot.json")
  .then(r => r.json());

const b = snap.bestTonight;
console.log(
  `Best bet: ${b.name} (${b.distanceKm} km, Bortle ${b.bortle}).`,
  `${b.tonight.verdict} — ${b.tonight.verdictReason}`,
  b.tieBreak.windowHours
    ? `Clear window: ${b.tieBreak.windowHours}h @ ${b.tieBreak.windowMeanCloudPct}% cloud.`
    : `No sustained clear window tonight.`
);

Prompt "Show me only sites with at least 3 hours of clear sky."

User: Only show me sites near Ottawa with at least 3 hours of clear sky tonight.
const snap = await fetch("https://ontarionightsky.com/near/ottawa/snapshot.json")
  .then(r => r.json());

const usable = snap.closestSites.filter(s => s.tieBreak.windowHours >= 3);
usable.sort((a, b) =>
  b.tieBreak.windowHours - a.tieBreak.windowHours ||
  a.tieBreak.windowMeanCloudPct - b.tieBreak.windowMeanCloudPct
);

Prompt "I want the darkest site, even if it's farther."

User: Find me the darkest Bortle site near Hamilton with a clear window tonight.
const snap = await fetch("https://ontarionightsky.com/near/hamilton/snapshot.json")
  .then(r => r.json());

const candidates = snap.closestSites
  .filter(s => s.tieBreak.windowHours > 0)
  .sort((a, b) =>
    a.tieBreak.bortle - b.tieBreak.bortle ||      // darker first
    b.tieBreak.windowHours - a.tieBreak.windowHours
  );

const pick = candidates[0];

Prompt "Explain why X was picked over Y."

User: Why did you pick Long Sault over Torrance Barrens tonight?

Walk the ladder. The first field where they differ is the answer.

function explain(a, b) {
  const ladder = [
    ["verdict",            (x, y) => y.verdictRank - x.verdictRank],
    ["window length",      (x, y) => y.windowHours - x.windowHours],
    ["cleaner window",     (x, y) => x.windowMeanCloudPct - y.windowMeanCloudPct],
    ["lower min cloud",    (x, y) => x.tonightMinCloudPct - y.tonightMinCloudPct],
    ["lower mean cloud",   (x, y) => x.tonightMeanCloudPct - y.tonightMeanCloudPct],
    ["darker (Bortle)",    (x, y) => x.bortle - y.bortle],
    ["closer",             (x, y) => x.distanceKm - y.distanceKm],
  ];
  for (const [reason, cmp] of ladder) {
    const d = cmp(a.tieBreak, b.tieBreak);
    if (d !== 0) return `${a.name} won on ${reason}.`;
  }
  return "Tied across all metrics.";
}

3. Aggregate scan — "Best city in Ontario tonight"

User: Of all major Ontario cities, which has the best stargazing tonight?
const agg = await fetch("https://ontarionightsky.com/near/snapshot.json")
  .then(r => r.json());

// Each city's `best.verdict` reflects its top-ranked nearby site.
const ranked = agg.cities
  .filter(c => c.best?.verdict)
  .sort((a, b) => {
    const order = { Excellent: 5, Good: 4, Fair: 3, Poor: 2, "No-go": 1 };
    return (order[b.best.verdict] || 0) - (order[a.best.verdict] || 0);
  });

// For full tie-break detail, fetch the per-city snapshot:
const detail = await fetch(ranked[0].json).then(r => r.json());

4. Detecting schema changes

Before parsing, check snapshotMeta.tieBreakVersion. If it changes, re-validate your assumptions against snapshotMeta.changelog[]:

const EXPECTED_TIE_BREAK = "2.0.0";

if (snap.snapshotMeta.tieBreakVersion !== EXPECTED_TIE_BREAK) {
  const recent = snap.snapshotMeta.changelog
    .filter(c => c.type === "tieBreak")[0];
  console.warn(`Ladder changed → ${recent.version}: ${recent.summary}`);
}

5. Field cheatsheet

PathTypeNotes
bestTonight.tieBreakobjectMetrics for the chosen site.
closestSites[].tieBreakobjectSame shape, one per site.
tieBreakLadderstring[]Human-readable sort order.
snapshotMeta.tieBreakVersionsemverBumps when the ladder changes.
snapshotMeta.metricsVersionsemverBumps when fields are added/removed.
snapshotMeta.changelog[]array{version, date, type, summary}