Skip to content

Phase 3 PR2: Weekly regional briefs (LLM seeder + RPC)#2989

Merged
koala73 merged 8 commits into
mainfrom
feat/regional-intelligence-weekly-briefs
Apr 12, 2026
Merged

Phase 3 PR2: Weekly regional briefs (LLM seeder + RPC)#2989
koala73 merged 8 commits into
mainfrom
feat/regional-intelligence-weekly-briefs

Conversation

@koala73

@koala73 koala73 commented Apr 12, 2026

Copy link
Copy Markdown
Owner

Summary

Phase 3 PR2 of the Regional Intelligence Model. Adds LLM-powered weekly intelligence briefs per region, completing the core server-side feature set. No UI changes.

What landed

New seeder: `scripts/seed-regional-briefs.mjs`

Standalone weekly cron script. For each non-global region: reads the latest snapshot + recent regime transitions from #2981's history log, calls the LLM once per region, writes to `intelligence:regional-briefs:v1:weekly:{region}` with 8-day TTL (survives one missed weekly run).

New module: `scripts/regional-snapshot/weekly-brief.mjs`

`generateWeeklyBrief(region, snapshot, transitions, opts?) -> brief`

Prompt includes: current regime, 7-axis balance, active triggers, last 7 days of regime transitions, latest narrative situation + 7d outlook. Output: `{ situation_recap, regime_trajectory, key_developments[], risk_outlook }`. Same provider chain + validate-in-loop + injectable-callLlm pattern as narrative.mjs.

Proto + RPC: `GetRegionalBrief`

`GetRegionalBriefRequest { region_id } -> GetRegionalBriefResponse { brief: RegionalBrief }`

`RegionalBrief { region_id, generated_at, period_start, period_end, situation_recap, regime_trajectory, key_developments[], risk_outlook, provider, model }`

Handler + premium gating

