Skip to main content
Glama

find_repeat_sites

Read-only

Rank the archive's most repeat-imaged sites — where change detection has something to measure.

The discovery step *before* every analysis verb. ``change`` / ``timescan`` /
``stack`` / ``pick_change_interval`` / ``narrate_change`` all answer *what*
changed at a site, but each assumes the caller already knows *which* site to
point them at. Umbra files every pass of an area under one task (site), so a
site's coverage is just how many dated passes share its task; the sites with
the most are exactly the ones where a time series exists to analyse. This
tool ranks them, so ``find_repeat_sites → pick_change_interval →
narrate_change`` is a complete chain a model can drive with no site known in
advance — the deterministic answer to "where is there something to look at?"
(the discovery moat; no model is called). Reach for this
(or ``search_catalog(area=..., fuzzy=True)``) before ``semantic=True`` when
the site name is roughly known.

The filters scope the *pool* of acquisitions the ranking is computed over,
and are the same ones ``search_catalog`` takes: ``bbox``
(``[min_lon, min_lat, max_lon, max_lat]`` WGS84), ``intersects`` (a GeoJSON
polygon, mutually exclusive with ``bbox`` / ``place``), ``place`` (free-text,
geocoded to a bbox), ``area`` (a task-name substring, loosened by ``fuzzy``),
``start`` / ``end`` date bounds, ``products`` (GEC / SICD / SIDD / CPHD), and
the SAR property filters ``polarizations`` / ``min_incidence`` /
``max_incidence`` / ``max_resolution``. ``top`` caps how many sites are
returned, and ``min_passes`` is how many passes a site needs to qualify (2 is
the minimum a change composite can use; raise it to find only deeply-revisited
series), counted on the depth ``rank_by`` measures -- raw dated passes by
default, the usable (comparable) series' depth under ``rank_by="comparable"``.

``rank_by`` chooses the order: ``"passes"`` (the default) ranks by raw pass
count; ``"comparable"`` ranks by *analysable* depth instead -- the
``comparable_passes`` largest same-polarization dated subset a change verb can
actually difference -- so a deep single-polarization series is not outranked by
a broader mixed-polarization site whose passes no analysis verb can compare, and
``min_passes`` then floors that same usable depth, so ``rank_by="comparable",
min_passes=3`` returns only sites whose differenceable series is at least three
passes deep. The two agree when every dated pass of every site shares one
polarization; prefer ``"comparable"`` when the next step is
``pick_change_interval`` / ``stack_stats``, which consume exactly that
differenceable subset. Three *temporal* orders rank on the axes the filters below
select on: ``"recency"`` orders by each site's newest dated pass (the
still-active site to monitor or task, which a depth order buries under a deeper
but dormant series), ``"span"`` by each site's observation baseline (the site
watched long enough for slow change to show), and ``"cadence"`` by each site's
*typical* revisit gap, tightest first (the most-frequently-imaged site -- the
median gap, not the worst one, so a single outage does not bury an otherwise
reliably-imaged series; the worst-case reading stays the ``max_revisit_days``
filter). ``min_passes`` still qualifies a site on raw depth under any of the
three, so a temporal order returns only sites with a series worth ordering.

