///|
/// Summarising a period of days.
///
/// A report is rarely one day. A monthly summary counts the days that were
/// good, the days that were heavy, the worst of them, and which pollutant
/// kept setting the index — the four things a period is described by.
///
/// Everything here is counted from the days the library has already indexed,
/// so a summary cannot disagree with the days it summarises.
///|
/// A running summary of days.
pub(all) struct Summary {
mut days : Int32
mut total_index : Int32
mut good_days : Int32
mut heavy_days : Int32
mut worst_index : Int32?
mut worst_station : String?
primary_counts : Array[(Pollutant, Int32)]
}
///|
/// An empty summary.
pub fn Summary::new() -> Summary {
{
days: 0,
total_index: 0,
good_days: 0,
heavy_days: 0,
worst_index: None,
worst_station: None,
primary_counts: [],
}
}
///|
/// Add one day's index, and the station it came from.
///
/// A day is counted as good when its index is at or below 100, which is the
/// boundary both standards draw for the second band; it is counted as heavy
/// above 200, where the fourth band begins. Those are the bands' own
/// boundaries rather than numbers invented here.
pub fn Summary::add(self : Summary, station : String, air : AirQuality) -> Unit {
let index = air.index()
self.days += 1
self.total_index += index
if index <= 100 {
self.good_days += 1
}
if index > 200 {
self.heavy_days += 1
}
self.worst_index = match self.worst_index {
Some(worst) =>
if index > worst {
self.worst_station = Some(station)
Some(index)
} else {
Some(worst)
}
None => {
self.worst_station = Some(station)
Some(index)
}
}
// Count the pollutant that set the index, once per day.
for pollutant in air.dominant() {
let mut seen = false
for entry in self.primary_counts {
if entry.0 == pollutant {
seen = true
}
}
if seen {
for i = 0; i < self.primary_counts.length(); i = i + 1 {
if self.primary_counts[i].0 == pollutant {
self.primary_counts[i] = (pollutant, self.primary_counts[i].1 + 1)
}
}
} else {
self.primary_counts.push((pollutant, 1))
}
}
}
///|
/// How many days have been added.
pub fn Summary::days(self : Summary) -> Int32 {
self.days
}
///|
/// How many of them were at or below 100.
pub fn Summary::good_days(self : Summary) -> Int32 {
self.good_days
}
///|
/// The share of days at or below 100, as a fraction of one, or `None` for an
/// empty summary.
pub fn Summary::good_rate(self : Summary) -> Double? {
if self.days == 0 {
None
} else {
Some(self.good_days.to_double() / self.days.to_double())
}
}
///|
/// How many days were above 200.
pub fn Summary::heavy_days(self : Summary) -> Int32 {
self.heavy_days
}
///|
/// The mean index over the days, or `None` for an empty summary.
pub fn Summary::average_index(self : Summary) -> Double? {
if self.days == 0 {
None
} else {
Some(self.total_index.to_double() / self.days.to_double())
}
}
///|
/// The worst index in the period.
pub fn Summary::worst_index(self : Summary) -> Int32? {
self.worst_index
}
///|
/// The station the worst day came from.
pub fn Summary::worst_station(self : Summary) -> String? {
self.worst_station
}
///|
/// How often each pollutant set the index, as pairs of pollutant and count,
/// in the order they were first seen.
pub fn Summary::primary_counts(self : Summary) -> Array[(Pollutant, Int32)] {
self.primary_counts
}
///|
/// The pollutant that set the index most often, or `None` when no day named
/// one.
pub fn Summary::most_frequent_primary(self : Summary) -> Pollutant? {
let mut best : Pollutant? = None
let mut best_count = 0
for entry in self.primary_counts {
if entry.1 > best_count {
best = Some(entry.0)
best_count = entry.1
}
}
best
}