Code walkthrough · branch cary/ingest-pipelines

Nine customers, one loaderfilesystem-common and the landing-bucket bronze buildout

57 commits · 211 files · +51,746 lines
of which 41,871 (81%) are uv.lock
review weight ≈ 2,500 lines · 2026-06 → 07
01

The diff at a glance

+51,746 lines looks daunting. It isn't: 81% is lockfiles, and most of the rest is documentation, generated config, and nine copies of the same CI workflow. Toggle the view — the “real change” scale is what a reviewer actually faces.

scaled to 2,900 lines — lockfiles hidden
uv.lock lockfiles10 files41,871
docs & DATA_NOTES9 × DATA_NOTES + 3 shared docs + REBUILD2,298
CI deploy workflows9 near-identical files1,568
bronze defs.yaml × 9~420 lines are comments1,240
library codefilesystem-common src1,075
packaging boilerplateDockerfile / pyproject / dbt config × 91,202
library unit tests11 files, 1:1 with core modules795
dbt silver × 226 templated dedup models656
defs generatorone-shot scaffolder, not runtime511
per-customer Pythonmneye + heatoneye sync/sensors/predicates470
project testspredicate tests only60
novel logic tests declarative config docs cookie-cutter lockfiles
Where the review cost actually lives~1,075 lines of shared library code, ~470 lines of per-customer Python, and one representative of each templated family (one defs.yaml per format family, one dbt model, one CI workflow). Everything else is prose, lockfiles, or copies. Total: about 2,500 lines of the 51,746 added.
02

The shape of the problem

Nine customers whose EHR/PM exports drop delimited files into the shared SFTP landing bucket — the non-EDI remainder after HL7/X12 feeds were carved off. Before this branch, none of them were ingested; the landing bucket (external, expiring) was the only copy.

The feeds share almost nothing byte-for-byte. Three delimiters (including the 3-byte ^|^ token), four encodings, three quoting regimes, NUL-byte sentinels, ragged rows, and five different drop-folder layouts. Worse, the inventory that described them — hand-transcribed once — was wrong four times: every ^|^ feed was labeled “pipe”, and jewishboard’s UTF-16 was labeled “mixed pipe/tab” (its tab bytes 0x09 0x00 read as ASCII). Three cp1252 feeds were declared utf-8 and would have mojibake’d silently.

That failure mode — a human transcribes format facts, the facts are wrong, bronze corrupts silently — is what the library is designed against. Every byte-level fact below is now either sniffed from the bytes, pinned explicitly, or asserted as a drift guard (§05).

customersourcedelimiterquotingtrue encodingragged rowslayout wrinkle
heatoneyenextgen|per-table "cp1252 — correctedreconstruct + quarantineFlow/ vs Archive/ subdirs; 2 ragged feeds
mneyenextgen^|^nonecp1252 — correctedclean daily feed, 78 landed_at= partitions
ushpnextgen|" RFC4180utf-8 (genuine)pad (1 row)3 practice subdirs unioned by */ mask
middletownmedicalecw^|^noneutf-8 (ASCII)padmanually-named drop folders; 3.3 GB backfill
sunlifeecw^|^noneutf-8 (ASCII)first customer migrated to the rebuilt core
ccpexperity|noneutf-8 (ASCII)daily drop is a rolling open-balance window
paaphygen," + embedded \nutf-8 + cp1252 × 2 — correctedper-file mixed encodings; glob traps
jewishboardnetsmartTABnoneutf-16 LE (BOM) — correctedas-of dates in filenames drift vs landed_at
southsoundradiologyris|" minimalcp1252 — correctedpad~35 intra-day full snapshots per weekday

“corrected” = the probe contradicted the declared/inventory label; all five would have been silent-corruption bugs. Every fact here was confirmed by PHI-authorized probes (structure/counts only) and is recorded per-customer in DATA_NOTES.md.

03

The data flow

One shape serves all nine customers. The landing bucket is treated as external and ephemeral; bronze raw — an append-only parquet log — is the durable replay source of truth. Everything downstream is a pure function of it.

EHR / PM export

NextGen, eCW, Experity, Netsmart, RIS, Phygen — nightly/weekly SFTP drops.

landing bucket

external & ephemeral — can expire or stop; never the replay source.

s3://…-datalake-landing/customer/<prefix>

FilesystemSourceComponent

one dlt asset per table, from defs.yaml

list files → incremental cursor
(modification_date: each file once)

per file:
assert / sniff format
→ transcode to utf-8
→ strip NUL sentinels
→ ragged policy
→ DuckDB parse, all-VARCHAR
→ + provenance columns

bronze · raw

append-only parquet log of every drop ever landed. Immutable; the durable replay source.

…/bronze/<source>/raw/<table>/ · all-VARCHAR
+ _source_path, _s3_modified

bronze · clean

dbt-duckdb dedup: latest row per PK, still source-shaped.

mneye + heatoneye today

bronze · frontier

current state + in-band tombstones (planned; dispatches on deletes).

sync → MSSQL

DuckDB bulk-swap into shadow Import*_dagster tables.

mneye + heatoneye today

silver (deferred)

source entities → our canonical entities; typed, conformed.

data movement external push external / will change bronze (source domain) silver (our domain)dashed = planned / deferred
Keys mirror pathsEvery asset is keyed customer/layer/source/step/tablemneye/bronze/nextgen/raw/charges materializes to s3://…/bronze/nextgen/raw/charges/, and the dbt clean step is the sibling …/nextgen/clean/charges. Key and S3 layout can never disagree, and consumers depend on the step (raw vs clean vs frontier), never inferring trust from the layer color.
04

One file through the reader

The whole byte path is one dlt transformer wrapping one tested function. Per file: read the bytes, resolve/check the format, decode to all-VARCHAR Arrow, attach provenance. No second code path, no live-only branch.

shared/filesystem-common/src/filesystem_common/reader.pythe one transformer body — fixed-format and sniffing readers differ only in resolve_fmt
def _reader(
    resolve_fmt, extractions, capture_s3_modified: bool,
    sink: "RejectSink | None" = None, asserts: Optional[dict] = None,
):
    """The one dlt transformer body: per file, resolve a :class:`CsvFormat` (fixed
    or sniffed), decode through the tested core, and optionally attach the S3
    ``modification_date`` as ``_s3_modified``. When a ``sink`` is given, quarantined
    rows are recorded into it (per file) instead of vanishing silently. ``asserts``
    is the **fixed-mode** drift guard (the sniff path asserts inside ``resolve_fmt``)."""

    @dlt.transformer
    def _read(items):
        for item in items:
            data = item.read_bytes()
            if asserts:
                check_asserts(data[:65536], asserts)
            path = _path_of(item)
            rejects: list[str] | None = [] if sink is not None else None
            table = process_file(data, path, resolve_fmt(data), extractions, rejects=rejects)
            if sink is not None and rejects:
                sink.record(path, rejects)
            if capture_s3_modified:
                table = apply_file_metadata(
                    table, {"modification_date": item.get("modification_date")}, _S3_MODIFIED
                )
            yield table

    return _read

Inside process_file → decode(), the file goes through four moves. The first two are unconditional; the third is the per-feed policy dial; the fourth is DuckDB (chosen because it natively parses the 3-byte ^|^delimiter, which pyarrow.csv cannot).