``active_since`` keeps only sites still imaged *on or after* that date -- a
recency filter on each site's newest pass, so a deep series that stopped long
ago is dropped and an actively-revisited one is kept. Use it to find live
monitoring targets ("repeat-imaged sites still being collected"). It accepts an
ISO date (``"2024-06-01"``), a bare year/month, or a relative expression
(``"6 months ago"``), is orthogonal to ``rank_by`` / ``min_passes`` (it gates on
the site's latest pass, whatever depth the ranking measures), and differs from
``start`` / ``end``: those bound which *passes* enter the pool (truncating every
series to a window), whereas this selects whole sites by recency and keeps each
survivor's full history.

``active_before`` is the complement -- keep only sites whose newest pass is *on
or before* that date (a dormant series that stopped imaging). Pass both to select
sites whose latest pass falls *within* a window (``active_since <= last <=
active_before``). Same grammar as ``active_since``, but a bare year/month covers
the whole named period (``active_before="2024"`` is "last imaged on or before
2024-12-31"), symmetric with ``end``.

``first_since`` / ``first_before`` are the onset (first-seen) twins of the
``active_*`` pair -- they gate each site's *earliest* pass rather than its newest.
``first_since`` keeps **newly-appeared** series (first imaged on or after the date --
ones that entered the archive recently) and ``first_before`` keeps
**long-established** ones (first imaged on or before it -- watched since before
then); pass both to bound the onset to a window
(``first_since <= first <= first_before``), exactly as the ``active_*`` pair bounds
the newest pass. Same grammar as ``active_since``, ``first_before`` snapping a bare
year/month to its last day. Orthogonal to the ``active_*`` recency filters, since
when a site started and whether it is still going are independent axes -- so
``first_since=X, active_before=Y`` finds series that appeared after X and have
already gone dormant by Y. ``None`` (the default) applies no onset filter.

``max_revisit_days`` keeps only sites revisited *at least this often* -- a cadence
filter on each site's **worst-case** revisit gap (in days), so a series with any
stretch longer than that between consecutive passes is dropped and a reliably
imaged one kept. Use it to find monitorable targets ("repeat-imaged sites with no
blind spot longer than N days"). It gates the cadence ``rank_by`` measures -- the
whole dated series by default, the usable (comparable) series' worst gap under
``rank_by="comparable"`` -- is orthogonal to ``active_since`` / ``active_before``,
and drops a site with fewer than two passes in the gated series. A non-positive
value is a ``ValueError``.

``median_revisit_days`` is the *typical*-cadence twin of ``max_revisit_days`` --
keep only sites whose **median** revisit gap (in days) is at most that, so a site
*usually* imaged often is kept even if a single stretch runs long, where
``max_revisit_days`` drops it the moment any gap exceeds the bound. Use it for
"usually imaged frequently" rather than "never blind for longer than N days"; set
both to combine the two. It gates the cadence ``rank_by`` measures (the usable
series' typical gap under ``rank_by="comparable"``), is orthogonal to
``max_revisit_days`` and the recency filters, and drops a site with fewer than two
passes in the gated series. A non-positive value is a ``ValueError``.

``min_span_days`` keeps only sites imaged over *at least this long* -- a baseline
filter on each site's observation **span** (in days, first pass to last), so a
series confined to a short window is dropped and a long-baseline one kept. Use it
to find targets for *slow* change (subsidence, construction, deforestation) that
needs a long observation window to be visible. It is a different axis from
``max_revisit_days``: cadence is the worst *gap* (how reliably a site is watched),
span is the total *baseline* (how long it has been watched at all). It gates the
span ``rank_by`` measures -- the whole dated series by default, the usable
(comparable) series' span under ``rank_by="comparable"`` -- is orthogonal to
``active_since`` / ``active_before`` / ``max_revisit_days``, and drops a site with
fewer than two passes in the gated series. A non-positive value is a ``ValueError``.

``max_span_days`` is the upper twin of ``min_span_days`` -- keep only sites imaged
over *at most this long* (a short-lived series, a burst of imaging now over), the
complement of the floor. Set with ``min_span_days`` the two bound each site's
baseline to a window (``min_span_days <= span <= max_span_days``), exactly as
``active_since`` / ``active_before`` bound the newest pass to one. It gates the same
span ``rank_by`` measures (the usable series under ``rank_by="comparable"``) and,
like the floor, drops a site with no measurable span so the window admits only a
confirmed baseline. A non-positive value is a ``ValueError``.

``limit`` sizes the *live/token* pool only. On the local index a site's depth
is measured **whole-archive** — one ``GROUP BY task`` over the entire index
(:meth:`CatalogIndex.rank_sites`), so a deeply-imaged site is ranked by all
its passes rather than by whatever the first ``limit`` rows admitted, and
``limit`` does not apply. On the live open bucket or the Canopy archive there
is no index to group over, so ``limit`` sizes the single re-listed pool the
ranking is computed from (bigger finds more sites and deeper series but scans
more of the archive; ``area`` narrows it instead).

Returns ``sites``, best-covered first: each a ``SiteCoverage`` with the site's
``task`` codename and place ``label``, its ``passes`` count, ``first`` /
``last`` dates and ``span_days``, ``min_revisit_days`` / ``median_revisit_days``
/ ``max_revisit_days`` cadence (the last being the longest gap, i.e. the site's
worst-case temporal resolution) with ``comparable_min_revisit_days`` /
``comparable_median_revisit_days`` / ``comparable_max_revisit_days`` the same
shortest / typical / worst triple over just the differenceable subset (each can
diverge from its raw twin when a cross-polarization pass fills a gap the
analysis verbs cannot use), union ``bbox`` footprint, ``products`` and
``polarizations`` — with ``comparable_polarizations`` naming *which* single
polarization the usable series is (the signature the ``comparable_*`` figures
are all measured over, a strict subset of ``polarizations`` when the site is
mixed) — and the
``hrefs`` — the site's pass STAC URLs, **oldest-first**, ready to hand straight
to ``pick_change_interval`` (or ``change_composite`` / ``stack_stats``). Also
``count`` (sites returned), the pool ``source``, the resolved
``place`` / ``bbox`` / ``area``, a ``query`` echo of the ranking-and-selection
inputs (``rank_by``, ``top``, ``min_passes`` and the recency / onset / cadence /
baseline bounds, as you passed them) so the answer records how it was ranked and
filtered, and the attribution line.

``local`` selects the backend exactly as ``search_catalog`` does — unset uses
the on-disk index when present (instant) and a live S3 walk otherwise. With a
Canopy token configured (``$UMBRA_CANOPY_TOKEN``) the pool is drawn from the
commercial archive instead. The ranking is single-sourced with the static
showcase's featured-gallery selector, so this and ``umbra sites`` cannot
disagree about what "most repeat-imaged" means.

Input Schema

NameRequiredDescriptionDefault
endNo
topNo
areaNo
bboxNo
fuzzyNo
limitNo
localNo
placeNo
startNo
rank_byNopasses
productsNo
intersectsNo
min_passesNo
first_sinceNo
active_sinceNo
first_beforeNo
active_beforeNo
max_incidenceNo
max_span_daysNo
min_incidenceNo
min_span_daysNo
polarizationsNo
max_resolutionNo
max_revisit_daysNo
median_revisit_daysNo

Output Schema

NameRequiredDescriptionDefault

No arguments

Schema Changelog

Changes observed during successful MCP inspections.

  1. First observed

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint, so the description carries most of the burden and does: backend selection (`local` unset → on-disk index vs live S3 walk, Canopy token), that no model is called, that `limit` is ignored on the local whole-archive GROUP BY path, that sub-two-pass sites are dropped by cadence/span gates, and that non-positive values raise ValueError. It even documents output field semantics and href ordering.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The purpose and usage routing are front-loaded, but the body is roughly 1,500 words of dense prose with heavy backtick formatting and repeated framing ("orthogonal to", "is the twin of", "It gates the ... `rank_by` measures") restated for nearly every filter. The `rank_by` paragraph alone runs several hundred words; a parameter table or three-line-per-param pattern would convey the same semantics in a fraction of the length.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 25-parameter discovery tool with an output schema, this covers selection, ranking, backend differences, filter interactions that are non-obvious (pool-scoping `start`/`end` vs whole-site `active_*`), failure modes, and what the return payload contains. Nothing an agent needs in order to call it correctly is left implicit.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0% across 25 parameters, and the description compensates thoroughly — dates accept ISO, bare year/month, or relative expressions; `bbox` is `[min_lon, min_lat, max_lon, max_lat]` WGS84; `intersects` is mutually exclusive with `bbox`/`place`; `rank_by` values are enumerated with the exact axis each ranks on. It also explains how paired bounds compose (`active_since` + `active_before`, `min_span_days` + `max_span_days`).

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb and resource: "Rank the archive's most repeat-imaged sites," and immediately frames its role as the discovery step before `change` / `timescan` / `stack` / `pick_change_interval` / `narrate_change`. It also distinguishes itself from `search_catalog(area=..., fuzzy=True)` so an agent can pick between them without reading schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

States explicitly when to reach for it ("the discovery step *before* every analysis verb", no site known in advance) and names alternatives with the condition that selects them (`search_catalog` with `fuzzy=True` when the site name is roughly known, before trying `semantic=True`). It also gives concrete filter recipes tied to intents (live monitoring targets, slow-change targets, newly-appeared vs long-established sites).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.