`get-regional-brief.ts` — simple `getCachedJson` read + `adaptBrief` adapter. Returns `upstreamUnavailable: true` on Redis failure (matching #2981 pattern). Premium-gated via `PREMIUM_RPC_PATHS` + `RPC_CACHE_TIER`.

Testing

27 new unit tests. `npm run test:data`: 4651/4651 pass. Typecheck + lint clean.

Post-Deploy Monitoring & Validation

PR sequence

Phase 3 PR2 of the Regional Intelligence Model. Adds LLM-powered
weekly intelligence briefs per region, completing the core feature set.

## New seeder: scripts/seed-regional-briefs.mjs

Standalone weekly cron script (not part of the 6h derived-signals bundle).
For each non-global region:
  1. Read the latest snapshot via two-hop Redis read
  2. Read recent regime transitions from the history log (#2981)
  3. Call the LLM once per region with regime trajectory + balance +
     triggers + narrative context
  4. Write structured brief to intelligence:regional-briefs:v1:weekly:{region}
     with 8-day TTL (survives one missed weekly run)

Reuses the same injectable-callLlm + parse-validation + provider-chain
pattern from narrative.mjs and weekly-brief.mjs.

## New module: scripts/regional-snapshot/weekly-brief.mjs

  generateWeeklyBrief(region, snapshot, transitions, opts?)
    -> { region_id, generated_at, period_start, period_end,
         situation_recap, regime_trajectory, key_developments[],
         risk_outlook, provider, model }

  buildBriefPrompt()    — pure prompt builder
  parseBriefJson()      — JSON parser with prose-extraction fallback
  emptyBrief()          — canonical empty shape

Global region is skipped. Provider chain: Groq -> OpenRouter. Validate
callback ensures only parseable responses pass (narrative.mjs PR #2960
review fix pattern).

## Proto + RPC: GetRegionalBrief

  proto/worldmonitor/intelligence/v1/get_regional_brief.proto

  - GetRegionalBriefRequest { region_id }
  - GetRegionalBriefResponse { brief: RegionalBrief }
  - RegionalBrief { region_id, generated_at, period_start, period_end,
                    situation_recap, regime_trajectory,
                    key_developments[], risk_outlook, provider, model }

## Server handler

  server/worldmonitor/intelligence/v1/get-regional-brief.ts

Simple getCachedJson read + adaptBrief snake->camel adapter.
Returns upstreamUnavailable: true on Redis failure so the gateway
skips caching (matching the get-regime-history pattern from #2981).

## Premium gating + cache tier

  src/shared/premium-paths.ts + server/gateway.ts RPC_CACHE_TIER

## Tests — 27 new unit tests

  buildBriefPrompt (5): region/balance/transitions/narrative rendered,
                        empty transitions handled, missing fields tolerated
  parseBriefJson (5): valid JSON, garbage, all-empty, cap at 5, prose extraction
  generateWeeklyBrief (6): success, global skip, LLM fail, garbage, exception,
                           period_start/end delta
  emptyBrief (2): region_id + empty fields
  handler (4): key prefix, adapter export, upstreamUnavailable, registration
  security (2): premium path + cache tier
  proto (3): RPC declared, import wired, RegionalBrief fields

## Verification

- npm run test:data: 4651/4651 pass
- npm run typecheck + typecheck:api: clean
- biome lint: clean
@mintlify

mintlify Bot commented Apr 12, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
WorldMonitor 🟢 Ready View Preview Apr 12, 2026, 4:15 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@vercel

vercel Bot commented Apr 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
worldmonitor Ignored Ignored Preview Apr 12, 2026 5:51am

Request Review

@greptile-apps

greptile-apps Bot commented Apr 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds LLM-powered weekly intelligence briefs per region: a new seeder (seed-regional-briefs.mjs), a brief-generator module (scripts/regional-snapshot/weekly-brief.mjs), a GetRegionalBrief proto RPC, and a premium-gated handler that reads cached briefs from Redis. The implementation follows the established snapshot/narrative pattern well overall, but the seeder's Redis write uses ?EX= as a query parameter instead of /EX/ as a path segment — the format Upstash requires — meaning the 8-day TTL will silently not be applied and briefs will accumulate indefinitely.

Confidence Score: 4/5

The P1 TTL bug in the seeder must be fixed before deploying — briefs would accumulate in Redis indefinitely without it.

One P1 defect: writeBrief uses ?EX= (query string) instead of /EX/ (path segment) for the Upstash REST SET command, so the 8-day TTL is never applied. The RPC handler, proto, premium gating, and test suite are all correct. The P2 findings (seed-meta coverage and validate consistency) are non-blocking.

scripts/seed-regional-briefs.mjs — writeBrief TTL format (line 110) and conditional seed-meta write (line 170).

Important Files Changed

Filename Overview
scripts/seed-regional-briefs.mjs Seeder for weekly briefs — contains a P1 bug where writeBrief uses ?EX= query param instead of /EX/ path segment for the Upstash TTL, silently leaving briefs without expiry; also skips seed-meta when generated === 0.
scripts/regional-snapshot/weekly-brief.mjs LLM brief generator with injectable callLlm, provider chain, and parseBriefJson — well-structured, but the valid check accepts briefs missing situation_recap, which is inconsistent with how the seeder subsequently filters them.
server/worldmonitor/intelligence/v1/get-regional-brief.ts Simple read-through handler using getCachedJson(key, true) (raw key, correct for seeder-written keys) with adaptBrief adapter; returns upstreamUnavailable on miss, matching the #2981 pattern.
server/worldmonitor/intelligence/v1/handler.ts Correctly imports and registers getRegionalBrief in the intelligence handler map.
src/shared/premium-paths.ts Adds /api/intelligence/v1/get-regional-brief to PREMIUM_RPC_PATHS; consistent with other Phase 3 regional endpoints.
server/gateway.ts Adds get-regional-brief to RPC_CACHE_TIER with a slow entry (per route-parity contract), with an inline comment explaining the premium short-circuit.
proto/worldmonitor/intelligence/v1/get_regional_brief.proto Defines GetRegionalBriefRequest (with buf.validate pattern + length constraints), GetRegionalBriefResponse, and RegionalBrief with all 10 fields including int64 timestamps with correct INT64_ENCODING_NUMBER annotation.
tests/regional-snapshot-weekly-brief.test.mjs 27 unit tests covering pure functions, LLM injection, error paths, and structural wiring checks; good coverage of edge cases including garbage LLM output and callLlm exceptions.

Sequence Diagram

sequenceDiagram
    participant Cron as Weekly Cron (Railway)
    participant Seeder as seed-regional-briefs.mjs
    participant Redis as Redis (Upstash)
    participant LLM as LLM (Groq / OpenRouter)
    participant Gateway as Vercel Edge Gateway
    participant Handler as get-regional-brief.ts
    participant Client as Client (Premium)

    Cron->>Seeder: trigger weekly run
    loop For each non-global region
        Seeder->>Redis: GET intelligence:snapshot:v1:{region}:latest
        Redis-->>Seeder: snapshot_id
        Seeder->>Redis: GET intelligence:snapshot-by-id:v1:{snapshot_id}
        Redis-->>Seeder: snapshot JSON
        Seeder->>Redis: LRANGE intelligence:regime-history:v1:{region} 0 49
        Redis-->>Seeder: regime transition entries
        Seeder->>LLM: POST chat completions (brief prompt)
        LLM-->>Seeder: JSON brief
        Seeder->>Redis: SET intelligence:regional-briefs:v1:weekly:{region} (8d TTL)
    end
    Seeder->>Redis: writeExtraKeyWithMeta (seed-meta, summary)

    Client->>Gateway: GET /api/intelligence/v1/get-regional-brief?region_id=mena
    Gateway->>Gateway: Auth check (PREMIUM_RPC_PATHS)
    Gateway->>Handler: getRegionalBrief(req)
    Handler->>Redis: getCachedJson(raw=true) intelligence:regional-briefs:v1:weekly:mena
    Redis-->>Handler: brief JSON (or null)
    Handler-->>Gateway: { brief } or { upstreamUnavailable: true }
    Gateway-->>Client: 200 JSON (slow-browser cache)
Loading

Reviews (1): Last reviewed commit: "feat(intelligence): weekly regional brie..." | Re-trigger Greptile

Comment thread scripts/seed-regional-briefs.mjs Outdated
const key = `${BRIEF_KEY_PREFIX}${regionId}`;
const payload = JSON.stringify(brief);
try {
const resp = await fetch(`${url}/set/${encodeURIComponent(key)}/${encodeURIComponent(payload)}?EX=${BRIEF_TTL}`, {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 TTL silently not applied — briefs will never expire

The Upstash REST API passes Redis command options as path segments (/EX/<seconds>), not as query parameters. Sending ?EX=${BRIEF_TTL} appends it to the query string, which Upstash ignores; the SET executes without any TTL, so every brief will persist indefinitely and Redis storage grows unboundedly across weekly runs.

Compare with the canonical pattern in server/_shared/redis.ts (setCachedJson):

/set/${key}/${value}/EX/${ttlSeconds}   ← correct path-segment form
/set/${key}/${value}?EX=${ttl}          ← current code — query string ignored
Suggested change
const resp = await fetch(`${url}/set/${encodeURIComponent(key)}/${encodeURIComponent(payload)}?EX=${BRIEF_TTL}`, {
const resp = await fetch(`${url}/set/${encodeURIComponent(key)}/${encodeURIComponent(payload)}/EX/${BRIEF_TTL}`, {

Alternatively, use the JSON-body pipeline form that writeExtraKey in _seed-utils.mjs already uses:

body: JSON.stringify(['SET', key, payload, 'EX', BRIEF_TTL]),

Comment thread scripts/seed-regional-briefs.mjs Outdated
Comment on lines +170 to +179
if (failed === 0 && generated > 0) {
await writeExtraKeyWithMeta(
`intelligence:regional-briefs:summary:v1`,
{ generatedAt: Date.now(), regionsGenerated: generated },
BRIEF_TTL,
generated,
`seed-meta:${SEED_META_KEY}`,
BRIEF_TTL,
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 seed-meta not refreshed when all regions are skipped

writeExtraKeyWithMeta is guarded by failed === 0 && generated > 0. If every region is skipped because no snapshot exists yet (or LLM fails across the board), generated remains 0 and the seed-meta key is never written. The AGENTS.md convention requires every seed script to write seed-meta:<key> on each run so health monitoring can confirm the seeder is executing — a perpetually absent seed-meta is indistinguishable from a seeder that has never run.

Consider writing the meta unconditionally (or at minimum when generated === 0 && failed === 0), carrying regionsSkipped in the payload so health tooling can distinguish "ran but nothing to do" from "never ran".

: [];
const risk_outlook = typeof p.risk_outlook === 'string' ? p.risk_outlook.trim() : '';

const hasContent = situation_recap.length > 0 || key_developments.length > 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Validate gate and seeder skip condition are inconsistent

parseBriefJson marks a response valid when situation_recap.length > 0 || key_developments.length > 0. A response with non-empty key_developments but an empty situation_recap therefore passes the validate callback in callLlmDefault, meaning the provider chain does not retry — it accepts the response and returns it. generateWeeklyBrief then returns that brief, but seed-regional-briefs.mjs immediately discards it (if (!brief.situation_recap) { skipped++; continue; }).

The net effect is that an LLM response that omits situation_recap exhausts the retry loop (since validation says it's fine) yet produces nothing useful. Tighten the validation to require situation_recap:

Suggested change
const hasContent = situation_recap.length > 0 || key_developments.length > 0;
const hasContent = situation_recap.length > 0;

P2 #1 — no consumer surface for GetRegionalBrief

Acknowledged. The consumer is the RegionalIntelligenceBoard panel,
which will call GetRegionalBrief and render a weekly brief block.
This wiring is Phase 3 PR3 (UI) scope — the RPC + Redis key are the
delivery mechanism, not the end surface. No code change in this commit;
the RPC is ready for the panel to consume.

P2 #2 — readRecentTransitions collapses failure to []

readRecentTransitions returned [] on Redis/network failure, which is
indistinguishable from a genuinely quiet week. The LLM then generates
a brief claiming "no regime transitions" when in reality the upstream
is down — fabricating false input.

Fix: return null on failure. The seeder skips the region with a clear
log message when transitions is null, so the brief is never written
with unreliable input. Empty array [] now only means genuinely no
transitions in the 7-day window.

P2 #3 — parseBriefJson accepts briefs the seeder rejects

parseBriefJson treated non-empty key_developments as valid even if
situation_recap was empty. The seeder gate only writes when
brief.situation_recap is truthy. That mismatch means the validator
pass + provider-fallback logic could accept a response that the seeder
then silently drops.

Fix: require situation_recap in parseBriefJson for valid=true, matching
the seeder gate. Now both checks agree on what constitutes a usable
brief, and the provider-fallback chain correctly falls through when
a provider returns a brief with developments but no recap.
koala73 added 2 commits April 12, 2026 08:41
…ptile P1+P2 on #2989)

P1 — TTL silently not applied (briefs never expire)

Upstash REST ignores query-string SET options (?EX=N). The correct
form is path-segment: /set/{key}/{value}/EX/{seconds}. Without this
fix every brief persists indefinitely and Redis storage grows
unboundedly across weekly runs.

P2 — seed-meta not written when all regions skipped

writeExtraKeyWithMeta was gated on generated > 0. If every region
was skipped (no snapshot yet, or LLM failed), seed-meta was never
written, making the seeder indistinguishable from "never ran" in
health tooling. Now writes seed-meta whenever failed === 0,
carrying regionsSkipped count.

P2 #3 (validate gate) — already fixed in previous commit (parseBriefJson
now requires situation_recap for valid=true).
koala73 added 2 commits April 12, 2026 09:35
…e-gated health (review P2s on #2989)

P2 #1 — false upstreamUnavailable before first seed

getCachedJson returns null for both "key missing" and "Redis failed",
so the handler was advertising an outage for every region before the
first weekly seed ran. Switched to getRawJson (throws on Redis errors)
so null = genuinely missing key → clean empty 200, and thrown error =
upstream failure → upstreamUnavailable: true for gateway no-store.

P2 #2 — partial run hides coverage loss in health

The seed-meta was written with generated count even if only 1 of 7
regions produced a brief. /api/health treats any positive recordCount
as healthy, so broad regional failure was invisible to operators.

Fix: recordCount is set to 0 when generated < ceil(expectedRegions/2).
This makes /api/health report EMPTY_DATA for severely partial runs
while still writing seed-meta (so the seeder is confirmed to have run).
coverageOk flag in the summary payload lets operators drill into the
exact coverage state.
@koala73
koala73 merged commit e070a97 into main Apr 12, 2026
11 checks passed
@koala73
koala73 deleted the feat/regional-intelligence-weekly-briefs branch April 12, 2026 05:56
koala73 added a commit that referenced this pull request Jun 6, 2026
…erage (#4164)

* fix(regional-briefs): bypass weekly cooldown when last run failed coverage

seed-bundle-regional gates the weekly brief sub-run behind a 6.5-day
cooldown read from seed-meta:intelligence:regional-briefs. When a briefs
run fails coverage (e.g. a transient OpenRouter-credits outage makes every
region return an empty brief → skipped, failed===0), seed-regional-briefs
deliberately writes recordCount=0 (+ coverageOk:false) so /api/health flips
to EMPTY_DATA instead of hiding partial failure (PR #2989).

The trap: that EMPTY_DATA crit then persists for the remainder of the
~6.5-day cooldown with no retry — even though re-running once credits are
restored would clear it on the next 6h tick. Live incident 2026-06-06:
"[bundle] briefs: last run 1.6 days ago, skipping (cooldown 6.5d)".

Fix: in shouldRunBriefs, bypass the cooldown when the last run's
recordCount===0. recordCount lives in the bare-shape seed-meta and is
exactly the field that drives /api/health's EMPTY_DATA verdict, so it is
the authoritative "last run failed coverage" signal — a successful run
writes a positive count and keeps the normal weekly cooldown. This turns a
transient failure into a self-healing retry-next-tick instead of a
multi-day stuck crit. Coverage logic in seed-regional-briefs is unchanged.

Adds an isMain guard + exports shouldRunBriefs so the bypass can be
behaviorally tested without executing the bundle on import.

* test(regional-briefs): loosen cooldown source guard

---------

Co-authored-by: e <[email protected]>
koala73 added a commit that referenced this pull request Jul 5, 2026
…try-budget cap (#4911)

* chore(llm): regional hygiene — narrative prompt-hash cache + brief retry-budget cap

Two of the #4896 waste patterns:

1. Narrative-before-dedup: generateRegionalNarrative fired its ~900-token
   call before any content-identity check — byte-identical world state
   (7 regions x 4 bundle runs/day) regenerated the same narrative every 6h,
   and same-15min-bucket re-runs burned it just for persistSnapshot's
   idempotency guard to discard the result. The generator now caches the
   parsed narrative under a hash of the exact prompt (whose only volatile
   input is a day-granular date), 24h TTL, injectable for tests, best-effort
   on Redis failure. Only VALID parsed narratives are cached.

2. Coverage-fail retry storm: the #2989 cooldown bypass re-ran ALL region
   briefs on every 6h tick for as long as an outage lasted (~28 LLM
   calls/day, timed exactly to provider incidents) because each failed
   attempt refreshes seed-meta fetchedAt. A rolling 24h retry budget
   (INCR+EXPIRE NX, 2 bypasses/window) keeps the fast transient self-heal —
   first retry on the next tick, one more after — then pauses until the
   window expires. Fails OPEN on Redis errors so a hiccup can never block
   self-healing; the scheduled weekly cadence never consults the budget.

Refs #4896

Claude-Session: https://claude.ai/code/session_01YTm4GsLG2M7ZuaHF9MpZih

* fix(narrative-cache): default cache must not process.exit when Redis creds are absent

getRedisCredentials() exits the process on missing env — the default
narrative cache killed the CI unit runner (and any credless local run)
the moment generateRegionalNarrative touched it without an injected
cache. Read env directly and no-op without creds: no cache, generation
proceeds exactly as before the cache existed. Caught by the unit job on
this PR; reproduced locally with UPSTASH_* unset.

Claude-Session: https://claude.ai/code/session_01YTm4GsLG2M7ZuaHF9MpZih
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant