Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/zz-plant/whether/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Signal Ops monitors regime assessments and fires alerts when meaningful threshold crossings occur. It ensures you never miss a regime transition that demands operational adjustments. Key capabilities:

Regime Change Alerts

Fires on SCARCITY↔EXPANSION and threshold crossings

Alert Reason Codes

Structured explanations for every trigger

Weekly Digests

Summary of all regime shifts over 7 days

Time Machine Links

Deep links to historical comparison views

Alert Triggers

Signal Ops creates alerts when specific conditions are met:
lib/signalOps.ts
export const shouldCreateSignalAlert = (
  payload: SignalAlertPayload,
  latestAlert?: RegimeAlertEvent
) => {
  const hasRequiredTrigger = payload.reasons.some((reason) =>
    [
      "regime-change",
      "tightness-upshift",
      "tightness-downshift",
      "risk-appetite-upshift",
      "risk-appetite-downshift",
    ].includes(reason.code)
  );

  if (!hasRequiredTrigger) {
    return false;
  }

  if (!latestAlert) {
    return true;
  }

  const nowMs = Date.now();
  const latestMs = Date.parse(latestAlert.createdAt);
  if (Number.isNaN(latestMs)) {
    return true;
  }

  const withinCooldown = nowMs - latestMs < ONE_DAY_MS;
  const regimeFlippedAgain =
    payload.currentAssessment.regime !== latestAlert.payload.currentAssessment.regime;

  if (withinCooldown && !regimeFlippedAgain) {
    return false;
  }

  return true;
};
1

Check for required trigger

Alert fires only if one of five reason codes appears:
  • regime-change (e.g., SCARCITY → DEFENSIVE)
  • tightness-upshift (tightness crosses above 70)
  • tightness-downshift (tightness falls below 70)
  • risk-appetite-upshift (risk appetite crosses above 50)
  • risk-appetite-downshift (risk appetite falls below 50)
2

Cooldown logic

If an alert fired in the last 24 hours, suppress duplicates unless the regime flipped again
3

Create alert

Generate RegimeAlertEvent with full payload and timestamp
24-hour cooldown prevents alert fatigue. Exception: if DEFENSIVE→SCARCITY→DEFENSIVE happens within 24 hours, you’ll get two alerts.

Alert Reason Codes

Every alert includes structured reasons explaining the trigger:
lib/regimeEngine.ts
export type RegimeChangeReason = {
  code: string;
  message: string;
};

Available Codes

Full regime transitionExample: "Regime shifted from EXPANSION to DEFENSIVE."
This is the highest-priority signal—always triggers alerts and demands immediate review.
Tightness crossed above thresholdExample: "Tightness crossed above 70."Indicates capital costs are rising above the regime boundary.
Tightness fell below thresholdExample: "Tightness fell below 70."Signals improving capital conditions.
Risk appetite crossed above thresholdExample: "Risk appetite crossed above 50."Market confidence is improving.
Risk appetite fell below thresholdExample: "Risk appetite fell below 50."Market confidence is deteriorating.
Base rate crossed above thresholdExample: "Base rate crossed above 5.0%."Policy rates are tightening (informational, doesn’t always trigger alerts).
Base rate fell below thresholdExample: "Base rate fell below 5.0%."Policy rates are easing.
Yield curve invertedExample: "Curve slope turned negative."Classic recession signal—10Y yield fell below 2Y.
Yield curve normalizedExample: "Curve slope turned positive."Inversion resolved—growth expectations improving.
Default reason when no threshold crossedExample: "Signal values updated since the last read."Data refresh occurred but no meaningful change detected.

Reason Code Generation

