syntax = "proto3";

package worldmonitor.forecast.v1;

import "sebuf/http/annotations.proto";

message GetSimulationOutcomeRequest {
  // Filter is active for outcomes within the 24h retention window (#3734).
  // If runId is supplied:
  //   - Hit on the by-run key → returns that outcome, processing=false.
  //   - By-run lookup returns the tombstone (worker write transiently failed) →
  //     falls through to :latest with a 'note' describing the transient failure.
  //   - No by-run match but runId is in the queue → returns processing=true.
  //   - No match anywhere → falls back to :latest with a 'note' describing
  //     expiry-or-mismatch.
  // If runId is empty: returns :latest (existing behavior).
  string run_id = 1 [(sebuf.http.query) = { name: "runId" }];
}

message GetSimulationOutcomeResponse {
  bool found = 1;
  string run_id = 2;
  string outcome_key = 3;
  string schema_version = 4;
  int32 theater_count = 5;
  // Unix timestamp in milliseconds (from Date.now()). Warning: Values > 2^53 may lose precision in JavaScript.
  int64 generated_at = 6 [(sebuf.http.int64_encoding) = INT64_ENCODING_NUMBER];
  // Populated when req.runId was supplied but does not match the returned outcome's runId,
  // or when the by-run lookup returned a tombstone (Redis transient write failure).
  // Distinguishes "your runId expired beyond 24h retention" from "by-run lookup failed
  // (Redis transient); returned latest" so callers can react differently.
  string note = 7;
  // Populated when the Redis lookup failed. Distinguish from healthy not-found (found=false, error="").
  // Value: "redis_unavailable" on Redis errors.
  string error = 8;
  // JSON-encoded array of theater summaries for the UI (populated when found=true).
  // Shape: Array<{ theaterId, theaterLabel, stateKind, topPaths: [{label, summary, confidence, keyActors}], dominantReactions, stabilizers, invalidators }>
  // Parse with JSON.parse() on the client. Empty string when found=false.
  string theater_summaries_json = 9;
  // True when req.runId was supplied AND the runId is currently in the simulation
  // queue (worker hasn't drained yet). Distinguishes "your run is processing"
  // from "your run expired beyond retention" — both previously returned NOT_FOUND.
  // Added in #3734 to prevent the 30-day demand experiment from being contaminated
  // by the queued-vs-expired indistinguishability.
  bool processing = 10;
}