shared/filesystem-common/src/filesystem_common/core/decode.pytranscode → scrub → ragged policy → DuckDB parse, always all-VARCHAR
def decode(data: bytes, fmt: CsvFormat, *, rejects: list | None = None):
    """Parse ``data`` into a pyarrow Table of all-VARCHAR columns named from the
    header. ``fmt`` carries the delimiter (possibly multi-byte), quote char (or
    None), source encoding, NUL-scrub flag, and ragged-row policy. For
    ``ragged="quarantine"``, when a ``rejects`` list is given the raw rejected lines
    are appended to it (visible, not silently dropped)."""
    import os
    import tempfile

    import duckdb

    data = _to_utf8(data, fmt.encoding)
    if fmt.strip_nul and b"\x00" in data:
        data = data.replace(b"\x00", b"")

    if fmt.ragged == "reconstruct":
        text = data.decode("utf-8")
        header = text.split("\n", 1)[0].rstrip("\r")
        width = header.count(fmt.delimiter) + 1
        data = _reconstruct(text, fmt.delimiter, width).encode("utf-8")
    elif fmt.ragged == "quarantine":
        data, rejected = _quarantine_filter(data, fmt)
        if rejects is not None:
            rejects.extend(rejected)

    # "error": no flags -> DuckDB raises on a ragged row. "reconstruct"/"quarantine":
    # the data was made rectangular above, so a plain parse suffices.
    kwargs = _csv_kwargs(fmt)

    tmp = tempfile.NamedTemporaryFile(suffix=".csv", delete=False)
    try:
        tmp.write(data)
        tmp.close()
        conn = duckdb.connect()
        try:
            return conn.read_csv(tmp.name, **kwargs).to_arrow_table()
        finally:
            conn.close()
    finally:
        os.unlink(tmp.name)

The ragged-row policy dial

Rows whose field count disagrees with the header are where feeds silently corrupt. CsvFormat.ragged makes the policy explicit per feed — and two of the four policies were driven in by one customer (heatoneye) whose feed had both failure shapes at once.

default  A ragged row is a fault: DuckDB raises, the asset run fails, a human looks. Right for feeds the probe showed are rectangular (mneye, sunlife, ccp, jewishboard, paa) — a ragged row there means the feed changed shape.

visit_id|cpt|dos|amount
70001|76700|2026-05-01|120.00
70002|76856|2026-05-02        ← 3 fields vs header 4 → CSV Error, run fails

ssr · ushp · middletown  Short rows are NULL-filled to header width (DuckDB’s null_padding). Right when the probe shows rare, benign short rows — trailing columns simply absent.

visit_id|cpt|dos|amount
70001|76700|2026-05-01|120.00
70002|76856|2026-05-02        → 70002|76856|2026-05-02|NULL

heatoneye appointments  An unquoted free-text field contains a newline, splitting one logical record across physical lines. Reconstruct accumulates lines until the delimiter count reaches header width, flattening the spurious newline to a space.

appt_id|status|note|amount
70003|booked|patient called
will reschedule|40.00              → 70003|booked|patient called will reschedule|40.00
core/decode.py — _reconstructrejoin to header width; an over-count is ambiguous and stays quarantine's job
def _reconstruct(text: str, delimiter: str, width: int) -> str:
    """Rejoin records split by an unquoted embedded newline: accumulate physical
    lines until the delimiter count reaches the header width, then that's one
    logical record. The spurious newline is flattened to a space. (Recovers the
    embedded-newline case; an *unescaped delimiter* over-count is ambiguous and is
    left to the ``quarantine`` policy.)"""
    lines = [ln.rstrip("\r") for ln in text.split("\n")]
    if lines and lines[-1] == "":
        lines.pop()
    if not lines:
        return text
    need = width - 1  # delimiters per complete record
    out = [lines[0]]
    buf = ""
    for line in lines[1:]:
        buf = line if buf == "" else f"{buf} {line}"
        if buf.count(delimiter) >= need:
            out.append(buf)
            buf = ""
    if buf:
        out.append(buf)  # trailing incomplete record -> DuckDB will flag it
    return "\n".join(out) + "\n"

heatoneye patient_insurance  An unescaped delimiter inside a field over-counts — reassembling it would be a guess. Quarantine keeps the rectangular rows, sidelines the bad ones visibly: a per-run RejectSink surfaces counts + a bounded sample as Dagster asset metadata, and the run logs a warning. Never silently dropped.

member_id|plan|copay|amount
88010|PPO|20.00|150.00
88011|PPO|copay|waived|10.00      ← 5 fields → rejected, surfaced in run metadata
core/decode.py — _quarantine_filtergood rows parse; raw rejected lines flow to the RejectSink → asset metadata
def _quarantine_filter(data: bytes, fmt: CsvFormat) -> tuple[bytes, list[str]]:
    """Split off rows whose field count != the header's. Returns (clean bytes with
    header + good rows, list of rejected raw lines) — so DuckDB then parses a
    rectangular file and the bad rows are *surfaced*, never silently dropped."""
    lines = data.decode("utf-8").split("\n")
    out: list[str] = []
    rejects: list[str] = []
    width: int | None = None
    for raw in lines:
        s = raw.rstrip("\r")
        if width is None:  # header
            width = _field_count(s, fmt)
            out.append(s)
            continue
        if s == "":
            continue
        if _field_count(s, fmt) == width:
            out.append(s)
        else:
            rejects.append(s)
    return ("\n".join(out) + "\n").encode("utf-8"), rejects
05

The library, module by module

filesystem-common was rebuilt test-first after eight customers were built against the original — the redesign encodes what the buildout kept tripping on. A pure, dlt-free core (90 unit tests, no S3 needed) with a thin Dagster/dlt adapter on top.

modulelocresponsibilityreplaces
core/format.py65CsvFormat — one frozen value object for every byte fact + the override() cascadesix positional booleans threaded through six signatures
core/sniff.py75sniff(head) → CsvFormat: BOM/encoding, delimiter, quote, NUL from the byteshand-transcribed inventory labels (wrong 4×)
core/decode.py133the one decoder: bytes + CsvFormat → all-VARCHAR Arrow (§04)two divergent CSV paths, one never unit-tested
core/spec.py82TableSpec/SourceSpec — pk / semantics / deletes / soft_delete as dataprose defs.yaml headers usable by nothing
core/provenance.py73declared Extractions → real columns; _source_path, _s3_modifieda magic filename column re-regexed in every silver model
core/select.py30narrow_to_latest — pin a key=* glob to the newest partitionthe all/latest/incremental enum
core/pipeline.py61process_file (decode+provenance) · reconcile_format / check_asserts (drift)a parse path first exercised in production
reader.py125the dlt transformer + RejectSink + config→spec mapping (dlt, no dagster)
component.py392the Dagster component: YAML → assets, validation, key scheme, quarantine metadatathe legacy FilesystemDltComponent (sgp still on it)

spec.py — prose became data

The defining change. Primary keys, snapshot/delta/rolling semantics, the soft-delete column, how deletes manifest — knowledge that used to live in a 100-line YAML comment header now travels as structured fields that silver reads (dedup key, tombstone filter), docs read, and bronze validates. Note deletes is deliberately orthogonal to semantics: a feed can be a physical snapshot whose rows age out, so absence ≠ deletion (§07).

