The snippet below sketches how an API consumer might compose the building blocks. It assumes the presence of application-specific scorer and solver implementations.
use geo::{Coord, Rect};
use wildside_core::{
InterestProfile, PointOfInterest, PoiStore, Scorer, SolveRequest, Solver,
SqlitePoiStore, Theme, TravelTimeProvider,
};
fn plan_visit(
store: &SqlitePoiStore,
scorer: &(impl Scorer + ?Sized),
solver: &(impl Solver + ?Sized),
travel_times: &(impl TravelTimeProvider + ?Sized),
) -> Result<(), Box<dyn std::error::Error>> {
let bbox = Rect::new(
Coord { x: -0.2, y: 51.45 },
Coord { x: -0.1, y: 51.55 },
);
let pois: Vec<PointOfInterest> = store.get_pois_in_bbox(&bbox).collect();
if pois.is_empty() {
println!("No points of interest found inside the bounding box");
return Ok(());
}
let travel_matrix = travel_times.get_travel_time_matrix(&pois)?;
let mut profile = InterestProfile::new();
profile.set_weight(Theme::History, 0.8);
profile.set_weight(Theme::Art, 0.6);
let request = SolveRequest {
start: Coord { x: -0.15, y: 51.5 },
end: None,
duration_minutes: 180,
interests: profile.clone(),
seed: 42,
max_nodes: None,
};
request.validate()?;
let response = solver.solve(&request)?;
let selection_scores: Vec<f32> = response
.route
.pois()
.iter()
.map(|poi| scorer.score(poi, &profile))
.collect();
let total_score: f32 = selection_scores.iter().sum();
println!("Route duration: {:?}", response.route.total_duration());
println!("Solver-reported score: {}", response.score);
println!("Recomputed score: {total_score}");
println!("Matrix size: {}×{}", travel_matrix.len(), travel_matrix[0].len());
Ok(())
}
This workflow highlights the responsibilities enforced by the implemented API: load POIs through a store, compute travel times, configure user interests, validate solver input, and rely on deterministic scoring and solving contracts for repeatable results.