Skip to content

Commit

Permalink
remove wrapping struct for location trust scores
Browse files Browse the repository at this point in the history
This means we don't need to unwrap anything, and the API is more
strictly dealing with data alone.

The location funtions are only public to the crate to prevent someone
from attempting to put together they're own coverage points calculator.

They must go through the calculate_coverage_points function.
  • Loading branch information
michaeldjeffrey committed Jun 7, 2024
1 parent 6e8fd68 commit 3c696c3
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 95 deletions.
28 changes: 6 additions & 22 deletions coverage_point_calculator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,7 @@
//! [mobile-poc-blog]: https://docs.helium.com/mobile/proof-of-coverage
//! [boosted-hex-restriction]: https://github.com/helium/oracles/pull/808
//!
use crate::{
hexes::CoveredHexes,
location::{LocationTrust, LocationTrustScores},
speedtest::Speedtest,
};
use crate::{hexes::CoveredHexes, location::LocationTrust, speedtest::Speedtest};
use coverage_map::{RankedCoverage, SignalLevel};
use hexes::CoveredHex;
use rust_decimal::{Decimal, RoundingStrategy};
Expand Down Expand Up @@ -113,11 +109,6 @@ pub struct CoveragePoints {
pub covered_hexes: Vec<CoveredHex>,
}

/// Entry point into the Coverage Point Calculator.
///
/// All of the necessary checks for rewardability are done during construction.
/// If you can successfully construct a [RewardableRadio], it can be passed to
/// [calculate_coverage_points].
pub fn calculate_coverage_points(
radio_type: RadioType,
radio_threshold: RadioThreshold,
Expand All @@ -126,19 +117,16 @@ pub fn calculate_coverage_points(
ranked_coverage: Vec<RankedCoverage>,
) -> Result<CoveragePoints> {
let location_trust_scores =
LocationTrustScores::new(radio_type, location_trust_scores, &ranked_coverage);
location::clean_trust_scores(location_trust_scores, &ranked_coverage);
let location_trust_multiplier = location::multiplier(radio_type, &location_trust_scores);

let boosted_hex_status = BoostedHexStatus::new(
&radio_type,
location_trust_scores.multiplier,
&radio_threshold,
);
let boosted_hex_status =
BoostedHexStatus::new(&radio_type, location_trust_multiplier, &radio_threshold);

let covered_hexes = CoveredHexes::new(radio_type, ranked_coverage, boosted_hex_status)?;
let speedtests = Speedtests::new(speedtests);

let hex_coverage_points = covered_hexes.calculated_coverage_points();
let location_trust_multiplier = location_trust_scores.multiplier;
let speedtest_multiplier = speedtests.multiplier;

let coverage_points = hex_coverage_points * location_trust_multiplier * speedtest_multiplier;
Expand All @@ -153,11 +141,7 @@ pub fn calculate_coverage_points(
radio_threshold,
boosted_hex_eligibility: boosted_hex_status,
speedtests: speedtests.speedtests.iter().map(|s| s.clone()).collect(),
location_trust_scores: location_trust_scores
.trust_scores
.iter()
.map(|s| s.clone())
.collect(),
location_trust_scores,
covered_hexes: covered_hexes.hexes.iter().map(|h| h.clone()).collect(),
})
}
Expand Down
115 changes: 42 additions & 73 deletions coverage_point_calculator/src/location.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,48 +11,40 @@ const RESTRICTIVE_MAX_DISTANCE: Meters = 50;

type Meters = u32;

