syntax = "proto3";

package worldmonitor.resilience.v1;

message DimensionFreshness {
  // Unix milliseconds when the oldest constituent signal in this
  // dimension was last observed (min fetchedAt across INDICATOR_REGISTRY
  // entries for this dimension). 0 when no signal has ever been
  // observed.
  int64 last_observed_at_ms = 1;
  // Worst staleness level across the dimension's constituent signals,
  // classified by classifyStaleness against each signal's cadence.
  // One of: "fresh", "aging", "stale". Empty string when no signals.
  string staleness = 2;
}

message ResilienceDimension {
  string id = 1;
  double score = 2;
  double coverage = 3;
  double observed_weight = 4;
  double imputed_weight = 5;
  // Four-class imputation taxonomy (Phase 1 T1.7). One of:
  // "stable-absence", "unmonitored", "source-failure", "not-applicable".
  // Empty string when the dimension has any observed data AND is not
  // structurally not-applicable. The "not-applicable" value (plan
  // 2026-04-26-001 §U3) is emitted when the dim's construct does not
  // apply to this country (e.g. sovereignFiscalBuffer for non-SWF
  // economies); it is paired with coverage:0 and observed_weight:0 so
  // the dim contributes nothing to the domain mean and is filtered
  // out of user-facing confidence/coverage signals on both server and
  // client. See docs/methodology/country-resilience-index.mdx.
  string imputation_class = 6;
  // Freshness aggregated across the constituent signals of the
  // dimension. Phase 1 T1.5 of the reference-grade upgrade plan.
  // Empty `last_observed_at_ms=0` + `staleness=""` when the dimension
  // has no signals tracked in the indicator registry.
  DimensionFreshness freshness = 7;
}

message ResilienceDomain {
  string id = 1;
  double score = 2;
  double weight = 3;
  repeated ResilienceDimension dimensions = 4;
}

message ResilienceRankingItem {
  string country_code = 1;
  double overall_score = 2;
  string level = 3;
  bool low_confidence = 4;
  double overall_coverage = 5;
  bool rank_stable = 6;
  // Current headline-ranking eligibility. True only when the country passes
  // the headline gate: coverage >= 0.65 AND (population >= 200k OR
  // coverage >= 0.85) AND !lowConfidence. GetResilienceRanking includes
  // only eligible countries in items[]; scored but ineligible countries
  // remain in greyedOut[].
  bool headline_eligible = 7;
}

// Phase 2 T2.1/T2.3 of the country-resilience reference-grade upgrade plan.
// Three-pillar response shape that regroups the 6 ResilienceDomains
// (economic, infrastructure, energy, social-governance, health-food,
// recovery) into long-run capacity (structural-readiness), current shock
// pressure (live-shock-exposure), and recovery capability (recovery-capacity).
// Pillar scores are real domain-weighted, coverage-scaled aggregates computed
// from the constituent domains; pillar coverage remains the mean of
// member-domain average dimension coverage. See _pillar-membership.ts for the mapping.
// When RESILIENCE_SCHEMA_V2_ENABLED and RESILIENCE_PILLAR_COMBINE_ENABLED
// are both true, the top-level overall_score on GetResilienceScoreResponse
// uses the pillar-combined score with the min-pillar penalty term in
// _shared.ts#penalizedPillarScore. The legacy six-domain weighted aggregate
// remains the flag-off rollback path.
message ResiliencePillar {
  // "structural-readiness" | "live-shock-exposure" | "recovery-capacity".
  string id = 1;
  // Pillar score in [0, 100], mean of member domains weighted by
  // domain.weight * average_dimension_coverage.
  double score = 2;
  // Pillar weight in the pillar-combined score. Per the plan: 0.40 / 0.35 / 0.25.
  double weight = 3;
  // Coverage in [0, 1], mean of member-domain average dimension coverage.
  double coverage = 4;
  // Subset of the 6 ResilienceDomains that compose this pillar.
  repeated ResilienceDomain domains = 5;
}