Reason codes are computed by comparing previous and current assessments:
lib/regimeEngine.ts
export const buildRegimeChangeReasons = (
  previous: RegimeAssessment | null,
  current: RegimeAssessment
): RegimeChangeReason[] => {
  if (!previous) {
    return [];
  }

  const reasons: RegimeChangeReason[] = [];
  const previousTightnessThreshold = previous.thresholds.tightnessRegime;
  const currentTightnessThreshold = current.thresholds.tightnessRegime;
  const previousRiskThreshold = previous.thresholds.riskAppetiteRegime;
  const currentRiskThreshold = current.thresholds.riskAppetiteRegime;

  const pushReason = (code: string, message: string) => {
    reasons.push({ code, message });
  };

  // Regime change
  if (previous.regime !== current.regime) {
    pushReason("regime-change", `Regime shifted from ${previous.regime} to ${current.regime}.`);
  }

  // Tightness shifts
  if (
    previous.scores.tightness <= previousTightnessThreshold &&
    current.scores.tightness > currentTightnessThreshold
  ) {
    pushReason("tightness-upshift", `Tightness crossed above ${currentTightnessThreshold}.`);
  } else if (
    previous.scores.tightness > previousTightnessThreshold &&
    current.scores.tightness <= currentTightnessThreshold
  ) {
    pushReason("tightness-downshift", `Tightness fell below ${currentTightnessThreshold}.`);
  }

  // Risk appetite shifts
  if (
    previous.scores.riskAppetite <= previousRiskThreshold &&
    current.scores.riskAppetite > currentRiskThreshold
  ) {
    pushReason("risk-appetite-upshift", `Risk appetite crossed above ${currentRiskThreshold}.`);
  } else if (
    previous.scores.riskAppetite > previousRiskThreshold &&
    current.scores.riskAppetite <= currentRiskThreshold
  ) {
    pushReason("risk-appetite-downshift", `Risk appetite fell below ${currentRiskThreshold}.`);
  }

  // Curve slope polarity shifts
  const previousSlope = previous.scores.curveSlope;
  const currentSlope = current.scores.curveSlope;
  if (previousSlope !== null && currentSlope !== null) {
    if (previousSlope >= 0 && currentSlope < 0) {
      pushReason("curve-slope-negative", "Curve slope turned negative.");
    } else if (previousSlope <= 0 && currentSlope > 0) {
      pushReason("curve-slope-positive", "Curve slope turned positive.");
    }
  }

  if (reasons.length === 0) {
    pushReason("signals-updated", "Signal values updated since the last read.");
  }

  return reasons;
};
Reason codes are stackable: a single alert can include multiple codes if several thresholds cross simultaneously.

Alert Payload Structure

Every alert includes full context:
lib/signalOps.ts
export type SignalAlertPayload = {
  previousRecordDate: string;             // ISO date of previous assessment
  currentRecordDate: string;              // ISO date of current assessment
  previousAssessment: RegimeAssessment;   // Full previous regime state
  currentAssessment: RegimeAssessment;    // Full current regime state
  reasons: RegimeChangeReason[];          // List of reason codes with messages
  sourceUrls: string[];                   // Treasury API source citations
  timeMachineHref: string;                // Deep link to historical comparison
};

export type RegimeAlertEvent = {
  id: string;                             // Unique alert ID
  createdAt: string;                      // ISO timestamp
  payload: SignalAlertPayload;            // Full alert payload
};
{
  id: "alert_2024-03-15_001",
  createdAt: "2024-03-15T14:32:00Z",
  payload: {
    previousRecordDate: "2024-03-08",
    currentRecordDate: "2024-03-15",
    previousAssessment: {
      regime: "EXPANSION",
      scores: { tightness: 58, riskAppetite: 65, ... },
      // ... full assessment
    },
    currentAssessment: {
      regime: "DEFENSIVE",
      scores: { tightness: 74, riskAppetite: 52, ... },
      // ... full assessment
    },
    reasons: [
      { code: "regime-change", message: "Regime shifted from EXPANSION to DEFENSIVE." },
      { code: "tightness-upshift", message: "Tightness crossed above 70." }
    ],
    sourceUrls: ["https://fiscaldata.treasury.gov/datasets/..."],
    timeMachineHref: "/regime?month=3&year=2024"
  }
}

Weekly Digest