#[derive(Debug, Clone, PartialEq)]
pub struct LocationTrustScores {
pub multiplier: Decimal,
pub trust_scores: Vec<LocationTrust>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct LocationTrust {
pub meters_to_asserted: Meters,
pub trust_score: Decimal,
}

impl LocationTrustScores {
pub fn new(
radio_type: RadioType,
trust_scores: Vec<LocationTrust>,
ranked_coverage: &[RankedCoverage],
) -> Self {
let any_boosted_hexes = ranked_coverage.iter().any(|hex| hex.boosted.is_some());

let cleaned_scores = if any_boosted_hexes {
trust_scores
.into_iter()
.map(LocationTrust::into_boosted)
.collect()
} else {
trust_scores
};

// CBRS radios are always trusted because they have internal GPS
let multiplier = if radio_type.is_cbrs() {
dec!(1)
} else {
multiplier(&cleaned_scores)
};
pub(crate) fn clean_trust_scores(
trust_scores: Vec<LocationTrust>,
ranked_coverage: &[RankedCoverage],
) -> Vec<LocationTrust> {
let any_boosted_hexes = ranked_coverage.iter().any(|hex| hex.boosted.is_some());

if any_boosted_hexes {
trust_scores
.into_iter()
.map(LocationTrust::into_boosted)
.collect()
} else {
trust_scores
}
}

Self {
multiplier,
trust_scores: cleaned_scores,
}
pub(crate) fn multiplier(radio_type: RadioType, trust_scores: &[LocationTrust]) -> Decimal {
// CBRS radios are always trusted because they have internal GPS
if radio_type.is_cbrs() {
return dec!(1);
}

let count = Decimal::from(trust_scores.len());
let scores: Decimal = trust_scores.iter().map(|l| l.trust_score).sum();

scores / count
}

impl LocationTrust {
fn into_boosted(self) -> Self {
// Cap multipliers to 0.25x when a radio covers _any_ boosted hex
Expand All @@ -70,13 +62,6 @@ impl LocationTrust {
}
}

fn multiplier(trust_scores: &[LocationTrust]) -> Decimal {
let count = Decimal::from(trust_scores.len());
let scores: Decimal = trust_scores.iter().map(|l| l.trust_score).sum();

scores / count
}

#[cfg(test)]
mod tests {
use std::num::NonZeroU32;
Expand All @@ -98,15 +83,11 @@ mod tests {
trust_score: dec!(0.5),
},
];
let boosted = LocationTrustScores::new(
RadioType::IndoorWifi,
trust_scores.clone(),
&boosted_ranked_coverage(),
);
let unboosted = LocationTrustScores::new(RadioType::IndoorWifi, trust_scores, &[]);

assert_eq!(dec!(0.5), boosted.multiplier);
assert_eq!(dec!(0.5), unboosted.multiplier);
let boosted = clean_trust_scores(trust_scores.clone(), &boosted_ranked_coverage());
let unboosted = clean_trust_scores(trust_scores, &[]);

assert_eq!(dec!(0.5), multiplier(RadioType::IndoorWifi, &boosted));
assert_eq!(dec!(0.5), multiplier(RadioType::IndoorWifi, &unboosted));
}

#[test]
Expand All @@ -122,15 +103,11 @@ mod tests {
},
];

let boosted = LocationTrustScores::new(
RadioType::IndoorWifi,
trust_scores.clone(),
&boosted_ranked_coverage(),
);
let unboosted = LocationTrustScores::new(RadioType::IndoorWifi, trust_scores, &[]);
let boosted = clean_trust_scores(trust_scores.clone(), &boosted_ranked_coverage());
let unboosted = clean_trust_scores(trust_scores, &[]);

assert_eq!(dec!(0.25), boosted.multiplier);
assert_eq!(dec!(0.5), unboosted.multiplier);
assert_eq!(dec!(0.25), multiplier(RadioType::IndoorWifi, &boosted));
assert_eq!(dec!(0.5), multiplier(RadioType::IndoorWifi, &unboosted));
}

#[test]
Expand All @@ -146,18 +123,14 @@ mod tests {
},
];

let boosted = LocationTrustScores::new(
RadioType::IndoorWifi,
trust_scores.clone(),
&boosted_ranked_coverage(),
);
let unboosted = LocationTrustScores::new(RadioType::IndoorWifi, trust_scores, &[]);
let boosted = clean_trust_scores(trust_scores.clone(), &boosted_ranked_coverage());
let unboosted = clean_trust_scores(trust_scores, &[]);

// location past distance limit trust score is degraded
let degraded_mult = (dec!(0.5) + dec!(0.25)) / dec!(2);
assert_eq!(degraded_mult, boosted.multiplier);
assert_eq!(degraded_mult, multiplier(RadioType::IndoorWifi, &boosted));
// location past distance limit trust score is untouched
assert_eq!(dec!(0.5), unboosted.multiplier);
assert_eq!(dec!(0.5), multiplier(RadioType::IndoorWifi, &unboosted));
}

#[test]
Expand All @@ -170,15 +143,11 @@ mod tests {
trust_score: dec!(0),
}];

let boosted = LocationTrustScores::new(
RadioType::IndoorCbrs,
trust_scores.clone(),
&boosted_ranked_coverage(),
);
let unboosted = LocationTrustScores::new(RadioType::IndoorCbrs, trust_scores, &[]);
let boosted = clean_trust_scores(trust_scores.clone(), &boosted_ranked_coverage());
let unboosted = clean_trust_scores(trust_scores, &[]);

assert_eq!(dec!(1), boosted.multiplier);
assert_eq!(dec!(1), unboosted.multiplier);
assert_eq!(dec!(1), multiplier(RadioType::IndoorCbrs, &boosted));
assert_eq!(dec!(1), multiplier(RadioType::IndoorCbrs, &unboosted));
}

fn boosted_ranked_coverage() -> Vec<RankedCoverage> {
Expand Down

0 comments on commit 3c696c3

Please sign in to comment.