Companion film
Live data in
Bring in a live FX rate, contain a failed ML signal with a deterministic fallback, and keep exposure recalculating.
19 secGrid 0.61.0
Open film page →Guided build · 07
Combine API-backed signals and remote scoring while keeping downstream decisions computable through pending work and failures.
You will finish with: An inspectable enrichment model with eager and lazy work, named boundaries, and explicit fallback policy.
A portable enrichment model that combines a local currency exposure with two API-backed exchange rates and a remote model score. The external bindings remain raw and inspectable, while downstream outputs apply explicit fallback policy.
You will learn to:
= or lazy ~= for asynchronous work;You should be comfortable with cells, ranges, functions, and THEN … ELSE.
The model validates without credentials, but live results require:
FX_RATE provider;ml.scoring route for ML_SCORE.Grid's default FX provider is the keyless Frankfurter v2 service for daily
central-bank/reference rates. A workspace can replace it with GRID_FX_RATE_BASE_URL.
ML_SCORE is workspace-configured, so its scale and exact result are not portable.
MODEL "External Enrichment"
DESCRIPTION "Portable model that delegates async work to external workers."
VERSION "1.0.0"
AUTHOR "Grid Team"
TAGS "canonical", "portable", "external", "async"
# Inputs
A1 IS currency = 250000
A2 IS percentage = 18pct
A3 = 0.12
A4 = 0.18
A5 = 0.27
A6 = 0.43
A7 = "EUR"
A8 = "USD"
A1 is the amount to convert. A3:A6 is the feature vector sent to the scorer. A7 and
A8 define the FX direction.
Checkpoint:
A1is250,000;A3:A6contains four numeric features; the currency pair is EUR to USD.
A2 is deliberately unused in the canonical fixture. Changing it should produce no
downstream recomputation. That is useful dependency-inspector evidence, but a production
model should remove or connect unused inputs.
# External signals
B1 = FX_RATE(A7 AS base, A8 AS quote)
B3 = FX_RATE("GBP", "USD")
Named arguments make the first call's direction explicit. FX_RATE(base, quote) returns
quote currency per unit of base currency, so B1 is USD per EUR.
Both calls use eager = because the model's primary outputs need them immediately.
Depending on timing, their status may move through dirty, queued, running, and
ready too quickly to observe every state.
Live checkpoint: when the provider succeeds,
B1andB3are provider-returned reference rates. Do not compare them with a fixed tutorial number; they vary by date and provider.
If the provider is unavailable and there is no satisfactory cached value, the raw binding fails. We will keep that failure visible and apply policy downstream.
B2 ~= ML_SCORE(A3:A6)
~= waits until something reads B2. It is appropriate for work that is expensive or not
always needed.
The later C2, C3, and C5 outputs depend on B2, so reading any of them will pull the
lazy score. An authoring surface that reads all visible outputs can therefore make the
score appear to start immediately.
Checkpoint: before a dependent is read,
B2can remain lazy. After readingB2,C2,C3, orC5, the scorer should be requested. Its numeric result is provider-specific.
# Fallback-safe analytics
C1 IS currency = ROUND(A1 * (B1 DEFAULT 1.05), 2)
C2 = B2 DEFAULT 0
DEFAULT consumes either BLANK or an error. It does not overwrite B1 or B2, so their
external status and raw results remain inspectable.
With no cached result while work is pending, the binding carries BLANK and the fallback
is used. After a terminal failure, the error is also consumed. If a permitted stale cached
value exists, that value remains available and DEFAULT does not replace it.
Fallback checkpoint: with neither provider result available,
C1is exactly262,500andC2is0.
When FX succeeds, C1 becomes ROUND(250000 * B1, 2). When scoring succeeds, C2
becomes the configured scorer's result.
C3 = C2 > 0.35 THEN "manual-review" ELSE "auto-approve"
C4 = ROUND((B3 DEFAULT 1.25) / (B1 DEFAULT 1.05), 4)
C5 = `eur_usd={B1 DEFAULT 1.05} score={C2}`
C3 converts the score into a review decision. C4 divides USD-per-GBP by USD-per-EUR,
yielding EUR per GBP when both rates are live. Each side has its own fallback, so one failed
provider result cannot erase the calculation.
Full fallback checkpoint:
Output Expected value C1262,500C20C3auto-approveC41.1905C5eur_usd=1.05 score=0
Live C1, C2, C4, and C5 are intentionally not fixed checkpoints. They depend on
provider data.
First change A1 from 250000 to 300000.
Only the converted amount needs a new local calculation. The FX call's arguments did not change, so this edit does not invalidate its cache key. A normal access-driven TTL refresh can still request a newer value.
Checkpoint: on the fallback path,
C1becomes exactly315,000. With a live rate it becomesROUND(300000 * B1, 2).
Now change A6 from 0.43 to 0.50 and read C3.
The changed feature vector invalidates B2; reading the decision pulls a new lazy score.
B1, B3, and C4 are unaffected.
Checkpoint: only the score-dependent branch is invalidated. The new score and decision remain provider-specific.
The canonical fallback score of 0 leads to auto-approve. That is useful for demonstrating
continuity, but it is permissive for a real risk workflow. Make an unavailable score require
review:
C3 = (ISBLANK(B2) OR ISERROR(B2)) THEN "manual-review" ELSE B2 > 0.35 THEN "manual-review" ELSE "auto-approve"
This catches a new pending call with no cached value as well as a failed call. A stale cached score is still a value, so use the runtime's external-status display if your policy must reject stale results too.
Then make the audit label follow the authored currency inputs:
C5 = `{A7}_{A8}={B1 DEFAULT 1.05} score={C2}`
For an advanced extension, replace an ambient HTTP_JSON fetch with declared authority:
REQUIRES prices = NETWORK("https://prices.example.com", GET)
spot(path) = NETWORK.GET_JSON(prices, path)
D1 = spot("/spot/EURUSD")
D2 = WITH payload = D1, rate = payload.rate
THEN rate ELSE 1.05
The example origin is a placeholder. It will run only after replacing it with a real
canonical HTTPS origin and receiving a matching host grant. NETWORK.GET_JSON must be the
entire body of its defining function; field extraction belongs downstream.
BLANK.TIMEOUT and EXTERNAL; a runtime can also report stale
fallback.1.05, 1.25, and 0 are business assumptions, not universal safe defaults.0.35 is meaningful only if the configured scorer is calibrated for it.A1 recomputes conversion without invalidating the scorer.