diff --git a/api/bootstrap.js b/api/bootstrap.js index d50d52af6..5b4cd6bd4 100644 --- a/api/bootstrap.js +++ b/api/bootstrap.js @@ -94,6 +94,7 @@ const BOOTSTRAP_CACHE_KEYS = { portwatchPortActivity: 'supply_chain:portwatch-ports:v1:_countries', oilStocksAnalysis: 'energy:oil-stocks-analysis:v1', lngVulnerability: 'energy:lng-vulnerability:v1', + sprPolicies: 'energy:spr-policies:v1', }; const SLOW_KEYS = new Set([ @@ -132,6 +133,7 @@ const SLOW_KEYS = new Set([ 'portwatchPortActivity', 'oilStocksAnalysis', 'lngVulnerability', + 'sprPolicies', ]); const FAST_KEYS = new Set([ 'earthquakes', 'outages', 'serviceStatuses', 'ddosAttacks', 'trafficAnomalies', 'macroSignals', 'chokepoints', diff --git a/api/health.js b/api/health.js index 02b78bae9..248255484 100644 --- a/api/health.js +++ b/api/health.js @@ -153,6 +153,7 @@ const STANDALONE_KEYS = { chokepointFlows: 'energy:chokepoint-flows:v1', emberElectricity: 'energy:ember:v1:_all', resilienceIntervals: 'resilience:intervals:v1:US', + sprPolicies: 'energy:spr-policies:v1', }; const SEED_META = { @@ -285,6 +286,7 @@ const SEED_META = { jodiGas: { key: 'seed-meta:energy:jodi-gas', maxStaleMin: 60 * 24 * 40 }, // monthly cron on 25th; 40d threshold matches 35d TTL + 5d buffer lngVulnerability: { key: 'seed-meta:energy:jodi-gas', maxStaleMin: 60 * 24 * 40 }, // written by jodi-gas seeder afterPublish; shares seed-meta key chokepointBaselines: { key: 'seed-meta:energy:chokepoint-baselines', maxStaleMin: 60 * 24 * 400 }, // 400 days + sprPolicies: { key: 'seed-meta:energy:spr-policies', maxStaleMin: 60 * 24 * 400 }, // 400 days; static registry, same cadence as chokepoint baselines portwatchChokepointsRef: { key: 'seed-meta:portwatch:chokepoints-ref', maxStaleMin: 60 * 24 * 2 }, // daily cron; 2d = 2× interval chokepointFlows: { key: 'seed-meta:energy:chokepoint-flows', maxStaleMin: 720 }, // 6h cron; 720min = 2x interval emberElectricity: { key: 'seed-meta:energy:ember', maxStaleMin: 2880 }, // daily cron (08:00 UTC); 2880min = 48h = 2x interval diff --git a/api/seed-health.js b/api/seed-health.js index 0421f73f7..f325e3f36 100644 --- a/api/seed-health.js +++ b/api/seed-health.js @@ -75,6 +75,7 @@ const SEED_DOMAINS = { 'energy:chokepoint-flows': { key: 'seed-meta:energy:chokepoint-flows', intervalMin: 360 }, // 6h relay loop; intervalMin = maxStaleMin / 2 (720 / 2) 'energy:spine': { key: 'seed-meta:energy:spine', intervalMin: 1440 }, // daily cron (0 6 * * *); intervalMin = maxStaleMin / 2 (2880 / 2) 'energy:ember': { key: 'seed-meta:energy:ember', intervalMin: 1440 }, // daily cron (0 8 * * *); intervalMin = maxStaleMin / 2 (2880 / 2) + 'energy:spr-policies': { key: 'seed-meta:energy:spr-policies', intervalMin: 288000 }, // annual static registry; intervalMin = health.js maxStaleMin / 2 (576000 / 2) }; async function getMetaBatch(keys) { diff --git a/docs/api/IntelligenceService.openapi.json b/docs/api/IntelligenceService.openapi.json index 1b094c4b8..0e7552479 100644 --- a/docs/api/IntelligenceService.openapi.json +++ b/docs/api/IntelligenceService.openapi.json @@ -1 +1 @@ -{"components":{"schemas":{"ByCountryEntry":{"properties":{"key":{"type":"string"},"value":{"type":"string"}},"type":"object"},"ByTypeEntry":{"properties":{"key":{"type":"string"},"value":{"format":"int32","type":"integer"}},"type":"object"},"CiiComponents":{"description":"CiiComponents represents the contributing factors to a CII score.","properties":{"ciiContribution":{"description":"CII index contribution (0-100).","format":"double","maximum":100,"minimum":0,"type":"number"},"geoConvergence":{"description":"Geographic convergence score (0-100).","format":"double","maximum":100,"minimum":0,"type":"number"},"militaryActivity":{"description":"Military activity contribution (0-100).","format":"double","maximum":100,"minimum":0,"type":"number"},"newsActivity":{"description":"News activity signal contribution (0-100).","format":"double","maximum":100,"minimum":0,"type":"number"}},"type":"object"},"CiiScore":{"description":"CiiScore represents a Composite Instability Index score for a region or country.","properties":{"combinedScore":{"description":"Combined weighted score (0-100).","format":"double","maximum":100,"minimum":0,"type":"number"},"components":{"$ref":"#/components/schemas/CiiComponents"},"computedAt":{"description":"Last computation time, as Unix epoch milliseconds.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"dynamicScore":{"description":"Dynamic real-time score (0-100).","format":"double","maximum":100,"minimum":0,"type":"number"},"region":{"description":"Region or country identifier.","type":"string"},"staticBaseline":{"description":"Static baseline score (0-100).","format":"double","maximum":100,"minimum":0,"type":"number"},"trend":{"description":"TrendDirection represents the directional movement of a metric over time.\n Used in markets, GDELT tension scores, and risk assessments.","enum":["TREND_DIRECTION_UNSPECIFIED","TREND_DIRECTION_RISING","TREND_DIRECTION_STABLE","TREND_DIRECTION_FALLING"],"type":"string"}},"type":"object"},"ClassifyEventRequest":{"description":"ClassifyEventRequest specifies an event to classify using AI.","properties":{"country":{"description":"Country context (ISO 3166-1 alpha-2).","type":"string"},"description":{"description":"Event description or body text.","type":"string"},"source":{"description":"Event source (e.g., \"reuters\", \"acled\").","type":"string"},"title":{"description":"Event title or headline.","minLength":1,"type":"string"}},"required":["title"],"type":"object"},"ClassifyEventResponse":{"description":"ClassifyEventResponse contains the AI-generated event classification.","properties":{"classification":{"$ref":"#/components/schemas/EventClassification"}},"type":"object"},"CompanySignal":{"properties":{"engagement":{"$ref":"#/components/schemas/SignalEngagement"},"source":{"type":"string"},"sourceTier":{"description":"Data quality tier (1 is authoritative, 5 is low confidence).","format":"int32","type":"integer"},"strength":{"description":"Qualitative strength of the signal (e.g. \"Strong\", \"Emerging\").","type":"string"},"timestampMs":{"description":"Unix timestamp in milliseconds of the event occurrence.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"title":{"type":"string"},"type":{"description":"Classification type (e.g. \"Hiring\", \"Product Launch\", \"Expansion\").","type":"string"},"url":{"type":"string"}},"type":"object"},"ComputeEnergyShockScenarioRequest":{"properties":{"chokepointId":{"type":"string"},"countryCode":{"type":"string"},"disruptionPct":{"format":"int32","type":"integer"},"fuelMode":{"type":"string"}},"type":"object"},"ComputeEnergyShockScenarioResponse":{"properties":{"assessment":{"type":"string"},"chokepointConfidence":{"type":"string"},"chokepointId":{"type":"string"},"comtradeCoverage":{"type":"boolean"},"countryCode":{"type":"string"},"coverageLevel":{"type":"string"},"crudeLossKbd":{"format":"double","type":"number"},"dataAvailable":{"type":"boolean"},"degraded":{"type":"boolean"},"disruptionPct":{"format":"int32","type":"integer"},"effectiveCoverDays":{"format":"int32","type":"integer"},"gasImpact":{"$ref":"#/components/schemas/GasImpact"},"gulfCrudeShare":{"format":"double","type":"number"},"ieaStocksCoverage":{"type":"boolean"},"jodiOilCoverage":{"description":"v2 fields","type":"boolean"},"limitations":{"items":{"type":"string"},"type":"array"},"liveFlowRatio":{"format":"double","type":"number"},"portwatchCoverage":{"type":"boolean"},"products":{"items":{"$ref":"#/components/schemas/ProductImpact"},"type":"array"}},"type":"object"},"CountryPortActivityResponse":{"properties":{"available":{"type":"boolean"},"fetchedAt":{"type":"string"},"ports":{"items":{"$ref":"#/components/schemas/PortActivityEntry"},"type":"array"}},"type":"object"},"CrossSourceSignal":{"description":"CrossSourceSignal represents a single detected cross-domain signal event.","properties":{"contributingTypes":{"items":{"description":"For COMPOSITE_ESCALATION: list of contributing signal type names.","type":"string"},"type":"array"},"detectedAt":{"description":"Detection timestamp (Unix epoch milliseconds).. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"id":{"description":"Unique signal identifier.","type":"string"},"severity":{"description":"CrossSourceSignalSeverity indicates the urgency tier of a detected signal.","enum":["CROSS_SOURCE_SIGNAL_SEVERITY_UNSPECIFIED","CROSS_SOURCE_SIGNAL_SEVERITY_LOW","CROSS_SOURCE_SIGNAL_SEVERITY_MEDIUM","CROSS_SOURCE_SIGNAL_SEVERITY_HIGH","CROSS_SOURCE_SIGNAL_SEVERITY_CRITICAL"],"type":"string"},"severityScore":{"description":"Raw severity score used for ranking (higher = more severe).. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"double","type":"number"},"signalCount":{"description":"For COMPOSITE_ESCALATION: number of co-firing signals in theater.","format":"int32","type":"integer"},"summary":{"description":"Human-readable summary of the signal.","type":"string"},"theater":{"description":"Theater / geographic context (e.g. \"Eastern Europe\", \"Red Sea\", \"Global Markets\").","type":"string"},"type":{"description":"CrossSourceSignalType enumerates all monitored cross-domain signal categories.","enum":["CROSS_SOURCE_SIGNAL_TYPE_UNSPECIFIED","CROSS_SOURCE_SIGNAL_TYPE_COMPOSITE_ESCALATION","CROSS_SOURCE_SIGNAL_TYPE_THERMAL_SPIKE","CROSS_SOURCE_SIGNAL_TYPE_GPS_JAMMING","CROSS_SOURCE_SIGNAL_TYPE_MILITARY_FLIGHT_SURGE","CROSS_SOURCE_SIGNAL_TYPE_UNREST_SURGE","CROSS_SOURCE_SIGNAL_TYPE_OREF_ALERT_CLUSTER","CROSS_SOURCE_SIGNAL_TYPE_VIX_SPIKE","CROSS_SOURCE_SIGNAL_TYPE_COMMODITY_SHOCK","CROSS_SOURCE_SIGNAL_TYPE_CYBER_ESCALATION","CROSS_SOURCE_SIGNAL_TYPE_SHIPPING_DISRUPTION","CROSS_SOURCE_SIGNAL_TYPE_SANCTIONS_SURGE","CROSS_SOURCE_SIGNAL_TYPE_EARTHQUAKE_SIGNIFICANT","CROSS_SOURCE_SIGNAL_TYPE_RADIATION_ANOMALY","CROSS_SOURCE_SIGNAL_TYPE_INFRASTRUCTURE_OUTAGE","CROSS_SOURCE_SIGNAL_TYPE_WILDFIRE_ESCALATION","CROSS_SOURCE_SIGNAL_TYPE_DISPLACEMENT_SURGE","CROSS_SOURCE_SIGNAL_TYPE_FORECAST_DETERIORATION","CROSS_SOURCE_SIGNAL_TYPE_MARKET_STRESS","CROSS_SOURCE_SIGNAL_TYPE_WEATHER_EXTREME","CROSS_SOURCE_SIGNAL_TYPE_MEDIA_TONE_DETERIORATION","CROSS_SOURCE_SIGNAL_TYPE_RISK_SCORE_SPIKE"],"type":"string"}},"type":"object"},"DeductSituationRequest":{"properties":{"framework":{"description":"Optional analytical framework instructions.","type":"string"},"geoContext":{"type":"string"},"query":{"type":"string"}},"type":"object"},"DeductSituationResponse":{"properties":{"analysis":{"type":"string"},"model":{"type":"string"},"provider":{"type":"string"}},"type":"object"},"EnrichedCompany":{"properties":{"description":{"type":"string"},"domain":{"type":"string"},"founded":{"format":"int32","type":"integer"},"location":{"type":"string"},"name":{"type":"string"},"website":{"type":"string"}},"type":"object"},"EnrichedGithub":{"properties":{"avatarUrl":{"type":"string"},"followers":{"format":"int32","type":"integer"},"publicRepos":{"format":"int32","type":"integer"}},"type":"object"},"Error":{"description":"Error is returned when a handler encounters an error. It contains a simple error message that the developer can customize.","properties":{"message":{"description":"Error message (e.g., 'user not found', 'database connection failed')","type":"string"}},"type":"object"},"EventClassification":{"description":"EventClassification represents an AI-generated classification of a real-world event.","properties":{"analysis":{"description":"Brief AI-generated analysis.","type":"string"},"category":{"description":"Event category (e.g., \"military\", \"economic\", \"social\").","type":"string"},"confidence":{"description":"Classification confidence (0.0 to 1.0).","format":"double","maximum":1,"minimum":0,"type":"number"},"entities":{"items":{"description":"Related entities identified.","type":"string"},"type":"array"},"severity":{"description":"SeverityLevel represents a three-tier severity classification used across domains.\n Maps to existing TS unions: 'low' | 'medium' | 'high'.","enum":["SEVERITY_LEVEL_UNSPECIFIED","SEVERITY_LEVEL_LOW","SEVERITY_LEVEL_MEDIUM","SEVERITY_LEVEL_HIGH"],"type":"string"},"subcategory":{"description":"Event subcategory.","type":"string"}},"type":"object"},"FieldViolation":{"description":"FieldViolation describes a single validation error for a specific field.","properties":{"description":{"description":"Human-readable description of the validation violation (e.g., 'must be a valid email address', 'required field missing')","type":"string"},"field":{"description":"The field path that failed validation (e.g., 'user.email' for nested fields). For header validation, this will be the header name (e.g., 'X-API-Key')","type":"string"}},"required":["field","description"],"type":"object"},"GasImpact":{"properties":{"assessment":{"type":"string"},"dataAvailable":{"type":"boolean"},"dataSource":{"type":"string"},"deficitPct":{"format":"double","type":"number"},"lngDisruptionTj":{"format":"double","type":"number"},"lngImportsTj":{"format":"double","type":"number"},"lngShareOfImports":{"format":"double","type":"number"},"storage":{"$ref":"#/components/schemas/GasStorageBuffer"},"totalDemandTj":{"format":"double","type":"number"}},"type":"object"},"GasStorageBuffer":{"properties":{"bufferDays":{"format":"double","type":"number"},"date":{"type":"string"},"fillPct":{"format":"double","type":"number"},"gasTwh":{"format":"double","type":"number"},"scope":{"type":"string"},"trend":{"type":"string"}},"type":"object"},"GdeltArticle":{"description":"GdeltArticle represents a single article from the GDELT document API.","properties":{"date":{"description":"Publication date string.","type":"string"},"image":{"description":"Article image URL.","type":"string"},"language":{"description":"Article language code.","type":"string"},"source":{"description":"Source domain name.","type":"string"},"title":{"description":"Article headline.","type":"string"},"tone":{"description":"GDELT tone score (negative = negative tone, positive = positive tone).","format":"double","type":"number"},"url":{"description":"Article URL.","type":"string"}},"type":"object"},"GdeltTensionPair":{"description":"GdeltTensionPair represents a bilateral tension score between two countries from GDELT.","properties":{"changePercent":{"description":"Percentage change from previous period.","format":"double","type":"number"},"countries":{"items":{"description":"Country pair (ISO 3166-1 alpha-2 codes).","type":"string"},"type":"array"},"id":{"description":"Pair identifier.","type":"string"},"label":{"description":"Human-readable label (e.g., \"US-China\").","type":"string"},"region":{"description":"Geographic region.","type":"string"},"score":{"description":"Tension score (0-100).","format":"double","maximum":100,"minimum":0,"type":"number"},"trend":{"description":"TrendDirection represents the directional movement of a metric over time.\n Used in markets, GDELT tension scores, and risk assessments.","enum":["TREND_DIRECTION_UNSPECIFIED","TREND_DIRECTION_RISING","TREND_DIRECTION_STABLE","TREND_DIRECTION_FALLING"],"type":"string"}},"type":"object"},"GdeltTimelinePoint":{"description":"GdeltTimelinePoint is a single data point in a tone or volume timeline.","properties":{"date":{"description":"Date string from GDELT (e.g. \"20240101T000000\").","type":"string"},"value":{"description":"Tone or volume value at this point.","format":"double","type":"number"}},"type":"object"},"GetCompanyEnrichmentRequest":{"description":"Request to fetch enrichment data for a company.\n Requires either domain (e.g. \"github.com\") or name (e.g. \"GitHub\").","properties":{"domain":{"type":"string"},"name":{"type":"string"}},"type":"object"},"GetCompanyEnrichmentResponse":{"description":"Aggregated company data from multiple public sources.","properties":{"company":{"$ref":"#/components/schemas/EnrichedCompany"},"enrichedAtMs":{"description":"Unix timestamp in milliseconds when this data was fetched.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"github":{"$ref":"#/components/schemas/EnrichedGithub"},"hackerNewsMentions":{"items":{"$ref":"#/components/schemas/HNMention"},"type":"array"},"secFilings":{"$ref":"#/components/schemas/SecFilings"},"sources":{"items":{"description":"List of sources successfully reached (e.g. \"github\", \"sec_edgar\").","type":"string"},"type":"array"},"techStack":{"items":{"$ref":"#/components/schemas/TechStackItem"},"type":"array"}},"type":"object"},"GetCountryEnergyProfileRequest":{"properties":{"countryCode":{"type":"string"}},"type":"object"},"GetCountryEnergyProfileResponse":{"properties":{"coalShare":{"format":"double","type":"number"},"crudeImportsKbd":{"format":"double","type":"number"},"dieselDemandKbd":{"format":"double","type":"number"},"dieselImportsKbd":{"format":"double","type":"number"},"electricityAvailable":{"description":"Phase 2 — EU electricity (ENTSO-E countries: DE FR ES IT NL BE PL PT GB NO SE)\n US electricity is stored by EIA balancing area, not ISO2 — not available here","type":"boolean"},"electricityDate":{"type":"string"},"electricityPriceMwh":{"format":"double","type":"number"},"electricitySource":{"type":"string"},"emberAvailable":{"type":"boolean"},"emberCoalShare":{"format":"double","type":"number"},"emberDataMonth":{"type":"string"},"emberDemandTwh":{"format":"double","type":"number"},"emberFossilShare":{"description":"Phase 3 — Ember monthly electricity mix","format":"double","type":"number"},"emberGasShare":{"format":"double","type":"number"},"emberNuclearShare":{"format":"double","type":"number"},"emberRenewShare":{"format":"double","type":"number"},"gasLngImportsTj":{"format":"double","type":"number"},"gasLngShare":{"format":"double","type":"number"},"gasPipeImportsTj":{"format":"double","type":"number"},"gasShare":{"format":"double","type":"number"},"gasStorageAvailable":{"description":"Phase 2 — EU gas storage","type":"boolean"},"gasStorageChange1d":{"format":"double","type":"number"},"gasStorageDate":{"type":"string"},"gasStorageFillPct":{"format":"double","type":"number"},"gasStorageTrend":{"type":"string"},"gasTotalDemandTj":{"format":"double","type":"number"},"gasolineDemandKbd":{"format":"double","type":"number"},"gasolineImportsKbd":{"format":"double","type":"number"},"hydroShare":{"format":"double","type":"number"},"ieaBelowObligation":{"type":"boolean"},"ieaDaysOfCover":{"format":"int32","type":"integer"},"ieaNetExporter":{"type":"boolean"},"ieaStocksAvailable":{"description":"Phase 2.5 — IEA oil stocks (~31 IEA members)","type":"boolean"},"ieaStocksDataMonth":{"type":"string"},"importShare":{"format":"double","type":"number"},"jetDemandKbd":{"format":"double","type":"number"},"jetImportsKbd":{"format":"double","type":"number"},"jodiGasAvailable":{"description":"Phase 2.5 — JODI Gas","type":"boolean"},"jodiGasDataMonth":{"type":"string"},"jodiOilAvailable":{"description":"Phase 2.5 — JODI Oil (~90+ countries)","type":"boolean"},"jodiOilDataMonth":{"type":"string"},"lpgDemandKbd":{"format":"double","type":"number"},"lpgImportsKbd":{"format":"double","type":"number"},"mixAvailable":{"description":"Phase 1 — OWID structural mix (~200 countries)","type":"boolean"},"mixYear":{"format":"int32","type":"integer"},"nuclearShare":{"format":"double","type":"number"},"oilShare":{"format":"double","type":"number"},"renewShare":{"format":"double","type":"number"},"solarShare":{"format":"double","type":"number"},"windShare":{"format":"double","type":"number"}},"type":"object"},"GetCountryFactsRequest":{"properties":{"countryCode":{"pattern":"^[A-Z]{2}$","type":"string"}},"required":["countryCode"],"type":"object"},"GetCountryFactsResponse":{"properties":{"areaSqKm":{"format":"double","type":"number"},"capital":{"type":"string"},"countryName":{"type":"string"},"currencies":{"items":{"type":"string"},"type":"array"},"headOfState":{"type":"string"},"headOfStateTitle":{"type":"string"},"languages":{"items":{"type":"string"},"type":"array"},"population":{"description":"Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"wikipediaSummary":{"type":"string"},"wikipediaThumbnailUrl":{"type":"string"}},"type":"object"},"GetCountryIntelBriefRequest":{"description":"GetCountryIntelBriefRequest specifies which country to generate a brief for.","properties":{"countryCode":{"description":"ISO 3166-1 alpha-2 country code.","pattern":"^[A-Z]{2}$","type":"string"},"framework":{"description":"Optional analytical framework instructions to append to system prompt. Max 2000 chars enforced at handler level.","type":"string"}},"required":["countryCode"],"type":"object"},"GetCountryIntelBriefResponse":{"description":"GetCountryIntelBriefResponse contains an AI-generated intelligence brief for a country.","properties":{"brief":{"description":"AI-generated intelligence brief text.","type":"string"},"countryCode":{"description":"ISO 3166-1 alpha-2 country code.","type":"string"},"countryName":{"description":"Country name.","type":"string"},"generatedAt":{"description":"Brief generation time, as Unix epoch milliseconds.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"model":{"description":"AI model used for generation.","type":"string"}},"type":"object"},"GetCountryPortActivityRequest":{"properties":{"countryCode":{"type":"string"}},"type":"object"},"GetCountryRiskRequest":{"description":"GetCountryRiskRequest specifies which country to retrieve risk intelligence for.","properties":{"countryCode":{"description":"ISO 3166-1 alpha-2 country code.","pattern":"^[A-Z]{2}$","type":"string"}},"required":["countryCode"],"type":"object"},"GetCountryRiskResponse":{"description":"GetCountryRiskResponse contains composite risk intelligence for a specific country.","properties":{"advisoryLevel":{"description":"Travel advisory level from government sources (e.g. \"do-not-travel\", \"reconsider\", \"caution\"). Empty if none.","type":"string"},"cii":{"$ref":"#/components/schemas/CiiScore"},"countryCode":{"description":"ISO 3166-1 alpha-2 country code.","type":"string"},"countryName":{"description":"Country name.","type":"string"},"fetchedAt":{"description":"Data freshness timestamp derived from CII computedAt, as Unix epoch milliseconds.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"sanctionsActive":{"description":"Whether this country has active OFAC sanctions designations.","type":"boolean"},"sanctionsCount":{"description":"Count of sanctioned entities associated with this country.","format":"int32","type":"integer"},"upstreamUnavailable":{"description":"True when all upstream Redis keys were unavailable. Signals CDN cache bypass.","type":"boolean"}},"type":"object"},"GetGdeltTopicTimelineRequest":{"description":"GetGdeltTopicTimelineRequest retrieves tone and volume timelines for a GDELT intel topic.","properties":{"topic":{"description":"Topic ID (military, cyber, nuclear, sanctions, intelligence, maritime).","type":"string"}},"type":"object"},"GetGdeltTopicTimelineResponse":{"description":"GetGdeltTopicTimelineResponse contains tone and volume timelines for a topic.","properties":{"error":{"description":"Error message if fetch failed.","type":"string"},"fetchedAt":{"description":"ISO timestamp when this data was fetched.","type":"string"},"tone":{"items":{"$ref":"#/components/schemas/GdeltTimelinePoint"},"type":"array"},"topic":{"description":"Topic ID.","type":"string"},"vol":{"items":{"$ref":"#/components/schemas/GdeltTimelinePoint"},"type":"array"}},"type":"object"},"GetPizzintStatusRequest":{"description":"GetPizzintStatusRequest specifies parameters for retrieving PizzINT and GDELT data.","properties":{"includeGdelt":{"description":"Whether to include GDELT tension pairs in the response.","type":"boolean"}},"type":"object"},"GetPizzintStatusResponse":{"description":"GetPizzintStatusResponse contains Pentagon Pizza Index and GDELT tension data.","properties":{"pizzint":{"$ref":"#/components/schemas/PizzintStatus"},"tensionPairs":{"items":{"$ref":"#/components/schemas/GdeltTensionPair"},"type":"array"}},"type":"object"},"GetRiskScoresRequest":{"description":"GetRiskScoresRequest specifies parameters for retrieving risk scores.","properties":{"region":{"description":"Optional region filter. Empty returns all tracked regions.","type":"string"}},"type":"object"},"GetRiskScoresResponse":{"description":"GetRiskScoresResponse contains composite risk scores and strategic assessments.","properties":{"ciiScores":{"items":{"$ref":"#/components/schemas/CiiScore"},"type":"array"},"strategicRisks":{"items":{"$ref":"#/components/schemas/StrategicRisk"},"type":"array"}},"type":"object"},"GetSocialVelocityRequest":{"type":"object"},"GetSocialVelocityResponse":{"properties":{"fetchedAt":{"description":"Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"posts":{"items":{"$ref":"#/components/schemas/SocialVelocityPost"},"type":"array"}},"type":"object"},"GpsJamHex":{"description":"GpsJamHex represents a geographic hexagon with detected GPS interference.","properties":{"aircraftCount":{"description":"Number of unique aircraft that reported in this hex.","format":"int32","type":"integer"},"h3":{"description":"H3 index of the hexagon.","minLength":1,"type":"string"},"lat":{"description":"Centroid latitude.","format":"double","type":"number"},"level":{"description":"InterferenceLevel represents the severity of detected signal interference.","enum":["INTERFERENCE_LEVEL_UNSPECIFIED","INTERFERENCE_LEVEL_LOW","INTERFERENCE_LEVEL_MEDIUM","INTERFERENCE_LEVEL_HIGH"],"type":"string"},"lon":{"description":"Centroid longitude.","format":"double","type":"number"},"npAvg":{"description":"Average Navigation Precision (np) - lower means more interference.","format":"double","type":"number"},"sampleCount":{"description":"Number of samples in this hex.","format":"int32","type":"integer"}},"required":["h3"],"type":"object"},"GpsJamStats":{"description":"GpsJamStats contains aggregate statistics for GPS interference.","properties":{"highCount":{"format":"int32","type":"integer"},"mediumCount":{"format":"int32","type":"integer"},"totalHexes":{"format":"int32","type":"integer"}},"type":"object"},"HNMention":{"properties":{"comments":{"format":"int32","type":"integer"},"createdAtMs":{"description":"Unix timestamp in milliseconds when the post was created.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"points":{"format":"int32","type":"integer"},"title":{"type":"string"},"url":{"type":"string"}},"type":"object"},"ListCompanySignalsRequest":{"description":"Request to discover and classify company signals (hiring, funding, tech changes).","properties":{"company":{"type":"string"},"domain":{"type":"string"}},"type":"object"},"ListCompanySignalsResponse":{"description":"Discovered company signals with classification and engagement metrics.","properties":{"company":{"type":"string"},"discoveredAtMs":{"description":"Unix timestamp in milliseconds when signals were discovered.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"domain":{"type":"string"},"signals":{"items":{"$ref":"#/components/schemas/CompanySignal"},"type":"array"},"summary":{"$ref":"#/components/schemas/SignalSummary"}},"type":"object"},"ListCrossSourceSignalsRequest":{"description":"ListCrossSourceSignalsRequest has no required parameters (returns all signals).","type":"object"},"ListCrossSourceSignalsResponse":{"description":"ListCrossSourceSignalsResponse contains ranked cross-domain signals.","properties":{"compositeCount":{"description":"Total number of composite escalation zones detected.","format":"int32","type":"integer"},"evaluatedAt":{"description":"Timestamp when the aggregator last ran (Unix epoch milliseconds).. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"signals":{"items":{"$ref":"#/components/schemas/CrossSourceSignal"},"type":"array"}},"type":"object"},"ListGpsInterferenceRequest":{"description":"ListGpsInterferenceRequest specifies filters for GPS interference data.","properties":{"region":{"description":"Optional region filter.","type":"string"}},"type":"object"},"ListGpsInterferenceResponse":{"description":"ListGpsInterferenceResponse contains GPS interference data and stats.","properties":{"fetchedAt":{"description":"Data fetch time, as Unix epoch milliseconds.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"hexes":{"items":{"$ref":"#/components/schemas/GpsJamHex"},"type":"array"},"source":{"description":"Data source name.","type":"string"},"stats":{"$ref":"#/components/schemas/GpsJamStats"}},"type":"object"},"ListMarketImplicationsRequest":{"properties":{"frameworkId":{"type":"string"}},"type":"object"},"ListMarketImplicationsResponse":{"properties":{"cards":{"items":{"$ref":"#/components/schemas/MarketImplicationCard"},"type":"array"},"degraded":{"type":"boolean"},"emptyReason":{"type":"string"},"generatedAt":{"type":"string"}},"type":"object"},"ListOrefAlertsRequest":{"description":"Request to fetch Israeli Red Alerts (OREF).","properties":{"mode":{"enum":["MODE_UNSPECIFIED","MODE_HISTORY"],"type":"string"}},"type":"object"},"ListOrefAlertsResponse":{"description":"OREF alert wave snapshot.","properties":{"alerts":{"items":{"$ref":"#/components/schemas/OrefAlert"},"type":"array"},"configured":{"description":"Whether the OREF bridge is configured.","type":"boolean"},"error":{"description":"Optional error message from the relay.","type":"string"},"history":{"items":{"$ref":"#/components/schemas/OrefWave"},"type":"array"},"historyCount24h":{"description":"Number of alerts in the last 24 hours.","format":"int32","type":"integer"},"timestampMs":{"description":"Unix timestamp in milliseconds of the response generation.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"totalHistoryCount":{"description":"Total alerts in the historical buffer.","format":"int32","type":"integer"}},"type":"object"},"ListSatellitesRequest":{"description":"ListSatellitesRequest specifies filters for orbital data.","properties":{"country":{"description":"Filter by country code. Empty returns all.","type":"string"}},"type":"object"},"ListSatellitesResponse":{"description":"ListSatellitesResponse contains the current orbital snapshot.","properties":{"satellites":{"items":{"$ref":"#/components/schemas/Satellite"},"type":"array"}},"type":"object"},"ListSecurityAdvisoriesRequest":{"type":"object"},"ListSecurityAdvisoriesResponse":{"properties":{"advisories":{"items":{"$ref":"#/components/schemas/SecurityAdvisoryItem"},"type":"array"},"byCountry":{"additionalProperties":{"type":"string"},"type":"object"}},"type":"object"},"ListTelegramFeedRequest":{"description":"Request to fetch real-time Telegram OSINT feed.","properties":{"channel":{"description":"Filter by specific channel ID or name.","type":"string"},"limit":{"description":"Maximum number of messages to return (default 50).","format":"int32","type":"integer"},"topic":{"description":"Filter by topic keyword (e.g. \"military\", \"cyber\").","type":"string"}},"type":"object"},"ListTelegramFeedResponse":{"description":"OSINT feed containing validated Telegram messages.","properties":{"count":{"description":"Total count of messages in the current window.","format":"int32","type":"integer"},"enabled":{"description":"Whether the bridge is currently active.","type":"boolean"},"error":{"description":"Detailed error message if the fetch failed.","type":"string"},"messages":{"items":{"$ref":"#/components/schemas/TelegramMessage"},"type":"array"}},"type":"object"},"MarketImplicationCard":{"properties":{"confidence":{"type":"string"},"direction":{"type":"string"},"driver":{"type":"string"},"name":{"type":"string"},"narrative":{"type":"string"},"riskCaveat":{"type":"string"},"ticker":{"type":"string"},"timeframe":{"type":"string"},"title":{"type":"string"},"transmissionChain":{"items":{"$ref":"#/components/schemas/TransmissionNode"},"type":"array"}},"type":"object"},"OrefAlert":{"description":"A single Red Alert event.","properties":{"cat":{"type":"string"},"data":{"items":{"type":"string"},"type":"array"},"desc":{"type":"string"},"id":{"type":"string"},"timestampMs":{"description":"Unix timestamp in milliseconds for when the alert was issued.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"title":{"type":"string"}},"type":"object"},"OrefWave":{"description":"A wave of alerts occurring at the same time.","properties":{"alerts":{"items":{"$ref":"#/components/schemas/OrefAlert"},"type":"array"},"timestampMs":{"description":"Unix timestamp in milliseconds for this wave.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"}},"type":"object"},"PizzintLocation":{"description":"PizzintLocation represents a single monitored pizza location near the Pentagon.","properties":{"address":{"description":"Street address.","type":"string"},"currentPopularity":{"description":"Current popularity score (0-200+).","format":"int32","type":"integer"},"dataFreshness":{"description":"DataFreshness represents how current the data is.","enum":["DATA_FRESHNESS_UNSPECIFIED","DATA_FRESHNESS_FRESH","DATA_FRESHNESS_STALE"],"type":"string"},"dataSource":{"description":"Data source identifier.","type":"string"},"isClosedNow":{"description":"Whether the location is currently closed.","type":"boolean"},"isSpike":{"description":"Whether activity constitutes a spike.","type":"boolean"},"lat":{"description":"Latitude of the location.","format":"double","type":"number"},"lng":{"description":"Longitude of the location.","format":"double","type":"number"},"name":{"description":"Location name.","type":"string"},"percentageOfUsual":{"description":"Percentage of usual activity. Zero if unavailable.","format":"int32","type":"integer"},"placeId":{"description":"Google Places ID.","type":"string"},"recordedAt":{"description":"Recording timestamp as ISO 8601 string.","type":"string"},"spikeMagnitude":{"description":"Spike magnitude above baseline. Zero if no spike.","format":"double","type":"number"}},"type":"object"},"PizzintStatus":{"description":"PizzintStatus represents the Pentagon Pizza Index status (proxy for late-night DC activity).","properties":{"activeSpikes":{"description":"Number of active spike locations.","format":"int32","type":"integer"},"aggregateActivity":{"description":"Aggregate activity score.","format":"double","type":"number"},"dataFreshness":{"description":"DataFreshness represents how current the data is.","enum":["DATA_FRESHNESS_UNSPECIFIED","DATA_FRESHNESS_FRESH","DATA_FRESHNESS_STALE"],"type":"string"},"defconLabel":{"description":"Human-readable DEFCON label.","type":"string"},"defconLevel":{"description":"DEFCON-style level (1-5).","format":"int32","maximum":5,"minimum":1,"type":"integer"},"locations":{"items":{"$ref":"#/components/schemas/PizzintLocation"},"type":"array"},"locationsMonitored":{"description":"Total monitored locations.","format":"int32","type":"integer"},"locationsOpen":{"description":"Currently open locations.","format":"int32","type":"integer"},"updatedAt":{"description":"Last data update time, as Unix epoch milliseconds.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"}},"type":"object"},"PortActivityEntry":{"properties":{"anomalySignal":{"type":"boolean"},"exportTankerDwt":{"format":"double","type":"number"},"importTankerDwt":{"format":"double","type":"number"},"lat":{"format":"double","type":"number"},"lon":{"format":"double","type":"number"},"portId":{"type":"string"},"portName":{"type":"string"},"tankerCalls30d":{"format":"int32","type":"integer"},"trendDeltaPct":{"format":"double","type":"number"}},"type":"object"},"ProductImpact":{"properties":{"deficitPct":{"format":"double","type":"number"},"demandKbd":{"format":"double","type":"number"},"outputLossKbd":{"format":"double","type":"number"},"product":{"type":"string"}},"type":"object"},"Satellite":{"description":"Satellite represents an orbital asset tracked by WorldMonitor.","properties":{"alt":{"description":"Orbital altitude in kilometers.","format":"double","type":"number"},"country":{"description":"ISO country code of the operator/owner.","type":"string"},"id":{"description":"NORAD identifier (e.g., \"25544\").","minLength":1,"type":"string"},"inclination":{"description":"Orbital inclination in degrees.","format":"double","type":"number"},"line1":{"description":"TLE line 1.","type":"string"},"line2":{"description":"TLE line 2.","type":"string"},"name":{"description":"Name of the satellite.","type":"string"},"type":{"description":"Purpose category (e.g., \"sar\", \"optical\", \"military\").","type":"string"},"velocity":{"description":"Velocity in km/s.","format":"double","type":"number"}},"required":["id"],"type":"object"},"SearchGdeltDocumentsRequest":{"description":"SearchGdeltDocumentsRequest specifies filters for searching GDELT news articles.","properties":{"maxRecords":{"description":"Maximum number of articles to return (1-250).","format":"int32","maximum":250,"minimum":1,"type":"integer"},"query":{"description":"Search query string.","minLength":1,"type":"string"},"sort":{"description":"Sort mode: \"DateDesc\" (default), \"ToneDesc\", \"ToneAsc\", \"HybridRel\".","type":"string"},"timespan":{"description":"Time span filter (e.g., \"15min\", \"1h\", \"24h\").","type":"string"},"toneFilter":{"description":"Tone filter appended to query (e.g., \"tone\u003e5\" for positive, \"tone\u003c-5\" for negative).\n Left empty to skip tone filtering.","type":"string"}},"required":["query"],"type":"object"},"SearchGdeltDocumentsResponse":{"description":"SearchGdeltDocumentsResponse contains GDELT article search results.","properties":{"articles":{"items":{"$ref":"#/components/schemas/GdeltArticle"},"type":"array"},"error":{"description":"Error message if the search failed.","type":"string"},"query":{"description":"Echo of the search query.","type":"string"}},"type":"object"},"SecFiling":{"properties":{"description":{"type":"string"},"fileDate":{"type":"string"},"form":{"type":"string"}},"type":"object"},"SecFilings":{"properties":{"recentFilings":{"items":{"$ref":"#/components/schemas/SecFiling"},"type":"array"},"totalFilings":{"format":"int32","type":"integer"}},"type":"object"},"SecurityAdvisoryItem":{"properties":{"country":{"type":"string"},"level":{"type":"string"},"link":{"type":"string"},"pubDate":{"type":"string"},"source":{"type":"string"},"sourceCountry":{"type":"string"},"title":{"type":"string"}},"type":"object"},"SignalEngagement":{"properties":{"comments":{"format":"int32","type":"integer"},"forks":{"format":"int32","type":"integer"},"mentions":{"format":"int32","type":"integer"},"points":{"format":"int32","type":"integer"},"stars":{"format":"int32","type":"integer"}},"type":"object"},"SignalSummary":{"properties":{"byType":{"additionalProperties":{"format":"int32","type":"integer"},"type":"object"},"signalDiversity":{"format":"int32","type":"integer"},"strongestSignal":{"$ref":"#/components/schemas/CompanySignal"},"totalSignals":{"format":"int32","type":"integer"}},"type":"object"},"SocialVelocityPost":{"description":"SocialVelocityPost represents a trending Reddit post with velocity scoring.","properties":{"createdAt":{"description":"Unix epoch milliseconds when posted.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"id":{"description":"Reddit post ID.","type":"string"},"numComments":{"description":"Number of comments.","format":"int32","type":"integer"},"score":{"description":"Reddit score (upvotes - downvotes).","format":"int32","type":"integer"},"subreddit":{"description":"Subreddit name (without r/ prefix).","type":"string"},"title":{"description":"Post title.","type":"string"},"upvoteRatio":{"description":"Upvote ratio (0.0–1.0).","format":"double","type":"number"},"url":{"description":"Direct URL to the post.","type":"string"},"velocityScore":{"description":"Composite velocity score accounting for recency, score, and ratio.","format":"double","type":"number"}},"type":"object"},"StrategicRisk":{"description":"StrategicRisk represents a strategic risk assessment for a country or region.","properties":{"factors":{"items":{"description":"Risk factors contributing to the assessment.","type":"string"},"type":"array"},"level":{"description":"SeverityLevel represents a three-tier severity classification used across domains.\n Maps to existing TS unions: 'low' | 'medium' | 'high'.","enum":["SEVERITY_LEVEL_UNSPECIFIED","SEVERITY_LEVEL_LOW","SEVERITY_LEVEL_MEDIUM","SEVERITY_LEVEL_HIGH"],"type":"string"},"region":{"description":"Country or region identifier.","type":"string"},"score":{"description":"Risk score (0-100).","format":"double","maximum":100,"minimum":0,"type":"number"},"trend":{"description":"TrendDirection represents the directional movement of a metric over time.\n Used in markets, GDELT tension scores, and risk assessments.","enum":["TREND_DIRECTION_UNSPECIFIED","TREND_DIRECTION_RISING","TREND_DIRECTION_STABLE","TREND_DIRECTION_FALLING"],"type":"string"}},"type":"object"},"TechStackItem":{"properties":{"category":{"type":"string"},"confidence":{"format":"float","type":"number"},"name":{"type":"string"}},"type":"object"},"TelegramMessage":{"description":"Validated OSINT post from Telegram channels.","properties":{"channelId":{"description":"Identifier of the originating channel.","type":"string"},"channelName":{"description":"Human-readable name of the channel.","type":"string"},"id":{"description":"Unique message identifier.","type":"string"},"mediaUrls":{"items":{"description":"Direct links to associated media (images, videos).","type":"string"},"type":"array"},"sourceUrl":{"description":"Link to the original post on Telegram.","type":"string"},"text":{"description":"Sanitized message content.","type":"string"},"timestampMs":{"description":"Unix timestamp in milliseconds for when the message was posted.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"topic":{"description":"Auto-classified topic based on content.","type":"string"}},"type":"object"},"TransmissionNode":{"properties":{"impactType":{"type":"string"},"logic":{"type":"string"},"node":{"type":"string"}},"type":"object"},"ValidationError":{"description":"ValidationError is returned when request validation fails. It contains a list of field violations describing what went wrong.","properties":{"violations":{"description":"List of validation violations","items":{"$ref":"#/components/schemas/FieldViolation"},"type":"array"}},"required":["violations"],"type":"object"}}},"info":{"title":"IntelligenceService API","version":"1.0.0"},"openapi":"3.1.0","paths":{"/api/intelligence/v1/classify-event":{"get":{"description":"ClassifyEvent analyzes a news event using AI models.","operationId":"ClassifyEvent","parameters":[{"description":"Event title or headline.","in":"query","name":"title","required":false,"schema":{"type":"string"}},{"description":"Event description or body text.","in":"query","name":"description","required":false,"schema":{"type":"string"}},{"description":"Event source (e.g., \"reuters\", \"acled\").","in":"query","name":"source","required":false,"schema":{"type":"string"}},{"description":"Country context (ISO 3166-1 alpha-2).","in":"query","name":"country","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClassifyEventResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"ClassifyEvent","tags":["IntelligenceService"]}},"/api/intelligence/v1/compute-energy-shock":{"get":{"description":"ComputeEnergyShockScenario computes on-demand product supply shock for a given country + chokepoint.","operationId":"ComputeEnergyShockScenario","parameters":[{"in":"query","name":"country_code","required":false,"schema":{"type":"string"}},{"in":"query","name":"chokepoint_id","required":false,"schema":{"type":"string"}},{"in":"query","name":"disruption_pct","required":false,"schema":{"format":"int32","type":"integer"}},{"in":"query","name":"fuel_mode","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComputeEnergyShockScenarioResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"ComputeEnergyShockScenario","tags":["IntelligenceService"]}},"/api/intelligence/v1/deduct-situation":{"post":{"description":"DeductSituation performs broad situational analysis using LLMs.","operationId":"DeductSituation","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeductSituationRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeductSituationResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"DeductSituation","tags":["IntelligenceService"]}},"/api/intelligence/v1/get-company-enrichment":{"get":{"description":"GetCompanyEnrichment aggregates company data from multiple public sources (GitHub, SEC, HN).","operationId":"GetCompanyEnrichment","parameters":[{"in":"query","name":"domain","required":false,"schema":{"type":"string"}},{"in":"query","name":"name","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetCompanyEnrichmentResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"GetCompanyEnrichment","tags":["IntelligenceService"]}},"/api/intelligence/v1/get-country-energy-profile":{"get":{"description":"GetCountryEnergyProfile aggregates Phase 1/2/2.5 energy data per country.","operationId":"GetCountryEnergyProfile","parameters":[{"in":"query","name":"country_code","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetCountryEnergyProfileResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"GetCountryEnergyProfile","tags":["IntelligenceService"]}},"/api/intelligence/v1/get-country-facts":{"get":{"description":"GetCountryFacts retrieves factual country data from RestCountries and Wikipedia.","operationId":"GetCountryFacts","parameters":[{"in":"query","name":"country_code","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetCountryFactsResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"GetCountryFacts","tags":["IntelligenceService"]}},"/api/intelligence/v1/get-country-intel-brief":{"get":{"description":"GetCountryIntelBrief generates a strategic brief for a specific country.","operationId":"GetCountryIntelBrief","parameters":[{"description":"ISO 3166-1 alpha-2 country code.","in":"query","name":"country_code","required":false,"schema":{"type":"string"}},{"description":"Optional analytical framework instructions to append to system prompt. Max 2000 chars enforced at handler level.","in":"query","name":"framework","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetCountryIntelBriefResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"GetCountryIntelBrief","tags":["IntelligenceService"]}},"/api/intelligence/v1/get-country-port-activity":{"get":{"description":"GetCountryPortActivity returns port-level tanker traffic and trade volumes for a country.","operationId":"GetCountryPortActivity","parameters":[{"in":"query","name":"country_code","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CountryPortActivityResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"GetCountryPortActivity","tags":["IntelligenceService"]}},"/api/intelligence/v1/get-country-risk":{"get":{"description":"GetCountryRisk retrieves composite risk intelligence for a specific country.","operationId":"GetCountryRisk","parameters":[{"description":"ISO 3166-1 alpha-2 country code.","in":"query","name":"country_code","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetCountryRiskResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"GetCountryRisk","tags":["IntelligenceService"]}},"/api/intelligence/v1/get-gdelt-topic-timeline":{"get":{"description":"GetGdeltTopicTimeline retrieves tone and volume timelines for a GDELT intel topic.","operationId":"GetGdeltTopicTimeline","parameters":[{"description":"Topic ID (military, cyber, nuclear, sanctions, intelligence, maritime).","in":"query","name":"topic","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetGdeltTopicTimelineResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"GetGdeltTopicTimeline","tags":["IntelligenceService"]}},"/api/intelligence/v1/get-pizzint-status":{"get":{"description":"GetPizzintStatus retrieves Pentagon Pizza Index and GDELT tension data.","operationId":"GetPizzintStatus","parameters":[{"description":"Whether to include GDELT tension pairs in the response.","in":"query","name":"include_gdelt","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetPizzintStatusResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"GetPizzintStatus","tags":["IntelligenceService"]}},"/api/intelligence/v1/get-risk-scores":{"get":{"description":"GetRiskScores retrieves composite risk scores and strategic assessments.","operationId":"GetRiskScores","parameters":[{"description":"Optional region filter. Empty returns all tracked regions.","in":"query","name":"region","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetRiskScoresResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"GetRiskScores","tags":["IntelligenceService"]}},"/api/intelligence/v1/get-social-velocity":{"get":{"description":"GetSocialVelocity returns trending Reddit posts from r/worldnews and r/geopolitics ranked by velocity.","operationId":"GetSocialVelocity","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSocialVelocityResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"GetSocialVelocity","tags":["IntelligenceService"]}},"/api/intelligence/v1/list-company-signals":{"get":{"description":"ListCompanySignals discovers activity signals for a company from public sources.","operationId":"ListCompanySignals","parameters":[{"in":"query","name":"company","required":false,"schema":{"type":"string"}},{"in":"query","name":"domain","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListCompanySignalsResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"ListCompanySignals","tags":["IntelligenceService"]}},"/api/intelligence/v1/list-cross-source-signals":{"get":{"description":"ListCrossSourceSignals returns cross-domain signals ranked by severity with composite escalation detection.","operationId":"ListCrossSourceSignals","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListCrossSourceSignalsResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"ListCrossSourceSignals","tags":["IntelligenceService"]}},"/api/intelligence/v1/list-gps-interference":{"get":{"description":"ListGpsInterference retrieves detected GPS/GNSS interference data (jamming).","operationId":"ListGpsInterference","parameters":[{"description":"Optional region filter.","in":"query","name":"region","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListGpsInterferenceResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"ListGpsInterference","tags":["IntelligenceService"]}},"/api/intelligence/v1/list-market-implications":{"get":{"description":"ListMarketImplications returns AI-generated trade-implication cards from live world state.","operationId":"ListMarketImplications","parameters":[{"in":"query","name":"frameworkId","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListMarketImplicationsResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"ListMarketImplications","tags":["IntelligenceService"]}},"/api/intelligence/v1/list-oref-alerts":{"get":{"description":"ListOrefAlerts retrieves Israeli Home Front Command alerts (Red Alerts).","operationId":"ListOrefAlerts","parameters":[{"description":"Mode selection. MODE_UNSPECIFIED defaults to active alerts.","in":"query","name":"mode","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListOrefAlertsResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"ListOrefAlerts","tags":["IntelligenceService"]}},"/api/intelligence/v1/list-satellites":{"get":{"description":"ListSatellites retrieves current orbital positions and metadata.","operationId":"ListSatellites","parameters":[{"description":"Filter by country code. Empty returns all.","in":"query","name":"country","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSatellitesResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"ListSatellites","tags":["IntelligenceService"]}},"/api/intelligence/v1/list-security-advisories":{"get":{"description":"ListSecurityAdvisories retrieves pre-seeded travel and health advisories.","operationId":"ListSecurityAdvisories","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSecurityAdvisoriesResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"ListSecurityAdvisories","tags":["IntelligenceService"]}},"/api/intelligence/v1/list-telegram-feed":{"get":{"description":"ListTelegramFeed retrieves real-time OSINT messages from monitored Telegram channels.","operationId":"ListTelegramFeed","parameters":[{"description":"Maximum number of messages to return (default 50).","in":"query","name":"limit","required":false,"schema":{"format":"int32","type":"integer"}},{"description":"Filter by topic keyword (e.g. \"military\", \"cyber\").","in":"query","name":"topic","required":false,"schema":{"type":"string"}},{"description":"Filter by specific channel ID or name.","in":"query","name":"channel","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListTelegramFeedResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"ListTelegramFeed","tags":["IntelligenceService"]}},"/api/intelligence/v1/search-gdelt-documents":{"get":{"description":"SearchGdeltDocuments searches the GDELT GKG API for relevant documentation.","operationId":"SearchGdeltDocuments","parameters":[{"description":"Search query string.","in":"query","name":"query","required":false,"schema":{"type":"string"}},{"description":"Maximum number of articles to return (1-250).","in":"query","name":"max_records","required":false,"schema":{"format":"int32","type":"integer"}},{"description":"Time span filter (e.g., \"15min\", \"1h\", \"24h\").","in":"query","name":"timespan","required":false,"schema":{"type":"string"}},{"description":"Tone filter appended to query (e.g., \"tone\u003e5\" for positive, \"tone\u003c-5\" for negative).\n Left empty to skip tone filtering.","in":"query","name":"tone_filter","required":false,"schema":{"type":"string"}},{"description":"Sort mode: \"DateDesc\" (default), \"ToneDesc\", \"ToneAsc\", \"HybridRel\".","in":"query","name":"sort","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchGdeltDocumentsResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"SearchGdeltDocuments","tags":["IntelligenceService"]}}}} \ No newline at end of file +{"components":{"schemas":{"ByCountryEntry":{"properties":{"key":{"type":"string"},"value":{"type":"string"}},"type":"object"},"ByTypeEntry":{"properties":{"key":{"type":"string"},"value":{"format":"int32","type":"integer"}},"type":"object"},"CiiComponents":{"description":"CiiComponents represents the contributing factors to a CII score.","properties":{"ciiContribution":{"description":"CII index contribution (0-100).","format":"double","maximum":100,"minimum":0,"type":"number"},"geoConvergence":{"description":"Geographic convergence score (0-100).","format":"double","maximum":100,"minimum":0,"type":"number"},"militaryActivity":{"description":"Military activity contribution (0-100).","format":"double","maximum":100,"minimum":0,"type":"number"},"newsActivity":{"description":"News activity signal contribution (0-100).","format":"double","maximum":100,"minimum":0,"type":"number"}},"type":"object"},"CiiScore":{"description":"CiiScore represents a Composite Instability Index score for a region or country.","properties":{"combinedScore":{"description":"Combined weighted score (0-100).","format":"double","maximum":100,"minimum":0,"type":"number"},"components":{"$ref":"#/components/schemas/CiiComponents"},"computedAt":{"description":"Last computation time, as Unix epoch milliseconds.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"dynamicScore":{"description":"Dynamic real-time score (0-100).","format":"double","maximum":100,"minimum":0,"type":"number"},"region":{"description":"Region or country identifier.","type":"string"},"staticBaseline":{"description":"Static baseline score (0-100).","format":"double","maximum":100,"minimum":0,"type":"number"},"trend":{"description":"TrendDirection represents the directional movement of a metric over time.\n Used in markets, GDELT tension scores, and risk assessments.","enum":["TREND_DIRECTION_UNSPECIFIED","TREND_DIRECTION_RISING","TREND_DIRECTION_STABLE","TREND_DIRECTION_FALLING"],"type":"string"}},"type":"object"},"ClassifyEventRequest":{"description":"ClassifyEventRequest specifies an event to classify using AI.","properties":{"country":{"description":"Country context (ISO 3166-1 alpha-2).","type":"string"},"description":{"description":"Event description or body text.","type":"string"},"source":{"description":"Event source (e.g., \"reuters\", \"acled\").","type":"string"},"title":{"description":"Event title or headline.","minLength":1,"type":"string"}},"required":["title"],"type":"object"},"ClassifyEventResponse":{"description":"ClassifyEventResponse contains the AI-generated event classification.","properties":{"classification":{"$ref":"#/components/schemas/EventClassification"}},"type":"object"},"CompanySignal":{"properties":{"engagement":{"$ref":"#/components/schemas/SignalEngagement"},"source":{"type":"string"},"sourceTier":{"description":"Data quality tier (1 is authoritative, 5 is low confidence).","format":"int32","type":"integer"},"strength":{"description":"Qualitative strength of the signal (e.g. \"Strong\", \"Emerging\").","type":"string"},"timestampMs":{"description":"Unix timestamp in milliseconds of the event occurrence.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"title":{"type":"string"},"type":{"description":"Classification type (e.g. \"Hiring\", \"Product Launch\", \"Expansion\").","type":"string"},"url":{"type":"string"}},"type":"object"},"ComputeEnergyShockScenarioRequest":{"properties":{"chokepointId":{"type":"string"},"countryCode":{"type":"string"},"disruptionPct":{"format":"int32","type":"integer"},"fuelMode":{"type":"string"}},"type":"object"},"ComputeEnergyShockScenarioResponse":{"properties":{"assessment":{"type":"string"},"chokepointConfidence":{"type":"string"},"chokepointId":{"type":"string"},"comtradeCoverage":{"type":"boolean"},"countryCode":{"type":"string"},"coverageLevel":{"type":"string"},"crudeLossKbd":{"format":"double","type":"number"},"dataAvailable":{"type":"boolean"},"degraded":{"type":"boolean"},"disruptionPct":{"format":"int32","type":"integer"},"effectiveCoverDays":{"format":"int32","type":"integer"},"gasImpact":{"$ref":"#/components/schemas/GasImpact"},"gulfCrudeShare":{"format":"double","type":"number"},"ieaStocksCoverage":{"type":"boolean"},"jodiOilCoverage":{"description":"v2 fields","type":"boolean"},"limitations":{"items":{"type":"string"},"type":"array"},"liveFlowRatio":{"format":"double","type":"number"},"portwatchCoverage":{"type":"boolean"},"products":{"items":{"$ref":"#/components/schemas/ProductImpact"},"type":"array"}},"type":"object"},"CountryPortActivityResponse":{"properties":{"available":{"type":"boolean"},"fetchedAt":{"type":"string"},"ports":{"items":{"$ref":"#/components/schemas/PortActivityEntry"},"type":"array"}},"type":"object"},"CrossSourceSignal":{"description":"CrossSourceSignal represents a single detected cross-domain signal event.","properties":{"contributingTypes":{"items":{"description":"For COMPOSITE_ESCALATION: list of contributing signal type names.","type":"string"},"type":"array"},"detectedAt":{"description":"Detection timestamp (Unix epoch milliseconds).. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"id":{"description":"Unique signal identifier.","type":"string"},"severity":{"description":"CrossSourceSignalSeverity indicates the urgency tier of a detected signal.","enum":["CROSS_SOURCE_SIGNAL_SEVERITY_UNSPECIFIED","CROSS_SOURCE_SIGNAL_SEVERITY_LOW","CROSS_SOURCE_SIGNAL_SEVERITY_MEDIUM","CROSS_SOURCE_SIGNAL_SEVERITY_HIGH","CROSS_SOURCE_SIGNAL_SEVERITY_CRITICAL"],"type":"string"},"severityScore":{"description":"Raw severity score used for ranking (higher = more severe).. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"double","type":"number"},"signalCount":{"description":"For COMPOSITE_ESCALATION: number of co-firing signals in theater.","format":"int32","type":"integer"},"summary":{"description":"Human-readable summary of the signal.","type":"string"},"theater":{"description":"Theater / geographic context (e.g. \"Eastern Europe\", \"Red Sea\", \"Global Markets\").","type":"string"},"type":{"description":"CrossSourceSignalType enumerates all monitored cross-domain signal categories.","enum":["CROSS_SOURCE_SIGNAL_TYPE_UNSPECIFIED","CROSS_SOURCE_SIGNAL_TYPE_COMPOSITE_ESCALATION","CROSS_SOURCE_SIGNAL_TYPE_THERMAL_SPIKE","CROSS_SOURCE_SIGNAL_TYPE_GPS_JAMMING","CROSS_SOURCE_SIGNAL_TYPE_MILITARY_FLIGHT_SURGE","CROSS_SOURCE_SIGNAL_TYPE_UNREST_SURGE","CROSS_SOURCE_SIGNAL_TYPE_OREF_ALERT_CLUSTER","CROSS_SOURCE_SIGNAL_TYPE_VIX_SPIKE","CROSS_SOURCE_SIGNAL_TYPE_COMMODITY_SHOCK","CROSS_SOURCE_SIGNAL_TYPE_CYBER_ESCALATION","CROSS_SOURCE_SIGNAL_TYPE_SHIPPING_DISRUPTION","CROSS_SOURCE_SIGNAL_TYPE_SANCTIONS_SURGE","CROSS_SOURCE_SIGNAL_TYPE_EARTHQUAKE_SIGNIFICANT","CROSS_SOURCE_SIGNAL_TYPE_RADIATION_ANOMALY","CROSS_SOURCE_SIGNAL_TYPE_INFRASTRUCTURE_OUTAGE","CROSS_SOURCE_SIGNAL_TYPE_WILDFIRE_ESCALATION","CROSS_SOURCE_SIGNAL_TYPE_DISPLACEMENT_SURGE","CROSS_SOURCE_SIGNAL_TYPE_FORECAST_DETERIORATION","CROSS_SOURCE_SIGNAL_TYPE_MARKET_STRESS","CROSS_SOURCE_SIGNAL_TYPE_WEATHER_EXTREME","CROSS_SOURCE_SIGNAL_TYPE_MEDIA_TONE_DETERIORATION","CROSS_SOURCE_SIGNAL_TYPE_RISK_SCORE_SPIKE"],"type":"string"}},"type":"object"},"DeductSituationRequest":{"properties":{"framework":{"description":"Optional analytical framework instructions.","type":"string"},"geoContext":{"type":"string"},"query":{"type":"string"}},"type":"object"},"DeductSituationResponse":{"properties":{"analysis":{"type":"string"},"model":{"type":"string"},"provider":{"type":"string"}},"type":"object"},"EnrichedCompany":{"properties":{"description":{"type":"string"},"domain":{"type":"string"},"founded":{"format":"int32","type":"integer"},"location":{"type":"string"},"name":{"type":"string"},"website":{"type":"string"}},"type":"object"},"EnrichedGithub":{"properties":{"avatarUrl":{"type":"string"},"followers":{"format":"int32","type":"integer"},"publicRepos":{"format":"int32","type":"integer"}},"type":"object"},"Error":{"description":"Error is returned when a handler encounters an error. It contains a simple error message that the developer can customize.","properties":{"message":{"description":"Error message (e.g., 'user not found', 'database connection failed')","type":"string"}},"type":"object"},"EventClassification":{"description":"EventClassification represents an AI-generated classification of a real-world event.","properties":{"analysis":{"description":"Brief AI-generated analysis.","type":"string"},"category":{"description":"Event category (e.g., \"military\", \"economic\", \"social\").","type":"string"},"confidence":{"description":"Classification confidence (0.0 to 1.0).","format":"double","maximum":1,"minimum":0,"type":"number"},"entities":{"items":{"description":"Related entities identified.","type":"string"},"type":"array"},"severity":{"description":"SeverityLevel represents a three-tier severity classification used across domains.\n Maps to existing TS unions: 'low' | 'medium' | 'high'.","enum":["SEVERITY_LEVEL_UNSPECIFIED","SEVERITY_LEVEL_LOW","SEVERITY_LEVEL_MEDIUM","SEVERITY_LEVEL_HIGH"],"type":"string"},"subcategory":{"description":"Event subcategory.","type":"string"}},"type":"object"},"FieldViolation":{"description":"FieldViolation describes a single validation error for a specific field.","properties":{"description":{"description":"Human-readable description of the validation violation (e.g., 'must be a valid email address', 'required field missing')","type":"string"},"field":{"description":"The field path that failed validation (e.g., 'user.email' for nested fields). For header validation, this will be the header name (e.g., 'X-API-Key')","type":"string"}},"required":["field","description"],"type":"object"},"GasImpact":{"properties":{"assessment":{"type":"string"},"dataAvailable":{"type":"boolean"},"dataSource":{"type":"string"},"deficitPct":{"format":"double","type":"number"},"lngDisruptionTj":{"format":"double","type":"number"},"lngImportsTj":{"format":"double","type":"number"},"lngShareOfImports":{"format":"double","type":"number"},"storage":{"$ref":"#/components/schemas/GasStorageBuffer"},"totalDemandTj":{"format":"double","type":"number"}},"type":"object"},"GasStorageBuffer":{"properties":{"bufferDays":{"format":"double","type":"number"},"date":{"type":"string"},"fillPct":{"format":"double","type":"number"},"gasTwh":{"format":"double","type":"number"},"scope":{"type":"string"},"trend":{"type":"string"}},"type":"object"},"GdeltArticle":{"description":"GdeltArticle represents a single article from the GDELT document API.","properties":{"date":{"description":"Publication date string.","type":"string"},"image":{"description":"Article image URL.","type":"string"},"language":{"description":"Article language code.","type":"string"},"source":{"description":"Source domain name.","type":"string"},"title":{"description":"Article headline.","type":"string"},"tone":{"description":"GDELT tone score (negative = negative tone, positive = positive tone).","format":"double","type":"number"},"url":{"description":"Article URL.","type":"string"}},"type":"object"},"GdeltTensionPair":{"description":"GdeltTensionPair represents a bilateral tension score between two countries from GDELT.","properties":{"changePercent":{"description":"Percentage change from previous period.","format":"double","type":"number"},"countries":{"items":{"description":"Country pair (ISO 3166-1 alpha-2 codes).","type":"string"},"type":"array"},"id":{"description":"Pair identifier.","type":"string"},"label":{"description":"Human-readable label (e.g., \"US-China\").","type":"string"},"region":{"description":"Geographic region.","type":"string"},"score":{"description":"Tension score (0-100).","format":"double","maximum":100,"minimum":0,"type":"number"},"trend":{"description":"TrendDirection represents the directional movement of a metric over time.\n Used in markets, GDELT tension scores, and risk assessments.","enum":["TREND_DIRECTION_UNSPECIFIED","TREND_DIRECTION_RISING","TREND_DIRECTION_STABLE","TREND_DIRECTION_FALLING"],"type":"string"}},"type":"object"},"GdeltTimelinePoint":{"description":"GdeltTimelinePoint is a single data point in a tone or volume timeline.","properties":{"date":{"description":"Date string from GDELT (e.g. \"20240101T000000\").","type":"string"},"value":{"description":"Tone or volume value at this point.","format":"double","type":"number"}},"type":"object"},"GetCompanyEnrichmentRequest":{"description":"Request to fetch enrichment data for a company.\n Requires either domain (e.g. \"github.com\") or name (e.g. \"GitHub\").","properties":{"domain":{"type":"string"},"name":{"type":"string"}},"type":"object"},"GetCompanyEnrichmentResponse":{"description":"Aggregated company data from multiple public sources.","properties":{"company":{"$ref":"#/components/schemas/EnrichedCompany"},"enrichedAtMs":{"description":"Unix timestamp in milliseconds when this data was fetched.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"github":{"$ref":"#/components/schemas/EnrichedGithub"},"hackerNewsMentions":{"items":{"$ref":"#/components/schemas/HNMention"},"type":"array"},"secFilings":{"$ref":"#/components/schemas/SecFilings"},"sources":{"items":{"description":"List of sources successfully reached (e.g. \"github\", \"sec_edgar\").","type":"string"},"type":"array"},"techStack":{"items":{"$ref":"#/components/schemas/TechStackItem"},"type":"array"}},"type":"object"},"GetCountryEnergyProfileRequest":{"properties":{"countryCode":{"type":"string"}},"type":"object"},"GetCountryEnergyProfileResponse":{"properties":{"coalShare":{"format":"double","type":"number"},"crudeImportsKbd":{"format":"double","type":"number"},"dieselDemandKbd":{"format":"double","type":"number"},"dieselImportsKbd":{"format":"double","type":"number"},"electricityAvailable":{"description":"Phase 2 — EU electricity (ENTSO-E countries: DE FR ES IT NL BE PL PT GB NO SE)\n US electricity is stored by EIA balancing area, not ISO2 — not available here","type":"boolean"},"electricityDate":{"type":"string"},"electricityPriceMwh":{"format":"double","type":"number"},"electricitySource":{"type":"string"},"emberAvailable":{"type":"boolean"},"emberCoalShare":{"format":"double","type":"number"},"emberDataMonth":{"type":"string"},"emberDemandTwh":{"format":"double","type":"number"},"emberFossilShare":{"description":"Phase 3 — Ember monthly electricity mix","format":"double","type":"number"},"emberGasShare":{"format":"double","type":"number"},"emberNuclearShare":{"format":"double","type":"number"},"emberRenewShare":{"format":"double","type":"number"},"gasLngImportsTj":{"format":"double","type":"number"},"gasLngShare":{"format":"double","type":"number"},"gasPipeImportsTj":{"format":"double","type":"number"},"gasShare":{"format":"double","type":"number"},"gasStorageAvailable":{"description":"Phase 2 — EU gas storage","type":"boolean"},"gasStorageChange1d":{"format":"double","type":"number"},"gasStorageDate":{"type":"string"},"gasStorageFillPct":{"format":"double","type":"number"},"gasStorageTrend":{"type":"string"},"gasTotalDemandTj":{"format":"double","type":"number"},"gasolineDemandKbd":{"format":"double","type":"number"},"gasolineImportsKbd":{"format":"double","type":"number"},"hydroShare":{"format":"double","type":"number"},"ieaBelowObligation":{"type":"boolean"},"ieaDaysOfCover":{"format":"int32","type":"integer"},"ieaNetExporter":{"type":"boolean"},"ieaStocksAvailable":{"description":"Phase 2.5 — IEA oil stocks (~31 IEA members)","type":"boolean"},"ieaStocksDataMonth":{"type":"string"},"importShare":{"format":"double","type":"number"},"jetDemandKbd":{"format":"double","type":"number"},"jetImportsKbd":{"format":"double","type":"number"},"jodiGasAvailable":{"description":"Phase 2.5 — JODI Gas","type":"boolean"},"jodiGasDataMonth":{"type":"string"},"jodiOilAvailable":{"description":"Phase 2.5 — JODI Oil (~90+ countries)","type":"boolean"},"jodiOilDataMonth":{"type":"string"},"lpgDemandKbd":{"format":"double","type":"number"},"lpgImportsKbd":{"format":"double","type":"number"},"mixAvailable":{"description":"Phase 1 — OWID structural mix (~200 countries)","type":"boolean"},"mixYear":{"format":"int32","type":"integer"},"nuclearShare":{"format":"double","type":"number"},"oilShare":{"format":"double","type":"number"},"renewShare":{"format":"double","type":"number"},"solarShare":{"format":"double","type":"number"},"sprAsOf":{"type":"string"},"sprAvailable":{"type":"boolean"},"sprCapacityMb":{"format":"double","type":"number"},"sprIeaMember":{"type":"boolean"},"sprNote":{"type":"string"},"sprOperator":{"type":"string"},"sprRegime":{"description":"Phase 4 — SPR policy classification","type":"string"},"sprSource":{"type":"string"},"sprStockholdingModel":{"type":"string"},"windShare":{"format":"double","type":"number"}},"type":"object"},"GetCountryFactsRequest":{"properties":{"countryCode":{"pattern":"^[A-Z]{2}$","type":"string"}},"required":["countryCode"],"type":"object"},"GetCountryFactsResponse":{"properties":{"areaSqKm":{"format":"double","type":"number"},"capital":{"type":"string"},"countryName":{"type":"string"},"currencies":{"items":{"type":"string"},"type":"array"},"headOfState":{"type":"string"},"headOfStateTitle":{"type":"string"},"languages":{"items":{"type":"string"},"type":"array"},"population":{"description":"Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"wikipediaSummary":{"type":"string"},"wikipediaThumbnailUrl":{"type":"string"}},"type":"object"},"GetCountryIntelBriefRequest":{"description":"GetCountryIntelBriefRequest specifies which country to generate a brief for.","properties":{"countryCode":{"description":"ISO 3166-1 alpha-2 country code.","pattern":"^[A-Z]{2}$","type":"string"},"framework":{"description":"Optional analytical framework instructions to append to system prompt. Max 2000 chars enforced at handler level.","type":"string"}},"required":["countryCode"],"type":"object"},"GetCountryIntelBriefResponse":{"description":"GetCountryIntelBriefResponse contains an AI-generated intelligence brief for a country.","properties":{"brief":{"description":"AI-generated intelligence brief text.","type":"string"},"countryCode":{"description":"ISO 3166-1 alpha-2 country code.","type":"string"},"countryName":{"description":"Country name.","type":"string"},"generatedAt":{"description":"Brief generation time, as Unix epoch milliseconds.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"model":{"description":"AI model used for generation.","type":"string"}},"type":"object"},"GetCountryPortActivityRequest":{"properties":{"countryCode":{"type":"string"}},"type":"object"},"GetCountryRiskRequest":{"description":"GetCountryRiskRequest specifies which country to retrieve risk intelligence for.","properties":{"countryCode":{"description":"ISO 3166-1 alpha-2 country code.","pattern":"^[A-Z]{2}$","type":"string"}},"required":["countryCode"],"type":"object"},"GetCountryRiskResponse":{"description":"GetCountryRiskResponse contains composite risk intelligence for a specific country.","properties":{"advisoryLevel":{"description":"Travel advisory level from government sources (e.g. \"do-not-travel\", \"reconsider\", \"caution\"). Empty if none.","type":"string"},"cii":{"$ref":"#/components/schemas/CiiScore"},"countryCode":{"description":"ISO 3166-1 alpha-2 country code.","type":"string"},"countryName":{"description":"Country name.","type":"string"},"fetchedAt":{"description":"Data freshness timestamp derived from CII computedAt, as Unix epoch milliseconds.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"sanctionsActive":{"description":"Whether this country has active OFAC sanctions designations.","type":"boolean"},"sanctionsCount":{"description":"Count of sanctioned entities associated with this country.","format":"int32","type":"integer"},"upstreamUnavailable":{"description":"True when all upstream Redis keys were unavailable. Signals CDN cache bypass.","type":"boolean"}},"type":"object"},"GetGdeltTopicTimelineRequest":{"description":"GetGdeltTopicTimelineRequest retrieves tone and volume timelines for a GDELT intel topic.","properties":{"topic":{"description":"Topic ID (military, cyber, nuclear, sanctions, intelligence, maritime).","type":"string"}},"type":"object"},"GetGdeltTopicTimelineResponse":{"description":"GetGdeltTopicTimelineResponse contains tone and volume timelines for a topic.","properties":{"error":{"description":"Error message if fetch failed.","type":"string"},"fetchedAt":{"description":"ISO timestamp when this data was fetched.","type":"string"},"tone":{"items":{"$ref":"#/components/schemas/GdeltTimelinePoint"},"type":"array"},"topic":{"description":"Topic ID.","type":"string"},"vol":{"items":{"$ref":"#/components/schemas/GdeltTimelinePoint"},"type":"array"}},"type":"object"},"GetPizzintStatusRequest":{"description":"GetPizzintStatusRequest specifies parameters for retrieving PizzINT and GDELT data.","properties":{"includeGdelt":{"description":"Whether to include GDELT tension pairs in the response.","type":"boolean"}},"type":"object"},"GetPizzintStatusResponse":{"description":"GetPizzintStatusResponse contains Pentagon Pizza Index and GDELT tension data.","properties":{"pizzint":{"$ref":"#/components/schemas/PizzintStatus"},"tensionPairs":{"items":{"$ref":"#/components/schemas/GdeltTensionPair"},"type":"array"}},"type":"object"},"GetRiskScoresRequest":{"description":"GetRiskScoresRequest specifies parameters for retrieving risk scores.","properties":{"region":{"description":"Optional region filter. Empty returns all tracked regions.","type":"string"}},"type":"object"},"GetRiskScoresResponse":{"description":"GetRiskScoresResponse contains composite risk scores and strategic assessments.","properties":{"ciiScores":{"items":{"$ref":"#/components/schemas/CiiScore"},"type":"array"},"strategicRisks":{"items":{"$ref":"#/components/schemas/StrategicRisk"},"type":"array"}},"type":"object"},"GetSocialVelocityRequest":{"type":"object"},"GetSocialVelocityResponse":{"properties":{"fetchedAt":{"description":"Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"posts":{"items":{"$ref":"#/components/schemas/SocialVelocityPost"},"type":"array"}},"type":"object"},"GpsJamHex":{"description":"GpsJamHex represents a geographic hexagon with detected GPS interference.","properties":{"aircraftCount":{"description":"Number of unique aircraft that reported in this hex.","format":"int32","type":"integer"},"h3":{"description":"H3 index of the hexagon.","minLength":1,"type":"string"},"lat":{"description":"Centroid latitude.","format":"double","type":"number"},"level":{"description":"InterferenceLevel represents the severity of detected signal interference.","enum":["INTERFERENCE_LEVEL_UNSPECIFIED","INTERFERENCE_LEVEL_LOW","INTERFERENCE_LEVEL_MEDIUM","INTERFERENCE_LEVEL_HIGH"],"type":"string"},"lon":{"description":"Centroid longitude.","format":"double","type":"number"},"npAvg":{"description":"Average Navigation Precision (np) - lower means more interference.","format":"double","type":"number"},"sampleCount":{"description":"Number of samples in this hex.","format":"int32","type":"integer"}},"required":["h3"],"type":"object"},"GpsJamStats":{"description":"GpsJamStats contains aggregate statistics for GPS interference.","properties":{"highCount":{"format":"int32","type":"integer"},"mediumCount":{"format":"int32","type":"integer"},"totalHexes":{"format":"int32","type":"integer"}},"type":"object"},"HNMention":{"properties":{"comments":{"format":"int32","type":"integer"},"createdAtMs":{"description":"Unix timestamp in milliseconds when the post was created.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"points":{"format":"int32","type":"integer"},"title":{"type":"string"},"url":{"type":"string"}},"type":"object"},"ListCompanySignalsRequest":{"description":"Request to discover and classify company signals (hiring, funding, tech changes).","properties":{"company":{"type":"string"},"domain":{"type":"string"}},"type":"object"},"ListCompanySignalsResponse":{"description":"Discovered company signals with classification and engagement metrics.","properties":{"company":{"type":"string"},"discoveredAtMs":{"description":"Unix timestamp in milliseconds when signals were discovered.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"domain":{"type":"string"},"signals":{"items":{"$ref":"#/components/schemas/CompanySignal"},"type":"array"},"summary":{"$ref":"#/components/schemas/SignalSummary"}},"type":"object"},"ListCrossSourceSignalsRequest":{"description":"ListCrossSourceSignalsRequest has no required parameters (returns all signals).","type":"object"},"ListCrossSourceSignalsResponse":{"description":"ListCrossSourceSignalsResponse contains ranked cross-domain signals.","properties":{"compositeCount":{"description":"Total number of composite escalation zones detected.","format":"int32","type":"integer"},"evaluatedAt":{"description":"Timestamp when the aggregator last ran (Unix epoch milliseconds).. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"signals":{"items":{"$ref":"#/components/schemas/CrossSourceSignal"},"type":"array"}},"type":"object"},"ListGpsInterferenceRequest":{"description":"ListGpsInterferenceRequest specifies filters for GPS interference data.","properties":{"region":{"description":"Optional region filter.","type":"string"}},"type":"object"},"ListGpsInterferenceResponse":{"description":"ListGpsInterferenceResponse contains GPS interference data and stats.","properties":{"fetchedAt":{"description":"Data fetch time, as Unix epoch milliseconds.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"hexes":{"items":{"$ref":"#/components/schemas/GpsJamHex"},"type":"array"},"source":{"description":"Data source name.","type":"string"},"stats":{"$ref":"#/components/schemas/GpsJamStats"}},"type":"object"},"ListMarketImplicationsRequest":{"properties":{"frameworkId":{"type":"string"}},"type":"object"},"ListMarketImplicationsResponse":{"properties":{"cards":{"items":{"$ref":"#/components/schemas/MarketImplicationCard"},"type":"array"},"degraded":{"type":"boolean"},"emptyReason":{"type":"string"},"generatedAt":{"type":"string"}},"type":"object"},"ListOrefAlertsRequest":{"description":"Request to fetch Israeli Red Alerts (OREF).","properties":{"mode":{"enum":["MODE_UNSPECIFIED","MODE_HISTORY"],"type":"string"}},"type":"object"},"ListOrefAlertsResponse":{"description":"OREF alert wave snapshot.","properties":{"alerts":{"items":{"$ref":"#/components/schemas/OrefAlert"},"type":"array"},"configured":{"description":"Whether the OREF bridge is configured.","type":"boolean"},"error":{"description":"Optional error message from the relay.","type":"string"},"history":{"items":{"$ref":"#/components/schemas/OrefWave"},"type":"array"},"historyCount24h":{"description":"Number of alerts in the last 24 hours.","format":"int32","type":"integer"},"timestampMs":{"description":"Unix timestamp in milliseconds of the response generation.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"totalHistoryCount":{"description":"Total alerts in the historical buffer.","format":"int32","type":"integer"}},"type":"object"},"ListSatellitesRequest":{"description":"ListSatellitesRequest specifies filters for orbital data.","properties":{"country":{"description":"Filter by country code. Empty returns all.","type":"string"}},"type":"object"},"ListSatellitesResponse":{"description":"ListSatellitesResponse contains the current orbital snapshot.","properties":{"satellites":{"items":{"$ref":"#/components/schemas/Satellite"},"type":"array"}},"type":"object"},"ListSecurityAdvisoriesRequest":{"type":"object"},"ListSecurityAdvisoriesResponse":{"properties":{"advisories":{"items":{"$ref":"#/components/schemas/SecurityAdvisoryItem"},"type":"array"},"byCountry":{"additionalProperties":{"type":"string"},"type":"object"}},"type":"object"},"ListTelegramFeedRequest":{"description":"Request to fetch real-time Telegram OSINT feed.","properties":{"channel":{"description":"Filter by specific channel ID or name.","type":"string"},"limit":{"description":"Maximum number of messages to return (default 50).","format":"int32","type":"integer"},"topic":{"description":"Filter by topic keyword (e.g. \"military\", \"cyber\").","type":"string"}},"type":"object"},"ListTelegramFeedResponse":{"description":"OSINT feed containing validated Telegram messages.","properties":{"count":{"description":"Total count of messages in the current window.","format":"int32","type":"integer"},"enabled":{"description":"Whether the bridge is currently active.","type":"boolean"},"error":{"description":"Detailed error message if the fetch failed.","type":"string"},"messages":{"items":{"$ref":"#/components/schemas/TelegramMessage"},"type":"array"}},"type":"object"},"MarketImplicationCard":{"properties":{"confidence":{"type":"string"},"direction":{"type":"string"},"driver":{"type":"string"},"name":{"type":"string"},"narrative":{"type":"string"},"riskCaveat":{"type":"string"},"ticker":{"type":"string"},"timeframe":{"type":"string"},"title":{"type":"string"},"transmissionChain":{"items":{"$ref":"#/components/schemas/TransmissionNode"},"type":"array"}},"type":"object"},"OrefAlert":{"description":"A single Red Alert event.","properties":{"cat":{"type":"string"},"data":{"items":{"type":"string"},"type":"array"},"desc":{"type":"string"},"id":{"type":"string"},"timestampMs":{"description":"Unix timestamp in milliseconds for when the alert was issued.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"title":{"type":"string"}},"type":"object"},"OrefWave":{"description":"A wave of alerts occurring at the same time.","properties":{"alerts":{"items":{"$ref":"#/components/schemas/OrefAlert"},"type":"array"},"timestampMs":{"description":"Unix timestamp in milliseconds for this wave.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"}},"type":"object"},"PizzintLocation":{"description":"PizzintLocation represents a single monitored pizza location near the Pentagon.","properties":{"address":{"description":"Street address.","type":"string"},"currentPopularity":{"description":"Current popularity score (0-200+).","format":"int32","type":"integer"},"dataFreshness":{"description":"DataFreshness represents how current the data is.","enum":["DATA_FRESHNESS_UNSPECIFIED","DATA_FRESHNESS_FRESH","DATA_FRESHNESS_STALE"],"type":"string"},"dataSource":{"description":"Data source identifier.","type":"string"},"isClosedNow":{"description":"Whether the location is currently closed.","type":"boolean"},"isSpike":{"description":"Whether activity constitutes a spike.","type":"boolean"},"lat":{"description":"Latitude of the location.","format":"double","type":"number"},"lng":{"description":"Longitude of the location.","format":"double","type":"number"},"name":{"description":"Location name.","type":"string"},"percentageOfUsual":{"description":"Percentage of usual activity. Zero if unavailable.","format":"int32","type":"integer"},"placeId":{"description":"Google Places ID.","type":"string"},"recordedAt":{"description":"Recording timestamp as ISO 8601 string.","type":"string"},"spikeMagnitude":{"description":"Spike magnitude above baseline. Zero if no spike.","format":"double","type":"number"}},"type":"object"},"PizzintStatus":{"description":"PizzintStatus represents the Pentagon Pizza Index status (proxy for late-night DC activity).","properties":{"activeSpikes":{"description":"Number of active spike locations.","format":"int32","type":"integer"},"aggregateActivity":{"description":"Aggregate activity score.","format":"double","type":"number"},"dataFreshness":{"description":"DataFreshness represents how current the data is.","enum":["DATA_FRESHNESS_UNSPECIFIED","DATA_FRESHNESS_FRESH","DATA_FRESHNESS_STALE"],"type":"string"},"defconLabel":{"description":"Human-readable DEFCON label.","type":"string"},"defconLevel":{"description":"DEFCON-style level (1-5).","format":"int32","maximum":5,"minimum":1,"type":"integer"},"locations":{"items":{"$ref":"#/components/schemas/PizzintLocation"},"type":"array"},"locationsMonitored":{"description":"Total monitored locations.","format":"int32","type":"integer"},"locationsOpen":{"description":"Currently open locations.","format":"int32","type":"integer"},"updatedAt":{"description":"Last data update time, as Unix epoch milliseconds.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"}},"type":"object"},"PortActivityEntry":{"properties":{"anomalySignal":{"type":"boolean"},"exportTankerDwt":{"format":"double","type":"number"},"importTankerDwt":{"format":"double","type":"number"},"lat":{"format":"double","type":"number"},"lon":{"format":"double","type":"number"},"portId":{"type":"string"},"portName":{"type":"string"},"tankerCalls30d":{"format":"int32","type":"integer"},"trendDeltaPct":{"format":"double","type":"number"}},"type":"object"},"ProductImpact":{"properties":{"deficitPct":{"format":"double","type":"number"},"demandKbd":{"format":"double","type":"number"},"outputLossKbd":{"format":"double","type":"number"},"product":{"type":"string"}},"type":"object"},"Satellite":{"description":"Satellite represents an orbital asset tracked by WorldMonitor.","properties":{"alt":{"description":"Orbital altitude in kilometers.","format":"double","type":"number"},"country":{"description":"ISO country code of the operator/owner.","type":"string"},"id":{"description":"NORAD identifier (e.g., \"25544\").","minLength":1,"type":"string"},"inclination":{"description":"Orbital inclination in degrees.","format":"double","type":"number"},"line1":{"description":"TLE line 1.","type":"string"},"line2":{"description":"TLE line 2.","type":"string"},"name":{"description":"Name of the satellite.","type":"string"},"type":{"description":"Purpose category (e.g., \"sar\", \"optical\", \"military\").","type":"string"},"velocity":{"description":"Velocity in km/s.","format":"double","type":"number"}},"required":["id"],"type":"object"},"SearchGdeltDocumentsRequest":{"description":"SearchGdeltDocumentsRequest specifies filters for searching GDELT news articles.","properties":{"maxRecords":{"description":"Maximum number of articles to return (1-250).","format":"int32","maximum":250,"minimum":1,"type":"integer"},"query":{"description":"Search query string.","minLength":1,"type":"string"},"sort":{"description":"Sort mode: \"DateDesc\" (default), \"ToneDesc\", \"ToneAsc\", \"HybridRel\".","type":"string"},"timespan":{"description":"Time span filter (e.g., \"15min\", \"1h\", \"24h\").","type":"string"},"toneFilter":{"description":"Tone filter appended to query (e.g., \"tone\u003e5\" for positive, \"tone\u003c-5\" for negative).\n Left empty to skip tone filtering.","type":"string"}},"required":["query"],"type":"object"},"SearchGdeltDocumentsResponse":{"description":"SearchGdeltDocumentsResponse contains GDELT article search results.","properties":{"articles":{"items":{"$ref":"#/components/schemas/GdeltArticle"},"type":"array"},"error":{"description":"Error message if the search failed.","type":"string"},"query":{"description":"Echo of the search query.","type":"string"}},"type":"object"},"SecFiling":{"properties":{"description":{"type":"string"},"fileDate":{"type":"string"},"form":{"type":"string"}},"type":"object"},"SecFilings":{"properties":{"recentFilings":{"items":{"$ref":"#/components/schemas/SecFiling"},"type":"array"},"totalFilings":{"format":"int32","type":"integer"}},"type":"object"},"SecurityAdvisoryItem":{"properties":{"country":{"type":"string"},"level":{"type":"string"},"link":{"type":"string"},"pubDate":{"type":"string"},"source":{"type":"string"},"sourceCountry":{"type":"string"},"title":{"type":"string"}},"type":"object"},"SignalEngagement":{"properties":{"comments":{"format":"int32","type":"integer"},"forks":{"format":"int32","type":"integer"},"mentions":{"format":"int32","type":"integer"},"points":{"format":"int32","type":"integer"},"stars":{"format":"int32","type":"integer"}},"type":"object"},"SignalSummary":{"properties":{"byType":{"additionalProperties":{"format":"int32","type":"integer"},"type":"object"},"signalDiversity":{"format":"int32","type":"integer"},"strongestSignal":{"$ref":"#/components/schemas/CompanySignal"},"totalSignals":{"format":"int32","type":"integer"}},"type":"object"},"SocialVelocityPost":{"description":"SocialVelocityPost represents a trending Reddit post with velocity scoring.","properties":{"createdAt":{"description":"Unix epoch milliseconds when posted.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"id":{"description":"Reddit post ID.","type":"string"},"numComments":{"description":"Number of comments.","format":"int32","type":"integer"},"score":{"description":"Reddit score (upvotes - downvotes).","format":"int32","type":"integer"},"subreddit":{"description":"Subreddit name (without r/ prefix).","type":"string"},"title":{"description":"Post title.","type":"string"},"upvoteRatio":{"description":"Upvote ratio (0.0–1.0).","format":"double","type":"number"},"url":{"description":"Direct URL to the post.","type":"string"},"velocityScore":{"description":"Composite velocity score accounting for recency, score, and ratio.","format":"double","type":"number"}},"type":"object"},"StrategicRisk":{"description":"StrategicRisk represents a strategic risk assessment for a country or region.","properties":{"factors":{"items":{"description":"Risk factors contributing to the assessment.","type":"string"},"type":"array"},"level":{"description":"SeverityLevel represents a three-tier severity classification used across domains.\n Maps to existing TS unions: 'low' | 'medium' | 'high'.","enum":["SEVERITY_LEVEL_UNSPECIFIED","SEVERITY_LEVEL_LOW","SEVERITY_LEVEL_MEDIUM","SEVERITY_LEVEL_HIGH"],"type":"string"},"region":{"description":"Country or region identifier.","type":"string"},"score":{"description":"Risk score (0-100).","format":"double","maximum":100,"minimum":0,"type":"number"},"trend":{"description":"TrendDirection represents the directional movement of a metric over time.\n Used in markets, GDELT tension scores, and risk assessments.","enum":["TREND_DIRECTION_UNSPECIFIED","TREND_DIRECTION_RISING","TREND_DIRECTION_STABLE","TREND_DIRECTION_FALLING"],"type":"string"}},"type":"object"},"TechStackItem":{"properties":{"category":{"type":"string"},"confidence":{"format":"float","type":"number"},"name":{"type":"string"}},"type":"object"},"TelegramMessage":{"description":"Validated OSINT post from Telegram channels.","properties":{"channelId":{"description":"Identifier of the originating channel.","type":"string"},"channelName":{"description":"Human-readable name of the channel.","type":"string"},"id":{"description":"Unique message identifier.","type":"string"},"mediaUrls":{"items":{"description":"Direct links to associated media (images, videos).","type":"string"},"type":"array"},"sourceUrl":{"description":"Link to the original post on Telegram.","type":"string"},"text":{"description":"Sanitized message content.","type":"string"},"timestampMs":{"description":"Unix timestamp in milliseconds for when the message was posted.. Warning: Values \u003e 2^53 may lose precision in JavaScript","format":"int64","type":"integer"},"topic":{"description":"Auto-classified topic based on content.","type":"string"}},"type":"object"},"TransmissionNode":{"properties":{"impactType":{"type":"string"},"logic":{"type":"string"},"node":{"type":"string"}},"type":"object"},"ValidationError":{"description":"ValidationError is returned when request validation fails. It contains a list of field violations describing what went wrong.","properties":{"violations":{"description":"List of validation violations","items":{"$ref":"#/components/schemas/FieldViolation"},"type":"array"}},"required":["violations"],"type":"object"}}},"info":{"title":"IntelligenceService API","version":"1.0.0"},"openapi":"3.1.0","paths":{"/api/intelligence/v1/classify-event":{"get":{"description":"ClassifyEvent analyzes a news event using AI models.","operationId":"ClassifyEvent","parameters":[{"description":"Event title or headline.","in":"query","name":"title","required":false,"schema":{"type":"string"}},{"description":"Event description or body text.","in":"query","name":"description","required":false,"schema":{"type":"string"}},{"description":"Event source (e.g., \"reuters\", \"acled\").","in":"query","name":"source","required":false,"schema":{"type":"string"}},{"description":"Country context (ISO 3166-1 alpha-2).","in":"query","name":"country","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClassifyEventResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"ClassifyEvent","tags":["IntelligenceService"]}},"/api/intelligence/v1/compute-energy-shock":{"get":{"description":"ComputeEnergyShockScenario computes on-demand product supply shock for a given country + chokepoint.","operationId":"ComputeEnergyShockScenario","parameters":[{"in":"query","name":"country_code","required":false,"schema":{"type":"string"}},{"in":"query","name":"chokepoint_id","required":false,"schema":{"type":"string"}},{"in":"query","name":"disruption_pct","required":false,"schema":{"format":"int32","type":"integer"}},{"in":"query","name":"fuel_mode","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComputeEnergyShockScenarioResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"ComputeEnergyShockScenario","tags":["IntelligenceService"]}},"/api/intelligence/v1/deduct-situation":{"post":{"description":"DeductSituation performs broad situational analysis using LLMs.","operationId":"DeductSituation","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeductSituationRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeductSituationResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"DeductSituation","tags":["IntelligenceService"]}},"/api/intelligence/v1/get-company-enrichment":{"get":{"description":"GetCompanyEnrichment aggregates company data from multiple public sources (GitHub, SEC, HN).","operationId":"GetCompanyEnrichment","parameters":[{"in":"query","name":"domain","required":false,"schema":{"type":"string"}},{"in":"query","name":"name","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetCompanyEnrichmentResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"GetCompanyEnrichment","tags":["IntelligenceService"]}},"/api/intelligence/v1/get-country-energy-profile":{"get":{"description":"GetCountryEnergyProfile aggregates Phase 1/2/2.5 energy data per country.","operationId":"GetCountryEnergyProfile","parameters":[{"in":"query","name":"country_code","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetCountryEnergyProfileResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"GetCountryEnergyProfile","tags":["IntelligenceService"]}},"/api/intelligence/v1/get-country-facts":{"get":{"description":"GetCountryFacts retrieves factual country data from RestCountries and Wikipedia.","operationId":"GetCountryFacts","parameters":[{"in":"query","name":"country_code","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetCountryFactsResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"GetCountryFacts","tags":["IntelligenceService"]}},"/api/intelligence/v1/get-country-intel-brief":{"get":{"description":"GetCountryIntelBrief generates a strategic brief for a specific country.","operationId":"GetCountryIntelBrief","parameters":[{"description":"ISO 3166-1 alpha-2 country code.","in":"query","name":"country_code","required":false,"schema":{"type":"string"}},{"description":"Optional analytical framework instructions to append to system prompt. Max 2000 chars enforced at handler level.","in":"query","name":"framework","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetCountryIntelBriefResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"GetCountryIntelBrief","tags":["IntelligenceService"]}},"/api/intelligence/v1/get-country-port-activity":{"get":{"description":"GetCountryPortActivity returns port-level tanker traffic and trade volumes for a country.","operationId":"GetCountryPortActivity","parameters":[{"in":"query","name":"country_code","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CountryPortActivityResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"GetCountryPortActivity","tags":["IntelligenceService"]}},"/api/intelligence/v1/get-country-risk":{"get":{"description":"GetCountryRisk retrieves composite risk intelligence for a specific country.","operationId":"GetCountryRisk","parameters":[{"description":"ISO 3166-1 alpha-2 country code.","in":"query","name":"country_code","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetCountryRiskResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"GetCountryRisk","tags":["IntelligenceService"]}},"/api/intelligence/v1/get-gdelt-topic-timeline":{"get":{"description":"GetGdeltTopicTimeline retrieves tone and volume timelines for a GDELT intel topic.","operationId":"GetGdeltTopicTimeline","parameters":[{"description":"Topic ID (military, cyber, nuclear, sanctions, intelligence, maritime).","in":"query","name":"topic","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetGdeltTopicTimelineResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"GetGdeltTopicTimeline","tags":["IntelligenceService"]}},"/api/intelligence/v1/get-pizzint-status":{"get":{"description":"GetPizzintStatus retrieves Pentagon Pizza Index and GDELT tension data.","operationId":"GetPizzintStatus","parameters":[{"description":"Whether to include GDELT tension pairs in the response.","in":"query","name":"include_gdelt","required":false,"schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetPizzintStatusResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"GetPizzintStatus","tags":["IntelligenceService"]}},"/api/intelligence/v1/get-risk-scores":{"get":{"description":"GetRiskScores retrieves composite risk scores and strategic assessments.","operationId":"GetRiskScores","parameters":[{"description":"Optional region filter. Empty returns all tracked regions.","in":"query","name":"region","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetRiskScoresResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"GetRiskScores","tags":["IntelligenceService"]}},"/api/intelligence/v1/get-social-velocity":{"get":{"description":"GetSocialVelocity returns trending Reddit posts from r/worldnews and r/geopolitics ranked by velocity.","operationId":"GetSocialVelocity","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetSocialVelocityResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"GetSocialVelocity","tags":["IntelligenceService"]}},"/api/intelligence/v1/list-company-signals":{"get":{"description":"ListCompanySignals discovers activity signals for a company from public sources.","operationId":"ListCompanySignals","parameters":[{"in":"query","name":"company","required":false,"schema":{"type":"string"}},{"in":"query","name":"domain","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListCompanySignalsResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"ListCompanySignals","tags":["IntelligenceService"]}},"/api/intelligence/v1/list-cross-source-signals":{"get":{"description":"ListCrossSourceSignals returns cross-domain signals ranked by severity with composite escalation detection.","operationId":"ListCrossSourceSignals","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListCrossSourceSignalsResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"ListCrossSourceSignals","tags":["IntelligenceService"]}},"/api/intelligence/v1/list-gps-interference":{"get":{"description":"ListGpsInterference retrieves detected GPS/GNSS interference data (jamming).","operationId":"ListGpsInterference","parameters":[{"description":"Optional region filter.","in":"query","name":"region","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListGpsInterferenceResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"ListGpsInterference","tags":["IntelligenceService"]}},"/api/intelligence/v1/list-market-implications":{"get":{"description":"ListMarketImplications returns AI-generated trade-implication cards from live world state.","operationId":"ListMarketImplications","parameters":[{"in":"query","name":"frameworkId","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListMarketImplicationsResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"ListMarketImplications","tags":["IntelligenceService"]}},"/api/intelligence/v1/list-oref-alerts":{"get":{"description":"ListOrefAlerts retrieves Israeli Home Front Command alerts (Red Alerts).","operationId":"ListOrefAlerts","parameters":[{"description":"Mode selection. MODE_UNSPECIFIED defaults to active alerts.","in":"query","name":"mode","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListOrefAlertsResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"ListOrefAlerts","tags":["IntelligenceService"]}},"/api/intelligence/v1/list-satellites":{"get":{"description":"ListSatellites retrieves current orbital positions and metadata.","operationId":"ListSatellites","parameters":[{"description":"Filter by country code. Empty returns all.","in":"query","name":"country","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSatellitesResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"ListSatellites","tags":["IntelligenceService"]}},"/api/intelligence/v1/list-security-advisories":{"get":{"description":"ListSecurityAdvisories retrieves pre-seeded travel and health advisories.","operationId":"ListSecurityAdvisories","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSecurityAdvisoriesResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"ListSecurityAdvisories","tags":["IntelligenceService"]}},"/api/intelligence/v1/list-telegram-feed":{"get":{"description":"ListTelegramFeed retrieves real-time OSINT messages from monitored Telegram channels.","operationId":"ListTelegramFeed","parameters":[{"description":"Maximum number of messages to return (default 50).","in":"query","name":"limit","required":false,"schema":{"format":"int32","type":"integer"}},{"description":"Filter by topic keyword (e.g. \"military\", \"cyber\").","in":"query","name":"topic","required":false,"schema":{"type":"string"}},{"description":"Filter by specific channel ID or name.","in":"query","name":"channel","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListTelegramFeedResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"ListTelegramFeed","tags":["IntelligenceService"]}},"/api/intelligence/v1/search-gdelt-documents":{"get":{"description":"SearchGdeltDocuments searches the GDELT GKG API for relevant documentation.","operationId":"SearchGdeltDocuments","parameters":[{"description":"Search query string.","in":"query","name":"query","required":false,"schema":{"type":"string"}},{"description":"Maximum number of articles to return (1-250).","in":"query","name":"max_records","required":false,"schema":{"format":"int32","type":"integer"}},{"description":"Time span filter (e.g., \"15min\", \"1h\", \"24h\").","in":"query","name":"timespan","required":false,"schema":{"type":"string"}},{"description":"Tone filter appended to query (e.g., \"tone\u003e5\" for positive, \"tone\u003c-5\" for negative).\n Left empty to skip tone filtering.","in":"query","name":"tone_filter","required":false,"schema":{"type":"string"}},{"description":"Sort mode: \"DateDesc\" (default), \"ToneDesc\", \"ToneAsc\", \"HybridRel\".","in":"query","name":"sort","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchGdeltDocumentsResponse"}}},"description":"Successful response"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}},"description":"Validation error"},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error response"}},"summary":"SearchGdeltDocuments","tags":["IntelligenceService"]}}}} \ No newline at end of file diff --git a/docs/api/IntelligenceService.openapi.yaml b/docs/api/IntelligenceService.openapi.yaml index da6eb1f54..9ce4143c3 100644 --- a/docs/api/IntelligenceService.openapi.yaml +++ b/docs/api/IntelligenceService.openapi.yaml @@ -2230,6 +2230,26 @@ components: type: string emberAvailable: type: boolean + sprRegime: + type: string + description: Phase 4 — SPR policy classification + sprCapacityMb: + type: number + format: double + sprOperator: + type: string + sprIeaMember: + type: boolean + sprStockholdingModel: + type: string + sprNote: + type: string + sprSource: + type: string + sprAsOf: + type: string + sprAvailable: + type: boolean ComputeEnergyShockScenarioRequest: type: object properties: diff --git a/proto/worldmonitor/intelligence/v1/get_country_energy_profile.proto b/proto/worldmonitor/intelligence/v1/get_country_energy_profile.proto index b601511db..68ca969cb 100644 --- a/proto/worldmonitor/intelligence/v1/get_country_energy_profile.proto +++ b/proto/worldmonitor/intelligence/v1/get_country_energy_profile.proto @@ -71,4 +71,15 @@ message GetCountryEnergyProfileResponse { double ember_demand_twh = 48; string ember_data_month = 49; bool ember_available = 50; // true when Ember monthly data exists for this country + + // Phase 4 — SPR policy classification + string spr_regime = 51; // "mandatory_stockholding"|"government_spr"|"spare_capacity"|"commercial_only"|"none"|"unknown" + double spr_capacity_mb = 52; // million barrels capacity (0 if unknown) + string spr_operator = 53; // operator name (empty if unknown) + bool spr_iea_member = 54; // IEA member country + string spr_stockholding_model = 55; // "government"|"mixed_public_private"|"industry"|"none" + string spr_note = 56; // human-readable context + string spr_source = 57; // data source citation + string spr_as_of = 58; // date string (YYYY-MM) + bool spr_available = 59; // true when registry has classified data (not 'unknown') } diff --git a/scripts/data/spr-policies.json b/scripts/data/spr-policies.json new file mode 100644 index 000000000..74a423ac0 --- /dev/null +++ b/scripts/data/spr-policies.json @@ -0,0 +1,597 @@ +{ + "referenceYear": 2025, + "metaSource": "IEA, OPEC, EIA, national energy ministries, ISPRL, JOGMEC, KNOC", + "policies": { + "US": { + "regime": "government_spr", + "operator": "DOE Strategic Petroleum Reserve", + "capacityMb": 714, + "ieaMember": true, + "ieaNetImporterObligation": false, + "stockholdingModel": "government", + "source": "DOE SPR Distribution Brochure 2025", + "asOf": "2025-03" + }, + "CN": { + "regime": "government_spr", + "operator": "CNPC/Sinopec (state-directed)", + "capacityMb": 476, + "ieaMember": false, + "stockholdingModel": "government", + "note": "Capacity from 3-phase construction program; fill levels not publicly disclosed", + "source": "EIA Today in Energy (2025), Wikipedia SPR China", + "asOf": "2025-10" + }, + "IN": { + "regime": "government_spr", + "operator": "ISPRL (Indian Strategic Petroleum Reserves Ltd)", + "capacityMb": 39, + "ieaMember": false, + "stockholdingModel": "government", + "note": "Phase 2 expansion to 87Mb planned (Chandikhol + Padur)", + "source": "ISPRL, PIB India", + "asOf": "2026-03" + }, + "JP": { + "regime": "mandatory_stockholding", + "operator": "JOGMEC + private sector", + "capacityMb": 260, + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "mixed_public_private", + "source": "IEA Japan Oil Security Policy", + "asOf": "2025-12" + }, + "KR": { + "regime": "mandatory_stockholding", + "operator": "KNOC + private sector", + "capacityMb": 146, + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "mixed_public_private", + "source": "IEA Korea Oil Security Policy", + "asOf": "2025-12" + }, + "DE": { + "regime": "mandatory_stockholding", + "operator": "EBV (Erdolbevorratungsverband)", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "industry", + "source": "IEA Germany Oil Security Policy", + "asOf": "2025-12" + }, + "FR": { + "regime": "mandatory_stockholding", + "operator": "SAGESS + private sector", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "mixed_public_private", + "source": "IEA France Oil Security Policy", + "asOf": "2025-12" + }, + "GB": { + "regime": "mandatory_stockholding", + "operator": "BEIS (industry obligation)", + "ieaMember": true, + "ieaNetImporterObligation": false, + "stockholdingModel": "industry", + "source": "IEA United Kingdom Oil Security Policy", + "asOf": "2025-12" + }, + "IT": { + "regime": "mandatory_stockholding", + "operator": "OCSIT + industry", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "mixed_public_private", + "source": "IEA Italy Oil Security Policy", + "asOf": "2025-12" + }, + "SA": { + "regime": "spare_capacity", + "ieaMember": false, + "stockholdingModel": "none", + "note": "No formal SPR; maintains spare production capacity as world's swing producer", + "source": "OPEC, Aramco Annual Report 2025", + "asOf": "2025-12" + }, + "SG": { + "regime": "government_spr", + "operator": "Ministry of Trade and Industry", + "capacityMb": 32, + "ieaMember": false, + "stockholdingModel": "government", + "note": "Strategic reserves at Jurong Island facilities", + "source": "MTI Singapore Energy Security", + "asOf": "2025-06" + }, + "ZA": { + "regime": "government_spr", + "operator": "CEF SOC (Saldanha Bay)", + "capacityMb": 10, + "ieaMember": false, + "stockholdingModel": "government", + "note": "Saldanha Bay tank farm; partially leased to traders", + "source": "CEF SOC South Africa, EIA", + "asOf": "2025-01" + }, + "BR": { + "regime": "none", + "ieaMember": false, + "stockholdingModel": "none", + "source": "ANP Brazil", + "asOf": "2025-01" + }, + "AU": { + "regime": "mandatory_stockholding", + "operator": "NESA (ticketed abroad)", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "industry", + "note": "Mostly ticketed stocks held abroad (US, EU); domestic reserves are small", + "source": "IEA Australia Oil Security Policy", + "asOf": "2025-12" + }, + "CA": { + "regime": "mandatory_stockholding", + "operator": "NEB (net exporter exemption)", + "ieaMember": true, + "ieaNetImporterObligation": false, + "stockholdingModel": "industry", + "source": "IEA Canada Oil Security Policy", + "asOf": "2025-12" + }, + "NO": { + "regime": "mandatory_stockholding", + "operator": "MoE (net exporter exemption)", + "ieaMember": true, + "ieaNetImporterObligation": false, + "stockholdingModel": "industry", + "source": "IEA Norway Oil Security Policy", + "asOf": "2025-12" + }, + "MX": { + "regime": "commercial_only", + "ieaMember": false, + "stockholdingModel": "none", + "note": "Pemex commercial stocks only; no formal government reserve program", + "source": "SENER Mexico", + "asOf": "2025-01" + }, + "NZ": { + "regime": "mandatory_stockholding", + "operator": "MBIE (ticketed abroad)", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "industry", + "note": "Mostly ticketed stocks held abroad", + "source": "IEA New Zealand Oil Security Policy", + "asOf": "2025-12" + }, + "AT": { + "regime": "mandatory_stockholding", + "operator": "ELG (Erdol-Lagergesellschaft)", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "mixed_public_private", + "source": "IEA Austria Oil Security Policy", + "asOf": "2025-12" + }, + "BE": { + "regime": "mandatory_stockholding", + "operator": "APETRA", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "mixed_public_private", + "source": "IEA Belgium Oil Security Policy", + "asOf": "2025-12" + }, + "CZ": { + "regime": "mandatory_stockholding", + "operator": "SSHR (Administration of State Material Reserves)", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "government", + "source": "IEA Czech Republic Oil Security Policy", + "asOf": "2025-12" + }, + "DK": { + "regime": "mandatory_stockholding", + "operator": "FDO (Foreningen Danske Olieberedskabslagre)", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "industry", + "source": "IEA Denmark Oil Security Policy", + "asOf": "2025-12" + }, + "EE": { + "regime": "mandatory_stockholding", + "operator": "MoECA (ticketed EU stocks)", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "industry", + "source": "IEA Estonia Oil Security Policy", + "asOf": "2025-12" + }, + "FI": { + "regime": "mandatory_stockholding", + "operator": "NESA (National Emergency Supply Agency)", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "government", + "source": "IEA Finland Oil Security Policy", + "asOf": "2025-12" + }, + "GR": { + "regime": "mandatory_stockholding", + "operator": "MoE + industry obligation", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "industry", + "source": "IEA Greece Oil Security Policy", + "asOf": "2025-12" + }, + "HU": { + "regime": "mandatory_stockholding", + "operator": "HUSA (Hungarian Hydrocarbon Stockpiling Association)", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "mixed_public_private", + "source": "IEA Hungary Oil Security Policy", + "asOf": "2025-12" + }, + "IE": { + "regime": "mandatory_stockholding", + "operator": "NORA (National Oil Reserves Agency)", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "mixed_public_private", + "source": "IEA Ireland Oil Security Policy", + "asOf": "2025-12" + }, + "LU": { + "regime": "mandatory_stockholding", + "operator": "MoE (ticketed EU stocks)", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "industry", + "source": "IEA Luxembourg Oil Security Policy", + "asOf": "2025-12" + }, + "NL": { + "regime": "mandatory_stockholding", + "operator": "COVA (Centraal Orgaan Voorraadvorming Aardolieproducten)", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "mixed_public_private", + "source": "IEA Netherlands Oil Security Policy", + "asOf": "2025-12" + }, + "PL": { + "regime": "mandatory_stockholding", + "operator": "ARM (Material Reserves Agency)", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "mixed_public_private", + "source": "IEA Poland Oil Security Policy", + "asOf": "2025-12" + }, + "PT": { + "regime": "mandatory_stockholding", + "operator": "ENMC (Entidade Nacional para o Mercado de Combustiveis)", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "mixed_public_private", + "source": "IEA Portugal Oil Security Policy", + "asOf": "2025-12" + }, + "SK": { + "regime": "mandatory_stockholding", + "operator": "EOSA (Emergency Oil Stocks Agency)", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "mixed_public_private", + "source": "IEA Slovakia Oil Security Policy", + "asOf": "2025-12" + }, + "ES": { + "regime": "mandatory_stockholding", + "operator": "CORES (Corporacion de Reservas Estrategicas)", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "mixed_public_private", + "source": "IEA Spain Oil Security Policy", + "asOf": "2025-12" + }, + "SE": { + "regime": "mandatory_stockholding", + "operator": "SEA (Swedish Energy Agency)", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "mixed_public_private", + "source": "IEA Sweden Oil Security Policy", + "asOf": "2025-12" + }, + "CH": { + "regime": "mandatory_stockholding", + "operator": "Carbura (industry stockpiling org)", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "industry", + "source": "IEA Switzerland Oil Security Policy", + "asOf": "2025-12" + }, + "TR": { + "regime": "mandatory_stockholding", + "operator": "EPDK (industry obligation)", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "industry", + "source": "IEA Turkey Oil Security Policy", + "asOf": "2025-12" + }, + "CL": { + "regime": "mandatory_stockholding", + "operator": "CNE (National Energy Commission)", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "industry", + "source": "IEA Chile Oil Security Policy", + "asOf": "2025-12" + }, + "CO": { + "regime": "mandatory_stockholding", + "operator": "MME (Ministry of Mines and Energy)", + "ieaMember": true, + "ieaNetImporterObligation": false, + "stockholdingModel": "industry", + "source": "IEA Colombia Oil Security Policy", + "asOf": "2025-12" + }, + "IL": { + "regime": "mandatory_stockholding", + "operator": "MoE + industry obligation", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "industry", + "source": "IEA Israel Oil Security Policy", + "asOf": "2025-12" + }, + "LV": { + "regime": "mandatory_stockholding", + "operator": "MoE (ticketed EU stocks)", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "industry", + "source": "IEA Latvia Oil Security Policy", + "asOf": "2025-12" + }, + "LT": { + "regime": "mandatory_stockholding", + "operator": "LITGAS (industry obligation)", + "ieaMember": true, + "ieaNetImporterObligation": true, + "stockholdingModel": "industry", + "source": "IEA Lithuania Oil Security Policy", + "asOf": "2025-12" + }, + "AE": { + "regime": "government_spr", + "operator": "ADNOC (state-directed)", + "capacityMb": 15, + "ieaMember": false, + "stockholdingModel": "government", + "note": "Underground Ruwais reserve; integrated with refinery operations", + "source": "ADNOC Annual Report, EIA", + "asOf": "2025-06" + }, + "KW": { + "regime": "spare_capacity", + "ieaMember": false, + "stockholdingModel": "none", + "note": "KPC maintains spare production capacity; no formal SPR", + "source": "KPC, OPEC Annual Statistical Bulletin", + "asOf": "2025-12" + }, + "ID": { + "regime": "government_spr", + "operator": "SKK Migas / Pertamina (pilot phase)", + "capacityMb": 6, + "ieaMember": false, + "stockholdingModel": "government", + "note": "Pilot reserve at Tuban; 6Mb initial capacity, expansion planned", + "source": "MEMR Indonesia, Jakarta Post", + "asOf": "2025-06" + }, + "RU": { + "regime": "commercial_only", + "ieaMember": false, + "stockholdingModel": "none", + "note": "Rosneft/Transneft commercial stocks; no government SPR program", + "source": "EIA Russia Country Analysis", + "asOf": "2025-01" + }, + "IR": { + "regime": "spare_capacity", + "ieaMember": false, + "stockholdingModel": "none", + "note": "NIOC maintains production flexibility; no publicly documented SPR", + "source": "OPEC, EIA Iran Country Analysis", + "asOf": "2025-01" + }, + "IQ": { + "regime": "commercial_only", + "ieaMember": false, + "stockholdingModel": "none", + "note": "SOMO commercial stocks at Basra; no formal government SPR", + "source": "EIA Iraq Country Analysis", + "asOf": "2025-01" + }, + "NG": { + "regime": "none", + "ieaMember": false, + "stockholdingModel": "none", + "note": "NNPCL commercial stocks; no strategic reserve mandate", + "source": "NNPCL Nigeria, EIA", + "asOf": "2025-01" + }, + "VE": { + "regime": "none", + "ieaMember": false, + "stockholdingModel": "none", + "source": "EIA Venezuela Country Analysis", + "asOf": "2025-01" + }, + "QA": { + "regime": "spare_capacity", + "ieaMember": false, + "stockholdingModel": "none", + "note": "QatarEnergy LNG-focused; spare gas capacity, not oil stockpile", + "source": "QatarEnergy, OPEC", + "asOf": "2025-12" + }, + "TH": { + "regime": "mandatory_stockholding", + "operator": "DOEB (industry obligation)", + "ieaMember": false, + "stockholdingModel": "industry", + "note": "IEA Association Country; 90-day voluntary stockholding obligation", + "source": "DOEB Thailand, IEA Association Countries", + "asOf": "2025-06" + }, + "PH": { + "regime": "mandatory_stockholding", + "operator": "DOE Philippines (industry mandate)", + "ieaMember": false, + "stockholdingModel": "industry", + "note": "30-day industry stockholding obligation", + "source": "DOE Philippines Oil Contingency Plan", + "asOf": "2025-06" + }, + "MY": { + "regime": "commercial_only", + "ieaMember": false, + "stockholdingModel": "none", + "note": "Petronas commercial stocks; no government SPR", + "source": "Petronas, EIA Malaysia Country Analysis", + "asOf": "2025-01" + }, + "TW": { + "regime": "government_spr", + "operator": "CPC Corporation + MOEA", + "capacityMb": 30, + "ieaMember": false, + "stockholdingModel": "mixed_public_private", + "note": "Government + industry 90-day strategic stocks mandate", + "source": "MOEA Taiwan, CPC Corporation", + "asOf": "2025-06" + }, + "PK": { + "regime": "none", + "ieaMember": false, + "stockholdingModel": "none", + "note": "No SPR program; relies on 20-day commercial stocks", + "source": "OGRA Pakistan, EIA", + "asOf": "2025-01" + }, + "EG": { + "regime": "commercial_only", + "ieaMember": false, + "stockholdingModel": "none", + "note": "EGPC commercial stocks; no government SPR", + "source": "EGPC, EIA Egypt Country Analysis", + "asOf": "2025-01" + }, + "DZ": { + "regime": "commercial_only", + "ieaMember": false, + "stockholdingModel": "none", + "note": "Sonatrach commercial stocks; no formal SPR", + "source": "Sonatrach, EIA Algeria Country Analysis", + "asOf": "2025-01" + }, + "LY": { + "regime": "none", + "ieaMember": false, + "stockholdingModel": "none", + "source": "EIA Libya Country Analysis", + "asOf": "2025-01" + }, + "AO": { + "regime": "none", + "ieaMember": false, + "stockholdingModel": "none", + "source": "EIA Angola Country Analysis", + "asOf": "2025-01" + }, + "KZ": { + "regime": "commercial_only", + "ieaMember": false, + "stockholdingModel": "none", + "note": "KazMunayGas commercial stocks; no government SPR", + "source": "EIA Kazakhstan Country Analysis", + "asOf": "2025-01" + }, + "HR": { + "regime": "mandatory_stockholding", + "operator": "HANDA (Croatian Hydrocarbon Agency)", + "ieaMember": false, + "ieaNetImporterObligation": true, + "stockholdingModel": "mixed_public_private", + "note": "EU stockholding directive; IEA candidate country", + "source": "HANDA Croatia, EU Oil Stocks Directive", + "asOf": "2025-12" + }, + "BG": { + "regime": "mandatory_stockholding", + "operator": "SSRA (State Agency for Strategic Reserves)", + "ieaMember": false, + "ieaNetImporterObligation": true, + "stockholdingModel": "government", + "note": "EU stockholding directive compliance", + "source": "SSRA Bulgaria, EU Oil Stocks Directive", + "asOf": "2025-12" + }, + "RO": { + "regime": "mandatory_stockholding", + "operator": "ANRM (National Agency for Mineral Resources)", + "ieaMember": false, + "ieaNetImporterObligation": true, + "stockholdingModel": "mixed_public_private", + "note": "EU stockholding directive compliance", + "source": "ANRM Romania, EU Oil Stocks Directive", + "asOf": "2025-12" + }, + "CY": { + "regime": "mandatory_stockholding", + "operator": "MoE (ticketed EU stocks)", + "ieaMember": false, + "ieaNetImporterObligation": true, + "stockholdingModel": "industry", + "note": "EU stockholding directive; small island economy, relies on ticketed stocks", + "source": "MoE Cyprus, EU Oil Stocks Directive", + "asOf": "2025-12" + }, + "MT": { + "regime": "mandatory_stockholding", + "operator": "MRA (Malta Resources Authority)", + "ieaMember": false, + "ieaNetImporterObligation": true, + "stockholdingModel": "industry", + "note": "EU stockholding directive; minimal domestic storage", + "source": "MRA Malta, EU Oil Stocks Directive", + "asOf": "2025-12" + }, + "SI": { + "regime": "mandatory_stockholding", + "operator": "ZODS (Commodity Reserves Agency)", + "ieaMember": false, + "ieaNetImporterObligation": true, + "stockholdingModel": "mixed_public_private", + "note": "EU stockholding directive compliance", + "source": "ZODS Slovenia, EU Oil Stocks Directive", + "asOf": "2025-12" + } + } +} diff --git a/scripts/seed-energy-spine.mjs b/scripts/seed-energy-spine.mjs index 5cc58b5f7..880002ccd 100644 --- a/scripts/seed-energy-spine.mjs +++ b/scripts/seed-energy-spine.mjs @@ -185,7 +185,7 @@ function buildSourceTimestamps(mix, jodiOil, jodiGas, ieaStocks, ember) { // electricity prices and gasStorage are intentionally excluded from the spine // (they update sub-daily; the spine seeds once at 06:00 UTC). However, Ember // monthly generation mix IS included — it updates at most twice monthly. -export function buildSpineEntry(iso2, { mix, jodiOil, jodiGas, ieaStocks, ember = null }) { +export function buildSpineEntry(iso2, { mix, jodiOil, jodiGas, ieaStocks, ember = null, sprPolicy = null }) { // Schema sentinel: OWID mix must have coalShare field if data is present if (mix != null && !('coalShare' in mix)) { throw new Error(`OWID mix schema changed for ${iso2} — missing coalShare field`); @@ -203,7 +203,7 @@ export function buildSpineEntry(iso2, { mix, jodiOil, jodiGas, ieaStocks, ember countryCode: iso2, updatedAt: new Date().toISOString(), sources: buildSourceTimestamps(mix, jodiOil, jodiGas, ieaStocks, ember), - coverage: { hasMix, hasJodiOil, hasJodiGas, hasIeaStocks, hasEmber }, + coverage: { hasMix, hasJodiOil, hasJodiGas, hasIeaStocks, hasEmber, hasSprPolicy: sprPolicy != null && sprPolicy.regime !== 'unknown' }, oil: buildOilFields(jodiOil, ieaStocks, hasIeaStocks), gas: buildGasFields(jodiGas), mix: buildMixFields(hasMix ? mix : null), @@ -218,6 +218,10 @@ export function buildSpineEntry(iso2, { mix, jodiOil, jodiGas, ieaStocks, ember shockInputs: { comtradeReporterCode: comtradeCode, supportedChokepoints: comtradeCode ? SHOCK_CHOKEPOINTS : [], + sprRegime: sprPolicy?.regime ?? 'unknown', + sprCapacityMb: sprPolicy?.capacityMb ?? null, + sprOperator: sprPolicy?.operator ?? null, + sprIeaMember: sprPolicy?.ieaMember ?? false, }, }; } @@ -286,6 +290,10 @@ export async function main() { } } + // Read SPR policy registry once (global key, not per-country) + const sprRegistry = await redisGet('energy:spr-policies:v1').catch(() => null); + const sprPolicies = sprRegistry?.policies ?? {}; + // Step 3: Batch-read all 6 domain keys per country via pipeline // Order: mix, jodiOil, jodiGas, ieaStocks (electricity + gasStorage excluded — they // update sub-daily and are always read directly by handlers, not from the spine) @@ -318,7 +326,8 @@ export async function main() { const ember = values[base + 4]; try { - const spine = buildSpineEntry(iso2, { mix, jodiOil, jodiGas, ieaStocks, ember }); + const sprPolicy = sprPolicies[iso2] ?? null; + const spine = buildSpineEntry(iso2, { mix, jodiOil, jodiGas, ieaStocks, ember, sprPolicy }); spineEntries.set(iso2, spine); } catch (err) { throw new Error(`Schema validation failed for ${iso2}: ${err.message}`); diff --git a/scripts/seed-spr-policies.mjs b/scripts/seed-spr-policies.mjs new file mode 100644 index 000000000..518fcf817 --- /dev/null +++ b/scripts/seed-spr-policies.mjs @@ -0,0 +1,69 @@ +#!/usr/bin/env node + +import { readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { loadEnvFile, runSeed } from './_seed-utils.mjs'; + +loadEnvFile(import.meta.url); + +export const CANONICAL_KEY = 'energy:spr-policies:v1'; +export const SPR_POLICIES_TTL_SECONDS = 34_560_000; // ~400 days + +const VALID_REGIMES = new Set([ + 'mandatory_stockholding', + 'government_spr', + 'spare_capacity', + 'commercial_only', + 'none', +]); + +const REQUIRED_COUNTRIES = ['CN', 'IN', 'JP', 'SA', 'US']; + +export function buildPayload() { + const __dirname = dirname(fileURLToPath(import.meta.url)); + const raw = readFileSync(resolve(__dirname, 'data', 'spr-policies.json'), 'utf-8'); + const registry = JSON.parse(raw); + return { + ...registry, + updatedAt: new Date().toISOString(), + }; +} + +export function validateFn(data) { + if (!data?.policies || typeof data.policies !== 'object') return false; + const entries = Object.entries(data.policies); + if (entries.length < 30) return false; + + const iso2Re = /^[A-Z]{2}$/; + for (const [key, entry] of entries) { + if (!iso2Re.test(key)) return false; + if (!VALID_REGIMES.has(entry.regime)) return false; + if (typeof entry.source !== 'string' || entry.source.length === 0) return false; + if (typeof entry.asOf !== 'string' || entry.asOf.length === 0) return false; + if ('capacityMb' in entry) { + if (typeof entry.capacityMb !== 'number' || !Number.isFinite(entry.capacityMb) || entry.capacityMb < 0) return false; + } + if ('estimatedFillPct' in entry) return false; + } + + for (const reqCode of REQUIRED_COUNTRIES) { + if (!(reqCode in data.policies)) return false; + } + + return true; +} + +const isMain = process.argv[1]?.endsWith('seed-spr-policies.mjs'); +if (isMain) { + runSeed('energy', 'spr-policies', CANONICAL_KEY, buildPayload, { + validateFn, + ttlSeconds: SPR_POLICIES_TTL_SECONDS, + sourceVersion: 'spr-policies-registry-v1', + recordCount: (data) => Object.keys(data?.policies ?? {}).length, + }).catch((err) => { + const cause = err.cause ? ` (cause: ${err.cause.message || err.cause.code || err.cause})` : ''; + console.error('FATAL:', (err.message || err) + cause); + process.exit(1); + }); +} diff --git a/server/_shared/cache-keys.ts b/server/_shared/cache-keys.ts index 670ef3e7b..322d73747 100644 --- a/server/_shared/cache-keys.ts +++ b/server/_shared/cache-keys.ts @@ -61,6 +61,7 @@ export const ENERGY_SPINE_COUNTRIES_KEY = 'energy:spine:v1:_countries'; export const EMBER_ELECTRICITY_KEY_PREFIX = 'energy:ember:v1:'; export const EMBER_ELECTRICITY_ALL_KEY = 'energy:ember:v1:_all'; export const SPR_KEY = 'economic:spr:v1'; +export const SPR_POLICIES_KEY = 'energy:spr-policies:v1'; export const REFINERY_UTIL_KEY = 'economic:refinery-util:v1'; /** @@ -171,6 +172,7 @@ export const BOOTSTRAP_CACHE_KEYS: Record = { portwatchPortActivity: 'supply_chain:portwatch-ports:v1:_countries', oilStocksAnalysis: 'energy:oil-stocks-analysis:v1', lngVulnerability: 'energy:lng-vulnerability:v1', + sprPolicies: 'energy:spr-policies:v1', }; export const PORTWATCH_PORT_ACTIVITY_KEY_PREFIX = 'supply_chain:portwatch-ports:v1:'; @@ -225,6 +227,7 @@ export const BOOTSTRAP_TIERS: Record = { portwatchPortActivity: 'slow', oilStocksAnalysis: 'slow', lngVulnerability: 'slow', + sprPolicies: 'slow', }; export const PORTWATCH_CHOKEPOINTS_REF_KEY = 'portwatch:chokepoints:ref:v1'; diff --git a/server/worldmonitor/intelligence/v1/chat-analyst-context.ts b/server/worldmonitor/intelligence/v1/chat-analyst-context.ts index 2b2e130de..75477f27d 100644 --- a/server/worldmonitor/intelligence/v1/chat-analyst-context.ts +++ b/server/worldmonitor/intelligence/v1/chat-analyst-context.ts @@ -8,6 +8,7 @@ import { ELECTRICITY_INDEX_KEY, ENERGY_INTELLIGENCE_KEY, SPR_KEY, + SPR_POLICIES_KEY, REFINERY_UTIL_KEY, ENERGY_SPINE_KEY_PREFIX, } from '../../../_shared/cache-keys'; @@ -465,8 +466,17 @@ async function buildGasFlows(iso2: string): Promise { async function buildOilStocksCover(iso2: string): Promise { try { - // Try spine first - const spine = await getCachedJson(`${ENERGY_SPINE_KEY_PREFIX}${iso2}`, true) as Record | null; + const parts: string[] = []; + + // Parallel-fetch spine + SPR registry + const [spineRaw, registryRaw] = await Promise.allSettled([ + getCachedJson(`${ENERGY_SPINE_KEY_PREFIX}${iso2}`, true), + getCachedJson(SPR_POLICIES_KEY, true), + ]); + const spine = spineRaw.status === 'fulfilled' ? spineRaw.value as Record | null : null; + const registry = registryRaw.status === 'fulfilled' ? registryRaw.value as Record | null : null; + + // IEA part (existing logic: try spine first, fallback to direct key) if (spine != null && typeof spine === 'object') { const cov = spine.coverage as Record | undefined; const oil = spine.oil as Record | undefined; @@ -475,28 +485,42 @@ async function buildOilStocksCover(iso2: string): Promise { const importNote = crudeImports != null && crudeImports > 0 ? ` (still imports ${crudeImports} kbd crude for refinery feedstock)` : ''; - return `IEA oil stocks: net oil exporter${importNote}`; + parts.push(`IEA oil stocks: net oil exporter${importNote}`); + } else if (cov?.hasIeaStocks && typeof oil?.daysOfCover === 'number') { + parts.push(`IEA oil stocks: ${oil.daysOfCover as number} days of cover`); } - if (cov?.hasIeaStocks && typeof oil?.daysOfCover === 'number') { - const days = oil.daysOfCover as number; - return `IEA oil stocks: ${days} days of cover`; + } else { + // Fallback to direct IEA key when spine is absent + const ieaDirect = await getCachedJson(`energy:iea-oil-stocks:v1:${iso2}`, true).catch(() => null) as Record | null; + if (ieaDirect != null && typeof ieaDirect === 'object') { + if (ieaDirect.netExporter === true) { + parts.push('IEA oil stocks: net oil exporter'); + } else if (typeof ieaDirect.daysOfCover === 'number') { + const threshold = typeof ieaDirect.obligationThreshold === 'number' ? ieaDirect.obligationThreshold as number : 90; + const breach = ieaDirect.belowObligation === true ? ' (below obligation)' : ''; + parts.push(`IEA oil stocks: ${ieaDirect.daysOfCover as number} days of cover (obligation: ${threshold} days)${breach}`); + } } - // Spine present but no IEA stocks coverage — return undefined (don't fall through) - if (spine.coverage != null) return undefined; } - // Fallback to direct key - const data = await getCachedJson(`energy:iea-oil-stocks:v1:${iso2}`, true); - if (!data || typeof data !== 'object') return undefined; - const d = data as Record; - if (d.netExporter === true) { - return 'IEA oil stocks: net oil exporter'; + // SPR part (new: enrich from policy registry) + const policies = (registry as { policies?: Record> } | null)?.policies; + const sprPolicy = policies?.[iso2]; + if (sprPolicy && sprPolicy.regime !== 'unknown') { + const regime = sprPolicy.regime === 'government_spr' ? 'government strategic reserve' + : sprPolicy.regime === 'mandatory_stockholding' ? 'IEA mandatory stockholding' + : sprPolicy.regime === 'spare_capacity' ? 'spare production capacity (no stockpile)' + : sprPolicy.regime === 'commercial_only' ? 'commercial stocks only (no government reserve)' + : sprPolicy.regime === 'none' ? 'no strategic reserve program' + : sprPolicy.regime as string; + const capacity = typeof sprPolicy.capacityMb === 'number' && sprPolicy.capacityMb > 0 + ? ` (${sprPolicy.capacityMb}Mb capacity)` : ''; + const operator = typeof sprPolicy.operator === 'string' && sprPolicy.operator + ? `, ${sprPolicy.operator}` : ''; + parts.push(`Reserve policy: ${regime}${operator}${capacity}`); } - const days = typeof d.daysOfCover === 'number' ? d.daysOfCover as number : null; - if (days == null) return undefined; - const threshold = typeof d.obligationThreshold === 'number' ? d.obligationThreshold as number : 90; - const breach = d.belowObligation === true ? ' (below obligation)' : ''; - return `IEA oil stocks: ${days} days of cover (obligation: ${threshold} days)${breach}`; + + return parts.length > 0 ? parts.join('. ') : undefined; } catch { return undefined; } diff --git a/server/worldmonitor/intelligence/v1/compute-energy-shock.ts b/server/worldmonitor/intelligence/v1/compute-energy-shock.ts index 0e9e05f64..226d01bc4 100644 --- a/server/worldmonitor/intelligence/v1/compute-energy-shock.ts +++ b/server/worldmonitor/intelligence/v1/compute-energy-shock.ts @@ -8,6 +8,7 @@ import type { } from '../../../../src/generated/server/worldmonitor/intelligence/v1/service_server'; import { getCachedJson, setCachedJson } from '../../../_shared/redis'; +import { SPR_POLICIES_KEY } from '../../../_shared/cache-keys'; import { clamp, CHOKEPOINT_EXPOSURE, @@ -229,6 +230,19 @@ export async function computeEnergyShockScenario( limitations.push('high fossil grid dependency: limited electricity substitution capacity'); } + if (needsOil) { + const sprRegistryRaw = await getCachedJson(SPR_POLICIES_KEY, true).catch(() => null) as Record | null; + const sprPolicies = (sprRegistryRaw as { policies?: Record } | null)?.policies; + const sprPolicy = sprPolicies?.[code]; + if (sprPolicy) { + if (sprPolicy.regime === 'government_spr' && !sprPolicy.ieaMember) { + limitations.push(`strategic reserves: ${sprPolicy.regime} (${sprPolicy.operator ?? 'state-run'}, ${sprPolicy.capacityMb ?? '?'}Mb capacity)`); + } + } else { + limitations.push('strategic reserve policy: not classified for this country'); + } + } + const effectiveGulfShare = !comtradeCoverage ? PROXIED_GULF_SHARE : rawGulfShare; const gulfCrudeShare = effectiveGulfShare * exposureMult; diff --git a/server/worldmonitor/intelligence/v1/get-country-energy-profile.ts b/server/worldmonitor/intelligence/v1/get-country-energy-profile.ts index a82d73b96..fc1d24a54 100644 --- a/server/worldmonitor/intelligence/v1/get-country-energy-profile.ts +++ b/server/worldmonitor/intelligence/v1/get-country-energy-profile.ts @@ -5,7 +5,7 @@ import type { } from '../../../../src/generated/server/worldmonitor/intelligence/v1/service_server'; import { getCachedJson } from '../../../_shared/redis'; -import { ENERGY_SPINE_KEY_PREFIX, EMBER_ELECTRICITY_KEY_PREFIX } from '../../../_shared/cache-keys'; +import { ENERGY_SPINE_KEY_PREFIX, EMBER_ELECTRICITY_KEY_PREFIX, SPR_POLICIES_KEY } from '../../../_shared/cache-keys'; interface OwidMix { year?: number | null; @@ -170,6 +170,15 @@ const EMPTY: GetCountryEnergyProfileResponse = { emberDemandTwh: 0, emberDataMonth: '', emberAvailable: false, + sprRegime: 'unknown', + sprCapacityMb: 0, + sprOperator: '', + sprIeaMember: false, + sprStockholdingModel: '', + sprNote: '', + sprSource: '', + sprAsOf: '', + sprAvailable: false, }; function n(v: number | null | undefined): number { @@ -180,6 +189,21 @@ function s(v: string | null | undefined): string { return typeof v === 'string' ? v : ''; } +interface SprPolicy { + regime?: string; + operator?: string; + capacityMb?: number; + ieaMember?: boolean; + stockholdingModel?: string; + note?: string; + source?: string; + asOf?: string; +} + +interface SprRegistry { + policies?: Record; +} + interface EmberData { fossilShare?: number | null; renewShare?: number | null; @@ -191,11 +215,35 @@ interface EmberData { [key: string]: unknown; } +function buildSprFields(sprPolicy: SprPolicy | null | undefined): Pick< + GetCountryEnergyProfileResponse, + 'sprRegime' | 'sprCapacityMb' | 'sprOperator' | 'sprIeaMember' | 'sprStockholdingModel' | 'sprNote' | 'sprSource' | 'sprAsOf' | 'sprAvailable' +> { + if (!sprPolicy) { + return { + sprRegime: 'unknown', sprCapacityMb: 0, sprOperator: '', sprIeaMember: false, + sprStockholdingModel: '', sprNote: '', sprSource: '', sprAsOf: '', sprAvailable: false, + }; + } + return { + sprRegime: s(sprPolicy.regime) || 'unknown', + sprCapacityMb: n(sprPolicy.capacityMb), + sprOperator: s(sprPolicy.operator), + sprIeaMember: sprPolicy.ieaMember === true, + sprStockholdingModel: s(sprPolicy.stockholdingModel), + sprNote: s(sprPolicy.note), + sprSource: s(sprPolicy.source), + sprAsOf: s(sprPolicy.asOf), + sprAvailable: true, + }; +} + function buildResponseFromSpine( spine: EnergySpine, gasStorage: GasStorage | null, electricity: ElectricityEntry | null, emberData: EmberData | null, + sprPolicy: SprPolicy | null | undefined, ): GetCountryEnergyProfileResponse { const cov = spine.coverage ?? {}; const src = spine.sources ?? {}; @@ -266,6 +314,7 @@ function buildResponseFromSpine( emberDemandTwh: n(resolvedEmber?.demandTwh), emberDataMonth: s(resolvedEmber?.dataMonth), emberAvailable: resolvedEmber != null && typeof resolvedEmber.fossilShare === 'number', + ...buildSprFields(sprPolicy), }; } @@ -279,15 +328,18 @@ export async function getCountryEnergyProfile( // Always read gas-storage and electricity directly — both update sub-daily // (gas storage ~10:30 UTC, electricity ~14:00 UTC) while the spine seeds once // at 06:00 UTC. Serving them from the spine would return stale data for up to 8h. - const [spineResult, gasStorageResult, electricityResult] = await Promise.allSettled([ + const [spineResult, gasStorageResult, electricityResult, sprRegistryResult] = await Promise.allSettled([ getCachedJson(`${ENERGY_SPINE_KEY_PREFIX}${code}`, true), getCachedJson(`energy:gas-storage:v1:${code}`, true), getCachedJson(`energy:electricity:v1:${code}`, true), + getCachedJson(SPR_POLICIES_KEY, true), ]); const spine = spineResult.status === 'fulfilled' ? (spineResult.value as EnergySpine | null) : null; const gasStorage = gasStorageResult.status === 'fulfilled' ? (gasStorageResult.value as GasStorage | null) : null; const electricity = electricityResult.status === 'fulfilled' ? (electricityResult.value as ElectricityEntry | null) : null; + const sprRegistry = sprRegistryResult.status === 'fulfilled' ? (sprRegistryResult.value as SprRegistry | null) : null; + const sprPolicy = sprRegistry?.policies?.[code] ?? null; if (spine != null && typeof spine === 'object' && spine.coverage != null) { let emberFallback: EmberData | null = null; @@ -297,7 +349,7 @@ export async function getCountryEnergyProfile( emberFallback = directEmber as EmberData; } } - return buildResponseFromSpine(spine, gasStorage, electricity, emberFallback); + return buildResponseFromSpine(spine, gasStorage, electricity, emberFallback, sprPolicy); } // Fallback: 4-key direct join (cold cache or countries not yet in spine) @@ -375,5 +427,6 @@ export async function getCountryEnergyProfile( emberDemandTwh: n(emberData?.demandTwh), emberDataMonth: s(emberData?.dataMonth), emberAvailable: emberData != null && typeof emberData.fossilShare === 'number', + ...buildSprFields(sprPolicy), }; } diff --git a/src/app/country-intel.ts b/src/app/country-intel.ts index f2ccfee12..6d527e6f7 100644 --- a/src/app/country-intel.ts +++ b/src/app/country-intel.ts @@ -337,6 +337,15 @@ export class CountryIntelManager implements AppModule { emberDemandTwh: profile.emberDemandTwh, emberDataMonth: profile.emberDataMonth, emberAvailable: profile.emberAvailable, + sprRegime: profile.sprRegime, + sprCapacityMb: profile.sprCapacityMb, + sprOperator: profile.sprOperator, + sprIeaMember: profile.sprIeaMember, + sprStockholdingModel: profile.sprStockholdingModel, + sprNote: profile.sprNote, + sprSource: profile.sprSource, + sprAsOf: profile.sprAsOf, + sprAvailable: profile.sprAvailable, }); }) .catch(() => { @@ -357,6 +366,9 @@ export class CountryIntelManager implements AppModule { emberFossilShare: 0, emberRenewShare: 0, emberNuclearShare: 0, emberCoalShare: 0, emberGasShare: 0, emberDemandTwh: 0, emberDataMonth: '', emberAvailable: false, + sprRegime: 'unknown', sprCapacityMb: 0, sprOperator: '', sprIeaMember: false, + sprStockholdingModel: '', sprNote: '', sprSource: '', sprAsOf: '', + sprAvailable: false, }); }); diff --git a/src/components/CountryBriefPanel.ts b/src/components/CountryBriefPanel.ts index 94351b05b..fae39152e 100644 --- a/src/components/CountryBriefPanel.ts +++ b/src/components/CountryBriefPanel.ts @@ -130,6 +130,15 @@ export interface CountryEnergyProfileData { emberDemandTwh: number; emberDataMonth: string; emberAvailable: boolean; + sprRegime: string; + sprCapacityMb: number; + sprOperator: string; + sprIeaMember: boolean; + sprStockholdingModel: string; + sprNote: string; + sprSource: string; + sprAsOf: string; + sprAvailable: boolean; } export interface CountryPortActivityData { diff --git a/src/components/CountryDeepDivePanel.ts b/src/components/CountryDeepDivePanel.ts index da2d6994c..e00390ca7 100644 --- a/src/components/CountryDeepDivePanel.ts +++ b/src/components/CountryDeepDivePanel.ts @@ -499,7 +499,7 @@ export class CountryDeepDivePanel implements CountryBriefPanel { const hasAny = data.mixAvailable || data.jodiOilAvailable || data.ieaStocksAvailable || data.jodiGasAvailable || data.gasStorageAvailable || data.electricityAvailable - || data.emberAvailable; + || data.emberAvailable || data.sprAvailable; if (!hasAny) { this.energyBody.append(this.makeEmpty('Energy data unavailable for this country.')); @@ -681,6 +681,33 @@ export class CountryDeepDivePanel implements CountryBriefPanel { this.energyBody.append(section); } + if (data.sprAvailable && data.sprRegime === 'government_spr' && !data.sprIeaMember) { + const section = this.el('div', ''); + section.style.cssText = 'margin-top:10px'; + const row = this.el('div', ''); + row.style.cssText = 'display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:12px'; + const badge = this.el('span', ''); + badge.style.cssText = 'background:#3b82f6;color:#fff;padding:1px 6px;border-radius:3px;font-size:11px'; + const capText = data.sprCapacityMb > 0 ? ` (${data.sprCapacityMb}Mb)` : ''; + badge.textContent = `Strategic Reserve: ${data.sprOperator || 'Government SPR'}${capText}`; + row.append(badge); + section.append(row); + this.energyBody.append(section); + } else if (data.sprAvailable && data.sprRegime === 'spare_capacity') { + const section = this.el('div', ''); + section.style.cssText = 'margin-top:10px'; + const muted = this.el('div', ''); + muted.style.cssText = 'color:#6b7280;font-size:11px'; + muted.textContent = 'Spare capacity producer (no formal SPR)'; + section.append(muted); + this.energyBody.append(section); + } else if (data.sprAvailable && data.sprRegime === 'none') { + const note = this.el('div', 'cdp-economic-source'); + note.style.cssText += ';color:#ef4444;opacity:0.7'; + note.textContent = 'No known strategic petroleum reserve program'; + this.energyBody.append(note); + } + const hasLiveSignals = data.gasStorageAvailable || data.electricityAvailable; if (hasLiveSignals) { const section = this.el('div', ''); diff --git a/src/generated/client/worldmonitor/intelligence/v1/service_client.ts b/src/generated/client/worldmonitor/intelligence/v1/service_client.ts index 339f90fb4..ada4361ec 100644 --- a/src/generated/client/worldmonitor/intelligence/v1/service_client.ts +++ b/src/generated/client/worldmonitor/intelligence/v1/service_client.ts @@ -538,6 +538,15 @@ export interface GetCountryEnergyProfileResponse { emberDemandTwh: number; emberDataMonth: string; emberAvailable: boolean; + sprRegime: string; + sprCapacityMb: number; + sprOperator: string; + sprIeaMember: boolean; + sprStockholdingModel: string; + sprNote: string; + sprSource: string; + sprAsOf: string; + sprAvailable: boolean; } export interface ComputeEnergyShockScenarioRequest { diff --git a/src/generated/server/worldmonitor/intelligence/v1/service_server.ts b/src/generated/server/worldmonitor/intelligence/v1/service_server.ts index 6773a742b..cfda07489 100644 --- a/src/generated/server/worldmonitor/intelligence/v1/service_server.ts +++ b/src/generated/server/worldmonitor/intelligence/v1/service_server.ts @@ -538,6 +538,15 @@ export interface GetCountryEnergyProfileResponse { emberDemandTwh: number; emberDataMonth: string; emberAvailable: boolean; + sprRegime: string; + sprCapacityMb: number; + sprOperator: string; + sprIeaMember: boolean; + sprStockholdingModel: string; + sprNote: string; + sprSource: string; + sprAsOf: string; + sprAvailable: boolean; } export interface ComputeEnergyShockScenarioRequest { diff --git a/tests/bootstrap.test.mjs b/tests/bootstrap.test.mjs index cbf1ce767..50c503231 100644 --- a/tests/bootstrap.test.mjs +++ b/tests/bootstrap.test.mjs @@ -253,7 +253,7 @@ describe('Bootstrap key hydration coverage', () => { const allSrc = srcFiles.map(f => readFileSync(f, 'utf-8')).join('\n'); // Keys with planned but not-yet-wired consumers - const PENDING_CONSUMERS = new Set(['correlationCards', 'euGasStorage', 'chokepointBaselines', 'imfMacro', 'portwatchChokepointsRef', 'portwatchPortActivity']); + const PENDING_CONSUMERS = new Set(['correlationCards', 'euGasStorage', 'chokepointBaselines', 'imfMacro', 'portwatchChokepointsRef', 'portwatchPortActivity', 'sprPolicies']); for (const key of keys) { if (PENDING_CONSUMERS.has(key)) continue; assert.ok( diff --git a/tests/energy-spine-seed.test.mjs b/tests/energy-spine-seed.test.mjs index 9e83ac386..c20b45062 100644 --- a/tests/energy-spine-seed.test.mjs +++ b/tests/energy-spine-seed.test.mjs @@ -383,6 +383,44 @@ describe('buildSpineEntry with Ember data', () => { }); }); +// --------------------------------------------------------------------------- +// buildSpineEntry with SPR policy data +// --------------------------------------------------------------------------- + +describe('buildSpineEntry with SPR policy data', () => { + it('includes SPR fields in shockInputs when policy is provided', () => { + const sprPolicy = { regime: 'government_spr', operator: 'CNPC/Sinopec', capacityMb: 476, ieaMember: false }; + const entry = buildSpineEntry('CN', { mix: makeMix(), jodiOil: makeJodiOil(), jodiGas: null, ieaStocks: null, sprPolicy }); + assert.equal(entry.shockInputs.sprRegime, 'government_spr'); + assert.equal(entry.shockInputs.sprCapacityMb, 476); + assert.equal(entry.shockInputs.sprOperator, 'CNPC/Sinopec'); + assert.equal(entry.shockInputs.sprIeaMember, false); + assert.equal(entry.coverage.hasSprPolicy, true); + }); + + it('defaults SPR fields to unknown when no policy provided', () => { + const entry = buildSpineEntry('AF', { mix: null, jodiOil: null, jodiGas: null, ieaStocks: null, sprPolicy: null }); + assert.equal(entry.shockInputs.sprRegime, 'unknown'); + assert.equal(entry.shockInputs.sprCapacityMb, null); + assert.equal(entry.shockInputs.sprOperator, null); + assert.equal(entry.shockInputs.sprIeaMember, false); + assert.equal(entry.coverage.hasSprPolicy, false); + }); + + it('hasSprPolicy is false for unknown regime', () => { + const entry = buildSpineEntry('XX', { mix: null, jodiOil: null, jodiGas: null, ieaStocks: null, sprPolicy: { regime: 'unknown' } }); + assert.equal(entry.coverage.hasSprPolicy, false); + }); + + it('hasSprPolicy is true for mandatory_stockholding regime', () => { + const sprPolicy = { regime: 'mandatory_stockholding', ieaMember: true }; + const entry = buildSpineEntry('DE', { mix: makeMix(), jodiOil: makeJodiOil(), jodiGas: null, ieaStocks: null, sprPolicy }); + assert.equal(entry.coverage.hasSprPolicy, true); + assert.equal(entry.shockInputs.sprRegime, 'mandatory_stockholding'); + assert.equal(entry.shockInputs.sprIeaMember, true); + }); +}); + // --------------------------------------------------------------------------- // Core-source guard when JODI and OWID are empty // --------------------------------------------------------------------------- diff --git a/tests/spr-policies-seed.test.mjs b/tests/spr-policies-seed.test.mjs new file mode 100644 index 000000000..eb885910a --- /dev/null +++ b/tests/spr-policies-seed.test.mjs @@ -0,0 +1,170 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { + buildPayload, + validateFn, + CANONICAL_KEY, + SPR_POLICIES_TTL_SECONDS, +} from '../scripts/seed-spr-policies.mjs'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +describe('SPR policies registry shape', () => { + const data = buildPayload(); + + it('has referenceYear and metaSource', () => { + assert.equal(typeof data.referenceYear, 'number'); + assert.ok(data.referenceYear >= 2025); + assert.equal(typeof data.metaSource, 'string'); + assert.ok(data.metaSource.length > 0); + }); + + it('has updatedAt timestamp', () => { + assert.equal(typeof data.updatedAt, 'string'); + assert.ok(new Date(data.updatedAt).getTime() > 0); + }); + + it('has policies object with at least 30 entries', () => { + assert.equal(typeof data.policies, 'object'); + assert.ok(Object.keys(data.policies).length >= 30); + }); +}); + +describe('SPR policies ISO2 key validation', () => { + const data = buildPayload(); + + it('every key is valid 2-character uppercase ISO2', () => { + const iso2Re = /^[A-Z]{2}$/; + for (const key of Object.keys(data.policies)) { + assert.match(key, iso2Re, `Invalid ISO2 key: ${key}`); + } + }); +}); + +describe('SPR policies regime enum validation', () => { + const data = buildPayload(); + const VALID_REGIMES = new Set([ + 'mandatory_stockholding', + 'government_spr', + 'spare_capacity', + 'commercial_only', + 'none', + ]); + + it('every entry has a valid regime', () => { + for (const [key, entry] of Object.entries(data.policies)) { + assert.ok(VALID_REGIMES.has(entry.regime), `Invalid regime '${entry.regime}' for ${key}`); + } + }); + + it('every entry has non-empty source and asOf', () => { + for (const [key, entry] of Object.entries(data.policies)) { + assert.equal(typeof entry.source, 'string', `${key} missing source`); + assert.ok(entry.source.length > 0, `${key} has empty source`); + assert.equal(typeof entry.asOf, 'string', `${key} missing asOf`); + assert.ok(entry.asOf.length > 0, `${key} has empty asOf`); + } + }); +}); + +describe('SPR policies required entries', () => { + const data = buildPayload(); + const REQUIRED = ['CN', 'IN', 'JP', 'SA', 'US']; + + for (const code of REQUIRED) { + it(`has entry for ${code}`, () => { + assert.ok(code in data.policies, `Missing required entry: ${code}`); + }); + } +}); + +describe('SPR policies no estimatedFillPct', () => { + const data = buildPayload(); + + it('no entry has estimatedFillPct field', () => { + for (const [key, entry] of Object.entries(data.policies)) { + assert.ok(!('estimatedFillPct' in entry), `${key} has forbidden estimatedFillPct field`); + } + }); +}); + +describe('SPR policies capacity validation', () => { + const data = buildPayload(); + + it('capacityMb is finite and >= 0 when present', () => { + for (const [key, entry] of Object.entries(data.policies)) { + if ('capacityMb' in entry) { + assert.equal(typeof entry.capacityMb, 'number', `${key} capacityMb not a number`); + assert.ok(Number.isFinite(entry.capacityMb), `${key} capacityMb is not finite`); + assert.ok(entry.capacityMb >= 0, `${key} capacityMb is negative`); + } + } + }); +}); + +describe('SPR policies validateFn', () => { + it('returns true for valid data', () => { + const data = buildPayload(); + assert.ok(validateFn(data)); + }); + + it('returns false for empty policies', () => { + assert.ok(!validateFn({ policies: {} })); + }); + + it('returns false for null', () => { + assert.ok(!validateFn(null)); + }); + + it('returns false when required country is missing', () => { + const data = buildPayload(); + delete data.policies.US; + assert.ok(!validateFn(data)); + }); + + it('returns false when entry has invalid regime', () => { + const data = buildPayload(); + data.policies.US.regime = 'invalid_regime'; + assert.ok(!validateFn(data)); + }); + + it('returns false when entry has estimatedFillPct', () => { + const data = buildPayload(); + data.policies.US.estimatedFillPct = 50; + assert.ok(!validateFn(data)); + }); +}); + +describe('SPR policies exported constants', () => { + it('CANONICAL_KEY matches expected value', () => { + assert.equal(CANONICAL_KEY, 'energy:spr-policies:v1'); + }); + + it('TTL is ~400 days', () => { + assert.equal(SPR_POLICIES_TTL_SECONDS, 34_560_000); + const days = SPR_POLICIES_TTL_SECONDS / 86400; + assert.ok(days >= 399 && days <= 401, `TTL is ${days} days, expected ~400`); + }); +}); + +describe('SPR policies ieaMember field', () => { + const data = buildPayload(); + + it('every entry has ieaMember boolean', () => { + for (const [key, entry] of Object.entries(data.policies)) { + assert.equal(typeof entry.ieaMember, 'boolean', `${key} missing ieaMember`); + } + }); +}); + +describe('SPR policies JSON file integrity', () => { + it('JSON file parses without error', () => { + const raw = readFileSync(resolve(__dirname, '..', 'scripts', 'data', 'spr-policies.json'), 'utf-8'); + const parsed = JSON.parse(raw); + assert.ok(parsed.policies); + }); +});