shared/filesystem-common/src/filesystem_common/core/spec.pythe three enums + TableSpec; in_band requires a soft_delete column, enforced at import
_SELECTIONS = ("all", "latest_partition", "incremental")
_SEMANTICS = ("snapshot", "delta", "rolling")
# How a deletion in the source becomes a tombstone in the frontier. Orthogonal to
# ``semantics`` (the physical shape): a full *snapshot* can still be ``in_band`` if
# rows age out of a window rather than being deleted (SSR), so absence is unreliable.
#   - ``none``    don't derive tombstones (default — never manufacture deletes).
#   - ``absence`` present-in-an-earlier-snapshot-but-absent-from-the-latest = deleted.
#                 ONLY sound for a *bounded* full snapshot (a true master/dimension
#                 export); the frontier must gate it behind a completeness fence.
#   - ``in_band`` the source carries the delete itself (a flag/status column, named
#                 by ``soft_delete``); absence means "aged out", NOT deleted.
_DELETES = ("none", "absence", "in_band")


@dataclass
class TableSpec:
    """One logical table. ``file_mask`` is a glob; the remaining fields are the
    structured metadata that silver consumes."""

    name: str
    file_mask: str
    selection: str = "incremental"
    selection_key: str = "landed_at"
    pk: list[str] = field(default_factory=list)
    semantics: Optional[str] = None
    soft_delete: Optional[str] = None
    deletes: str = "none"
    provenance: list[Extraction] = field(default_factory=list)

format.py — omission means inherit

One frozen value object per feed, with a customer→table cascade. The subtlety: override() applies only the keys passed, so quote=None — a genuinely unquoted feed, which five customers are — survives the cascade instead of being confused with “inherit”. A TDD round-trip caught exactly that overload in the original design.

