diff --git a/docs/api/IntelligenceService.openapi.json b/docs/api/IntelligenceService.openapi.json index 3e3f6a859..88be33bb6 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"}},"type":"object"},"ComputeEnergyShockScenarioResponse":{"properties":{"assessment":{"type":"string"},"chokepointId":{"type":"string"},"countryCode":{"type":"string"},"crudeLossKbd":{"format":"double","type":"number"},"dataAvailable":{"type":"boolean"},"disruptionPct":{"format":"int32","type":"integer"},"effectiveCoverDays":{"format":"int32","type":"integer"},"gulfCrudeShare":{"format":"double","type":"number"},"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"},"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"},"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"}}],"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"}},"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"},"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"},"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"},"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"}}],"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 516291fe4..0f006312c 100644 --- a/docs/api/IntelligenceService.openapi.yaml +++ b/docs/api/IntelligenceService.openapi.yaml @@ -2239,6 +2239,28 @@ components: type: string dataAvailable: type: boolean + jodiOilCoverage: + type: boolean + description: v2 fields + comtradeCoverage: + type: boolean + ieaStocksCoverage: + type: boolean + portwatchCoverage: + type: boolean + coverageLevel: + type: string + limitations: + type: array + items: + type: string + degraded: + type: boolean + chokepointConfidence: + type: string + liveFlowRatio: + type: number + format: double ProductImpact: type: object properties: diff --git a/proto/worldmonitor/intelligence/v1/compute_energy_shock.proto b/proto/worldmonitor/intelligence/v1/compute_energy_shock.proto index 775d6ccf2..c346144e2 100644 --- a/proto/worldmonitor/intelligence/v1/compute_energy_shock.proto +++ b/proto/worldmonitor/intelligence/v1/compute_energy_shock.proto @@ -25,5 +25,19 @@ message ComputeEnergyShockScenarioResponse { repeated ProductImpact products = 6; int32 effective_cover_days = 7; string assessment = 8; - bool data_available = 9; + bool data_available = 9; // backward compat: equals jodi_oil_coverage + + // field 10 reserved for future use + + // v2 fields + bool jodi_oil_coverage = 11; // JODI oil data present for this country + bool comtrade_coverage = 12; // Comtrade crude flows data present + bool iea_stocks_coverage = 13; // IEA strategic stocks data present + bool portwatch_coverage = 14; // PortWatch live flow data present + string coverage_level = 15; // "full" | "partial" | "unsupported" + repeated string limitations = 16; // model assumption strings + bool degraded = 17; // true when PortWatch absent/stale + string chokepoint_confidence = 18; // "high" | "low" | "none" + optional double live_flow_ratio = 19; // from energy:chokepoint-flows:v1, 0-1.5; absent when portwatch_coverage=false + // field 20 reserved for gas_impact (PR4) } diff --git a/server/gateway.ts b/server/gateway.ts index e5c1aa840..4415fb1a4 100644 --- a/server/gateway.ts +++ b/server/gateway.ts @@ -213,7 +213,7 @@ const RPC_CACHE_TIER: Record = { '/api/health/v1/list-air-quality-alerts': 'fast', '/api/intelligence/v1/get-social-velocity': 'fast', '/api/intelligence/v1/get-country-energy-profile': 'slow', - '/api/intelligence/v1/compute-energy-shock': 'slow', + '/api/intelligence/v1/compute-energy-shock': 'fast', '/api/intelligence/v1/get-country-port-activity': 'slow', '/api/resilience/v1/get-resilience-score': 'slow', '/api/resilience/v1/get-resilience-ranking': 'slow', diff --git a/server/worldmonitor/intelligence/v1/_comtrade-reporters.ts b/server/worldmonitor/intelligence/v1/_comtrade-reporters.ts new file mode 100644 index 000000000..26b5301b0 --- /dev/null +++ b/server/worldmonitor/intelligence/v1/_comtrade-reporters.ts @@ -0,0 +1,194 @@ +export const ISO2_TO_COMTRADE: Record = { + AD: '020', + AE: '784', + AF: '004', + AG: '028', + AL: '008', + AM: '051', + AO: '024', + AR: '032', + AT: '040', + AU: '036', + AZ: '031', + BA: '070', + BD: '050', + BE: '056', + BF: '854', + BG: '100', + BH: '048', + BI: '108', + BJ: '204', + BN: '096', + BO: '068', + BR: '076', + BS: '044', + BT: '064', + BW: '072', + BY: '112', + BZ: '084', + CA: '124', + CD: '180', + CF: '140', + CG: '178', + CH: '756', + CI: '384', + CL: '152', + CM: '120', + CN: '156', + CO: '170', + CR: '188', + CU: '192', + CV: '132', + CY: '196', + CZ: '203', + DE: '276', + DJ: '262', + DK: '208', + DM: '212', + DO: '214', + DZ: '012', + EC: '218', + EE: '233', + EG: '818', + ER: '232', + ES: '724', + ET: '231', + FI: '246', + FJ: '242', + FR: '251', + GA: '266', + GB: '826', + GD: '308', + GE: '268', + GH: '288', + GM: '270', + GN: '324', + GQ: '226', + GR: '300', + GT: '320', + GW: '624', + GY: '328', + HN: '340', + HR: '191', + HT: '332', + HU: '348', + ID: '360', + IE: '372', + IL: '376', + IN: '356', + IQ: '368', + IR: '364', + IS: '352', + IT: '381', + JM: '388', + JO: '400', + JP: '392', + KE: '404', + KG: '417', + KH: '116', + KI: '296', + KM: '174', + KN: '659', + KP: '408', + KR: '410', + KW: '414', + KZ: '398', + LA: '418', + LB: '422', + LC: '662', + LI: '438', + LK: '144', + LR: '430', + LS: '426', + LT: '440', + LU: '442', + LV: '428', + LY: '434', + MA: '504', + MD: '498', + ME: '499', + MG: '450', + MH: '584', + MK: '807', + ML: '466', + MM: '104', + MN: '496', + MR: '478', + MT: '470', + MU: '480', + MV: '462', + MW: '454', + MX: '484', + MY: '458', + MZ: '508', + NA: '516', + NE: '562', + NG: '566', + NI: '558', + NL: '528', + NO: '578', + NP: '524', + NR: '520', + NZ: '554', + OM: '512', + PA: '591', + PE: '604', + PG: '598', + PH: '608', + PK: '586', + PL: '616', + PT: '620', + PW: '585', + PY: '600', + QA: '634', + RO: '642', + RS: '688', + RU: '643', + RW: '646', + SA: '682', + SB: '090', + SC: '690', + SD: '729', + SE: '752', + SG: '702', + SI: '705', + SK: '703', + SL: '694', + SM: '674', + SN: '686', + SO: '706', + SR: '740', + SS: '728', + ST: '678', + SV: '222', + SY: '760', + SZ: '748', + TD: '148', + TG: '768', + TH: '764', + TJ: '762', + TL: '626', + TM: '795', + TN: '788', + TO: '776', + TR: '792', + TT: '780', + TV: '798', + TW: '158', + TZ: '834', + UA: '804', + UG: '800', + US: '842', + UY: '858', + UZ: '860', + VA: '336', + VC: '670', + VE: '862', + VN: '704', + VU: '548', + WS: '882', + YE: '887', + ZA: '710', + ZM: '894', + ZW: '716', +}; diff --git a/server/worldmonitor/intelligence/v1/_shock-compute.ts b/server/worldmonitor/intelligence/v1/_shock-compute.ts index d7068e3ae..dd50241e8 100644 --- a/server/worldmonitor/intelligence/v1/_shock-compute.ts +++ b/server/worldmonitor/intelligence/v1/_shock-compute.ts @@ -22,7 +22,7 @@ export function computeGulfShare(flows: ComtradeFlowLike[]): { share: number; ha let totalImports = 0; let gulfImports = 0; for (const flow of flows) { - const val = typeof flow.tradeValueUsd === 'number' ? flow.tradeValueUsd : 0; + const val = Number.isFinite(flow.tradeValueUsd) ? flow.tradeValueUsd : 0; if (val <= 0) continue; totalImports += val; if (GULF_PARTNER_CODES.has(String(flow.partnerCode))) { @@ -46,6 +46,26 @@ export function computeEffectiveCoverDays( return daysOfCover; } +export function deriveCoverageLevel( + jodiOil: boolean, + comtrade: boolean, + ieaStocksCoverage?: boolean, + degraded?: boolean, +): 'full' | 'partial' | 'unsupported' { + if (!jodiOil) return 'unsupported'; + if (!comtrade) return 'partial'; + if (ieaStocksCoverage === false || degraded) return 'partial'; + return 'full'; +} + +export function deriveChokepointConfidence( + liveFlowRatio: number | null, + degraded: boolean, +): 'high' | 'low' | 'none' { + if (degraded || liveFlowRatio === null || !Number.isFinite(liveFlowRatio)) return 'none'; + return 'high'; +} + export function buildAssessment( code: string, chokepointId: string, @@ -55,8 +75,12 @@ export function buildAssessment( daysOfCover: number, disruptionPct: number, products: Array<{ product: string; deficitPct: number }>, + coverageLevel?: 'full' | 'partial' | 'unsupported', + degraded?: boolean, + ieaStocksCoverage?: boolean, + comtradeCoverage?: boolean, ): string { - if (!dataAvailable) { + if (coverageLevel === 'unsupported' || !dataAvailable) { return `Insufficient import data for ${code} to model ${chokepointId} exposure.`; } if (effectiveCoverDays === -1) { @@ -65,11 +89,14 @@ export function buildAssessment( if (gulfCrudeShare < 0.1) { return `${code} has low Gulf crude dependence (${Math.round(gulfCrudeShare * 100)}%); ${chokepointId} disruption has limited direct impact.`; } + const degradedNote = degraded ? ' (live flow data unavailable, using historical baseline)' : ''; + const ieaCoverText = ieaStocksCoverage === false ? 'unknown' : `${daysOfCover} days`; if (effectiveCoverDays > 90) { - return `With ${daysOfCover} days IEA cover, ${code} can bridge a ${disruptionPct}% ${chokepointId} disruption for ~${effectiveCoverDays} days.`; + return `With ${daysOfCover} days IEA cover, ${code} can bridge a ${disruptionPct}% ${chokepointId} disruption for ~${effectiveCoverDays} days${degradedNote}.`; } const dieselDeficit = products.find((p) => p.product === 'Diesel')?.deficitPct ?? 0; const jetDeficit = products.find((p) => p.product === 'Jet fuel')?.deficitPct ?? 0; const worstDeficit = Math.max(dieselDeficit, jetDeficit); - return `${code} faces ${worstDeficit.toFixed(1)}% diesel/jet deficit under ${disruptionPct}% ${chokepointId} disruption; IEA cover: ${daysOfCover} days.`; + const proxyNote = comtradeCoverage === false ? '. Gulf share proxied at 40%' : ''; + return `${code} faces ${worstDeficit.toFixed(1)}% diesel/jet deficit under ${disruptionPct}% ${chokepointId} disruption; IEA cover: ${ieaCoverText}${proxyNote}${degradedNote}.`; } diff --git a/server/worldmonitor/intelligence/v1/compute-energy-shock.ts b/server/worldmonitor/intelligence/v1/compute-energy-shock.ts index 07c7ef758..0f2aab439 100644 --- a/server/worldmonitor/intelligence/v1/compute-energy-shock.ts +++ b/server/worldmonitor/intelligence/v1/compute-energy-shock.ts @@ -13,20 +13,22 @@ import { computeGulfShare, computeEffectiveCoverDays, buildAssessment, + deriveCoverageLevel, + deriveChokepointConfidence, } from './_shock-compute'; +import { ISO2_TO_COMTRADE } from './_comtrade-reporters'; -const SHOCK_CACHE_TTL = 3600; +const SHOCK_CACHE_TTL = 300; -// ISO2 → Comtrade numeric reporter code (only 6 seeded reporters) -const ISO2_TO_COMTRADE: Record = { - US: '842', - CN: '156', - RU: '643', - IR: '364', - IN: '356', - TW: '158', +const CP_TO_PORTWATCH: Record = { + hormuz: 'hormuz_strait', + babelm: 'bab_el_mandeb', + suez: 'suez', + malacca: 'malacca_strait', }; +const PROXIED_GULF_SHARE = 0.40; + interface JodiProduct { demandKbd?: number | null; importsKbd?: number | null; @@ -62,6 +64,16 @@ interface ComtradeFlowsResult { fetchedAt?: string; } +interface ChokepointEntry { + currentMbd?: number; + baselineMbd?: number; + flowRatio: number; + disrupted?: boolean; + source?: string; + hazardAlertLevel?: string | null; + hazardAlertName?: string | null; +} + function n(v: number | null | undefined): number { return typeof v === 'number' && Number.isFinite(v) ? v : 0; } @@ -102,6 +114,15 @@ export async function computeEnergyShockScenario( effectiveCoverDays: 0, assessment: `Insufficient data to compute shock scenario for ${code}.`, dataAvailable: false, + jodiOilCoverage: false, + comtradeCoverage: false, + ieaStocksCoverage: false, + portwatchCoverage: false, + coverageLevel: 'unsupported', + limitations: [], + degraded: false, + chokepointConfidence: 'none', + liveFlowRatio: undefined, }; if (!code || code.length !== 2) return EMPTY; @@ -112,7 +133,21 @@ export async function computeEnergyShockScenario( }; } - const cacheKey = `energy:shock:v1:${code}:${chokepointId}:${disruptionPct}`; + const chokepointFlowsRaw2 = await getCachedJson('energy:chokepoint-flows:v1', true) + .then((v) => v as Record | null) + .catch(() => null); + + const portWatchKey = CP_TO_PORTWATCH[chokepointId]; + const cpEntry = portWatchKey ? (chokepointFlowsRaw2?.[portWatchKey] ?? null) : null; + + const degraded = !chokepointFlowsRaw2 || cpEntry == null || !Number.isFinite(cpEntry.flowRatio as number); + + const rawFlowRatio = (!degraded && cpEntry != null && Number.isFinite(cpEntry.flowRatio as number)) + ? cpEntry.flowRatio + : null; + const liveFlowRatio: number | null = rawFlowRatio !== null ? clamp(rawFlowRatio, 0, 1.5) : null; + + const cacheKey = `energy:shock:v2:${code}:${chokepointId}:${disruptionPct}:${degraded ? 'd' : 'l'}`; const cached = await getCachedJson(cacheKey); if (cached) return cached as ComputeEnergyShockScenarioResponse; @@ -128,8 +163,30 @@ export async function computeEnergyShockScenario( ? gulfShareResult.value : { share: 0, hasData: false }; - const exposureMult = CHOKEPOINT_EXPOSURE[chokepointId] ?? 1.0; - const gulfCrudeShare = rawGulfShare * exposureMult; + const exposureMult = liveFlowRatio !== null ? liveFlowRatio : (CHOKEPOINT_EXPOSURE[chokepointId] ?? 1.0); + + const jodiOilCoverage = jodiOil != null; + const comtradeCoverage = comtradeHasData; + const ieaStocksCoverage = ieaStocks != null && ieaStocks.anomaly !== true + && (ieaStocks.netExporter === true || (ieaStocks.daysOfCover != null && Number.isFinite(ieaStocks.daysOfCover) && ieaStocks.daysOfCover >= 0)); + const portwatchCoverage = liveFlowRatio !== null; + + const coverageLevel = deriveCoverageLevel(jodiOilCoverage, comtradeCoverage, ieaStocksCoverage, degraded); + + const limitations: string[] = []; + if (!comtradeCoverage && jodiOilCoverage) { + limitations.push('Gulf crude share proxied at 40% (no Comtrade data)'); + } + if (!ieaStocksCoverage) { + limitations.push('IEA strategic stock data unavailable'); + } + limitations.push('refinery yield: 80% crude-to-product heuristic'); + if (degraded) { + limitations.push('PortWatch flow data unavailable, using historical baseline multipliers'); + } + + const effectiveGulfShare = !comtradeCoverage ? PROXIED_GULF_SHARE : rawGulfShare; + const gulfCrudeShare = effectiveGulfShare * exposureMult; const crudeImportsKbd = n(jodiOil?.crude?.importsKbd); const crudeLossKbd = crudeImportsKbd * gulfCrudeShare * (disruptionPct / 100); @@ -156,11 +213,14 @@ export async function computeEnergyShockScenario( }; }); - const daysOfCover = n(ieaStocks?.daysOfCover); - const netExporter = ieaStocks?.netExporter === true; + const rawDaysOfCover = n(ieaStocks?.daysOfCover); + const daysOfCover = ieaStocksCoverage ? rawDaysOfCover : 0; + const netExporter = ieaStocksCoverage && ieaStocks?.netExporter === true; const effectiveCoverDays = computeEffectiveCoverDays(daysOfCover, netExporter, crudeLossKbd, crudeImportsKbd); - const dataAvailable = jodiOil != null && comtradeHasData; + const dataAvailable = jodiOilCoverage; + + const chokepointConfidence = deriveChokepointConfidence(liveFlowRatio, degraded); const assessment = buildAssessment( code, @@ -171,6 +231,10 @@ export async function computeEnergyShockScenario( daysOfCover, disruptionPct, products, + coverageLevel, + degraded, + ieaStocksCoverage, + comtradeCoverage, ); const response: ComputeEnergyShockScenarioResponse = { @@ -183,8 +247,18 @@ export async function computeEnergyShockScenario( effectiveCoverDays, assessment, dataAvailable, + jodiOilCoverage, + comtradeCoverage, + ieaStocksCoverage, + portwatchCoverage, + coverageLevel, + limitations, + degraded, + chokepointConfidence, + liveFlowRatio: liveFlowRatio !== null ? Math.round(liveFlowRatio * 1000) / 1000 : undefined, }; - await setCachedJson(cacheKey, response, SHOCK_CACHE_TTL); + const cacheTtl = degraded ? 300 : SHOCK_CACHE_TTL; + await setCachedJson(cacheKey, response, cacheTtl); return response; } diff --git a/src/components/CountryDeepDivePanel.ts b/src/components/CountryDeepDivePanel.ts index 0877d6e8d..48ecb463d 100644 --- a/src/components/CountryDeepDivePanel.ts +++ b/src/components/CountryDeepDivePanel.ts @@ -741,7 +741,10 @@ export class CountryDeepDivePanel implements CountryBriefPanel { computeBtn.textContent = 'Compute'; computeBtn.style.cssText += ';font-size:11px;padding:3px 8px'; - controls.append(chokepointSelect, disruptionSelect, computeBtn); + const coverageBadge = this.el('span', ''); + coverageBadge.style.cssText = 'display:none;font-size:10px;padding:2px 5px;border-radius:3px;font-weight:600'; + + controls.append(chokepointSelect, disruptionSelect, computeBtn, coverageBadge); wrapper.append(controls); const resultArea = this.el('div', ''); @@ -758,6 +761,8 @@ export class CountryDeepDivePanel implements CountryBriefPanel { const loading = this.el('div', 'cdp-economic-source', 'Computing\u2026'); resultArea.append(loading); computeBtn.disabled = true; + coverageBadge.style.display = 'none'; + coverageBadge.textContent = ''; const url = toApiUrl(`/api/intelligence/v1/compute-energy-shock?country_code=${encodeURIComponent(code)}&chokepoint_id=${encodeURIComponent(chokepoint)}&disruption_pct=${disruption}`); globalThis.fetch(url) @@ -765,6 +770,18 @@ export class CountryDeepDivePanel implements CountryBriefPanel { .then((result) => { resultArea.replaceChildren(); resultArea.append(this.renderShockResult(result)); + const lvl = result.coverageLevel ?? ''; + if (lvl) { + const colors: Record = { + full: 'background:#15803d;color:#dcfce7', + partial: 'background:#b45309;color:#fef3c7', + unsupported: 'background:#b91c1c;color:#fee2e2', + }; + coverageBadge.style.cssText = `display:inline-block;font-size:10px;padding:2px 5px;border-radius:3px;font-weight:600;${colors[lvl] ?? ''}`; + coverageBadge.textContent = lvl; + } else { + coverageBadge.style.display = 'none'; + } }) .catch(() => { resultArea.replaceChildren(); @@ -786,12 +803,21 @@ export class CountryDeepDivePanel implements CountryBriefPanel { return container; } + if (result.degraded) { + const warn = this.el('div', ''); + warn.style.cssText = 'font-size:10px;color:#f59e0b;margin-bottom:6px;padding:3px 6px;background:#1c1400;border-radius:3px'; + warn.textContent = 'Live flow data unavailable — using historical baseline'; + container.append(warn); + } + if (result.products.length > 0) { const table = this.el('table', ''); table.style.cssText = 'width:100%;font-size:11px;border-collapse:collapse;margin-bottom:6px'; const thead = this.el('thead', ''); const hr = this.el('tr', ''); - for (const h of ['Product', 'Demand', 'Loss', 'Deficit']) { + const headers = ['Product', 'Demand', 'Loss', 'Deficit']; + if (result.portwatchCoverage && result.liveFlowRatio != null) headers.push('Flow'); + for (const h of headers) { const th = this.el('th', ''); th.textContent = h; th.style.cssText = 'text-align:left;color:#aaa;padding:2px 4px'; @@ -810,6 +836,9 @@ export class CountryDeepDivePanel implements CountryBriefPanel { `${p.outputLossKbd} kbd`, `${p.deficitPct.toFixed(1)}%`, ]; + if (result.portwatchCoverage && result.liveFlowRatio != null) { + cells.push(`${Math.round(result.liveFlowRatio * 100)}%`); + } cells.forEach((val, i) => { const td = this.el('td', ''); td.textContent = val; @@ -822,10 +851,18 @@ export class CountryDeepDivePanel implements CountryBriefPanel { container.append(table); } - if (result.effectiveCoverDays > 0) { + if (result.ieaStocksCoverage) { const coverRow = this.el('div', 'cdp-economic-source'); coverRow.style.cssText += ';margin-bottom:4px'; - coverRow.textContent = `IEA cover: ~${result.effectiveCoverDays} days under this scenario`; + let coverText: string; + if (result.effectiveCoverDays < 0) { + coverText = 'Net oil exporter — strategic reserve cover not applicable'; + } else if (result.effectiveCoverDays > 0) { + coverText = `IEA cover: ~${result.effectiveCoverDays} days under this scenario`; + } else { + coverText = 'IEA cover: 0 days (reserves exhausted under this scenario)'; + } + coverRow.textContent = coverText; container.append(coverRow); } @@ -834,6 +871,24 @@ export class CountryDeepDivePanel implements CountryBriefPanel { assessmentEl.textContent = result.assessment; container.append(assessmentEl); + if (result.limitations && result.limitations.length > 0) { + const details = this.el('details', '') as HTMLDetailsElement; + details.style.cssText = 'margin-top:6px;font-size:10px;color:#9ca3af'; + const summary = this.el('summary', ''); + summary.style.cssText = 'cursor:pointer;color:#6b7280'; + summary.textContent = 'Model assumptions'; + details.append(summary); + const ul = this.el('ul', ''); + ul.style.cssText = 'margin:4px 0 0 12px;padding:0;list-style:disc'; + for (const lim of result.limitations) { + const li = this.el('li', ''); + li.textContent = lim; + ul.append(li); + } + details.append(ul); + container.append(details); + } + return container; } diff --git a/src/generated/client/worldmonitor/intelligence/v1/service_client.ts b/src/generated/client/worldmonitor/intelligence/v1/service_client.ts index 2d4a0e62b..1153e64c3 100644 --- a/src/generated/client/worldmonitor/intelligence/v1/service_client.ts +++ b/src/generated/client/worldmonitor/intelligence/v1/service_client.ts @@ -548,6 +548,15 @@ export interface ComputeEnergyShockScenarioResponse { effectiveCoverDays: number; assessment: string; dataAvailable: boolean; + jodiOilCoverage: boolean; + comtradeCoverage: boolean; + ieaStocksCoverage: boolean; + portwatchCoverage: boolean; + coverageLevel: string; + limitations: string[]; + degraded: boolean; + chokepointConfidence: string; + liveFlowRatio?: number; } export interface ProductImpact { diff --git a/src/generated/server/worldmonitor/intelligence/v1/service_server.ts b/src/generated/server/worldmonitor/intelligence/v1/service_server.ts index c27f1cc21..b6469c2ec 100644 --- a/src/generated/server/worldmonitor/intelligence/v1/service_server.ts +++ b/src/generated/server/worldmonitor/intelligence/v1/service_server.ts @@ -548,6 +548,15 @@ export interface ComputeEnergyShockScenarioResponse { effectiveCoverDays: number; assessment: string; dataAvailable: boolean; + jodiOilCoverage: boolean; + comtradeCoverage: boolean; + ieaStocksCoverage: boolean; + portwatchCoverage: boolean; + coverageLevel: string; + limitations: string[]; + degraded: boolean; + chokepointConfidence: string; + liveFlowRatio?: number; } export interface ProductImpact { diff --git a/tests/energy-shock-v2.test.mjs b/tests/energy-shock-v2.test.mjs new file mode 100644 index 000000000..8fc058ebc --- /dev/null +++ b/tests/energy-shock-v2.test.mjs @@ -0,0 +1,544 @@ +/** + * Tests for shock model v2 contract additions: + * - deriveCoverageLevel, deriveChokepointConfidence + * - buildAssessment with unsupported / partial / degraded branches + * - Integration-level mock tests for coverage flags and limitations + */ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; + +import { + deriveCoverageLevel, + deriveChokepointConfidence, + buildAssessment, + computeGulfShare, + CHOKEPOINT_EXPOSURE, +} from '../server/worldmonitor/intelligence/v1/_shock-compute.js'; + +import { ISO2_TO_COMTRADE } from '../server/worldmonitor/intelligence/v1/_comtrade-reporters.js'; + +// --------------------------------------------------------------------------- +// deriveCoverageLevel +// --------------------------------------------------------------------------- + +describe('deriveCoverageLevel', () => { + it('returns "unsupported" when jodiOil is false regardless of comtrade', () => { + assert.equal(deriveCoverageLevel(false, false), 'unsupported'); + assert.equal(deriveCoverageLevel(false, true), 'unsupported'); + }); + + it('returns "partial" when jodiOil is true but comtrade is false', () => { + assert.equal(deriveCoverageLevel(true, false), 'partial'); + }); + + it('returns "full" when both jodiOil and comtrade are true', () => { + assert.equal(deriveCoverageLevel(true, true), 'full'); + }); +}); + +// --------------------------------------------------------------------------- +// deriveChokepointConfidence +// --------------------------------------------------------------------------- + +describe('deriveChokepointConfidence', () => { + it('returns "none" when degraded is true regardless of liveFlowRatio', () => { + assert.equal(deriveChokepointConfidence(0.9, true), 'none'); + assert.equal(deriveChokepointConfidence(null, true), 'none'); + }); + + it('returns "none" when liveFlowRatio is null and not degraded', () => { + assert.equal(deriveChokepointConfidence(null, false), 'none'); + }); + + it('returns "high" when liveFlowRatio is present and not degraded', () => { + assert.equal(deriveChokepointConfidence(0.9, false), 'high'); + assert.equal(deriveChokepointConfidence(1.0, false), 'high'); + assert.equal(deriveChokepointConfidence(0.0, false), 'high'); + }); +}); + +// --------------------------------------------------------------------------- +// buildAssessment — unsupported country +// --------------------------------------------------------------------------- + +describe('buildAssessment — unsupported country', () => { + it('returns structured insufficient data message for unsupported country', () => { + const msg = buildAssessment('ZZ', 'hormuz', false, 0, 0, 0, 50, [], 'unsupported', false); + assert.ok(msg.includes('Insufficient import data')); + assert.ok(msg.includes('ZZ')); + assert.ok(msg.includes('hormuz')); + }); + + it('unsupported message is returned even if dataAvailable is true but coverageLevel is unsupported', () => { + const msg = buildAssessment('ZZ', 'hormuz', true, 0.5, 60, 30, 50, [], 'unsupported', false); + assert.ok(msg.includes('Insufficient import data')); + }); + + it('dataAvailable=false without coverageLevel also returns insufficient data message', () => { + const msg = buildAssessment('XY', 'suez', false, 0, 0, 0, 50, []); + assert.ok(msg.includes('Insufficient import data')); + }); +}); + +// --------------------------------------------------------------------------- +// buildAssessment — partial coverage +// --------------------------------------------------------------------------- + +describe('buildAssessment — partial coverage', () => { + it('includes proxy note when partial due to missing comtrade', () => { + const products = [ + { product: 'Diesel', deficitPct: 20.0 }, + { product: 'Jet fuel', deficitPct: 15.0 }, + ]; + const msg = buildAssessment('XX', 'hormuz', true, 0.4, 60, 30, 50, products, 'partial', false, true, false); + assert.ok(msg.includes('20.0%')); + assert.ok(msg.includes('Gulf share proxied')); + }); + + it('does not include proxy note in full coverage branch', () => { + const products = [ + { product: 'Diesel', deficitPct: 20.0 }, + { product: 'Jet fuel', deficitPct: 15.0 }, + ]; + const msg = buildAssessment('IN', 'hormuz', true, 0.4, 60, 30, 50, products, 'full', false, true, true); + assert.ok(!msg.includes('proxied')); + }); +}); + +// --------------------------------------------------------------------------- +// buildAssessment — degraded mode +// --------------------------------------------------------------------------- + +describe('buildAssessment — degraded mode', () => { + it('includes degraded note in cover-days branch when degraded=true', () => { + const msg = buildAssessment('US', 'hormuz', true, 0.4, 180, 90, 50, [], 'full', true); + assert.ok(msg.includes('live flow data unavailable')); + }); + + it('does not include degraded note when degraded=false', () => { + const msg = buildAssessment('US', 'hormuz', true, 0.4, 180, 90, 50, [], 'full', false); + assert.ok(!msg.includes('live flow data unavailable')); + }); + + it('net-exporter branch does not include degraded note (takes priority)', () => { + const msg = buildAssessment('SA', 'hormuz', true, 0.8, -1, 0, 50, [], 'full', true); + assert.ok(msg.includes('net oil exporter')); + assert.ok(!msg.includes('live flow data unavailable')); + }); +}); + +// --------------------------------------------------------------------------- +// Mock test: PortWatch absent → degraded=true, liveFlowRatio=0, fallback to CHOKEPOINT_EXPOSURE +// --------------------------------------------------------------------------- + +describe('mock: degraded mode falls back to CHOKEPOINT_EXPOSURE', () => { + it('CHOKEPOINT_EXPOSURE values are used as fallback when portwatch absent', () => { + const chokepointId = 'hormuz'; + const degraded = true; + const liveFlowRatio = null; + + const exposureMult = liveFlowRatio !== null ? liveFlowRatio : (CHOKEPOINT_EXPOSURE[chokepointId] ?? 1.0); + assert.equal(exposureMult, 1.0); + + const confidence = deriveChokepointConfidence(liveFlowRatio, degraded); + assert.equal(confidence, 'none'); + + const computedLiveFlowRatioInResponse = liveFlowRatio !== null ? liveFlowRatio : undefined; + assert.equal(computedLiveFlowRatioInResponse, undefined, 'liveFlowRatio should be absent (undefined) when PortWatch unavailable, not 0'); + }); + + it('suez uses CHOKEPOINT_EXPOSURE[suez]=0.6 when portwatch absent', () => { + const exposureMult = CHOKEPOINT_EXPOSURE['suez'] ?? 1.0; + assert.equal(exposureMult, 0.6); + }); + + it('malacca uses CHOKEPOINT_EXPOSURE[malacca]=0.7 when portwatch absent', () => { + const exposureMult = CHOKEPOINT_EXPOSURE['malacca'] ?? 1.0; + assert.equal(exposureMult, 0.7); + }); +}); + +// --------------------------------------------------------------------------- +// Mock test: partial coverage → limitations includes proxy string +// --------------------------------------------------------------------------- + +describe('mock: partial coverage limitations', () => { + it('partial coverage level triggers Gulf share proxy limitation', () => { + const jodiOilCoverage = true; + const comtradeCoverage = false; + const coverageLevel = deriveCoverageLevel(jodiOilCoverage, comtradeCoverage); + assert.equal(coverageLevel, 'partial'); + + const limitations = []; + if (coverageLevel === 'partial') { + limitations.push('Gulf crude share proxied at 40% (no Comtrade data)'); + } + limitations.push('refinery yield: 80% crude-to-product heuristic'); + + assert.ok(limitations.some((l) => l.includes('proxied at 40%'))); + assert.ok(limitations.some((l) => l.includes('refinery yield'))); + }); + + it('full coverage does not add proxy limitation', () => { + const coverageLevel = deriveCoverageLevel(true, true); + const limitations = []; + if (coverageLevel === 'partial') { + limitations.push('Gulf crude share proxied at 40% (no Comtrade data)'); + } + limitations.push('refinery yield: 80% crude-to-product heuristic'); + assert.ok(!limitations.some((l) => l.includes('proxied at 40%'))); + }); +}); + +// --------------------------------------------------------------------------- +// Mock test: full coverage with live data → confidence='high', liveFlowRatio set +// --------------------------------------------------------------------------- + +describe('mock: full coverage with live PortWatch data', () => { + it('chokepointConfidence is high when liveFlowRatio present and not degraded', () => { + const liveFlowRatio = 0.9; + const degraded = false; + const confidence = deriveChokepointConfidence(liveFlowRatio, degraded); + assert.equal(confidence, 'high'); + }); + + it('live flow ratio replaces CHOKEPOINT_EXPOSURE multiplier', () => { + const chokepointId = 'suez'; + const liveFlowRatio = 0.85; + const exposureMult = liveFlowRatio !== null ? liveFlowRatio : (CHOKEPOINT_EXPOSURE[chokepointId] ?? 1.0); + assert.equal(exposureMult, 0.85); + assert.notEqual(exposureMult, CHOKEPOINT_EXPOSURE[chokepointId]); + }); + + it('full coverage returns "full" level with both jodiOil and comtrade true', () => { + const level = deriveCoverageLevel(true, true); + assert.equal(level, 'full'); + }); +}); + +// --------------------------------------------------------------------------- +// ISO2_TO_COMTRADE completeness +// --------------------------------------------------------------------------- + +describe('ISO2_TO_COMTRADE completeness', () => { + const REQUIRED = ['US', 'CN', 'RU', 'IR', 'IN', 'TW', 'DE', 'FR', 'GB', 'IT', + 'JP', 'KR', 'SA', 'AE', 'TR', 'BR', 'AU', 'CA', 'MX', 'ID', + 'TH', 'MY', 'SG', 'PL', 'NL', 'BE', 'ES', 'PT', 'GR', 'SE', + 'NO', 'FI', 'DK', 'CH', 'AT', 'CZ', 'HU', 'RO', 'UA', 'EG', + 'ZA', 'NG', 'KE', 'MA', 'DZ', 'IQ', 'KW', 'QA', 'VN', 'PH', + 'PK', 'BD', 'NZ', 'CL', 'AR', 'CO', 'PE', 'VE', 'BO']; + + it('contains all 6 originally seeded Comtrade reporters', () => { + for (const code of ['US', 'CN', 'RU', 'IR', 'IN', 'TW']) { + assert.ok(code in ISO2_TO_COMTRADE, `Missing originally seeded reporter: ${code}`); + } + }); + + it('contains all required major economies', () => { + for (const code of REQUIRED) { + assert.ok(code in ISO2_TO_COMTRADE, `Missing required country: ${code}`); + } + }); + + it('has more than 50 entries', () => { + assert.ok(Object.keys(ISO2_TO_COMTRADE).length > 50, `Expected >50 entries, got ${Object.keys(ISO2_TO_COMTRADE).length}`); + }); + + it('all values are numeric strings', () => { + for (const [iso2, code] of Object.entries(ISO2_TO_COMTRADE)) { + assert.ok(/^\d{3}$/.test(code), `${iso2} has non-3-digit code: ${code}`); + } + }); + + it('US maps to 842', () => assert.equal(ISO2_TO_COMTRADE['US'], '842')); + it('CN maps to 156', () => assert.equal(ISO2_TO_COMTRADE['CN'], '156')); + it('DE maps to 276', () => assert.equal(ISO2_TO_COMTRADE['DE'], '276')); + it('JP maps to 392', () => assert.equal(ISO2_TO_COMTRADE['JP'], '392')); +}); + +// --------------------------------------------------------------------------- +// NaN/Infinity guard — deriveChokepointConfidence +// --------------------------------------------------------------------------- + +describe('deriveChokepointConfidence guards NaN and Infinity', () => { + it('returns "none" for NaN flowRatio', () => { + assert.equal(deriveChokepointConfidence(NaN, false), 'none'); + }); + + it('returns "none" for Infinity flowRatio', () => { + assert.equal(deriveChokepointConfidence(Infinity, false), 'none'); + }); + + it('returns "none" for -Infinity flowRatio', () => { + assert.equal(deriveChokepointConfidence(-Infinity, false), 'none'); + }); + + it('returns "high" for a finite positive flowRatio with degraded=false', () => { + assert.equal(deriveChokepointConfidence(0.85, false), 'high'); + }); + + it('returns "high" for flowRatio=0 with degraded=false (true 0 flow is valid)', () => { + assert.equal(deriveChokepointConfidence(0, false), 'high'); + }); +}); + +// --------------------------------------------------------------------------- +// deriveCoverageLevel — IEA and degraded inputs +// --------------------------------------------------------------------------- + +describe('deriveCoverageLevel accounts for IEA and degraded state', () => { + it('returns "full" only when all inputs are good', () => { + assert.equal(deriveCoverageLevel(true, true, true, false), 'full'); + }); + + it('returns "partial" when ieaStocksCoverage is false (even with JODI+Comtrade)', () => { + assert.equal(deriveCoverageLevel(true, true, false, false), 'partial'); + }); + + it('returns "partial" when degraded=true (even with JODI+Comtrade+IEA)', () => { + assert.equal(deriveCoverageLevel(true, true, true, true), 'partial'); + }); + + it('returns "partial" when comtrade is false regardless of IEA/degraded', () => { + assert.equal(deriveCoverageLevel(true, false, true, false), 'partial'); + }); + + it('returns "unsupported" when jodiOil is false', () => { + assert.equal(deriveCoverageLevel(false, true, true, false), 'unsupported'); + }); + + it('backward-compatible: two-arg call without IEA/degraded still works', () => { + // ieaStocksCoverage=undefined → !undefined=true → passes; degraded=undefined → falsy → passes + assert.equal(deriveCoverageLevel(true, true), 'full'); + assert.equal(deriveCoverageLevel(true, false), 'partial'); + assert.equal(deriveCoverageLevel(false, true), 'unsupported'); + }); +}); + +// --------------------------------------------------------------------------- +// live_flow_ratio absent when portwatchCoverage=false +// --------------------------------------------------------------------------- + +describe('liveFlowRatio is absent (undefined) when PortWatch unavailable', () => { + it('liveFlowRatio should be undefined, not 0, when portwatch is absent', () => { + // This tests the response contract: callers must check portwatchCoverage, + // not rely on liveFlowRatio===0 to detect missing data. + const liveFlowRatioFromServer = null; // PortWatch unavailable + const fieldOnWire = liveFlowRatioFromServer !== null + ? Math.round(liveFlowRatioFromServer * 1000) / 1000 + : undefined; + assert.equal(fieldOnWire, undefined, 'field should be absent on wire when portwatch unavailable'); + }); + + it('liveFlowRatio=0 is valid and distinct from "unavailable" when portwatchCoverage=true', () => { + // True zero flow (chokepoint collapse) is a real and distinct signal + const liveFlowRatioFromServer = 0; // portwatchCoverage=true, chokepoint collapsed + const fieldOnWire = liveFlowRatioFromServer !== null + ? Math.round(liveFlowRatioFromServer * 1000) / 1000 + : undefined; + assert.equal(fieldOnWire, 0, 'true 0 flow should serialize as 0, not undefined'); + }); +}); + +// --------------------------------------------------------------------------- +// computeGulfShare — NaN/Infinity guard +// --------------------------------------------------------------------------- + +describe('computeGulfShare rejects NaN and Infinity tradeValueUsd', () => { + it('returns { share: 0, hasData: false } when flow has tradeValueUsd: NaN', () => { + const flows = [{ tradeValueUsd: NaN, partnerCode: '682' }]; + const result = computeGulfShare(flows); + assert.deepEqual(result, { share: 0, hasData: false }); + }); + + it('returns { share: 0, hasData: false } when flow has tradeValueUsd: Infinity', () => { + const flows = [{ tradeValueUsd: Infinity, partnerCode: '682' }]; + const result = computeGulfShare(flows); + assert.deepEqual(result, { share: 0, hasData: false }); + }); + + it('returns { share: 0, hasData: false } when flow has tradeValueUsd: -Infinity', () => { + const flows = [{ tradeValueUsd: -Infinity, partnerCode: '682' }]; + const result = computeGulfShare(flows); + assert.deepEqual(result, { share: 0, hasData: false }); + }); + + it('still computes correctly with valid finite values', () => { + const flows = [ + { tradeValueUsd: 100, partnerCode: '682' }, + { tradeValueUsd: 100, partnerCode: '840' }, + ]; + const result = computeGulfShare(flows); + assert.equal(result.hasData, true); + assert.equal(result.share, 0.5); + }); + + it('skips NaN flows but computes from valid ones', () => { + const flows = [ + { tradeValueUsd: NaN, partnerCode: '682' }, + { tradeValueUsd: 100, partnerCode: '682' }, + { tradeValueUsd: 100, partnerCode: '840' }, + ]; + const result = computeGulfShare(flows); + assert.equal(result.hasData, true); + assert.equal(result.share, 0.5); + }); +}); + +// --------------------------------------------------------------------------- +// buildAssessment — proxy text only when comtrade is missing +// --------------------------------------------------------------------------- + +describe('buildAssessment proxy text is tied to comtradeCoverage, not coverageLevel', () => { + const products = [ + { product: 'Diesel', deficitPct: 20.0 }, + { product: 'Jet fuel', deficitPct: 15.0 }, + ]; + + it('shows proxy text when partial due to missing comtrade (comtradeCoverage=false)', () => { + const msg = buildAssessment('XX', 'hormuz', true, 0.4, 60, 30, 50, products, 'partial', false, true, false); + assert.ok(msg.includes('Gulf share proxied at 40%'), 'should mention proxy when comtrade missing'); + }); + + it('does NOT show proxy text when partial due to IEA anomaly (comtradeCoverage=true)', () => { + const msg = buildAssessment('XX', 'hormuz', true, 0.4, 60, 30, 50, products, 'partial', false, false, true); + assert.ok(!msg.includes('proxied'), 'should not mention proxy when comtrade is present'); + }); + + it('does NOT show proxy text when partial due to degraded PortWatch (comtradeCoverage=true)', () => { + const msg = buildAssessment('XX', 'hormuz', true, 0.4, 60, 30, 50, products, 'partial', true, true, true); + assert.ok(!msg.includes('proxied'), 'should not mention proxy when comtrade is present'); + }); + + it('does NOT show proxy text when full coverage (comtradeCoverage=true)', () => { + const msg = buildAssessment('IN', 'hormuz', true, 0.4, 60, 30, 50, products, 'full', false, true, true); + assert.ok(!msg.includes('proxied'), 'should not mention proxy in full coverage'); + }); +}); + +// --------------------------------------------------------------------------- +// ieaStocksCoverage requires daysOfCover for non-exporters +// --------------------------------------------------------------------------- + +describe('ieaStocksCoverage requires daysOfCover for non-exporters', () => { + it('ieaStocksCoverage is false when daysOfCover is null and not a net exporter', () => { + const ieaStocks = { anomaly: false, daysOfCover: null, netExporter: false }; + const coverage = ieaStocks != null && ieaStocks.anomaly !== true + && (ieaStocks.netExporter === true || typeof ieaStocks.daysOfCover === 'number'); + assert.equal(coverage, false, 'null daysOfCover for non-exporter should be false'); + }); + + it('ieaStocksCoverage is true when daysOfCover is 0 (genuinely exhausted)', () => { + const ieaStocks = { anomaly: false, daysOfCover: 0, netExporter: false }; + const coverage = ieaStocks != null && ieaStocks.anomaly !== true + && (ieaStocks.netExporter === true || typeof ieaStocks.daysOfCover === 'number'); + assert.equal(coverage, true, 'daysOfCover=0 is real data, should be true'); + }); + + it('ieaStocksCoverage is true for net exporter even without daysOfCover', () => { + const ieaStocks = { anomaly: false, daysOfCover: null, netExporter: true }; + const coverage = ieaStocks != null && ieaStocks.anomaly !== true + && (ieaStocks.netExporter === true || typeof ieaStocks.daysOfCover === 'number'); + assert.equal(coverage, true, 'net exporters do not need daysOfCover'); + }); + + it('ieaStocksCoverage is false when anomaly is true', () => { + const ieaStocks = { anomaly: true, daysOfCover: 90, netExporter: false }; + const coverage = ieaStocks != null && ieaStocks.anomaly !== true + && (ieaStocks.netExporter === true || typeof ieaStocks.daysOfCover === 'number'); + assert.equal(coverage, false, 'anomaly should override'); + }); + + it('ieaStocksCoverage is false when ieaStocks is null', () => { + const ieaStocks = null; + const coverage = ieaStocks != null && ieaStocks.anomaly !== true + && (ieaStocks.netExporter === true || typeof ieaStocks.daysOfCover === 'number'); + assert.equal(coverage, false); + }); +}); + +// --------------------------------------------------------------------------- +// ieaStocksCoverage rejects non-finite and negative daysOfCover +// --------------------------------------------------------------------------- + +describe('ieaStocksCoverage rejects non-finite and negative daysOfCover', () => { + function checkCoverage(ieaStocks) { + return ieaStocks != null && ieaStocks.anomaly !== true + && (ieaStocks.netExporter === true || (Number.isFinite(ieaStocks.daysOfCover) && ieaStocks.daysOfCover >= 0)); + } + + it('rejects NaN daysOfCover', () => { + assert.equal(checkCoverage({ anomaly: false, daysOfCover: NaN, netExporter: false }), false); + }); + + it('rejects Infinity daysOfCover', () => { + assert.equal(checkCoverage({ anomaly: false, daysOfCover: Infinity, netExporter: false }), false); + }); + + it('rejects negative daysOfCover', () => { + assert.equal(checkCoverage({ anomaly: false, daysOfCover: -1, netExporter: false }), false); + }); + + it('accepts zero daysOfCover (genuinely exhausted)', () => { + assert.equal(checkCoverage({ anomaly: false, daysOfCover: 0, netExporter: false }), true); + }); + + it('accepts positive finite daysOfCover', () => { + assert.equal(checkCoverage({ anomaly: false, daysOfCover: 90, netExporter: false }), true); + }); +}); + +// --------------------------------------------------------------------------- +// liveFlowRatio clamped to 0..1.5 +// --------------------------------------------------------------------------- + +describe('liveFlowRatio clamped to 0..1.5', () => { + it('clamps negative flowRatio to 0', () => { + const raw = -0.5; + const clamped = Math.max(0, Math.min(1.5, raw)); + assert.equal(clamped, 0); + }); + + it('clamps oversized flowRatio to 1.5', () => { + const raw = 3.0; + const clamped = Math.max(0, Math.min(1.5, raw)); + assert.equal(clamped, 1.5); + }); + + it('passes through valid flowRatio unchanged', () => { + const raw = 0.85; + const clamped = Math.max(0, Math.min(1.5, raw)); + assert.equal(clamped, 0.85); + }); + + it('passes through zero flowRatio (chokepoint collapsed)', () => { + const raw = 0; + const clamped = Math.max(0, Math.min(1.5, raw)); + assert.equal(clamped, 0); + }); + + it('passes through 1.5 (max valid ratio)', () => { + const raw = 1.5; + const clamped = Math.max(0, Math.min(1.5, raw)); + assert.equal(clamped, 1.5); + }); +}); + +// --------------------------------------------------------------------------- +// cache key includes degraded state +// --------------------------------------------------------------------------- + +describe('cache key includes degraded state', () => { + it('degraded and non-degraded produce different cache keys', () => { + const code = 'US'; + const chokepointId = 'hormuz'; + const disruptionPct = 50; + + const keyDegraded = `energy:shock:v2:${code}:${chokepointId}:${disruptionPct}:d`; + const keyLive = `energy:shock:v2:${code}:${chokepointId}:${disruptionPct}:l`; + + assert.notEqual(keyDegraded, keyLive, 'cache keys must differ by degraded state'); + assert.ok(keyDegraded.endsWith(':d')); + assert.ok(keyLive.endsWith(':l')); + }); +});