GitHub
ESC

JSON & Bands

JSON round-trip

Score#to_json emits the same row shape the FIRST API uses:

{
  "cve": "CVE-2022-27225",
  "epss": "0.001870000",
  "percentile": "0.401290000",
  "date": "2026-05-18"
}

Probabilities are stringified to nine decimal places — the FIRST API's own precision — so a serialized payload is byte-identical to one fetched fresh.

score = EPSS.score("CVE-2022-27225").not_nil!
cached = score.to_json    # store
restored = EPSS.from_json(cached).first
restored == score         # => true

EPSS.from_json accepts either:

For an envelope with scope=time-series, the nested per-day entries are flattened into the returned Array(EPSS::Score).

Bands

EPSS::Band is a qualitative bucket — useful for surfacing rows in dashboards or filtering noise out of long lists. EPSS itself ships only numeric values; the band cutoffs in epss.cr are conventions chosen to fit common operational policies.

Band EPSS probability Percentile rank
None < 0.01 < 0.50
Low [0.01, 0.10) [0.50, 0.80)
Medium [0.10, 0.30) [0.80, 0.90)
High [0.30, 0.70) [0.90, 0.99)
Critical >= 0.70 >= 0.99

Pick whichever set fits the policy you're enforcing:

EPSS::Band.from_epss(0.92)       # => EPSS::Band::Critical
EPSS::Band.from_percentile(0.85) # => EPSS::Band::Medium

score.band             # uses the EPSS probability cutoffs
score.percentile_band  # uses the percentile cutoffs

Band is Comparable, so you can threshold against it directly:

critical_or_higher = scores.select { |s| s.band >= EPSS::Band::Critical }

Equality, hashing, and ordering

Score is structurally equal on every field — cve, epss, percentile, and date — so two scores from different snapshots of the same CVE are not equal. The == override exists because including Comparable otherwise derives equality from <=>, which compares only the EPSS probability.

a = EPSS::Score.new("CVE-1", 0.5, 0.6, Time.utc(2026, 5, 18))
b = EPSS::Score.new("CVE-1", 0.5, 0.6, Time.utc(2026, 5, 17))
a == b      # => false (different date)
a <=> b     # => 0     (same EPSS probability)

Score is also a valid Set / Hash key.

Sorting by EPSS probability is built-in:

scores.sort.last   # highest-probability CVE
scores.min         # lowest