Signal Ops generates weekly summaries of regime activity:
lib/signalOps.ts
export const buildWeeklyDigest = (alerts: RegimeAlertEvent[]) => {
  const latest = alerts[0];
  const previous = alerts[1];

  if (!latest) {
    return {
      summary: "No regime-change alerts this week.",
      bullets: ["Signals updated with no threshold crossings."],
    };
  }

  const currentScores = latest.payload.currentAssessment.scores;
  const previousScores = previous?.payload.currentAssessment.scores;
  const tightnessDelta = previousScores ? currentScores.tightness - previousScores.tightness : 0;
  const riskDelta = previousScores ? currentScores.riskAppetite - previousScores.riskAppetite : 0;

  return {
    summary: `${latest.payload.currentAssessment.regime} regime as of ${latest.payload.currentRecordDate}.`,
    bullets: [
      `Tightness delta: ${tightnessDelta >= 0 ? "+" : ""}${tightnessDelta.toFixed(2)}.`,
      `Risk appetite delta: ${riskDelta >= 0 ? "+" : ""}${riskDelta.toFixed(2)}.`,
      ...latest.payload.reasons.map((reason) => `${reason.code}: ${reason.message}`),
    ],
  };
};
DEFENSIVE regime as of 2024-03-15.

- Tightness delta: +16.00.
- Risk appetite delta: -13.00.
- regime-change: Regime shifted from EXPANSION to DEFENSIVE.
- tightness-upshift: Tightness crossed above 70.
- risk-appetite-downshift: Risk appetite fell below 50.
Weekly digests are ideal for asynchronous Slack channels or email summaries sent to leadership on Monday mornings.

Alert Delivery

Signal Ops supports multiple delivery channels:
lib/signalOps.ts
export type AlertChannel = "slack" | "email" | "webhook";

Delivery Summary

lib/signalOps.ts
export const buildDeliverySummary = (alert: RegimeAlertEvent) => {
  const reasons = alert.payload.reasons.map((reason) => reason.code).join(", ");
  return `${alert.payload.currentAssessment.regime} (${alert.payload.currentRecordDate}) · ${reasons}`;
};
{
  channel: "#planning",
  status: "sent",
  summary: "DEFENSIVE (2024-03-15) · regime-change, tightness-upshift"
}
Every alert includes a timeMachineHref for instant historical comparison:
const timeMachineHref = `/regime?month=${month}&year=${year}`;
1

Alert fires

Signal Ops detects regime change from EXPANSION to DEFENSIVE
2

Generate Time Machine link

Build URL pointing to the exact month/year of the change
3

Include in payload

Add timeMachineHref to alert payload
4

Operator clicks link

Opens Time Machine view with previous and current regime side-by-side
Time Machine links let you instantly compare “before” and “after” conditions without manual date selection.

Usage Example

import { evaluateRegime, buildRegimeChangeReasons } from "@/lib/regimeEngine";
import { shouldCreateSignalAlert, buildWeeklyDigest } from "@/lib/signalOps";
import type { SignalAlertPayload, RegimeAlertEvent } from "@/lib/signalOps";

// Fetch current and previous assessments
const previousTreasury = await fetchTreasuryYields("2024-03-08");
const currentTreasury = await fetchTreasuryYields("2024-03-15");

const previousAssessment = evaluateRegime(previousTreasury);
const currentAssessment = evaluateRegime(currentTreasury);

// Generate reasons
const reasons = buildRegimeChangeReasons(previousAssessment, currentAssessment);

// Build payload
const payload: SignalAlertPayload = {
  previousRecordDate: previousTreasury.record_date,
  currentRecordDate: currentTreasury.record_date,
  previousAssessment,
  currentAssessment,
  reasons,
  sourceUrls: [currentTreasury.source],
  timeMachineHref: `/regime?month=3&year=2024`,
};

// Check if alert should fire
const latestAlert = await getLatestAlert();
if (shouldCreateSignalAlert(payload, latestAlert)) {
  const alert: RegimeAlertEvent = {
    id: generateAlertId(),
    createdAt: new Date().toISOString(),
    payload,
  };
  
  await saveAlert(alert);
  await deliverToSlack(alert);
  
  console.log("Alert fired:", buildDeliverySummary(alert));
}

// Generate weekly digest
const recentAlerts = await getAlertsForWeek();
const digest = buildWeeklyDigest(recentAlerts);
console.log(digest.summary);
digest.bullets.forEach((bullet) => console.log(`- ${bullet}`));
Always check shouldCreateSignalAlert() before firing alerts. Without cooldown logic, regime oscillations can spam channels.

Regime Engine

Understand regime classification logic

Decision Shield

Re-evaluate decisions when alerts fire

Time Machine

Follow Time Machine links from alerts

Briefing Pack

Include alerts in weekly stakeholder briefs