Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions rust/bambam-core/src/model/bambam_typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@

use std::collections::HashMap;

use itertools::Itertools;
use routee_compass::plugin::output::OutputPluginError;
use routee_compass_core::model::cost::TraversalCost;
use serde_json::{json, Value};

use crate::model::{
bambam_field,
destination::{BinningConfig, DestinationPredicate},
destination::{BinningConfig, DestinationFilter, DestinationPredicate},
output_plugin::{
isochrone::{GeometryModelConfig, IsochroneAlgorithm, IsochroneOutputFormat},
opportunity::{OpportunityFormat, OpportunityOrientation},
Expand Down Expand Up @@ -116,6 +117,26 @@ impl<'a> BambamOutputRow<'a> {
) -> Result<(), OutputPluginError> {
set_field(self.0, bambam_field::OPPORTUNITY_TOTALS, totals)
}

/// grabs any destination filters from both two places:
/// - info section (set via the config file)
/// - request section (set via the search query)
pub fn get_destination_filter(&self) -> Result<Option<DestinationFilter>, OutputPluginError> {
let info = self.info_ref()?;
let info_filter = info.get_config_destination_predicates()?;
let req = self.request()?;
let req_filter: Option<Vec<DestinationPredicate>> =
get_field_opt(req.0, bambam_field::DESTINATION_FILTER)?;
match (info_filter, req_filter) {
(None, None) => Ok(None),
(None, Some(preds)) => Ok(Some(DestinationFilter(preds))),
(Some(preds), None) => Ok(Some(DestinationFilter(preds))),
(Some(preds_config), Some(preds_query)) => {
let preds = preds_config.into_iter().chain(preds_query).collect_vec();
Ok(Some(DestinationFilter(preds)))
}
}
}
}

// ─── request section ──────────────────────────────────────────────────────────
Expand Down Expand Up @@ -152,7 +173,8 @@ impl<'a> InfoSectionRef<'a> {
get_field_opt(self.0, bambam_field::BIN_RANGE)
}

pub fn get_destination_filter(
/// gets any destination predicates set on the BAMBAM config.
pub fn get_config_destination_predicates(
&self,
) -> Result<Option<Vec<DestinationPredicate>>, OutputPluginError> {
get_field_opt(self.0, bambam_field::DESTINATION_FILTER)
Expand Down Expand Up @@ -541,7 +563,7 @@ mod tests {
let info = row.info_ref().unwrap();
assert!(info.get_activity_types().unwrap().is_none());
assert!(info.get_bin_range().unwrap().is_none());
assert!(info.get_destination_filter().unwrap().is_none());
assert!(info.get_config_destination_predicates().unwrap().is_none());
assert!(info.get_opportunity_format().unwrap().is_none());
assert!(info.get_opportunity_orientation().unwrap().is_none());
assert!(info.get_geometry_model().unwrap().is_none());
Expand Down
25 changes: 13 additions & 12 deletions rust/bambam-core/src/model/destination/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ pub struct DestinationFilter(pub Vec<DestinationPredicate>);

/// additional modifiers to apply when collecting destinations for a bin.
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum DestinationPredicate {
/// only accept destinations where the provided feature, a boolean value,
/// is true (or, if negate == true, where the feature is false).
Boolean {
/// state variable feature to match
feature: String,
/// if true, invert the value stored at the feature
negate: bool,
negate: Option<bool>,
},
}

Expand All @@ -43,7 +44,7 @@ impl std::fmt::Display for DestinationPredicate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
DestinationPredicate::Boolean { feature, negate } => {
if !negate {
if !negate.unwrap_or_default() {
format!("{feature}=true")
} else {
format!("{feature}=false")
Expand All @@ -68,7 +69,7 @@ impl DestinationPredicate {
error: e,
}
})?;
Ok(variable != *negate) // if negate=false, variable should be true, and vice versa
Ok(variable != negate.unwrap_or_default()) // if negate=false, variable should be true, and vice versa
}
}
}
Expand Down Expand Up @@ -105,7 +106,7 @@ mod tests {
// When feature is true and negate is false, should return true
let predicate = DestinationPredicate::Boolean {
feature: "is_available".to_string(),
negate: false,
negate: Some(false),
};

let (state_model, state) = mock(&[("is_available", true)]);
Expand All @@ -120,7 +121,7 @@ mod tests {
// When feature is true and negate is true, should return false
let predicate = DestinationPredicate::Boolean {
feature: "is_available".to_string(),
negate: true,
negate: Some(true),
};

let (state_model, state) = mock(&[("is_available", true)]);
Expand All @@ -135,7 +136,7 @@ mod tests {
// When feature is false and negate is false, should return false
let predicate = DestinationPredicate::Boolean {
feature: "is_available".to_string(),
negate: false,
negate: Some(false),
};

let (state_model, state) = mock(&[("is_available", false)]);
Expand All @@ -150,7 +151,7 @@ mod tests {
// When feature is false and negate is true, should return true
let predicate = DestinationPredicate::Boolean {
feature: "is_available".to_string(),
negate: true,
negate: Some(true),
};

let (state_model, state) = mock(&[("is_available", false)]);
Expand All @@ -165,7 +166,7 @@ mod tests {
// Filter should only check the specified feature and ignore other variables
let predicate = DestinationPredicate::Boolean {
feature: "is_available".to_string(),
negate: false,
negate: Some(false),
};
let filter = DestinationFilter(vec![predicate]);

Expand All @@ -181,11 +182,11 @@ mod tests {
// Multiple predicates in a filter should all pass for the filter to return true
let pred1 = DestinationPredicate::Boolean {
feature: "is_available".to_string(),
negate: false,
negate: Some(false),
};
let pred2 = DestinationPredicate::Boolean {
feature: "is_active".to_string(),
negate: false,
negate: Some(false),
};
let filter = DestinationFilter(vec![pred1, pred2]);

Expand All @@ -201,11 +202,11 @@ mod tests {
// If one predicate fails, the entire filter should fail
let pred1 = DestinationPredicate::Boolean {
feature: "is_available".to_string(),
negate: false,
negate: Some(false),
};
let pred2 = DestinationPredicate::Boolean {
feature: "is_active".to_string(),
negate: false,
negate: Some(false),
};
let filter = DestinationFilter(vec![pred1, pred2]);

Expand Down
34 changes: 28 additions & 6 deletions rust/bambam-core/src/model/state/multimodal_state_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,14 @@ pub fn contains_leg(
state: &[StateVariable],
leg_idx: LegIdx,
state_model: &StateModel,
max_trip_legs: NonZeroU64,
) -> Result<bool, StateModelError> {
if leg_idx >= max_trip_legs.get() {
return Ok(false);
}
let name = fieldname::leg_mode_fieldname(leg_idx);
let label = state_model.get_custom_i64(state, &name)?;
Ok(label >= 0)
Ok(label > -1)
}

/// get the travel mode for a leg.
Expand All @@ -110,7 +114,10 @@ pub fn get_leg_mode_label(
state_model: &StateModel,
max_trip_legs: NonZeroU64,
) -> Result<Option<i64>, StateModelError> {
validate_leg_idx(leg_idx, max_trip_legs)?;
validate_leg_idx(leg_idx, max_trip_legs).map_err(|e| {
let msg = format!("while getting leg mode label, found requested leg idx is invalid: {e}");
StateModelError::RuntimeError(msg)
})?;
let name = fieldname::leg_mode_fieldname(leg_idx);
let label = state_model.get_custom_i64(state, &name)?;
if label < 0 {
Expand All @@ -129,7 +136,11 @@ pub fn get_existing_leg_mode<'a>(
max_trip_legs: NonZeroU64,
mode_to_state: &'a CategoricalStateMapping,
) -> Result<&'a str, StateModelError> {
let label_opt = get_leg_mode_label(state, leg_idx, state_model, max_trip_legs)?;
let label_opt =
get_leg_mode_label(state, leg_idx, state_model, max_trip_legs).map_err(|e| {
let msg = format!("while getting existing leg mode, error getting the mode label: {e}");
StateModelError::RuntimeError(msg)
})?;
match label_opt {
None => Err(StateModelError::RuntimeError(format!(
"Internal Error: get_leg_mode called on leg idx {leg_idx} but mode label is not set"
Expand Down Expand Up @@ -212,7 +223,13 @@ pub fn get_mode_label_sequence(
let mut labels: Vec<i64> = vec![];

for leg_idx in 0..max_trip_legs.get() {
let mode_label_opt = get_leg_mode_label(state, leg_idx, state_model, max_trip_legs)?;
let mode_label_opt = get_leg_mode_label(state, leg_idx, state_model, max_trip_legs)
.map_err(|e| {
let msg = format!(
"while getting model label sequence, failed to get mode label for index: {e}"
);
StateModelError::RuntimeError(msg)
})?;
match mode_label_opt {
None => break,
Some(mode_label) => {
Expand All @@ -234,12 +251,14 @@ pub fn get_mode_sequence(
) -> Result<Vec<String>, StateModelError> {
let mut modes: Vec<String> = vec![];
let mut leg_idx = 0;
while contains_leg(state, leg_idx, state_model)? {

while contains_leg(state, leg_idx, state_model, max_trip_legs)? {
let mode =
get_existing_leg_mode(state, leg_idx, state_model, max_trip_legs, mode_to_state)?;
modes.push(mode.to_string());
leg_idx += 1;
}

Ok(modes)
}

Expand All @@ -256,7 +275,10 @@ pub fn increment_active_leg_idx(
let next_leg_idx_u64 = match get_active_leg_idx(state, state_model)? {
Some(leg_idx) => {
let next = leg_idx + 1;
validate_leg_idx(next, max_trip_legs)?;
validate_leg_idx(next, max_trip_legs).map_err(|e| {
let msg = format!("while incrementing active leg idx, next index is invalid: {e}");
StateModelError::RuntimeError(msg)
})?;
next
}
None => 0,
Expand Down
1 change: 1 addition & 0 deletions rust/bambam-gtfs-flex/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ bambam-core = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true }
csv = { workspace = true }
env_logger = { workspace = true }
flate2 = { workspace = true }
geo = { workspace = true }
geo-types = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions rust/bambam-gtfs-flex/src/app/gtfs_flex_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub struct GtfsFLexCliArguments {
impl Cli {
/// runs the app
pub fn run(&self) -> Result<(), GtfsFlexError> {
env_logger::init();
match &self.command {
Commands::ProcessGtfsFlexFeeds(args) => {
let in_dir = Path::new(&args.input_directory);
Expand Down
Loading
Loading