shared/filesystem-common/src/filesystem_common/core/format.pyevery byte fact in one place; adding a capability is one field, not a six-file edit
@dataclass(frozen=True)
class CsvFormat:
    """How to turn a delimited file's bytes into rows. Bronze always lands every
    column as VARCHAR (typing is a silver concern), so there is deliberately no
    column/dtype machinery here — only the bytes-to-rows facts.

    delimiter: str = ","
    quote: Optional[str] = '"'
    encoding: str = "utf-8"
    header: bool = True
    strip_nul: bool = False
    ragged: RaggedPolicy = "error"

    def override(self, **fields) -> "CsvFormat":
        """Return a copy with the provided fields replaced. **Omission means
        inherit** — a per-table override passes only the keys it changes — while a
        passed value (including ``None`` for ``quote``, i.e. "no quoting", or
        ``False`` for a bool) is applied. This avoids overloading ``None`` to mean
        both "inherit" and "no quoting", which a quote-less feed genuinely needs."""
        known = {f.name for f in dataclasses.fields(self)}
        unknown = set(fields) - known
        if unknown:
            raise ValueError(f"unknown CsvFormat field(s): {sorted(unknown)}")
        return replace(self, **fields)

sniff.py + pipeline.py — trust the bytes, pin the facts

Born from the inventory being wrong on every ^|^ feed and on UTF-16. The sniffer reads a file’s head and infers the byte facts; config asserts pin any fact so a feed that silently changes shape fails loudly instead of corrupting bronze. The two compose into three modes: sniff & parse, declare & parse, and — the default all nine customers use — declare & parse, sniff only to check (a per-file drift guard that never trusts the sniff to parse). One rule: only assert sniff-visible facts. cp1252 has no BOM and sniffs as utf-8, so it is pinned(encoding: cp1252), never asserted.

core/sniff.py — sniff()multi-byte tokens first: a ^|^ header also contains bare |
def sniff(head: bytes) -> CsvFormat:
    """Infer a :class:`CsvFormat` from a file's leading bytes (header + a few rows).

    - encoding: from the BOM (UTF-16 LE/BE, UTF-8 BOM), else UTF-8.
    - delimiter: a multi-byte token (``^|^``) if present in the header, else the
      single-char candidate that occurs most in the header line (tab/pipe/semicolon/
      comma), defaulting to comma.
    - quote: ``"`` if a quote byte appears anywhere in the head, else ``None``.
    - strip_nul: True if a ``U+0000`` (post-decode) field sentinel appears.
    """
    encoding, text = _decode_head(head)
    header = _first_line(text)

    delimiter = ","
    for token in _MULTI_DELIMS:
        if token in header:
            delimiter = token
            break
    else:
        counts = {d: header.count(d) for d in _SINGLE_DELIMS}
        best = max(_SINGLE_DELIMS, key=lambda d: counts[d])
        if counts[best] > 0:
            delimiter = best

    quote = '"' if '"' in text else None
    strip_nul = "\x00" in text

    return CsvFormat(
        delimiter=delimiter,
        quote=quote,
        encoding=encoding,
        strip_nul=strip_nul,
    )
core/pipeline.py — reconcile / assertFormatAssertionError = the feed drifted; the run fails before a byte lands
def _assert_facts(fmt: CsvFormat, asserts: dict | None) -> None:
    for key, expected in (asserts or {}).items():
        actual = getattr(fmt, key)
        if actual != expected:
            raise FormatAssertionError(
                f"format drift: {key} is {actual!r} but config asserts {expected!r}"
            )


def reconcile_format(
    head: bytes, *, override: dict | None = None, asserts: dict | None = None
) -> CsvFormat:
    """Infer the format from ``head``, apply config ``override`` (None values
    inherit the sniff), then verify every key in ``asserts`` matches — raising
    :class:`FormatAssertionError` on mismatch so feed drift fails loudly instead of
    silently corrupting bronze."""
    fmt = sniff(head).override(**(override or {}))
    _assert_facts(fmt, asserts)
    return fmt


def check_asserts(head: bytes, asserts: dict | None) -> None:
    """Drift guard for a **fixed-format** feed: sniff ``head`` and verify the asserted
    facts hold, raising :class:`FormatAssertionError` on mismatch. The sniff is used
    *only* to check, never to parse — so a feed can keep an explicitly-declared format
    (e.g. cp1252, which has no BOM and sniffs as utf-8) yet still fail loudly if the
    bytes drift. Corollary: only assert facts the sniff can actually see (delimiter,
    quote, BOM-based encodings) — asserting an unsniffable encoding would false-alarm."""
    if asserts:
        _assert_facts(sniff(head), asserts)

reader.py — quarantine you can see

The legacy failure mode for bad rows was silence. The rebuilt reader records every rejected line into a per-run sink, bounded so a pathological file can’t blow up run metadata; the component merges the counts + sample onto the asset’s materialization (§04).

shared/filesystem-common/src/filesystem_common/reader.pyone sink per asset run; the reader records into it per file
class RejectSink:
    """Accumulates quarantined (malformed) rows across a load so the component can
    surface them as Dagster metadata, rather than ``ragged="quarantine"`` dropping
    them with no trace. One sink per asset run; the reader records into it per file.

    ``total``/``files`` are the row and file counts; ``sample`` holds up to
    ``sample_limit`` ``"<path>: <raw line>"`` strings for a glance at what was
    rejected (bounded so a pathological file can't blow up run metadata)."""

    def __init__(self, sample_limit: int = 20):
        self.sample_limit = sample_limit
        self.total = 0
        self.files = 0
        self.sample: list[str] = []

    def record(self, path: str, rejected: list[str]) -> None:
        if not rejected:
            return
        self.total += len(rejected)
        self.files += 1
        room = self.sample_limit - len(self.sample)
        if room > 0:
            self.sample.extend(f"{path}: {line}" for line in rejected[:room])

component.py — fail at definition-build, not mid-run

The component maps Resolvable YAML onto the core and builds one dlt asset per table. Two details worth review attention. First, _validate(): the run path dispatches on string equality, so before this guard a typo like selection: incrmental would have silently flipped an append-only raw log to replace — destroying the replay history invariant. Validation routes the YAML through the same build_source_spec seam the core tests exercise, so config and spec can’t drift apart.

component.py — _validate()misreadable config fails while the code location loads, not mid-run
    def _validate(self) -> None:
        """Fail at definition-build time on config the run path would misread.

        The run path dispatches on string equality (``selection == "incremental"``
        decides append vs replace), so an unvalidated typo like ``incrmental`` would
        silently flip an append-only raw log to ``replace``. The enums (``selection``,
        ``semantics``/``deletes``, ``in_band`` requires ``soft_delete``, duplicate
        table names) are enforced by mapping the config through the same
        ``build_source_spec`` seam the core tests exercise, so YAML and
        :class:`~filesystem_common.core.spec.TableSpec` can't drift apart silently."""
        known_facts = {f.name for f in dataclass_fields(CsvFormat)}
        unknown = set(self.format.asserts) - known_facts
        if unknown:
            raise ValueError(
                f"format.asserts keys {sorted(unknown)} are not CsvFormat facts "
                f"({sorted(known_facts)})"
            )
        self.format.to_core()  # validates ragged + non-empty delimiter

Second, the seams to dlt: each table becomes a filesystem | reader source with an incremental cursor on S3 modification_date (each file ingested exactly once), and the translation stamps the 5-part key that mirrors the S3 layout.

component.py — _make_source()the incremental hint is the whole 'each file once' mechanism
    def _make_source(
        self, t: TableConfig, *, mask: Optional[str] = None, sink: Optional[RejectSink] = None
    ):
        creds = _aws_credentials(self.credentials)
        incremental = t.selection == "incremental"
        uri, name = self.uri, t.name
        file_mask = t.file_mask if mask is None else mask
        reader = self._build_reader(t, sink)

        @dlt.source(name=name)
        def _src():
            files = filesystem(bucket_url=uri, file_glob=file_mask, credentials=creds)
            if incremental:
                files.apply_hints(incremental=dlt.sources.incremental("modification_date"))
            return (files | reader).with_name(name)

        return _src()
component.py — _make_translation()customer/layer/source/step/table — key ≡ S3 path
def _make_translation(customer_id: str, source: str, layer: str):
    """Key each bronze table ``customer/layer/source/raw/table`` (e.g.
    ``mneye/bronze/nextgen/raw/charges``) — layer-first with an explicit ``raw``
    step, mirroring the S3 layout ``{customer-bucket}/{layer}/{source}/raw/{table}``.
    Group ``{customer}_{layer}_{source}`` (the dbt cleanup writes the sibling
    ``clean`` step under the same ``{layer}/{source}/``); ``customer``/``source``/
    ``layer``/``step`` are tags."""

    def _translate(spec: AssetSpec, data: DltResourceTranslatorData) -> AssetSpec:
        table = spec.key.path[-1]
        return spec.replace_attributes(
            key=AssetKey([customer_id, layer, source, "raw", table]),
            group_name=f"{customer_id}_{layer}_{source}",
            tags={"customer": customer_id, "source": source, "layer": layer, "step": "raw"},
        )

    return _translate

And on the run path, execute() is where quarantine becomes observable — the sink is complete once dlt finishes extraction, so its counts are merged onto the MaterializeResult:

component.py — execute()append vs replace is decided by selection; rejects surface as metadata + a warning
        creds = self.credentials or None
        mask = self._resolve_mask(table_config, creds)
        disposition = "append" if table_config.selection == "incremental" else "replace"
        sink = RejectSink()
        # dlt fully extracts (running our reader, populating the sink) before the
        # resource yields its MaterializeResult, so the sink is complete by then —
        # merge the quarantine counts onto the result instead of dropping them.
        for result in dlt_pipeline_resource.run(
            context=context,
            dlt_source=self._make_source(table_config, mask=mask, sink=sink),
            dlt_pipeline=self._make_pipeline(table_config.name),
            loader_file_format="parquet",
            write_disposition=disposition,
        ):
            if sink.total and isinstance(result, dg.MaterializeResult):
                result = result._replace(
                    metadata={**(result.metadata or {}), **_reject_metadata(sink)}
                )
            yield result
        if sink.total:
            context.log.warning(
                "%s: quarantined %d malformed row(s) across %d file(s); "
                "see asset metadata for a sample",
                table_config.name, sink.total, sink.files,
            )
06

Bronze principles

The per-customer decisions in §07 all trace back to four principles (pipelines/docs/bronze-principles.md) — worth internalizing before reading any defs.yaml, because they explain the choices that look odd at first (reference tables appended daily, 35 intra-day snapshots all kept, no SCD-2 anywhere).

P1

History is replayable

Raw is an immutable append-only log of every drop. Current state is a pure, re-runnable function of it — replay = filter raw to _s3_modified ≤ Tand re-derive. No SCD-2, no table format; history lives in the log.

P2

Landing doesn’t count

The SFTP landing bucket is external and ephemeral — it can expire or stop. The moment a drop lands, raw captures it; replayability is defined against raw, never against landing.

P3

Never manufacture deletes

Where the source carries deletes (a flag column), the frontier exposes tombstones. Where it doesn’t, absence in a rolling/windowed feed means aged out, not deleted. The per-table deletes field exists precisely so this is never guessed.

P4

Bronze is the source’s domain

raw → clean → frontier are all faithful to what the source asserts — source column names, deliberately all-VARCHAR. Mapping onto ourcanonical entities is the bronze→silver boundary.

The boundary is a domain boundary, not a cleanliness boundaryEvery column in bronze answers to the source’s contract; every column in silver answers to ours. “How clean is it” is a spectrum and a weak seam; “whose semantics govern this column” is crisp and testable. (This is the Data Vault raw-vault / business-vault split wearing medallion vocabulary — the frontier is a source-domain current-state, like a PIT view, and stays in bronze on purpose.)

Three frontier reductions, picked off the structured fields

Given a complete raw log, deriving “current state + tombstones” is one of three reductions — and the choice is data, dispatched on each table’s semantics + deletes:

shapereductioncustomerswhy
Asnapshot-reduce — latest row per PK; tombstone from the in-band flagmneye · heatoneye · ushp · middletown · sunlife · paa · ssreach drop is a full snapshot; absence = aged out, ignored
Bwindow-accumulate — latest per PK across all history; a key keeps its last-known state after leaving the windowccpopen-balance AR/ERA window churns both ways; a closed row isn’t deleted
Cledger-accumulate — union all drops, dedup by transaction key, keep reversals; current = the netjewishboardreversals are sign-flipped new rows linked via JOIN_TO_* — overwrite would erase them

Cross-cutting fact: not one of the nine customers uses deletes: absence. Every feed either rolls or carries deletes in-band — which is exactly why the field is explicit.

07

The equivalence classes

Nine customers, five families. Within a family the defs.yaml files are near-clones — review one, skim the rest. Across families, each one pushed a different capability into the library; the tabs tell that story.

Family A — the “MDClarity standard export” (NextGen)

heatoneye · mneye · ushp13 tables eachdaily landed_at= drops8 rolling + 5 referencefrontier A · snapshot-reduce

The founding family. heatoneye was the pilot; mneye and ushp share its 13-table schema, the rolling-window transaction tables, and the NextGen soft-delete scheme (delete_ind on operational tables, DeleteInd on masters — casing carries meaning).

The instructive part is where the siblings diverge: heatoneye is bare-| with two ragged feeds, mneye is ^|^ and cp1252, ushp is genuinely UTF-8 with three practice subdirs per drop. A family shares semantics, not bytes — which is why format is per-customer config and why the probe re-checks every sibling instead of cloning (“do not blindly clone an encoding across the family”).

projects/mneye/…/bronze/defs.yaml^|^ + cp1252 pin + a glob that must exclude a sibling table
# mneye — NextGen Family A, but with the ^|^ token and a cp1252 fix
format:
  delimiter: "^|^"
  quote: ""
  encoding: cp1252       # FIX: probe found windows-1252 bytes; utf-8 mis-decodes them
  strip_nul: true
  asserts:
    delimiter: "^|^"     # drift guard: sniff-visible; a feed that changes shape fails loudly
tables:
  - name: appointments
    file_mask: "landing/source=sftp/landed_at=*/ExportAppointments*"
    selection: incremental
    pk: [appt_id]
    semantics: rolling
    deletes: in_band         # also cancel_ind/appt_status/workflow_status
    soft_delete: delete_ind
  - name: patient
    # [!I] excludes ExportPatientInsurance* (a bare ExportPatient* glob swallows
    # those files too and silently widens this table's schema with foreign rows)
    file_mask: "landing/source=sftp/landed_at=*/ExportPatient[!I]*"
    selection: incremental
    pk: [PersonNumber]       # best-effort (the clean model's dedup key)
    semantics: rolling
    deletes: none            # no flag
projects/heatoneye/…/bronze/defs.yamlthe two ragged feeds that drove reconstruct + quarantine
# heatoneye — same NextGen schema as mneye, but bare "|" and two ragged feeds
- name: adjustments
  file_mask: "landing/source=sftp/landed_at=*/TempExportAdjustments*"
  selection: incremental
  format_override: { quote: "\"" }
  pk: [RowID]                # best-effort
  semantics: rolling
  deletes: none              # no delete flag; TransactionStatus candidate (values unknown)
- name: appointments
  file_mask: "landing/source=sftp/landed_at=*/Flow/ExportAppointments*"
  selection: incremental
  format_override: { ragged: reconstruct }   # unquoted embedded newlines -> rejoin to header width
  pk: [appt_id]              # best-effort (the clean model's dedup key)
  semantics: rolling
  deletes: in_band
  soft_delete: delete_ind    # Y/N; also cancel_ind / appt_status / workflow_status
- name: patient_insurance
  file_mask: "landing/source=sftp/landed_at=*/Flow/ExportPatientInsurance*"
  selection: incremental
  format_override: { ragged: quarantine }    # unescaped-pipe over-count -> visible rejects
  pk: [PersonNumber, person_payer_id]  # best-effort composite — confirm vs source DDL
  semantics: rolling
  deletes: in_band
  soft_delete: delete_ind
projects/ushp/…/bronze/defs.yamlgenuine utf-8 sibling; */ unions three practice subdirs, _source_path records which
# ushp — third NextGen sibling: genuine UTF-8 (do NOT clone cp1252 across the
# family) and THREE practice subdirs per drop, unioned by a */ wildcard.
format:
  delimiter: "|"
  quote: "\""            # RFC4180 quoting — quoted embedded newlines parse cleanly
  encoding: utf-8        # genuine multibyte utf-8 (U+2022 bullet in PatientInsurance)
  strip_nul: true
  asserts:
    delimiter: "|"
tables:
  - name: adjustments
    # */ unions the MDClarity / Stuart101124 / USHP126051 practice subdirs;
    # _source_path records which subdir each row came from, for a silver split
    file_mask: "landing/source=sftp/landed_at=*/*/TempExportAdjustments.txt"
    selection: incremental
    pk: [RowID]               # confirmed by probe (1.000 unique, 0 null)
    semantics: rolling
    deletes: none             # only TransactionStatus (candidate, unconfirmed)
  • Drove into the library: ragged: reconstruct and ragged: quarantine + the visible RejectSink (heatoneye had both failure shapes at once).
  • The cp1252 pin-don’t-assert rule (mneye/heatoneye sniff as utf-8 — no BOM).
  • The reference-table reversal: latest_partition/replace kept only the newest snapshot and silently discarded dimension history — all five reference tables now append, and the dbt clean step reduces to the latest landed_at.
  • Glob discipline: ExportPatient[!I]*, because a bare glob swallows ExportPatientInsurance and silently widens the patient table with foreign rows.

Family B — eClinicalWorks (the ^|^ lineage)

sunlife · middletownmedical16 tables eachdeleteFlag scheme, casing driftsfrontier A · snapshot-reduce

Same export lineage, opposite landing discipline. sunlife is the tidy one — clean daily landed_at= partitions — and became the first customer migrated onto the rebuilt core. middletown is the chaotic one: drop folders are manually named (MDClarity_5-Junlanded June 9; future-dated empty placeholders exist), three folder layouts coexist, a legacy bare-| baseline sits next to the ^|^ feeds, and one drop was an 18M-row / 3.3 GB historical backfill.

middletown is why _s3_modified is captured on every row: S3 server write time is strictly ordered across drops when folder names lie. It is also the accepted memory ceiling — strip_nul must mutate the bytes, which forces whole-file buffering, so replaying the backfill needs task memory sized to the largest file.

projects/middletownmedical/…/bronze/defs.yamlrecursive ** mask anchored to keep a bare-| baseline out; asserts as the backstop
# middletownmedical — eClinicalWorks: manually-named drop folders (not sortable),
# three coexisting folder layouts, and a bare-"|" legacy baseline to keep OUT.
format:
  delimiter: "^|^"
  quote: ""
  encoding: utf-8
  strip_nul: true
  asserts:
    delimiter: "^|^"   # drift guard: a bare-"|" file (e.g. the excluded legacy
                       # baseline) fails loudly instead of parsing as one column
tables:
  - name: charge_items
    # ** matches both the flat MDClarity_<D>-<Mon>/ and nested NN-Jun/MDClarity_NN-Jun/
    # layouts; anchoring on MDClarity_* keeps the bare-pipe MDClarity/ baseline out
    file_mask: "source=ecw/**/MDClarity_*/db_nymidm_ExportChargeItems.txt"
    selection: incremental
    pk: [ItemID]
    semantics: rolling        # rows leave and re-enter the window
    deletes: in_band          # absence != deletion; tombstone from the flag
    soft_delete: deleteFlag
projects/sunlife/…/bronze/defs.yamlthe tidy sibling — note visit_codes' inverse-polarity flag
# sunlife — same ECW ^|^ lineage as middletown; the first customer migrated
# onto the rebuilt core. Watch the per-table delete-flag casing drift.
format:
  delimiter: "^|^"
  quote: ""           # unquoted feed
  encoding: utf-8     # genuine utf-8 (pure ASCII per probe) — explicit, not defaulted
  strip_nul: true
  asserts:
    delimiter: "^|^"
tables:
  - name: charge_items
    file_mask: "landing/source=sftp/landed_at=*/ExportChargeItems.txt"
    selection: incremental
    pk: [ItemID]
    semantics: rolling
    deletes: in_band
    soft_delete: deleteFlag
  - name: visit_codes
    file_mask: "landing/source=sftp/landed_at=*/ExportVisitCodes.txt"
    selection: incremental
    pk: [CodeID]
    semantics: snapshot
    deletes: in_band
    soft_delete: Active      # INVERSE polarity: Active=N means inactive/deleted
  • Drove into the library: _s3_modified as a first-class, per-row ordering column (never trust a folder name).
  • The asserts-as-backstop pattern: any bare-| file reaching a ^|^ reader parses as one column with zero delimiter tokens — ragged never fires — so the assert is the only loud failure available.
  • null_padding (ragged: pad) wiring, and the documented buffering ceiling.
  • Soft-delete casing as data: deleteFlag / DeleteFlag / delflag / inactive / inverse-polarity Active — five spellings across two customers, each recorded in soft_delete.

Family C — Experity AR/ERA (the rolling window)

ccp17 tablesdaily open-balance windowfrontier B · window-accumulate

The semantics teacher. ccp’s daily Export*Table.txt files look like full snapshots but are a churning open-balance window: rows leave when balances close and re-enter when they reopen, skewing dropped rows toward zero-balance. So disappearance never means deletion, deletes: absence is never valid, and the frontier must keep a key’s last-known state after it leaves the window — the window-accumulate reduction exists because of this feed.

It also set the PK-honesty convention: eight tables lead with Practice, a tenant discriminator, not a row id. Rather than key on a guess, pk is left unset and the real composite is documented in DATA_NOTES.md for confirmation against source DDL.

projects/ccp/…/bronze/defs.yamlrolling ≠ snapshot even when it looks like one; pk left unset rather than guessed
# ccp — Experity AR/ERA: each daily drop is a ROLLING OPEN-BALANCE WINDOW, not a
# snapshot. Rows leave as balances close — disappearance is NEVER a deletion.
format:
  delimiter: "|"
  quote: ""
  encoding: utf-8
  strip_nul: true
  asserts:
    delimiter: "|"
tables:
  # Practice-led composite PK (Practice + per-row id) — pk left UNSET, not guessed
  - name: ar_detail
    file_mask: "landing/source=sftp/landed_at=*/ExportARDetailTable.txt"
    selection: incremental
    semantics: rolling        # open-balance window; disappearance != deletion
    deletes: in_band          # reversals signaled in-band, not physically removed
    soft_delete: Reverse_Flag # also ManualReverse
  - name: insurance
    file_mask: "landing/source=sftp/landed_at=*/ExportInsuranceTable.txt"
    selection: incremental
    pk: [InsuranceId]
    semantics: snapshot       # reference
    deletes: none
  • Drove into the library: the semantics / deletes orthogonality — physical shape and delete channel are independent axes.
  • The window-accumulate frontier reduction (B).
  • The “don’t guess PKs” convention: absent pk is a statement, not an omission.

Family D — Netsmart (UTF-16, the ledger)

jewishboard9 tables · weeklyutf-16 LE + BOM · TABfrontier C · ledger-accumulate

The format odd-one-out. Every file is UTF-16 LE with a BOM — the inventory’s “mixed pipe/tab” label was its tab bytes (0x09 0x00) read as ASCII. The encoding: knob transcodes to UTF-8 first; the BOM makes utf-16 sniff-visible, so this is the one feed that assertsits encoding as a drift guard.

Semantically it’s two populations: billing tables are a rolling recent-activity window; dimension tables are true weekly deltas(only new/changed rows — no weekly file is ever complete). And deletion is simply not a concept in the feed: reversals arrive as newsign-flipped rows linked via JOIN_TO_* keys. Overwriting latest-per-PK would erase the reversal — hence the ledger-accumulate frontier, where “current” is the net of the ledger.

projects/jewishboard/…/bronze/defs.yamldelta dims + rolling billing; deletes: none everywhere — reversals are rows
# jewishboard — Netsmart, the encoding odd-one-out: UTF-16 LE with a BOM on every
# file. The inventory's "mixed pipe/tab" label was UTF-16 tab bytes read as ASCII.
format:
  delimiter: "\t"
  quote: ""
  encoding: utf-16    # CORRECT AS-IS: UTF-16 LE + BOM; do not change
  strip_nul: true     # strips the all-U+0000 sentinels left after transcoding
  asserts:
    delimiter: "\t"   # drift guards: both sniff-visible (the BOM pins utf-16),
    encoding: utf-16  # so a feed that changes shape or encoding fails loudly
tables:
  - name: patient_data
    file_mask: "landing/source=sftp/landed_at=*/JB_MD_C_Patient_Data*.txt"
    selection: incremental
    pk: [patientid]                  # CORRECTED: episode_number is NOT unique
    semantics: delta                 # dims ship only new/changed rows each week
    deletes: none
  - name: billing_tx_history
    file_mask: "landing/source=sftp/landed_at=*/JB_MD_C_billing_tx_history*.txt"
    selection: incremental
    # reversal signal (NOT a delete): service_status_value. Reversals are NEW ROWS
    # linked via JOIN_TO_* keys — "deletion" is not a concept in this feed.
    semantics: rolling
    deletes: none
  • Drove into the library: the encoding knob itself (utf-16 → utf-8 transcode before parsing), plus post-transcode NUL stripping.
  • semantics: delta — the third snapshot shape, forcing accumulate-forever dims.
  • A probe-driven PK correction: patientid, not the repeating episode_number the schema suggests.

Family E — scheduling snapshots (the future that ages out)

southsoundradiology · paafuture-only full snapshotsrows age out as dates passfrontier A · leaky in-band cancels

Both feeds snapshot the future schedule, so every row eventually leaves the file simply by the date passing — the purest illustration of why absence must never be a tombstone. SSR lands a fresh full snapshot every ~30 minutes (~35/day); raw ingests all of them — replayability lives in raw, reduction lives in the frontier, and no reduce-at-extraction shortcut is taken.

paa adds the messiest format story: ten heterogeneous comma-CSVs where two files are secretly cp1252 inside a utf-8 customer — the case that justifies format_override being per-table. Its cancel signal is leaky (some future cases vanish with no cancelled status), which still doesn’t license absence-tombstoning — it becomes a soft “absent-from-window” hint in the frontier, nothing more.

projects/southsoundradiology/…/bronze/defs.yamlsnapshot + in_band together: physically a snapshot, but absence ≠ deletion
# southsoundradiology — ~35 intra-day FULL snapshots per weekday. Raw ingests ALL
# of them (replayability); the frontier reduces to latest-state-per-VisitNumber.
format:
  delimiter: "|"
  quote: "\""
  encoding: cp1252       # PINNED: probe found windows-1252; unsniffable (no BOM)
  strip_nul: true
  ragged: pad            # rare short rows -> NULL-fill to header width
  asserts:
    delimiter: "|"       # | IS sniff-visible, so it's asserted as a drift guard
tables:
  - name: scheduled_appointments
    file_mask: "landing/source=sftp/landed_at=*/Scheduled_Appointments_*.csv"
    selection: incremental
    pk: [VisitNumber]
    semantics: snapshot       # physically a full snapshot...
    deletes: in_band          # ...but rows age out, so absence != deletion
    soft_delete: CancelledOn  # the real tombstone signal (+ Status='CANCELLED')
projects/paa/…/bronze/defs.yamlper-table encoding override + start-anchored globs
# paa — 10 heterogeneous scheduling CSVs, per-FILE mixed encodings, and glob
# ambiguity handled with start-anchored masks.
format:
  delimiter: ","
  quote: "\""          # RFC4180: AnFutSched* carry quoted fields with EMBEDDED NEWLINES
  encoding: utf-8
  strip_nul: true
  asserts:
    delimiter: ","
tables:
  - name: an_fut_sched
    # AnFutSched[0-9]* matches ONLY the base feed — a bare AnFutSched* would also
    # swallow AnFutSchedEndo / AnFutSchedMobile into this table
    file_mask: "landing/source=sftp/landed_at=*/AnFutSched[0-9]*.csv"
    selection: incremental
    semantics: snapshot     # future-only full snapshot; rows age out
    deletes: none           # cancel signal UNCONFIRMED for this feed (follow-up)
    format_override: { encoding: cp1252 }  # THIS file is windows-1252 (É/á/é)
  - name: fut_sched
    file_mask: "landing/source=sftp/landed_at=*/FutSched[0-9]*.csv"
    selection: incremental
    pk: [CaseID]
    semantics: snapshot
    deletes: in_band        # cancellations show in CaseStatus — but LEAKY: some
    soft_delete: CaseStatus # future cases vanish with no prior cancelled status
  • Drove into the library: the pin-vs-assert distinction itself — SSR’s cp1252 was the original unsniffable-encoding find.
  • Per-table format_override (paa’s two cp1252 files inside a utf-8 customer).
  • ragged: pad at customer level (SSR’s rare short rows).
  • Start-anchored glob masks (AnFutSched[0-9]*) — verified against fsspec’s glob translation, because the unanchored form swallows two sibling feeds.

Capability × customer

The library grew exactly one capability per real failure shape. Fir = set at customer level; violet = reached via per-table override.

capabilityheatoneyemneyeushpmiddletownsunlifeccpjewishbdssrpaawhy it exists
^|^ multi-byte delimiterwhy DuckDB, not pyarrow.csv
unquoted feed (quote: "")None ≠ inherit — the override() lesson
RFC4180 quotingquoted embedded newlines parse cleanly
cp1252 pinned (unsniffable)no BOM → pin, never assert
utf-16 transcodethe encoding knob
NUL-sentinel stripforces whole-file buffering
ragged: padNULL-fill short rows
ragged: reconstructunquoted embedded newlines
ragged: quarantine → RejectSinkvisible rejects in run metadata
asserts drift guardjewishboard also asserts encoding
per-table format_overridequote / ragged / encoding
glob discipline ([!I] · [0-9] · ** · */)masks that can silently swallow siblings
customer-level per-table override not used
08

The full pipelines: mneye & heatoneye

Seven customers stop at bronze raw (silver deferred pending feed-health checks and owner confirmations). Two run the whole chain — and are the template the other seven will clone when their silver is greenlit.

raw × 13

dlt append-log per table

mneye/bronze/nextgen/raw/*

clean × 13

dbt-duckdb dedup + row filter

…/nextgen/clean/* · dt= partitioned

sync × 13

DuckDB → MSSQL rebuild into shadow tables

dbo.Import*_dagster

pipeline_complete

sensor fires on 13/13, same batch; writes the run-complete marker

legacy product

MDClarity tasks consume Import* tables

clean: the dedup contract, in one model

All 26 dbt models are this same 19-line shape — review one, trust the template. It is the consumer of everything bronze promised: the append-log (dedup latest-per-PK), the provenance column (landed_at parsed from _source_path), and the S3 write time as tiebreak (_s3_modified, because a re-drop day holds two files and there is no row-level watermark).

projects/mneye/dbt/models/clean_patient.sqllatest-per-PK by landed_at, tiebroken by _s3_modified
-- depends_on: {{ source('mneye', 'patient') }}
{{ config(location=cleaned_location()) }}
-- Rolling-window feed: bronze is an append-log of daily drops (records age out and
-- return). Keep the latest row per PK by landed_at (parsed from the _source_path
-- provenance column), tiebroken by _s3_modified (S3 write time) so a re-drop or
-- multi-file day picks the newest file deterministically; there is no row-level
-- modified timestamp to use instead.
WITH ranked AS (
    SELECT *,
        ROW_NUMBER() OVER (
            PARTITION BY PersonNumber
            ORDER BY regexp_extract(_source_path, 'landed_at=(\d{4}-\d{2}-\d{2})', 1) DESC,
                _s3_modified DESC
        ) AS _rn
    FROM {{ mneye_bronze_parquet('patient') }}
)
SELECT * EXCLUDE (_rn, _source_path, _s3_modified)
FROM ranked
WHERE _rn = 1 AND NOT ({{ bad_row_predicate('patient') }})
projects/mneye/dbt/macros/*.sqlthe raw-parquet locator + the row-filter escape hatch
{# {entity: precompiled SQL predicate}, set on the environment by MneyeCleanupComponent. #}
{% macro bad_row_predicate(entity) -%}
{{- fromjson(env_var('MNEYE_PREDICATES', '{}')).get(entity, 'FALSE') -}}
{%- endmacro %}

{% macro mneye_bronze_parquet(entity) -%}
read_parquet('{{ var("customer_root") }}/bronze/{{ var("source") }}/raw/{{ entity }}/*.parquet', union_by_name=true)
{%- endmacro %}
projects/mneye/dbt/models/_sources.ymleach dbt source pinned to the real 5-part raw asset key
version: 2

# asset_key pins each dbt source to the 5-part dlt RAW bronze key
# (customer/bronze/source/raw/entity, e.g. mneye/bronze/nextgen/raw/charges) so the
# clean models depend on the real raw bronze asset rather than a 2-part orphan. Keep
# in sync with the FilesystemSourceComponent key scheme in defs/bronze/defs.yaml
# (layer-first, mirrors the S3 layout; the clean step is the sibling .../clean/...).
sources:
  - name: mneye
    tables:
      - name: adjustments
        meta: { dagster: { asset_key: ["mneye", "bronze", "nextgen", "raw", "adjustments"] } }
      - name: appointments

predicates: config-driven row filters, compiled to SQL

Bad-row rules (test rows, known-junk locations) live in a YAML file on S3 — editable without a deploy — and compile to a SQL predicate injected into every clean model via bad_row_predicate(). Missing file means filter nothing; the compiler is the one piece of per-customer Python with unit tests.

projects/mneye/src/mneye/predicates.pyfilter_rules.yaml → SQL; absent file → keep everything
def compile_filters(filters: dict[str, list[dict]]) -> dict[str, str]:
    result = {}
    for entity, entries in filters.items():
        if not entries:
            continue
        parts = [_compile_entry(e) for e in entries]
        if len(parts) == 1:
            result[entity] = parts[0]
        else:
            result[entity] = "(" + " OR ".join(parts) + ")"
    return result


def load_filter_rules(datalake_root: str) -> dict[str, str]:
    path = f"{datalake_root}/config/filter_rules.yaml"
    fs = s3fs.S3FileSystem()
    try:
        with fs.open(path, "r") as f:
            doc = yaml.safe_load(f) or {}
    except FileNotFoundError:
        # No rules file yet → no rows filtered. The bad_row_predicate macro
        # defaults each entity to FALSE (keep everything) on a missing key.
        return {}
    filters = doc.get("filters", {})
    return compile_filters(filters)

sync: shadow tables until cutover

Each clean parquet is bulk-loaded into MSSQL through sqlserver_common.rebuild_table — into Import*_dagstershadow tables while the legacy pipeline still owns the live ones. Cutover is a one-line change (drop the suffix, swap rebuild → reload) once the parallel run is trusted.

projects/mneye/src/mneye/defs/sync/assets.pyclean parquet → dbo.Import*_dagster; _dlt_* internals dropped at the boundary
def _sync_asset(entity: str, import_table: str):
    # Shadow target while testing; cutover drops "_dagster" and swaps
    # rebuild_table -> reload_table into the live Import* tables (and confirms
    # which entities the product actually consumes).
    target = f"{import_table}_dagster"

    @dg.asset(
        name=entity,
        key_prefix=["mneye", "sync", "nextgen"],
        group_name="mneye_sync_nextgen",
        deps=[dg.AssetKey(["mneye", "bronze", "nextgen", "clean", entity])],
        metadata={"dagster/table_name": f"dbo.{target}"},
    )
    def _asset(
        context: AssetExecutionContext,
        mssql_target: MssqlTargetResource,
        customer_secrets: SecretsManagerSecretsResource,
    ) -> dg.MaterializeResult:
        cleaned = _cleaned_path(entity, run_date(context))
        # COLUMNS(...) drops dlt's internal _dlt_* columns from the load.
        source_sql = (
            f"SELECT COLUMNS(c -> NOT starts_with(c, '_dlt_')) "
            f"FROM read_parquet('{cleaned}', hive_partitioning=false)"
        )
        user, password = fetch_mssql_login(customer_secrets)
        conn = _connect_s3()
        try:
            rows = rebuild_table(
                conn,
                connection_string=mssql_target.duckdb_connection_string(user, password),
                source_sql=source_sql,
                target_table=target,
                log=context.log,
            )
        finally:
            conn.close()
        return dg.MaterializeResult(
            metadata={
                "dagster/table_name": dg.MetadataValue.text(f"dbo.{target}"),
                "dagster/row_count": dg.MetadataValue.int(rows),
            }
        )

    return _asset

the completion gate: one sensor, one batch invariant

The legacy product must never see a half-synced day. The sensor fires pipeline_complete only when all 13 sync assets have materialized and their runs carry the same date_segment tag — a cross-run batch-coherence check, not just a count.

projects/mneye/src/mneye/defs/orchestration/sensors.py13/13 present + one date_segment, else skip with a reason
@dg.multi_asset_sensor(
    monitored_assets=list(_SYNC_TARGET_KEYS),
    job=pipeline_complete_job,
    name="mneye_pipeline_complete_sensor",
    minimum_interval_seconds=30,
    default_status=dg.DefaultSensorStatus.RUNNING,
)
def mneye_pipeline_complete_sensor(
    context: dg.MultiAssetSensorEvaluationContext,
):
    records = context.latest_materialization_records_by_key()

    for key in _SYNC_TARGET_KEYS:
        if records.get(key) is None:
            return dg.SkipReason(f"waiting on {key.to_user_string()}")

    expected_batch: str | None = None
    for key in _SYNC_TARGET_KEYS:
        record = records[key]
        run = context.instance.get_run_by_id(record.run_id)
        batch = run.tags.get("date_segment") if run else None
        if batch is None:
            return dg.SkipReason(
                f"{key.to_user_string()} run {record.run_id} has no date_segment tag"
            )
        if expected_batch is None:
            expected_batch = batch
        elif batch != expected_batch:
            return dg.SkipReason(
                f"sync records span two date_segments: "
                f"{expected_batch} vs {batch}"
            )

    context.advance_all_cursors()
    return dg.RunRequest(tags={"date_segment": expected_batch})
09

Reading order

The 57 commits tell a deliberate story — build against reality first, then rebuild the abstraction from what reality taught. Reviewing in that order makes every library decision legible.

phasecommitswhat happened
1 · Pilotheatoneye + first library extensionsheatoneye proves the shape: component + per-table strategies from a 4-day PHI profile; load strategies, formats and dlt incremental land in filesystem-common.
2 · Buildoutmneye, then 7 customers overnightOne customer per commit — middletown, ushp, sunlife, ccp, jewishboard, paa, ssr — each preceded by a PHI-authorized probe whose findings are the defs.yaml header + DATA_NOTES.md.
3 · Rebuildfilesystem-common core, TDDWith eight real feeds as the spec, the loader is redesigned red→green: CsvFormat, sniff+assert, the one decoder, visible quarantine, structured TableSpec. Legacy modules untouched.
4 · Migrationall 9 onto FilesystemSourceComponentsunlife first, then the six bronze-only customers, then mneye/heatoneye with their paired silver. Prose headers shrink; semantics move into fields.
5 · Keyscustomer/layer/source/step/tableAsset keys reshaped to mirror the S3 layout exactly (raw and clean as sibling steps), groups become {customer}_{layer}_{source}.
6 · Hardeningvalidation, globs, simplificationDefinition-build-time config validation (the append→replace typo class), silent-corruption glob fixes, drift guards, and deletion of every zero-consumer surface the TDD pass had speculatively built.

Where to spend attention

surfacelinestreatment
filesystem_common/ core + component1,075line-by-line — the engine all nine share
mneye/heatoneye Python (sync, sensors, predicates, cleanup)~470line-by-line — touches prod MSSQL
one defs.yaml per family (mneye · middletown · ccp · jewishboard · ssr)~600read header + spot-check masks; siblings are clones
one dbt clean model + one CI workflow~200template representatives; the other 25 / 8 are copies
library tests795skim names for coverage shape
DATA_NOTES × 9, shared docs, generator, packaging, lockfiles~46,600context, not review surface
The one-sentence versionA ~1,100-line tested loader core, ~470 lines of per-customer glue, and nine declarative feed contracts whose every field was earned by a probe — wrapped in 46k lines of lockfiles, docs, and templates that make the diff look ten times bigger than it reviews.