audit-labs/audit-tools

A collection of scripts, queries, and other goodies you can use in an audit.

clone: git clone https://gitbay.org/audit-labs/audit-tools.git

v1.0.0: sampling/sampling_tool/filters.py · raw

 1"""Exact-match filter parsing and application."""
 2
 3from __future__ import annotations
 4
 5import pandas as pd
 6
 7from .io import AuditSamplingError
 8
 9
10def parse_filters(filter_values: list[str] | None) -> dict[str, str]:
11    parsed: dict[str, str] = {}
12    for value in filter_values or []:
13        if "=" not in value:
14            raise AuditSamplingError(
15                f"Invalid filter '{value}'. Expected format: Column=Value"
16            )
17        column, expected = value.split("=", 1)
18        column = column.strip()
19        if not column:
20            raise AuditSamplingError(
21                f"Invalid filter '{value}'. Filter column cannot be blank."
22            )
23        parsed[column] = expected.strip()
24    return parsed
25
26
27def apply_filters(
28    population: pd.DataFrame, filters: dict[str, str]
29) -> tuple[pd.DataFrame, pd.DataFrame]:
30    if not filters:
31        return population.copy(), population.iloc[0:0].copy()
32
33    missing = [column for column in filters if column not in population.columns]
34    if missing:
35        raise AuditSamplingError(f"Filter column(s) not found: {', '.join(missing)}")
36
37    keep_mask = pd.Series(True, index=population.index)
38    for column, expected in filters.items():
39        keep_mask &= population[column].astype("string").fillna("") == expected
40
41    excluded = population.loc[~keep_mask].copy()
42    if not excluded.empty:
43        excluded["_exclusion_reason"] = "Filtered out"
44    return population.loc[keep_mask].copy(), excluded