event_matcher/lib.rs
1//! # Event matcher
2//!
3//! A Rust library for matching event records following the
4//! [schema.org/Event](https://schema.org/Event) data model. The crate
5//! implements both **deterministic** and **probabilistic** matching
6//! algorithms.
7//!
8//! The library is **deterministic**, **stateless**, **panic-free** in
9//! library code, and **`Send + Sync`** so it can be used freely across
10//! threads.
11//!
12//! ## What it does
13//!
14//! Given two [`Event`] records — typically drawn from different source
15//! systems — the [`MatchingEngine`] decides whether they refer to the
16//! same event. The output is either a hard boolean (deterministic) or a
17//! scored [`MatchResult`] with a per-field [`matcher::MatchBreakdown`] so
18//! an auditor or downstream system can inspect the decision.
19//!
20//! ## Crate layout
21//!
22//! | Module | Purpose |
23//! |---|---|
24//! | [`models`] | Data types: [`Event`], [`EventBuilder`], [`Address`], [`Location`], [`EventCategory`], [`EventStatus`], [`EventAttendanceMode`], [`EventId`], [`EventIdScheme`]. |
25//! | [`normalizer`] | Text and ISO 8601 normalisation: names, postcodes, addresses, phonetic codes, date-times. |
26//! | [`scorer`] | String, geographic, and temporal similarity primitives (Jaro-Winkler, Levenshtein, Haversine, Gaussian decay). |
27//! | [`matcher`] | Orchestration: [`MatchingEngine`], [`MatchConfig`], [`MatchResult`]. |
28//! | [`error`] | Error enum [`MatchingError`] and [`Result`] alias. |
29//!
30//! ## Quick start — probabilistic match
31//!
32//! ```
33//! use event_matcher::{MatchingEngine, MatchConfig, Event};
34//!
35//! let a = Event::builder()
36//! .name("Glastonbury Festival 2024")
37//! .add_alternate_name("Glasto 2024")
38//! .start_date("2024-06-26T09:00:00Z")
39//! .end_date("2024-06-30T23:59:00Z")
40//! .build();
41//!
42//! let b = Event::builder()
43//! .name("Glasto 2024")
44//! .start_date("2024-06-26T09:15:00Z")
45//! .end_date("2024-06-30T23:59:00Z")
46//! .build();
47//!
48//! let engine = MatchingEngine::new(MatchConfig::default());
49//! let result = engine.match_events(&a, &b);
50//!
51//! assert!(result.is_match);
52//! ```
53//!
54//! ## Inspecting the per-field breakdown
55//!
56//! Every probabilistic match returns a per-field score so the decision is
57//! auditable end-to-end. Missing or unparseable fields score `None` rather
58//! than zero — they do not penalise the event.
59//!
60//! ```
61//! use event_matcher::{MatchingEngine, Event};
62//!
63//! let p = Event::builder()
64//! .name("RustConf 2024")
65//! .start_date("2024-09-10T09:00:00Z")
66//! .build();
67//! let q = p.clone();
68//!
69//! let result = MatchingEngine::default_config().match_events(&p, &q);
70//! assert!(result.breakdown.name_score.unwrap() > 0.99);
71//! assert!(result.breakdown.start_date_score.unwrap() > 0.99);
72//! ```
73//!
74//! ## Configuration presets
75//!
76//! Three configurations cover most use cases. Use [`MatchConfig::strict`]
77//! when callers must rely on the answer; use [`MatchConfig::lenient`]
78//! to triage large candidate sets where false negatives are worse than
79//! false positives.
80//!
81//! ```
82//! use event_matcher::{MatchConfig, MatchingEngine};
83//!
84//! let strict = MatchingEngine::new(MatchConfig::strict());
85//! let default = MatchingEngine::default_config();
86//! let lenient = MatchingEngine::new(MatchConfig::lenient());
87//!
88//! // All three engines share the same scoring pipeline; only the
89//! // threshold and a couple of weights differ.
90//! # let _ = (strict, default, lenient);
91//! ```
92//!
93//! ## Determinism and safety
94//!
95//! - **Deterministic.** Same inputs => same outputs. No clocks, no RNGs,
96//! no environment variables.
97//! - **No `unsafe`.** This crate forbids `unsafe` code.
98//! - **No IO.** The library does not log, read files, or open sockets.
99//! - **No panics** in library code paths; every fallible input returns
100//! `None` from a scorer or a [`MatchingError`].
101
102// Always start with high quality coding conventions.
103#![forbid(unsafe_code)]
104#![deny(missing_docs)]
105#![warn(clippy::pedantic)]
106
107pub mod error;
108pub mod matcher;
109pub mod models;
110pub mod normalizer;
111pub mod scorer;
112
113pub use error::{MatchingError, Result};
114pub use matcher::{Confidence, MatchBreakdown, MatchConfig, MatchResult, MatchingEngine};
115pub use models::{
116 Address, Event, EventAttendanceMode, EventBuilder, EventCategory, EventId, EventIdScheme,
117 EventStatus, Location,
118};
119pub use normalizer::{Normalizer, ParsedAddressLine};
120pub use scorer::{Scorer, SimilarityAlgorithm};