1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//! The [`SourceBinder`] trait: maps fetched DataFrames to [`BoundSource`]s.
//!
//! Data crates implement this alongside their `SourceFactory` implementations
//! (from `synapse-data-source`). The application layer calls
//! `factory.fetch()` → `factory.normalize()` → `binder.bind()` to produce
//! a `BoundSource` for the ETL universe.
//!
//! # Example
//!
//! ```rust,ignore
//! use etl_unit::{BoundSource, SourceBinder, ColumnNameExt};
//!
//! struct ScadaBinder;
//!
//! impl SourceBinder for ScadaBinder {
//! fn bind(&self, df: polars::prelude::DataFrame) -> BoundSource {
//! BoundSource::new("scada", df)
//! .map("station_name".canonical(), "station_name".source())
//! .map("timestamp".canonical(), "observation_time".source())
//! .map("sump".canonical(), "sump".source())
//! // ...
//! }
//! }
//! ```
use DataFrame;
use crateBoundSource;
/// Maps a fetched + normalized DataFrame to a [`BoundSource`] for ETL processing.
///
/// Data crates implement this to specify how their physical columns map
/// to canonical ETL names, configure unpivots, and declare which ETL
/// units the source provides.
///
/// Each `SourceBinder` implementation is paired with a `SourceFactory`
/// (from `synapse-data-source`) and a [`SourceChain`]. The chain
/// fetches and concatenates data, then the binder maps it to the ETL
/// schema.
///
/// # Contract
///
/// The `bind()` method must:
/// 1. Create a `BoundSource` with a stable name
/// 2. Map all relevant columns (canonical ← source)
/// 3. Configure unpivots if needed (e.g., engine columns → engine_status)
/// 4. Declare which units it provides via `.provides()`