use crate::model::comparison::{EvidenceCompleteness, EvidenceReason};
use crate::model::metric::{MetricKey, MetricPoint};
use crate::model::run::RunId;
const EXTREMA_PER_BUCKET: usize = 4;
const MAX_SCREEN_QUERY_POINTS: usize = 1_000_000;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AlignmentAxis {
Step,
ElapsedTime,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AlignmentViewport {
start: i64,
end: i64,
}
impl AlignmentViewport {
pub const fn new(start: i64, end: i64) -> Result<Self, AlignmentQueryError> {
if start > end {
return Err(AlignmentQueryError::InvalidViewport);
}
Ok(Self { start, end })
}
pub const fn start(self) -> i64 {
self.start
}
pub const fn end(self) -> i64 {
self.end
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AlignmentReduction {
Full,
ScreenBudget(AlignmentScreenBudget),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AlignmentScreenBudget {
pixel_width: u32,
points_per_pixel: u16,
}
impl AlignmentReduction {
pub const fn screen_budget(
pixel_width: u32,
points_per_pixel: u16,
) -> Result<Self, AlignmentQueryError> {
if pixel_width == 0 || points_per_pixel == 0 {
return Err(AlignmentQueryError::InvalidScreenBudget);
}
Ok(Self::ScreenBudget(AlignmentScreenBudget {
pixel_width,
points_per_pixel,
}))
}
pub fn max_points(self) -> Option<usize> {
match self {
Self::Full => None,
Self::ScreenBudget(budget) => Some(
(budget.pixel_width as usize)
.saturating_mul(budget.points_per_pixel as usize)
.clamp(EXTREMA_PER_BUCKET, MAX_SCREEN_QUERY_POINTS),
),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AlignmentQuery {
pub run_id: RunId,
pub metric_key: MetricKey,
pub axis: AlignmentAxis,
pub viewport: AlignmentViewport,
pub reduction: AlignmentReduction,
}
#[derive(Clone, Debug, PartialEq)]
pub struct AlignedMetricPoint {
pub point: MetricPoint,
pub axis_value: i64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum AlignmentReason {
MissingRunStart,
NegativeAxis,
DecreasingAxis,
}
#[derive(Clone, Debug, PartialEq)]
pub struct AlignmentQueryResult {
pub points: Vec<AlignedMetricPoint>,
pub source_row_count: u64,
pub reasons: Vec<AlignmentReason>,
}
impl AlignmentQueryResult {
pub fn downsampled(&self) -> bool {
self.source_row_count > self.points.len() as u64
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct AlignedMetricResult {
pub points: Vec<AlignedMetricPoint>,
pub source_row_count: u64,
pub completeness: EvidenceCompleteness,
pub reasons: Vec<EvidenceReason>,
}
impl AlignedMetricResult {
pub fn downsampled(&self) -> bool {
self.source_row_count > self.points.len() as u64
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
pub enum AlignmentQueryError {
#[error("alignment viewport must be a non-decreasing closed range")]
InvalidViewport,
#[error("screen query pixel width and point density must be positive")]
InvalidScreenBudget,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn viewport_accepts_a_single_closed_coordinate() {
let viewport = AlignmentViewport::new(4, 4).expect("single coordinate should be valid");
assert_eq!((viewport.start(), viewport.end()), (4, 4));
}
#[test]
fn viewport_rejects_decreasing_bounds() {
let error = AlignmentViewport::new(5, 4).expect_err("decreasing viewport should fail");
assert_eq!(error, AlignmentQueryError::InvalidViewport);
}
#[test]
fn screen_budget_is_positive_and_bounded() {
assert_eq!(
AlignmentReduction::screen_budget(0, 1),
Err(AlignmentQueryError::InvalidScreenBudget)
);
let reduction = AlignmentReduction::screen_budget(u32::MAX, u16::MAX)
.expect("positive budget should be valid");
assert_eq!(reduction.max_points(), Some(MAX_SCREEN_QUERY_POINTS));
}
}