1use crate::{
4 Address, AddressErrorKind, AddressStatus, Geographic, IntoCsv, Io, PartialAddress,
5 PartialAddresses, SubaddressType, from_csv, to_csv,
6};
7use derive_more::{Deref, DerefMut};
8use indicatif::ParallelProgressIterator;
9use rayon::prelude::*;
10use serde::{Deserialize, Serialize};
11use tracing::info;
12
13#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
16pub enum Mismatch {
17 SubaddressType(String),
19 Floor(String),
21 Building(String),
23 Status(String),
25}
26
27impl Mismatch {
28 pub fn subaddress_type(from: Option<SubaddressType>, to: Option<SubaddressType>) -> Self {
31 let message = format!("{:?} not equal to {:?}", from, to);
32 Self::SubaddressType(message)
33 }
34
35 pub fn floor(from: Option<i64>, to: Option<i64>) -> Self {
37 let message = format!("{:?} not equal to {:?}", from, to);
38 Self::Floor(message)
39 }
40
41 pub fn building(from: Option<String>, to: Option<String>) -> Self {
43 let message = format!("{:?} not equal to {:?}", from, to);
44 Self::Building(message)
45 }
46
47 pub fn status(from: AddressStatus, to: AddressStatus) -> Self {
49 let message = format!("{} not equal to {}", from, to);
50 Self::Status(message)
51 }
52}
53
54#[derive(
56 Debug,
57 Default,
58 Clone,
59 PartialEq,
60 PartialOrd,
61 Eq,
62 Ord,
63 Hash,
64 Serialize,
65 Deserialize,
66 Deref,
67 DerefMut,
68)]
69pub struct Mismatches(Vec<Mismatch>);
70
71impl Mismatches {
72 pub fn new(fields: Vec<Mismatch>) -> Self {
74 Mismatches(fields)
75 }
76}
77
78#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
81pub struct AddressMatch {
82 pub coincident: bool,
85 pub mismatches: Option<Mismatches>,
88}
89
90impl AddressMatch {
91 pub fn new(coincident: bool, fields: Vec<Mismatch>) -> Self {
93 let mismatches = if fields.is_empty() {
94 None
95 } else {
96 Some(Mismatches::new(fields))
97 };
98 AddressMatch {
99 coincident,
100 mismatches,
101 }
102 }
103}
104
105#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
113pub enum MatchStatus {
114 Matching,
116 Divergent,
120 #[default]
121 Missing,
123}
124
125#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
128pub struct MatchRecord {
129 pub match_status: MatchStatus,
131 pub address_label: String,
133 pub subaddress_type: Option<String>,
136 pub floor: Option<String>,
138 pub building: Option<String>,
141 pub status: Option<String>,
144 pub longitude: f64,
147 pub latitude: f64,
150 pub id: uuid::Uuid,
152}
153
154impl Geographic for MatchRecord {
155 fn latitude(&self) -> f64 {
156 self.latitude
157 }
158
159 fn longitude(&self) -> f64 {
160 self.longitude
161 }
162}
163
164#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Serialize, Deserialize, Deref, DerefMut)]
166pub struct MatchRecords(Vec<MatchRecord>);
167
168impl MatchRecords {
169 pub fn new<T: Address + Geographic, U: Address + Geographic>(
175 self_address: &T,
176 other_addresses: &[U],
177 ) -> Self {
178 let address_label = self_address.label();
179 let latitude = self_address.latitude();
180 let longitude = self_address.longitude();
181 let id = uuid::Uuid::new_v4();
182
183 let mut match_record = Vec::new();
184
185 for address in other_addresses {
186 let address_match = self_address.coincident(address);
187 if address_match.coincident {
188 let mut subaddress_type = None;
189 let mut floor = None;
190 let mut building = None;
191 let mut status = None;
192 match address_match.mismatches {
193 None => match_record.push(MatchRecord {
194 match_status: MatchStatus::Matching,
195 address_label: address_label.clone(),
196 subaddress_type,
197 floor,
198 building,
199 status,
200 longitude,
201 latitude,
202 id,
203 }),
204 Some(mismatches) => {
205 for mismatch in mismatches.iter() {
206 match mismatch {
207 Mismatch::SubaddressType(message) => {
208 subaddress_type = Some(message.to_owned())
209 }
210 Mismatch::Floor(message) => floor = Some(message.to_owned()),
211 Mismatch::Building(message) => building = Some(message.to_owned()),
212 Mismatch::Status(message) => status = Some(message.to_owned()),
213 }
214 }
215 match_record.push(MatchRecord {
216 match_status: MatchStatus::Divergent,
217 address_label: address_label.clone(),
218 subaddress_type,
219 floor,
220 building,
221 status,
222 longitude,
223 latitude,
224 id,
225 })
226 }
227 }
228 }
229 }
230 if match_record.is_empty() {
231 match_record.push(MatchRecord {
232 match_status: MatchStatus::Missing,
233 address_label,
234 subaddress_type: None,
235 floor: None,
236 building: None,
237 status: None,
238 longitude,
239 latitude,
240 id,
241 })
242 }
243 MatchRecords(match_record)
244 }
245
246 pub fn compare<T: Address + Geographic + Send + Sync, U: Address + Geographic + Send + Sync>(
250 self_addresses: &[T],
251 other_addresses: &[U],
252 ) -> Self {
253 let style = indicatif::ProgressStyle::with_template(
254 "[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {'Comparing addresses.'}",
255 )
256 .unwrap();
257 let record = self_addresses
258 .par_iter()
259 .map(|address| MatchRecords::new(address, other_addresses))
260 .progress_with_style(style)
261 .collect::<Vec<MatchRecords>>();
262 let mut records = Vec::new();
263 for mut item in record {
264 records.append(&mut item);
265 }
266 MatchRecords(records)
267 }
268
269 pub fn filter(mut self, filter: &str) -> Self {
276 match filter {
277 "matching" => self.retain(|r| r.match_status == MatchStatus::Matching),
278 "missing" => self.retain(|r| r.match_status == MatchStatus::Missing),
279 "divergent" => self.retain(|r| r.match_status == MatchStatus::Divergent),
280 "subaddress" => self.retain(|r| {
281 r.match_status == MatchStatus::Divergent && r.subaddress_type.is_some()
282 }),
283 "floor" => {
284 self.retain(|r| r.match_status == MatchStatus::Divergent && r.floor.is_some())
285 }
286 "building" => {
287 self.retain(|r| r.match_status == MatchStatus::Divergent && r.building.is_some())
288 }
289 "status" => {
290 self.retain(|r| r.match_status == MatchStatus::Divergent && r.status.is_some())
291 }
292 _ => info!("Invalid filter provided."),
293 }
294 self
295 }
296}
297
298impl IntoCsv<MatchRecords> for MatchRecords {
299 fn from_csv<P: AsRef<std::path::Path>>(path: P) -> Result<Self, Io> {
300 let records = from_csv(path)?;
301 Ok(Self(records))
302 }
303
304 fn to_csv<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<(), AddressErrorKind> {
305 to_csv(&mut self.0, path.as_ref().into())
306 }
307}
308
309#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
311pub struct MatchPartialRecord {
312 match_status: MatchStatus,
314 address_label: String,
316 other_label: Option<String>,
318 longitude: Option<f64>,
320 latitude: Option<f64>,
322}
323
324impl MatchPartialRecord {
325 pub fn coincident<T: Address + Geographic>(
329 partial: &PartialAddress,
330 address: &T,
331 ) -> Option<MatchPartialRecord> {
332 let mut match_status = MatchStatus::Missing;
333
334 if let Some(value) = partial.address_number {
335 if value == address.number() {
336 match_status = MatchStatus::Matching;
337 }
338 }
339
340 if &partial.street_name_pre_directional != address.directional()
341 && match_status == MatchStatus::Matching
342 {
343 match_status = MatchStatus::Missing;
344 }
345
346 if let Some(value) = &partial.street_name {
347 if value != address.street_name() && match_status == MatchStatus::Matching {
348 match_status = MatchStatus::Missing;
349 }
350 }
351
352 if let Some(value) = partial.street_name_post_type() {
353 if let &Some(street_type) = address.street_type() {
354 if value != street_type && match_status == MatchStatus::Matching {
355 match_status = MatchStatus::Missing;
356 }
357 }
358 }
359
360 if &partial.subaddress_identifier() != address.subaddress_id()
361 && match_status == MatchStatus::Matching
362 {
363 match_status = MatchStatus::Divergent;
364 }
365
366 if address.subaddress_id().is_none()
367 && &partial.building() != address.building()
368 && match_status == MatchStatus::Matching
369 {
370 match_status = MatchStatus::Divergent;
371 }
372
373 if address.subaddress_id().is_none()
374 && address.building().is_none()
375 && &partial.floor() != address.floor()
376 && match_status == MatchStatus::Matching
377 {
378 match_status = MatchStatus::Divergent;
379 }
380
381 if match_status != MatchStatus::Missing {
382 Some(MatchPartialRecord {
383 match_status,
384 address_label: partial.label(),
385 other_label: Some(address.label()),
386 longitude: Some(address.longitude()),
387 latitude: Some(address.latitude()),
388 })
389 } else {
390 None
391 }
392 }
393
394 pub fn compare<T: Address + Geographic>(
397 partial: &PartialAddress,
398 addresses: &[T],
399 ) -> MatchPartialRecords {
400 let mut records = Vec::new();
401 for address in addresses {
402 let coincident = MatchPartialRecord::coincident(partial, address);
403 if let Some(record) = coincident {
404 records.push(record);
405 }
406 }
407 if records.is_empty() {
408 records.push(MatchPartialRecord {
409 match_status: MatchStatus::Missing,
410 address_label: partial.label(),
411 other_label: None,
412 longitude: None,
413 latitude: None,
414 })
415 }
416 let compared = MatchPartialRecords(records);
417 let matching = compared.clone().filter("matching");
418 if matching.is_empty() {
419 compared
420 } else {
421 matching
422 }
423 }
424
425 pub fn match_status(&self) -> MatchStatus {
427 self.match_status.to_owned()
428 }
429
430 pub fn address_label(&self) -> String {
432 self.address_label.to_owned()
433 }
434
435 pub fn other_label(&self) -> Option<String> {
437 self.other_label.clone()
438 }
439
440 pub fn longitude(&self) -> Option<f64> {
442 self.longitude
443 }
444
445 pub fn latitude(&self) -> Option<f64> {
447 self.latitude
448 }
449}
450
451#[derive(
453 Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize, Deref, DerefMut, derive_new::new,
454)]
455pub struct MatchPartialRecords(Vec<MatchPartialRecord>);
456
457impl MatchPartialRecords {
458 pub fn compare<T: Address + Geographic + Send + Sync>(
462 self_addresses: &PartialAddresses,
463 other_addresses: &[T],
464 ) -> Self {
465 let style = indicatif::ProgressStyle::with_template(
466 "[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {'Comparing addresses.'}",
467 )
468 .unwrap();
469 let record = self_addresses
470 .par_iter()
471 .map(|address| MatchPartialRecord::compare(address, other_addresses))
472 .progress_with_style(style)
473 .collect::<Vec<MatchPartialRecords>>();
474 let mut records = Vec::new();
475 for mut item in record {
476 records.append(&mut item);
477 }
478 MatchPartialRecords(records)
479 }
480
481 pub fn filter(mut self, filter: &str) -> Self {
486 match filter {
487 "missing" => self.retain(|r| r.match_status == MatchStatus::Missing),
488 "divergent" => self.retain(|r| r.match_status == MatchStatus::Divergent),
489 "matching" => self.retain(|r| r.match_status == MatchStatus::Matching),
490 _ => info!("Invalid filter provided."),
491 }
492 self
493 }
494}
495
496impl IntoCsv<MatchPartialRecords> for MatchPartialRecords {
497 fn from_csv<P: AsRef<std::path::Path>>(path: P) -> Result<Self, Io> {
498 let records = from_csv(path)?;
499 Ok(Self(records))
500 }
501
502 fn to_csv<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<(), AddressErrorKind> {
503 to_csv(&mut self.0, path.as_ref().into())
504 }
505}