Skills
A skill is a package of structured files that teaches an AI coding agent how to work with a specific tool or framework. This project ships multiple skills — use the switcher below to browse each one. Install a skill in your agent and it will be able to run commands, edit configuration, write content, and troubleshoot problems without step-by-step guidance from you.
Any agent — install all with npx:
npx skills add https://posit-dev.github.io/pointblank/CLI — install all skills in a project:
great-docs skill install pointblankCodex / OpenCode
Tell the agent to fetch these skill files:
https://posit-dev.github.io/pointblank/.well-known/agent-skills/pointblank/SKILL.md
https://posit-dev.github.io/pointblank/.well-known/agent-skills/write-validation/SKILL.md
https://posit-dev.github.io/pointblank/.well-known/agent-skills/validate-yaml/SKILL.md
https://posit-dev.github.io/pointblank/.well-known/agent-skills/draft-validation/SKILL.md
https://posit-dev.github.io/pointblank/.well-known/agent-skills/define-contracts/SKILL.md
https://posit-dev.github.io/pointblank/.well-known/agent-skills/scan-and-profile/SKILL.md
https://posit-dev.github.io/pointblank/.well-known/agent-skills/generate-data/SKILL.mdOr browse the skill files below.
SKILL LAYOUT
pointblank/
├── SKILL.md
└── references/
├── column-selectors.md
├── data-backends.md
└── validation-methods.md
SKILL.md
--- name: pointblank description: > Validate DataFrames and database tables with Pointblank. Covers the Validate workflow (plan, interrogate, report), column selectors, data backends (Polars, Pandas, DuckDB, databases via Ibis), threshold levels, actions, and result extraction. Use when building, running, or troubleshooting data-validation pipelines. license: MIT compatibility: Requires Python >=3.10. metadata: author: rich-iannone version: "1.0" tags: - data-validation - data-quality - polars - pandas - duckdb - ibis --- # Pointblank A data-validation library for Python. Define validation steps against a table, execute them with `interrogate()`, then inspect results as a rich tabular report or programmatically via pass/fail counts and data extracts. ## Quick start ```python import pointblank as pb validation = ( pb.Validate(data=pb.load_dataset("small_table")) .col_vals_gt(columns="d", value=100) .col_vals_not_null(columns="date_time") .col_vals_in_set(columns="f", set=["low", "mid", "high"]) .interrogate() ) validation # displays the HTML validation report ``` ## Skill directory structure This skill ships with companion files for agent consumption: ``` skills/pointblank/ +-- SKILL.md <- This file +-- references/ +-- validation-methods.md <- All col_vals_* / row / schema methods +-- column-selectors.md <- col(), starts_with(), matches(), etc. +-- data-backends.md <- Polars, Pandas, DuckDB, Ibis, files ``` ## When to use what | I want to... | Use | | ------------------------------------ | ------------------------------------ | | Check column values meet a condition | `col_vals_gt/lt/eq/...` | | Ensure no nulls in a column | `col_vals_not_null` | | Check values are in an allowed set | `col_vals_in_set` | | Match a regex pattern | `col_vals_regex` | | Validate table schema | `col_schema_match` | | Check row/column counts | `row_count_match`, `col_count_match` | | Find duplicate rows | `rows_distinct` | | Check data freshness | `data_freshness` | | Combine multiple conditions | `conjointly` | | Run a custom check | `specially` | | Use LLM-based validation | `prompt` | | Select columns by pattern | `starts_with`, `contains`, `matches` | | Set failure thresholds | `Thresholds` | | Trigger actions on failure | `Actions`, `FinalActions` | | Get failing rows | `get_data_extracts` | | Split data into pass/fail | `get_sundered_data` | | Profile a dataset first | `DataScan` | | Define validation in YAML | `yaml_interrogate` | | Enforce contracts in a pipeline | `Contract`, `Pipeline` | | Generate test data | `Schema.generate`, field classes | | Draft validation with an LLM | `DraftValidation` | ## Core concepts ### The Validate workflow Every validation follows three phases: 1. **Plan** -- Create a `Validate` object with a data source and chain validation methods to define steps. 2. **Interrogate** -- Call `.interrogate()` to execute all steps against the data. 3. **Report** -- View results with the built-in HTML report (just evaluate the object), or extract results programmatically. ```python import pointblank as pb validation = ( pb.Validate(data=df, tbl_name="orders", label="Daily order check") .col_vals_gt(columns="amount", value=0) .col_vals_not_null(columns="customer_id") .col_vals_between(columns="quantity", left=1, right=1000) .interrogate() ) ``` ### Data backends Pointblank works with multiple table types through the same API: | Backend | How to supply data | | ----------- | ------------------------------------------------------------------- | | Polars | `pl.DataFrame` or `pl.LazyFrame` | | Pandas | `pd.DataFrame` | | DuckDB | `ibis.Table` via `pb.connect_to_table("duckdb://path.db::table")` | | PostgreSQL | `ibis.Table` via `pb.connect_to_table("postgresql://...")` | | MySQL | `ibis.Table` via `pb.connect_to_table("mysql://...")` | | SQLite | `ibis.Table` via `pb.connect_to_table("sqlite://...")` | | Snowflake | `ibis.Table` via `pb.connect_to_table("snowflake://...")` | | CSV/Parquet | File path string: `"data/orders.csv"`, `"s3://bucket/file.parquet"` | ```python tbl = pb.connect_to_table("duckdb:///warehouse.db::sales") validation = pb.Validate(data=tbl).col_vals_gt(columns="revenue", value=0).interrogate() ``` ### Column selectors Instead of naming columns one by one, use selectors to target groups: ```python from pointblank import col, starts_with, ends_with, contains, matches, everything # All columns starting with "price" .col_vals_gt(columns=starts_with("price"), value=0) # Combine selectors with operators .col_vals_not_null(columns=starts_with("id") | ends_with("_key")) # Exclude columns .col_vals_not_null(columns=everything() - matches("_tmp$")) ``` Selectors: `col()`, `starts_with()`, `ends_with()`, `contains()`, `matches()`, `everything()`, `first_n()`, `last_n()`. Operators: `&` (and), `|` (or), `-` (difference), `~` (not). ### Thresholds and actions Set failure thresholds at three severity levels: ```python validation = ( pb.Validate( data=df, thresholds=pb.Thresholds(warning=0.05, error=0.10, critical=0.25), actions=pb.Actions( warning="Step {step}: {col} has warnings", critical=pb.send_slack_notification(webhook_url="..."), ), ) .col_vals_gt(columns="amount", value=0) .interrogate() ) ``` Threshold values: `<1` = fraction of failing rows, `>=1` = absolute count, `True` = any failure (equivalent to 1). Per-step thresholds override the global setting. ### Extracting results ```python validation.all_passed() # True if every step passed validation.n_passed(i=1) # count of passing units in step 1 validation.f_failed(i=2) # fraction of failing units in step 2 # Get the rows that failed step 1 extracts = validation.get_data_extracts(i=1, frame=True) # Split data into passing and failing subsets pass_df = validation.get_sundered_data(type="pass") fail_df = validation.get_sundered_data(type="fail") # JSON report for downstream systems json_str = validation.get_json_report() ``` ### Reporting ```python # Full HTML report (default display) validation.get_tabular_report() # Per-step detail report validation.get_step_report(i=1, limit=20) # Customize report sections validation.get_tabular_report( title="Nightly Checks", incl_header=True, incl_footer=True, incl_footer_timings=True, ) ``` ### Serialization Save and reload validation objects for auditing or scheduling: ```python pb.write_file(validation, filename="nightly_check.pb") restored = pb.read_file("nightly_check.pb") ``` ## Related skills | Skill | When to use it | | ---------------- | ------------------------------------------------- | | write-validation | Detailed guidance on choosing and composing steps | | define-contracts | Contract and Pipeline boundary validation | | scan-and-profile | Profile data before writing validation rules | | validate-yaml | Define validation plans in YAML | | generate-data | Create synthetic test data from schemas | | draft-validation | LLM-assisted validation drafting and editing | ## Gotchas 1. **Call `.interrogate()` last.** Validation methods only define steps; nothing executes until `interrogate()` is called. 2. **Column selectors are case-insensitive by default.** Pass `case_sensitive=True` to `starts_with()`, `contains()`, etc. if needed. 3. **Threshold fractions vs counts.** A threshold of `0.05` means 5% of rows may fail; a threshold of `5` means at most 5 rows may fail. 4. **`na_pass=False` is the default.** Null values count as failures unless you set `na_pass=True`. 5. **Database tables require Ibis.** Use `pb.connect_to_table()` with a connection string to get an Ibis table object. 6. **File paths work directly.** Pass `"data.csv"` or `"data.parquet"` as `data=` and Pointblank reads it automatically. 7. **Reports render as HTML.** In notebooks, just evaluate the Validate object. In scripts, call `get_tabular_report()` explicitly.
references/column-selectors.md
# Column selectors reference Column selectors let you target multiple columns by pattern instead of listing them individually. ## Available selectors | Selector | Description | Example | |---------------------|------------------------------------------|-------------------------------| | `col("name")` | Explicit column name | `col("revenue")` | | `starts_with(text)` | Columns whose name starts with text | `starts_with("price")` | | `ends_with(text)` | Columns whose name ends with text | `ends_with("_id")` | | `contains(text)` | Columns whose name contains text | `contains("amount")` | | `matches(pattern)` | Columns matching a regex | `matches(r"^col_\d+")` | | `everything()` | All columns | `everything()` | | `first_n(n)` | First n columns | `first_n(3)` | | `last_n(n)` | Last n columns | `last_n(2)` | ## Parameters All text-based selectors accept `case_sensitive: bool = False`. `first_n` and `last_n` accept `offset: int = 0` to skip columns before counting. ## Combining selectors with operators | Operator | Meaning | Example | |----------|------------|------------------------------------------| | `\|` | Union | `starts_with("a") \| starts_with("b")` | | `&` | Intersect | `contains("price") & ends_with("_usd")` | | `-` | Difference | `everything() - matches("_tmp$")` | | `~` | Negate | `~contains("debug")` | ## Expression columns (for conjointly) `expr_col()` creates column expressions for use in `conjointly()`: ```python from pointblank import expr_col .conjointly( lambda v: v.col_vals_expr(expr_col("start") < expr_col("end")), lambda v: v.col_vals_gt(columns="duration", value=0), ) ``` `expr_col` supports: `>`, `<`, `==`, `!=`, `>=`, `<=`, `+`, `-`, `*`, `/`, `is_null()`, `is_not_null()`, `&`, `|`. ## Reference columns `ref()` references a column in the reference table for aggregate comparisons: ```python validation = ( pb.Validate(data=current_df, reference=previous_df) .col_sum_eq(columns="revenue", value=ref("revenue")) .interrogate() ) ``` When `value=None` in aggregate methods and a reference table is set, `ref(column)` is used automatically. ## Usage in validation methods Selectors work in any method that accepts `columns`: ```python import pointblank as pb from pointblank import starts_with, ends_with, everything ( pb.Validate(data=df) .col_vals_gt(columns=starts_with("price"), value=0) .col_vals_not_null(columns=everything() - ends_with("_notes")) .col_vals_in_set(columns="status", set=["active", "inactive"]) .interrogate() ) ```
references/data-backends.md
# Data backends reference Pointblank validates tables from multiple backends through a unified API. The validation methods work identically regardless of the backend. ## Supported backends | Backend | Input type | Extra dependency | |---------------|-------------------------|----------------------| | Polars | `pl.DataFrame`, `pl.LazyFrame` | `polars` | | Pandas | `pd.DataFrame` | `pandas` | | DuckDB | `ibis.Table` | `ibis-framework[duckdb]` | | PostgreSQL | `ibis.Table` | `ibis-framework[postgres]` | | MySQL | `ibis.Table` | `ibis-framework[mysql]` | | SQLite | `ibis.Table` | `ibis-framework[sqlite]` | | Snowflake | `ibis.Table` | `ibis-framework[snowflake]` | | CSV files | File path string | (none) | | Parquet files | File path string | (none) | ## Connecting to databases Use `pb.connect_to_table()` with a connection string. Append `::table_name` to specify the table: ```python import pointblank as pb # DuckDB tbl = pb.connect_to_table("duckdb:///warehouse.db::sales") # PostgreSQL tbl = pb.connect_to_table("postgresql://user:pass@host:5432/db::orders") # MySQL tbl = pb.connect_to_table("mysql://user:pass@host:3306/db::customers") # SQLite tbl = pb.connect_to_table("sqlite:///local.db::events") # Snowflake tbl = pb.connect_to_table("snowflake://user:pass@account/db/schema::table") ``` ## Using file paths directly Pass CSV or Parquet paths as the `data` argument: ```python validation = ( pb.Validate(data="data/orders.csv") .col_vals_gt(columns="amount", value=0) .interrogate() ) # Parquet files validation = ( pb.Validate(data="s3://bucket/data.parquet") .col_vals_not_null(columns="id") .interrogate() ) ``` ## Built-in datasets Pointblank includes example datasets for testing: ```python df = pb.load_dataset("small_table") # default: Polars df = pb.load_dataset("game_revenue", tbl_type="pandas") df = pb.load_dataset("nycflights", tbl_type="duckdb") df = pb.load_dataset("global_sales", tbl_type="polars") ``` Available datasets: `small_table`, `game_revenue`, `nycflights`, `global_sales`. Available types: `"polars"` (default), `"pandas"`, `"duckdb"`. ## Listing database tables ```python pb.print_database_tables("duckdb:///warehouse.db") ``` ## Utility functions ```python pb.get_row_count(df) # row count for any backend pb.get_column_count(df) # column count for any backend pb.preview(df) # quick visual preview as GT table ```
references/validation-methods.md
# Validation methods reference Complete list of validation methods available on `Validate`. ## Value comparison methods All share a common signature pattern: ```python .col_vals_gt( columns, # str, list[str], or column selector value, # numeric value or col("other_column") na_pass=False, # treat nulls as passing? missing=None, # MissingSpec for structured missingness pre=None, # callable to transform data before check segments=None, # segment the check by group values thresholds=None, # per-step thresholds override actions=None, # per-step actions override brief=None, # custom brief text or False to suppress active=True, # bool or callable to conditionally skip dimension=None, # tag for dimensional scoring ) ``` | Method | Checks that values are... | |---------------------|--------------------------------------| | `col_vals_gt` | greater than `value` | | `col_vals_lt` | less than `value` | | `col_vals_ge` | greater than or equal to `value` | | `col_vals_le` | less than or equal to `value` | | `col_vals_eq` | equal to `value` | | `col_vals_ne` | not equal to `value` | ## Range methods ```python .col_vals_between(columns, left, right, inclusive=(True, True), ...) .col_vals_outside(columns, left, right, inclusive=(True, True), ...) ``` `inclusive` controls boundary inclusion: `(True, True)` = closed interval, `(False, False)` = open interval. ## Set membership ```python .col_vals_in_set(columns, set=["a", "b", "c"], ...) .col_vals_not_in_set(columns, set=["x", "y"], ...) ``` ## Monotonicity ```python .col_vals_increasing(columns, allow_stationary=False, decreasing_tol=None, ...) .col_vals_decreasing(columns, allow_stationary=False, increasing_tol=None, ...) ``` ## Null checks ```python .col_vals_null(columns, ...) # all values must be null .col_vals_not_null(columns, ...) # no values may be null ``` No `na_pass` or `missing` parameters on these methods. ## Pattern and spec matching ```python .col_vals_regex(columns, pattern="^[A-Z]{3}$", inverse=False, ...) .col_vals_within_spec(columns, spec="email", ...) ``` ## Expression-based ```python from pointblank import expr_col .col_vals_expr(expr_col("price") * expr_col("qty") > 0, ...) ``` ## Aggregate comparison methods Pattern: `col_{agg}_{comp}()` where agg is `sum`, `avg`, or `sd` and comp is `eq`, `gt`, `ge`, `lt`, or `le`. ```python .col_sum_gt(columns, value=1000, tol=0, ...) .col_avg_between(columns, value=50.0, tol=0.5, ...) .col_sd_lt(columns, value=10, ...) ``` When a reference table is set and `value=None`, automatically compares against the reference column. ## Null percentage ```python .col_pct_null(columns, p=0.05, tol=0, ...) # null % <= p .col_pct_missing(columns, missing=spec, max_pct=0.10, ...) ``` ## Structural checks ```python .col_exists(columns, ...) .col_schema_match(schema, complete=True, in_order=True, ...) .col_count_match(count=10, inverse=False, ...) .row_count_match(count=1000, tol=0, inverse=False, ...) .rows_distinct(columns_subset=None, ...) .rows_complete(columns_subset=None, ...) ``` ## Data freshness ```python .data_freshness(column="updated_at", max_age="2h", reference_time=None, ...) ``` `max_age` accepts strings like `"2h"`, `"30m"`, `"1d"`, or `datetime.timedelta` objects. ## Table comparison ```python .tbl_match(tbl_compare=other_df, ...) ``` ## Compound and custom checks ```python # Multiple conditions must all hold for each row .conjointly( lambda v: v.col_vals_gt(columns="a", value=0), lambda v: v.col_vals_lt(columns="a", value=100), ) # Fully custom check .specially(lambda tbl: len(tbl) > 0) ``` ## LLM-based validation ```python .prompt( prompt="Check if product names are appropriate", model="anthropic:claude-sonnet-4-20250514", columns_subset=["product_name"], batch_size=1000, max_concurrent=3, ) ``` ## Common parameters | Parameter | Type | Description | |--------------|-------------------------|------------------------------------------| | `columns` | `str \| list \| selector` | Target column(s) | | `value` | `numeric \| col()` | Comparison value or column reference | | `na_pass` | `bool` | Treat nulls as passing (default `False`) | | `missing` | `MissingSpec` | Structured missingness definition | | `pre` | `Callable` | Transform data before check | | `segments` | `SegmentSpec` | Segment validation by groups | | `thresholds` | `Thresholds` | Per-step failure thresholds | | `actions` | `Actions` | Per-step actions on threshold breach | | `brief` | `str \| bool` | Step description text | | `active` | `bool \| Callable` | Conditionally skip step | | `dimension` | `str` | Tag for dimensional scoring |
SKILL LAYOUT
write-validation/
├── SKILL.md
└── references/
├── step-selection-guide.md
└── thresholds-and-actions.md
SKILL.md
--- name: write-validation description: > Write data-validation plans with Pointblank. Covers choosing the right validation methods for each data quality concern, composing multi-step plans, setting thresholds and actions, using segments, conditional steps, handling nulls and missing values, and extracting results. Use when building or improving a validation workflow. license: MIT compatibility: Requires Python >=3.10, pointblank installed. metadata: author: rich-iannone version: "1.0" tags: - data-validation - data-quality - validation-plan - thresholds - actions --- # Write Validation Skill for composing effective data-validation plans with Pointblank. A validation plan is a sequence of steps that check different aspects of your data, organized to catch issues early and report clearly. ## Quick start ```python import pointblank as pb validation = ( pb.Validate( data=df, tbl_name="orders", label="Order validation", thresholds=pb.Thresholds(warning=0.01, error=0.05), ) # Structural checks first .col_exists(columns=["order_id", "amount", "status"]) .row_count_match(count=1000, tol=50) # Value checks .col_vals_gt(columns="amount", value=0) .col_vals_not_null(columns="order_id") .col_vals_in_set(columns="status", set=["pending", "shipped", "delivered"]) # Execute .interrogate() ) ``` ## Skill directory structure ``` skills/write-validation/ +-- SKILL.md <- This file +-- references/ +-- step-selection-guide.md <- Which method for which concern +-- thresholds-and-actions.md <- Configuring failure responses ``` ## When to use what | Data quality concern | Recommended method | | --------------------------------- | ------------------------------------ | | Column exists | `col_exists` | | Schema matches expectations | `col_schema_match` | | Expected row count | `row_count_match` | | Expected column count | `col_count_match` | | No null values | `col_vals_not_null` | | All null (placeholder column) | `col_vals_null` | | No duplicate rows | `rows_distinct` | | All rows complete (no nulls) | `rows_complete` | | Values above/below a bound | `col_vals_gt/lt/ge/le` | | Values in a numeric range | `col_vals_between` | | Values outside a range | `col_vals_outside` | | Values from an allowed set | `col_vals_in_set` | | Values not in a forbidden set | `col_vals_not_in_set` | | Values match a pattern | `col_vals_regex` | | Values match a format (email etc) | `col_vals_within_spec` | | Values are monotonically ordered | `col_vals_increasing/decreasing` | | Null percentage within budget | `col_pct_null` | | Structured missingness | `col_pct_missing` with `MissingSpec` | | Aggregate comparison (sum, avg) | `col_sum_eq`, `col_avg_gt`, etc. | | Data is recent enough | `data_freshness` | | Table matches another table | `tbl_match` | | Multiple conditions per row | `conjointly` | | Custom logic | `specially` | | LLM-based semantic check | `prompt` | ## Core concepts ### Ordering your steps Organize validation steps from structural to semantic: 1. **Schema checks** -- `col_exists`, `col_schema_match`, `col_count_match`, `row_count_match` 2. **Completeness checks** -- `col_vals_not_null`, `rows_complete`, `col_pct_null` 3. **Uniqueness checks** -- `rows_distinct` 4. **Value-range checks** -- `col_vals_gt`, `col_vals_between`, etc. 5. **Format checks** -- `col_vals_regex`, `col_vals_within_spec` 6. **Set membership** -- `col_vals_in_set`, `col_vals_not_in_set` 7. **Cross-column checks** -- `conjointly`, `col_vals_expr` 8. **Aggregate checks** -- `col_sum_eq`, `col_avg_gt` 9. **Freshness checks** -- `data_freshness` 10. **Custom/semantic checks** -- `specially`, `prompt` This order means structural problems surface first before value-level checks run. ### Handling nulls By default, null values count as failures in value checks. Control this per step: ```python # Nulls count as failures (default) .col_vals_gt(columns="amount", value=0) # Nulls are treated as passing .col_vals_gt(columns="amount", value=0, na_pass=True) ``` For structured missingness (sentinel values like -999, "N/A"): ```python missing_spec = pb.MissingSpec( reasons={-999: "not collected", -1: "redacted"}, null_is_missing=True, null_reason="unknown", ) .col_vals_gt(columns="measurement", value=0, missing=missing_spec) .col_pct_missing(columns="measurement", missing=missing_spec, max_pct=0.10) ``` ### Segmented validation Break validation into groups to see which segments fail: ```python .col_vals_gt( columns="revenue", value=0, segments=pb.seg_group(["region_a", "region_b", "region_c"]), ) ``` ### Conditional steps Skip steps dynamically based on the data: ```python # Only run if the column exists .col_vals_gt( columns="new_feature", value=0, active=pb.has_columns("new_feature"), ) # Only run if the table has enough rows .rows_distinct(active=pb.has_rows(min=100)) ``` ### Pre-processing data Transform data before a check with `pre`: ```python .col_vals_gt( columns="price", value=0, pre=lambda df: df.filter(pl.col("status") == "active"), ) ``` ### Thresholds Set thresholds at three severity levels: ```python # Global thresholds (apply to all steps) pb.Validate( data=df, thresholds=pb.Thresholds(warning=0.01, error=0.05, critical=0.25), ) # Per-step override .col_vals_gt( columns="amount", value=0, thresholds=pb.Thresholds(warning=5, error=20), ) ``` - Values `< 1`: fraction of failing test units (e.g., `0.05` = 5%) - Values `>= 1`: absolute count of failures (e.g., `5` = 5 rows) - `True`: any failure triggers (equivalent to `1`) ### Actions Trigger responses when thresholds are exceeded: ```python pb.Actions( warning="Warning: {col} step {step} at {time}", error=lambda: send_alert("Data quality error"), critical=[ pb.send_slack_notification(webhook_url="https://..."), "Critical failure in {col}", ], highest_only=True, # only fire the highest triggered level ) ``` Template variables: `{type}`, `{level}`, `{step}`, `{col}`, `{val}`, `{time}`. ### Final actions Run after all steps complete, with access to the full summary: ```python def check_overall(summary=None): summary = pb.get_validation_summary() if summary and summary["n_failed_steps"] > 0: send_report(summary) pb.Validate( data=df, final_actions=pb.FinalActions(check_overall), ) ``` ### Comparing against reference data Track drift by comparing current data to a reference table: ```python validation = ( pb.Validate(data=current_df, reference=previous_df) .col_sum_eq(columns="revenue") # value=None -> uses ref() .col_avg_eq(columns="quantity") .interrogate() ) ``` ### Extracting results ```python # Did everything pass? validation.all_passed() # Counts and fractions by step validation.n_passed(i=1, scalar=True) validation.f_failed(i=[1, 2, 3]) # Get failing rows for a step extracts = validation.get_data_extracts(i=1, frame=True) # Split into pass/fail subsets pass_df = validation.get_sundered_data(type="pass") fail_df = validation.get_sundered_data(type="fail") # Machine-readable report json_report = validation.get_json_report() ``` ### Saving and reloading ```python pb.write_file(validation, filename="daily_check.pb") restored = pb.read_file("daily_check.pb") ``` ## Workflows ### Building a validation plan from scratch 1. Profile the data with `pb.DataScan(data=df)` to understand distributions, nulls, and types. 2. Start with structural checks (`col_exists`, `col_schema_match`). 3. Add completeness checks (`col_vals_not_null`, `rows_complete`). 4. Add value constraints based on domain knowledge. 5. Set thresholds appropriate to the use case. 6. Run `interrogate()` and review the report. 7. Iterate: adjust thresholds, add missing checks, remove noisy ones. ### Adding checks to an existing plan Read the current validation code, identify uncovered columns or concerns, and add steps in the appropriate position (structural before semantic). Preserve the existing threshold/action configuration unless changing it is part of the task. ### Diagnosing validation failures 1. Run `interrogate()` and check the report. 2. Use `get_data_extracts(i=N, frame=True)` to see failing rows. 3. Use `get_step_report(i=N)` for a detailed per-step view. 4. Determine whether the check or the data is wrong. 5. Fix the check (adjust value/threshold) or flag the data issue. ## Gotchas 1. **`.interrogate()` must be called.** Steps are declarative until executed. 2. **`na_pass` defaults to `False`.** Nulls fail value checks unless you opt in. 3. **Threshold `0.05` vs `5`.** Fractional = percentage, integer = count. 4. **`col_vals_between` is closed by default.** Pass `inclusive=(False, False)` for an open interval. 5. **`conjointly` takes lambdas, not method calls.** Each argument is `lambda v: v.col_vals_gt(...)`. 6. **Aggregate methods compare one value per column**, not per row. They produce a single pass/fail per column. 7. **`segments` splits the check into sub-groups.** Each segment is reported separately in the validation report.
references/step-selection-guide.md
# Step selection guide Decision tree for choosing the right validation method. ## By data type ### Numeric columns | Concern | Method | |-----------------------------|-----------------------------------------| | Positive values only | `col_vals_gt(columns, value=0)` | | Within a range | `col_vals_between(columns, left, right)`| | Not zero | `col_vals_ne(columns, value=0)` | | Sum matches expected | `col_sum_eq(columns, value=total)` | | Average within tolerance | `col_avg_between(columns, value, tol)` | | Standard deviation bounded | `col_sd_lt(columns, value=max_sd)` | | Monotonically increasing | `col_vals_increasing(columns)` | | Null percentage under limit | `col_pct_null(columns, p=0.05)` | ### String columns | Concern | Method | |-----------------------------|-------------------------------------------| | Matches a pattern | `col_vals_regex(columns, pattern)` | | Valid email/URL/phone/etc. | `col_vals_within_spec(columns, spec)` | | From a known set | `col_vals_in_set(columns, set)` | | Not a forbidden value | `col_vals_not_in_set(columns, set)` | | Not null | `col_vals_not_null(columns)` | ### Date/datetime columns | Concern | Method | |-----------------------------|-------------------------------------------| | After a cutoff date | `col_vals_gt(columns, value=cutoff)` | | Within a date range | `col_vals_between(columns, left, right)` | | Data is recent | `data_freshness(column, max_age="2h")` | | Chronologically ordered | `col_vals_increasing(columns)` | ### Boolean columns | Concern | Method | |-----------------------------|-------------------------------------------| | All true | `col_vals_eq(columns, value=True)` | | All false | `col_vals_eq(columns, value=False)` | ## By concern type ### Completeness ```python .col_vals_not_null(columns="required_field") .rows_complete() # no nulls in any column .rows_complete(columns_subset=["a", "b"]) # no nulls in a, b .col_pct_null(columns="optional", p=0.20) # at most 20% null ``` ### Uniqueness ```python .rows_distinct() # all rows unique .rows_distinct(columns_subset=["id"]) # id column unique .rows_distinct(columns_subset=["a", "b"]) # composite unique ``` ### Consistency (cross-column) ```python from pointblank import expr_col .conjointly( lambda v: v.col_vals_expr(expr_col("start") < expr_col("end")), lambda v: v.col_vals_gt(columns="duration", value=0), ) ``` ### Referential (against another table) ```python pb.Validate(data=current, reference=previous) .col_sum_eq(columns="total") # sums match reference .col_avg_eq(columns="avg_price") # averages match reference .tbl_match(tbl_compare=expected) # tables are identical ``` ## Available spec values for col_vals_within_spec Use these with `.col_vals_within_spec(columns, spec="...")`: - `"email"` -- valid email addresses - `"url"` -- valid URLs - `"ipv4"` -- IPv4 addresses - `"ipv6"` -- IPv6 addresses - `"phone"` -- phone numbers (E.164) ## MissingSpec for structured missingness When sentinel values represent missing data: ```python spec = pb.MissingSpec( reasons={ -999: "not collected", -1: "redacted", "N/A": "not applicable", }, categories={ "system": ["not collected"], "policy": ["redacted", "not applicable"], }, null_is_missing=True, null_reason="unknown", description="Clinical trial data missingness codes", ) ``` Use with `missing=spec` in value checks, or standalone: ```python .col_pct_missing(columns="lab_value", missing=spec, max_pct=0.10, reason="not collected", category="system") ```
references/thresholds-and-actions.md
# Thresholds and actions reference ## Thresholds `Thresholds(warning=None, error=None, critical=None)` ### Value interpretation | Value | Meaning | Example | |-----------|-------------------------------|----------------------| | `0.05` | 5% of test units may fail | 50 failures in 1000 | | `5` | At most 5 test units may fail | Absolute count | | `True` | Any failure triggers | Same as `1` | | `None` | Level not used | No threshold set | ### Setting thresholds ```python # Global -- applies to all steps pb.Validate( data=df, thresholds=pb.Thresholds(warning=0.01, error=0.05, critical=0.25), ) # Per-step -- overrides global for that step .col_vals_gt( columns="amount", value=0, thresholds=pb.Thresholds(warning=True), # any failure warns ) # Shorthand -- integer/float sets warning level pb.Validate(data=df, thresholds=3) # warning at 3 failures ``` ## Actions `Actions(warning=None, error=None, critical=None, default=None, highest_only=True)` Each level accepts: - `str` -- message template (printed to stdout) - `Callable` -- function to call - `list[str | Callable]` -- multiple actions - `None` -- no action ### Template variables | Variable | Description | |-------------------|----------------------------------| | `{type}` | Validation method name | | `{level}` | Threshold level triggered | | `{step}` or `{i}` | Step number | | `{col}` or `{column}` | Column name | | `{val}` or `{value}` | Comparison value | | `{time}` | Timestamp | ### Examples ```python # String messages pb.Actions( warning="Step {step}: {col} has warnings at {time}", error="ERROR in {col}: {type} check failed", ) # Callable actions def alert_on_error(): metadata = pb.get_action_metadata() send_email(f"Error in step {metadata['step']}") pb.Actions(error=alert_on_error) # Slack notifications pb.Actions( critical=pb.send_slack_notification( webhook_url="https://hooks.slack.com/services/...", ), ) # OpenTelemetry pb.Actions( warning=pb.emit_otel(service_name="data-pipeline"), ) # Multiple actions per level pb.Actions( error=[ "Error in {col}", lambda: log_to_database(), pb.send_slack_notification(webhook_url="..."), ], ) ``` ### highest_only When `True` (default), only fires actions for the highest triggered level. When `False`, fires all triggered levels. ### get_action_metadata() Inside an action callable, call `pb.get_action_metadata()` to access step details: ```python def my_action(): meta = pb.get_action_metadata() # meta keys: step, column, type, level, value, time, ... ``` ## Final actions `FinalActions(*actions)` -- run after all steps complete. ```python def summary_check(): summary = pb.get_validation_summary() if summary["n_failed_steps"] > 0: create_jira_ticket(summary) pb.Validate( data=df, final_actions=pb.FinalActions(summary_check, "Validation complete at {time}"), ) ``` ### get_validation_summary() Inside a final action callable, call `pb.get_validation_summary()`: ```python def report(): s = pb.get_validation_summary() # s keys: n_steps, n_passed_steps, n_failed_steps, # warn_count, error_count, critical_count, ... ``` ## Common patterns ### Warn-then-stop pipeline ```python pb.Validate( data=df, thresholds=pb.Thresholds(warning=0.01, critical=0.10), actions=pb.Actions( warning="Data quality warning: {col}", critical=lambda: sys.exit(1), ), ) ``` ### Slack on any failure ```python pb.Validate( data=df, thresholds=pb.Thresholds(warning=True), actions=pb.Actions( warning=pb.send_slack_notification( webhook_url="https://hooks.slack.com/services/...", ), ), ) ``` ### Log to OpenTelemetry ```python pb.Validate( data=df, thresholds=pb.Thresholds(warning=0.05), actions=pb.Actions( warning=pb.emit_otel(service_name="my-pipeline"), ), ) ```
SKILL LAYOUT
validate-yaml/
├── SKILL.md
└── references/
└── yaml-schema.md
SKILL.md
--- name: validate-yaml description: > Define Pointblank validation plans in YAML instead of Python code. Covers the YAML schema, validate_yaml() for syntax checking, yaml_interrogate() for execution, yaml_to_python() for code generation, and data source configuration. Use when defining validation plans declaratively or sharing them across teams. license: MIT compatibility: Requires Python >=3.10, pointblank installed. metadata: author: rich-iannone version: "1.0" tags: - data-validation - yaml - declarative - configuration --- # Validate YAML Skill for defining data-validation plans in YAML. YAML-based plans are declarative, version-controllable, and shareable across teams without requiring Python knowledge. ## Quick start ```yaml # validation.yaml tbl: "data/orders.csv" tbl_name: orders label: Order validation thresholds: warning: 0.01 error: 0.05 steps: - method: col_vals_gt columns: amount value: 0 - method: col_vals_not_null columns: order_id - method: col_vals_in_set columns: status set: [pending, shipped, delivered] ``` ```python import pointblank as pb # Execute the YAML plan validation = pb.yaml_interrogate("validation.yaml") validation.get_tabular_report() ``` ## Skill directory structure ``` skills/validate-yaml/ +-- SKILL.md <- This file +-- references/ +-- yaml-schema.md <- Full YAML key reference ``` ## When to use what | I want to... | Use | | ----------------------------------- | ---------------------------------- | | Check YAML syntax without running | `validate_yaml()` | | Execute a YAML validation plan | `yaml_interrogate()` | | Convert YAML to Python code | `yaml_to_python()` | | Override the data source at runtime | `yaml_interrogate(set_tbl=df)` | | Use custom functions in YAML steps | `yaml_interrogate(namespaces=...)` | ## Core concepts ### YAML structure A YAML validation plan has two required keys (`tbl` and `steps`) and several optional keys: ```yaml # Required tbl: "path/to/data.csv" # data source steps: # validation steps - method: col_vals_gt columns: amount value: 0 # Optional metadata tbl_name: orders label: Daily order check owner: data-team consumers: [analytics, reporting] version: "1.0" lang: en locale: en_US df_library: polars # polars (default) or pandas # Optional thresholds and actions thresholds: warning: 0.01 error: 0.05 critical: 0.25 actions: warning: "Warning: {col} failed at {time}" error: "Error in step {step}: {col}" final_actions: - "Validation complete" # Optional brief setting brief: true # Optional reference table reference: "path/to/reference.csv" # Optional missing specs missing_specs: measurement: reasons: -999: not collected -1: redacted null_is_missing: true ``` ### Data sources in YAML The `tbl` key accepts: | Value | Interpreted as | | ------------------------------ | ---------------------------- | | `"data.csv"` | CSV file path | | `"data.parquet"` | Parquet file path | | `"duckdb:///db.ddb::table"` | DuckDB connection string | | `"postgresql://...::table"` | PostgreSQL connection string | | `"sqlite:///db.sqlite::table"` | SQLite connection string | ### Steps in YAML Each step is a dictionary with `method` and the method's parameters: ```yaml steps: # Value comparison - method: col_vals_gt columns: amount value: 0 na_pass: true # Range check - method: col_vals_between columns: score left: 0 right: 100 inclusive: [true, true] # Set membership - method: col_vals_in_set columns: status set: [active, inactive, pending] # Pattern match - method: col_vals_regex columns: email pattern: ".+@.+\\..+" # Null check - method: col_vals_not_null columns: [id, name, email] # Schema match - method: col_schema_match schema: id: Int64 name: String amount: Float64 complete: true in_order: true # Row count - method: row_count_match count: 1000 tol: 50 # Structural - method: rows_distinct columns_subset: [id] - method: rows_complete # Per-step thresholds - method: col_vals_gt columns: revenue value: 0 thresholds: warning: 5 error: 20 # Conditional step - method: col_vals_gt columns: new_feature value: 0 active: false ``` ### Validating YAML syntax Check that a YAML file is well-formed before running: ```python pb.validate_yaml("validation.yaml") # raises on errors ``` ### Executing a YAML plan ```python # Run from file validation = pb.yaml_interrogate("validation.yaml") # Override the data source validation = pb.yaml_interrogate("validation.yaml", set_tbl=my_df) # Provide custom namespaces for functions validation = pb.yaml_interrogate( "validation.yaml", namespaces={"my_module": my_module}, ) ``` ### Converting YAML to Python Generate equivalent Python code from a YAML plan: ```python python_code = pb.yaml_to_python("validation.yaml") print(python_code) ``` Output: ```python import pointblank as pb validation = ( pb.Validate( data="data/orders.csv", tbl_name="orders", label="Daily order check", thresholds=pb.Thresholds(warning=0.01, error=0.05), ) .col_vals_gt(columns="amount", value=0) .col_vals_not_null(columns="order_id") .col_vals_in_set(columns="status", set=["pending", "shipped", "delivered"]) .interrogate() ) ``` ## Workflows ### Creating a YAML validation plan 1. Profile the data to understand its shape and types. 2. Write the YAML file with `tbl` and `steps`. 3. Run `pb.validate_yaml()` to check syntax. 4. Run `pb.yaml_interrogate()` to execute. 5. Review the report and iterate. ### Sharing plans across teams 1. Define the plan in YAML. 2. Commit to version control. 3. Team members execute with `pb.yaml_interrogate()`. 4. Override the data source with `set_tbl=` as needed. ### Migrating from YAML to Python 1. Run `pb.yaml_to_python("plan.yaml")` to generate code. 2. Review and customize the generated Python. 3. Add features not available in YAML (e.g., `pre` transforms, `specially` with custom callables). ## Gotchas 1. **Escape regex backslashes.** YAML requires `\\` for a literal backslash: `pattern: "\\d+"`. 2. **Lists use YAML syntax.** Write `set: [a, b, c]` or use the block form with `- a`. 3. **`tbl` is required in the file** but can be overridden with `set_tbl=` at runtime. 4. **`inclusive` is a list, not a tuple.** Write `inclusive: [true, true]` in YAML. 5. **Not all parameters are available.** `pre` (callable transforms), `specially`, and `conjointly` with lambdas require Python code. Use `yaml_to_python()` to migrate when you need these features. 6. **`df_library` defaults to `"polars"`.** Set to `"pandas"` if your downstream code expects Pandas DataFrames.
references/yaml-schema.md
# YAML schema reference Complete reference for all keys in a Pointblank YAML validation plan. ## Top-level keys | Key | Type | Required | Default | Description | |------------------|---------------------|----------|------------|------------------------------------| | `tbl` | `str` | yes | -- | Data source (path or connection) | | `steps` | `list[dict]` | yes | -- | Validation steps | | `tbl_name` | `str` | no | `null` | Display name for the table | | `label` | `str` | no | `null` | Validation plan label | | `owner` | `str` | no | `null` | Data owner identifier | | `consumers` | `str \| list[str]` | no | `null` | Data consumers | | `version` | `str` | no | `null` | Plan version | | `lang` | `str` | no | `null` | Report language code | | `locale` | `str` | no | `null` | Locale for value formatting | | `df_library` | `str` | no | `"polars"` | DataFrame library (`polars`/`pandas`) | | `brief` | `bool \| str` | no | `null` | Global brief setting | | `reference` | `str` | no | `null` | Reference table path | | `thresholds` | `dict` | no | `null` | Global thresholds | | `actions` | `dict` | no | `null` | Global actions | | `final_actions` | `list[str]` | no | `null` | Post-validation actions | | `missing_specs` | `dict` | no | `null` | Named MissingSpec definitions | ## Step keys Each step in the `steps` list is a dictionary: | Key | Type | Required | Description | |---------------|---------------------|----------|---------------------------------| | `method` | `str` | yes | Validation method name | | `columns` | `str \| list[str]` | varies | Target column(s) | | `value` | `any` | varies | Comparison value | | `left` | `number` | varies | Left bound (between/outside) | | `right` | `number` | varies | Right bound (between/outside) | | `inclusive` | `list[bool]` | no | Boundary inclusion | | `set` | `list` | varies | Allowed/forbidden values | | `pattern` | `str` | varies | Regex pattern | | `spec` | `str` | varies | Format spec (email, url, etc.) | | `schema` | `dict` | varies | Schema definition | | `count` | `int` | varies | Expected count | | `tol` | `number` | no | Tolerance for count checks | | `na_pass` | `bool` | no | Treat nulls as passing | | `inverse` | `bool` | no | Invert the check | | `complete` | `bool` | no | Schema completeness | | `in_order` | `bool` | no | Schema column ordering | | `columns_subset` | `list[str]` | no | Column subset for distinct/complete | | `thresholds` | `dict` | no | Per-step thresholds | | `actions` | `dict` | no | Per-step actions | | `brief` | `str \| bool` | no | Step brief text | | `active` | `bool` | no | Enable/disable step | | `dimension` | `str` | no | Dimensional scoring tag | ## Thresholds format ```yaml thresholds: warning: 0.01 # fraction (< 1) or count (>= 1) error: 0.05 critical: 0.25 ``` ## Actions format ```yaml actions: warning: "Warning: {col} step {step}" error: "Error in {col} at {time}" critical: "CRITICAL: {type} failed for {col}" ``` Template variables: `{type}`, `{level}`, `{step}`, `{i}`, `{col}`, `{column}`, `{val}`, `{value}`, `{time}`. ## Missing specs format ```yaml missing_specs: column_name: reasons: -999: not collected -1: redacted categories: system: [not collected] policy: [redacted] null_is_missing: true null_reason: unknown description: Clinical data missingness ``` ## Data source formats ```yaml # File paths tbl: "data/orders.csv" tbl: "data/sales.parquet" # Database connections (append ::table_name) tbl: "duckdb:///warehouse.db::sales" tbl: "postgresql://user:pass@host:5432/db::orders" tbl: "mysql://user:pass@host:3306/db::customers" tbl: "sqlite:///local.db::events" ``` ## Complete example ```yaml tbl: "duckdb:///warehouse.db::daily_orders" tbl_name: daily_orders label: "Daily order quality check" owner: data-engineering consumers: [analytics, finance] version: "2.1" lang: en df_library: polars thresholds: warning: 0.01 error: 0.05 critical: 0.25 actions: warning: "Step {step}: {col} warning at {time}" critical: "CRITICAL failure in {col}" final_actions: - "Validation run completed" missing_specs: amount: reasons: -1: refunded null_is_missing: true steps: - method: col_schema_match schema: order_id: Int64 customer_id: Int64 amount: Float64 status: String created_at: Datetime complete: true in_order: false - method: row_count_match count: 1000 tol: 200 - method: col_vals_not_null columns: [order_id, customer_id, status] - method: rows_distinct columns_subset: [order_id] - method: col_vals_gt columns: amount value: 0 na_pass: true - method: col_vals_in_set columns: status set: [pending, processing, shipped, delivered, cancelled] - method: col_vals_regex columns: customer_id pattern: "\\d+" - method: col_pct_null columns: amount p: 0.05 ```
SKILL LAYOUT
draft-validation/
├── SKILL.md
└── references/
└── providers-reference.md
SKILL.md
--- name: draft-validation description: > Use LLMs to draft and edit Pointblank validation plans. Covers DraftValidation for generating plans from data, EditValidation for modifying existing plans with natural language, and the interactive assistant() chat interface. Supports Anthropic, OpenAI, Ollama, Bedrock, and Azure OpenAI providers. Use when bootstrapping validation for a new dataset or refining existing plans. license: MIT compatibility: Requires Python >=3.10, pointblank installed, plus an LLM provider SDK. metadata: author: rich-iannone version: "1.0" tags: - llm - ai-assisted - validation-drafting - code-generation - anthropic - openai --- # Draft Validation Skill for using LLMs to bootstrap and refine data-validation plans. Instead of writing every check by hand, describe your data and let an LLM generate a starting plan, then iterate with natural language instructions. ## Quick start ```python import pointblank as pb # Draft a validation plan from data draft = pb.DraftValidation( data=df, model="anthropic:claude-sonnet-4-20250514", ) # View the generated code print(draft.code) # Check syntax draft.validate_syntax() ``` ## Skill directory structure ``` skills/draft-validation/ +-- SKILL.md <- This file +-- references/ +-- providers-reference.md <- LLM provider configuration ``` ## When to use what | I want to... | Use | | ---------------------------------------- | ------------------------- | | Generate a validation plan from data | `DraftValidation` | | Edit an existing plan with instructions | `EditValidation` | | Chat interactively about validation | `assistant()` | | See what the LLM generated | `draft.code` | | Check generated code is valid | `draft.validate_syntax()` | | See what changed in an edit | `edit.diff()` | | Accept an edit and get a Validate object | `edit.accept()` | ## Core concepts ### DraftValidation Give data to an LLM and get back a validation plan: ```python draft = pb.DraftValidation( data=df, model="anthropic:claude-sonnet-4-20250514", api_key=None, # uses env var by default max_reprompts=1, # retries on invalid code ) ``` The LLM analyzes the data's columns, types, distributions, and patterns to generate appropriate validation steps. ```python # The raw LLM response draft.response # The extracted Python code draft.code # Check if the code is valid Python draft.validate_syntax() # True/False # See which steps were generated draft.changed_steps() # list of step dicts ``` ### EditValidation Modify an existing validation plan with natural language: ```python # From an existing Validate object edit = pb.EditValidation( validation=existing_validation, instruction="Add a check that order_id is unique and amount is positive", model="anthropic:claude-sonnet-4-20250514", ) # From Python code string edit = pb.EditValidation( validation=code_string, instruction="Remove the regex check and add a between check for age", model="openai:gpt-4o", ) # From a YAML file edit = pb.EditValidation( validation="validation.yaml", instruction="Add threshold warnings at 5%", model="anthropic:claude-sonnet-4-20250514", ) ``` Working with edits: ```python # See the generated code edit.to_code() # See what changed edit.diff() # See which steps were modified edit.changed_steps() # Accept the edit and get a Validate object validation = edit.accept() validation.interrogate() ``` You can supply data to the edit for context: ```python edit = pb.EditValidation( validation=existing_validation, instruction="Add checks for the new columns", model="anthropic:claude-sonnet-4-20250514", data=updated_df, ) ``` ### Interactive assistant Chat with an LLM about data validation: ```python # Browser-based chat (default) pb.assistant( model="anthropic:claude-sonnet-4-20250514", data=df, tbl_name="orders", ) # Terminal-based chat pb.assistant( model="anthropic:claude-sonnet-4-20250514", data=df, display="terminal", ) ``` The assistant can: - Suggest validation steps for your data - Explain Pointblank concepts and methods - Help debug validation failures - Generate code snippets ### Model string format All LLM features use the format `"provider:model_name"`: ```python # Anthropic model="anthropic:claude-sonnet-4-20250514" model="anthropic:claude-haiku-4-5-20251001" # OpenAI model="openai:gpt-4o" model="openai:gpt-4o-mini" # Ollama (local) model="ollama:llama3" model="ollama:mistral" # AWS Bedrock model="bedrock:anthropic.claude-sonnet-4-20250514-v1:0" # Azure OpenAI model="azure-openai:my-deployment-name" ``` ### API key handling By default, the API key is read from environment variables: | Provider | Environment variable | | ------------ | ---------------------- | | Anthropic | `ANTHROPIC_API_KEY` | | OpenAI | `OPENAI_API_KEY` | | Ollama | (no key needed) | | Bedrock | AWS credentials | | Azure OpenAI | `AZURE_OPENAI_API_KEY` | Or pass explicitly: ```python draft = pb.DraftValidation( data=df, model="anthropic:claude-sonnet-4-20250514", api_key="sk-...", ) ``` ## Workflows ### Bootstrapping validation for a new dataset 1. Load your data. 2. Run `pb.DraftValidation(data=df, model="...")`. 3. Review the generated code with `draft.code`. 4. Check syntax with `draft.validate_syntax()`. 5. Copy the code into your project and customize. 6. Run `interrogate()` and iterate. ### Iterating on a validation plan 1. Start with a draft or existing validation. 2. Use `EditValidation` with natural language instructions. 3. Review changes with `edit.diff()`. 4. Accept with `edit.accept()` or iterate with another edit. ### Interactive exploration 1. Start `pb.assistant(model="...", data=df)`. 2. Ask questions about your data and validation needs. 3. Copy suggested code into your project. ## Gotchas 1. **LLM output is not guaranteed correct.** Always review generated code before using in production. 2. **`validate_syntax()` checks Python syntax, not semantics.** The code may parse but still have incorrect method calls. 3. **`max_reprompts` controls retries.** If the LLM generates invalid code, it will retry up to this many times. 4. **Ollama runs locally.** No API key needed but the model must be downloaded first with `ollama pull`. 5. **`accept()` returns an uninterrogated Validate object.** Call `.interrogate()` to execute. 6. **The assistant requires a running display.** `"browser"` opens a web interface; `"terminal"` uses the console. Neither works in non-interactive environments. 7. **Large tables may be sampled.** The LLM sees a profile/sample of the data, not every row. ## Related skills | Skill | When to use it | | ---------------- | ------------------------------------------- | | pointblank | Full Validate workflow after drafting | | write-validation | Manual validation plan composition | | scan-and-profile | Profile data before asking the LLM to draft |
references/providers-reference.md
# LLM providers reference Configuration for each supported LLM provider. ## Provider model strings Format: `"provider:model_name"` ### Anthropic ```python model="anthropic:claude-sonnet-4-20250514" model="anthropic:claude-haiku-4-5-20251001" model="anthropic:claude-opus-4-20250514" ``` Environment variable: `ANTHROPIC_API_KEY` Install: `pip install anthropic` ### OpenAI ```python model="openai:gpt-4o" model="openai:gpt-4o-mini" model="openai:gpt-4-turbo" ``` Environment variable: `OPENAI_API_KEY` Install: `pip install openai` ### Ollama (local) ```python model="ollama:llama3" model="ollama:mistral" model="ollama:codellama" ``` No API key needed. Requires Ollama running locally. Setup: ```bash # Install Ollama: https://ollama.ai ollama pull llama3 ``` ### AWS Bedrock ```python model="bedrock:anthropic.claude-sonnet-4-20250514-v1:0" model="bedrock:anthropic.claude-haiku-4-5-20251001-v1:0" ``` Uses AWS credentials from the environment (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION) or AWS profiles. Install: `pip install boto3` ### Azure OpenAI ```python model="azure-openai:my-deployment-name" ``` Environment variables: - `AZURE_OPENAI_API_KEY` - `AZURE_OPENAI_ENDPOINT` - `AZURE_OPENAI_API_VERSION` Install: `pip install openai` ## API key options ### Environment variables (recommended) ```bash export ANTHROPIC_API_KEY="sk-ant-..." export OPENAI_API_KEY="sk-..." ``` ### Explicit in code ```python draft = pb.DraftValidation( data=df, model="anthropic:claude-sonnet-4-20250514", api_key="sk-ant-...", ) ``` ### SSL verification For environments with custom certificates: ```python draft = pb.DraftValidation( data=df, model="anthropic:claude-sonnet-4-20250514", verify_ssl=False, ) ``` ## Feature support by function | Feature | DraftValidation | EditValidation | assistant() | |--------------------|:-:|:-:|:-:| | Anthropic | yes | yes | yes | | OpenAI | yes | yes | yes | | Ollama | yes | yes | yes | | Bedrock | yes | yes | yes | | Azure OpenAI | yes | yes | no | | `api_key` param | yes | yes | yes | | `verify_ssl` param | yes | yes | no | | `max_reprompts` | yes | yes | no | | Browser display | no | no | yes | | Terminal display | no | no | yes |
SKILL LAYOUT
define-contracts/
├── SKILL.md
└── references/
├── contract-reference.md
└── pipeline-reference.md
SKILL.md
--- name: define-contracts description: > Define data contracts and pipeline validation with Pointblank. Covers Contract, Step, Schema, Pipeline, and PipelineResult for enforcing structural and semantic expectations at data boundaries. Use when setting up source/target contracts, pipeline validation, or contract serialization to YAML. license: MIT compatibility: Requires Python >=3.10, pointblank installed. metadata: author: rich-iannone version: "1.0" tags: - data-contracts - pipeline-validation - schema - data-quality --- # Define Contracts Skill for defining data contracts that enforce expectations at the boundaries of data pipelines. A contract declares what a dataset must look like (schema) and what properties it must satisfy (steps), along with metadata about ownership and violation behavior. ## Quick start ```python import pointblank as pb contract = pb.Contract( name="orders-source", direction="source", schema=pb.Schema( order_id="Int64", amount="Float64", status="String", ), steps=[ pb.Step("col_vals_gt", columns="amount", value=0), pb.Step("col_vals_not_null", columns="order_id"), pb.Step("col_vals_in_set", columns="status", set=["pending", "shipped", "delivered"]), ], on_violation="raise", ) # Validate data against the contract validation = contract.validate(data=df) ``` ## Skill directory structure ``` skills/define-contracts/ +-- SKILL.md <- This file +-- references/ +-- contract-reference.md <- Contract, Step, on_violation details +-- pipeline-reference.md <- Pipeline, PipelineResult details ``` ## When to use what | I want to... | Use | | ------------------------------------- | ---------------------- | | Declare expected table structure | `Schema` | | Declare a semantic check as a step | `Step` | | Bundle schema + steps into a contract | `Contract` | | Validate data at pipeline ingestion | `Pipeline` with source | | Validate data after transformation | `Pipeline` with target | | Validate both source and target | `Pipeline` with both | | Serialize a contract to YAML | `contract.to_yaml()` | | Load a contract from YAML | `Contract.from_yaml()` | | Warn on violation without stopping | `on_violation="warn"` | | Raise an exception on violation | `on_violation="raise"` | | Log violations silently | `on_violation="log"` | ## Core concepts ### Contract A `Contract` bundles: - **name** -- identifier for the contract - **direction** -- `"source"` (incoming data) or `"target"` (output) - **schema** -- expected column names and types - **steps** -- list of `Step` objects defining semantic checks - **on_violation** -- what to do when validation fails ```python contract = pb.Contract( name="customer-data", direction="source", schema=pb.Schema( id="Int64", name="String", email="String", age="Int32", ), steps=[ pb.Step("col_vals_not_null", columns="id"), pb.Step("col_vals_gt", columns="age", value=0), pb.Step("col_vals_regex", columns="email", pattern=r".+@.+\..+"), pb.Step("rows_distinct", columns_subset=["id"]), ], version="1.0", owner="data-team", consumers=["analytics", "ml-pipeline"], description="Customer master data contract", on_violation="raise", ) ``` ### Step A `Step` is a declarative representation of a validation method call: ```python pb.Step("col_vals_gt", columns="amount", value=0) pb.Step("col_vals_between", columns="score", left=0, right=100) pb.Step("col_vals_in_set", columns="status", set=["a", "b", "c"]) pb.Step("col_vals_not_null", columns="id") pb.Step("rows_distinct", columns_subset=["id"]) pb.Step("col_schema_match", schema=my_schema, complete=True) pb.Step("row_count_match", count=1000, tol=50) ``` The `method` argument is any Validate method name. All remaining keyword arguments are passed to that method. ### Schema Define expected table structure: ```python # From keyword arguments schema = pb.Schema(id="Int64", name="String", age="Int32") # From a dictionary schema = pb.Schema({"id": "Int64", "name": "String"}) # From a list of tuples schema = pb.Schema([("id", "Int64"), ("name", "String")]) # Column names only (no type checking) schema = pb.Schema(["id", "name", "age"]) # Infer from an existing table schema = pb.schema_from_tbl(df) schema = pb.Schema.from_table(df, infer_constraints=True) ``` ### Validating against a contract ```python # Returns a Validate object (already interrogated) validation = contract.validate(data=df) # Check results validation.all_passed() validation.get_tabular_report() ``` Or convert to a Validate object for further customization: ```python v = contract.to_validate(data=df) # Add more steps if needed v = v.col_vals_gt(columns="extra_col", value=0) v = v.interrogate() ``` ### on_violation behavior | Value | Behavior | | --------- | ------------------------------------ | | `"warn"` | Print a warning message (default) | | `"raise"` | Raise an exception if any step fails | | `"log"` | Log the violation silently | ### Pipeline A `Pipeline` orchestrates source and target contract validation around a data transformation: ```python source_contract = pb.Contract( name="raw-orders", direction="source", schema=pb.Schema(id="Int64", amount="Float64"), steps=[pb.Step("col_vals_not_null", columns="id")], on_violation="raise", ) target_contract = pb.Contract( name="clean-orders", direction="target", schema=pb.Schema(id="Int64", amount="Float64", is_valid="Boolean"), steps=[ pb.Step("col_vals_gt", columns="amount", value=0), pb.Step("col_vals_not_null", columns="is_valid"), ], on_violation="warn", ) pipeline = pb.Pipeline( source=source_contract, target=target_contract, label="Order cleaning pipeline", short_circuit=True, # skip target if source fails ) def transform(df): return df.with_columns(is_valid=pl.col("amount") > 0) result = pipeline.run(data=raw_df, transform=transform) ``` ### PipelineResult ```python result.passed # True if both source and target passed result.source_passed # True if source contract passed result.target_passed # True if target contract passed result.source_validation # Validate object for source result.target_validation # Validate object for target result.transform_output # the transformed data result.get_report() # summary report string ``` ### Serialization ```python # Save contract to YAML contract.to_yaml("contracts/orders-source.yaml") # Load contract from YAML contract = pb.Contract.from_yaml("contracts/orders-source.yaml") # Dictionary round-trip d = contract.to_dict() contract = pb.Contract.from_dict(d) # Pipeline serialization pipeline.to_yaml("pipelines/order-cleaning.yaml") pipeline = pb.Pipeline.from_yaml("pipelines/order-cleaning.yaml") ``` ## Workflows ### Setting up a new contract 1. Profile the data with `pb.DataScan(data=df)` to understand its shape, types, and distributions. 2. Infer a starting schema: `schema = pb.schema_from_tbl(df)`. 3. Define steps for the semantic rules your domain requires. 4. Choose `on_violation` based on criticality. 5. Test with `contract.validate(data=df)`. 6. Serialize to YAML for version control. ### Adding contracts to an existing pipeline 1. Define source and target contracts. 2. Wrap the transformation in a `Pipeline`. 3. Use `short_circuit=True` to skip the transform when source validation fails. 4. Check `result.passed` to gate downstream processing. ### Evolving contracts over time When schema or rules change: 1. Update the schema and steps in the contract YAML. 2. Bump the `version` field. 3. Test against representative data. 4. Communicate changes to `consumers`. ## Gotchas 1. **`direction` is metadata, not enforcement.** It documents intent but doesn't change validation behavior. 2. **`on_violation="raise"` stops execution.** Use `"warn"` or `"log"` when you want to continue despite failures. 3. **`short_circuit=True` skips target validation** if source validation fails. Set to `False` to always run both. 4. **Schema type strings are backend-specific.** Use the dtype names from your backend (e.g., `"Int64"` for Polars, `"int64"` for Pandas). 5. **`to_validate()` does not call `interrogate()`.** Call it yourself if you add steps. Use `validate()` for automatic interrogation. 6. **Steps reference method names as strings.** Typos in method names surface at validation time, not at contract creation.
references/contract-reference.md
# Contract reference ## Contract constructor ```python pb.Contract( name: str, # required direction: Literal["source", "target"] = "source", schema: Schema | None = None, steps: list[Step] = [], version: str | None = None, owner: str | None = None, consumers: str | list[str] | None = None, description: str | None = None, thresholds: Thresholds | None = None, on_violation: Literal["warn", "raise", "log"] = "warn", ) ``` ## Contract methods | Method | Returns | Description | |------------------------|----------------|--------------------------------| | `validate(data)` | `Validate` | Interrogate data immediately | | `to_validate(data)` | `Validate` | Build Validate without running | | `to_dict()` | `dict` | Serialize to dictionary | | `from_dict(cls, data)` | `Contract` | Deserialize from dictionary | | `from_yaml(cls, path)` | `Contract` | Load from YAML file | | `to_yaml(path=None)` | `str \| None` | Save to YAML (or return str) | ## Step constructor ```python pb.Step(method: str, **kwargs) ``` `method` is the name of any Validate method (e.g., `"col_vals_gt"`, `"rows_distinct"`). All other keyword arguments are forwarded to that method. ## Step methods | Method | Returns | Description | |------------------------|------------|----------------------------| | `to_dict()` | `dict` | Serialize to dictionary | | `from_dict(cls, data)` | `Step` | Deserialize from dictionary| ## Valid step methods - `col_vals_gt`, `col_vals_lt`, `col_vals_ge`, `col_vals_le`, `col_vals_eq`, `col_vals_ne` - `col_vals_between`, `col_vals_outside` - `col_vals_in_set`, `col_vals_not_in_set` - `col_vals_null`, `col_vals_not_null` - `col_vals_regex`, `col_vals_within_spec`, `col_vals_expr` - `col_vals_increasing`, `col_vals_decreasing` - `col_exists`, `col_schema_match` - `col_count_match`, `row_count_match` - `col_pct_null`, `col_pct_missing` - `rows_distinct`, `rows_complete` - `data_freshness`, `tbl_match` - `conjointly`, `specially` ## YAML format ```yaml name: orders-source direction: source version: "1.0" owner: data-team consumers: - analytics - ml-pipeline description: Order data source contract on_violation: raise schema: order_id: Int64 amount: Float64 status: String steps: - method: col_vals_not_null columns: order_id - method: col_vals_gt columns: amount value: 0 - method: col_vals_in_set columns: status set: [pending, shipped, delivered] - method: rows_distinct columns_subset: [order_id] thresholds: warning: 0.01 error: 0.05 ``` ## on_violation behavior | Value | On failure... | |-----------|-----------------------------------------------| | `"warn"` | Prints warning to stderr, continues | | `"raise"` | Raises `ContractViolationError` | | `"log"` | Logs via Python `logging` module |
references/pipeline-reference.md
# Pipeline reference ## Pipeline constructor ```python pb.Pipeline( source: Contract | None = None, target: Contract | None = None, thresholds: Thresholds | None = None, actions: Actions | None = None, final_actions: FinalActions | None = None, label: str | None = None, short_circuit: bool = True, ) ``` ## Pipeline methods | Method | Returns | Description | |--------------------------|------------------|-----------------------------------| | `run(data, transform)` | `PipelineResult` | Full pipeline: source + transform + target | | `validate_source(data)` | `Validate` | Run source contract only | | `validate_target(data)` | `Validate` | Run target contract only | | `to_dict()` | `dict` | Serialize to dictionary | | `from_dict(cls, data)` | `Pipeline` | Deserialize from dictionary | | `from_yaml(cls, path)` | `Pipeline` | Load from YAML file | | `to_yaml(path=None)` | `str \| None` | Save to YAML (or return str) | ## PipelineResult | Attribute / Method | Type | Description | |-----------------------|-------------------|--------------------------------| | `source_validation` | `Validate \| None`| Source validation result | | `target_validation` | `Validate \| None`| Target validation result | | `transform_output` | `Any` | Output of the transform | | `source_passed` | `bool` | Source contract passed? | | `target_passed` | `bool` | Target contract passed? | | `passed` | `bool` | Both passed? | | `get_report()` | `str` | Summary report text | ## Pipeline execution flow ``` 1. Validate source contract against input data | +-- If source fails and short_circuit=True -> stop, return result | 2. Run transform(data) -> transformed_data | 3. Validate target contract against transformed_data | 4. Return PipelineResult ``` ## short_circuit behavior | `short_circuit` | Source fails | Source passes | |-----------------|---------------------|------------------------| | `True` | Skip transform+target| Run transform+target | | `False` | Run transform+target | Run transform+target | ## YAML format ```yaml label: Order cleaning pipeline short_circuit: true source: name: raw-orders direction: source schema: id: Int64 amount: Float64 steps: - method: col_vals_not_null columns: id on_violation: raise target: name: clean-orders direction: target schema: id: Int64 amount: Float64 is_valid: Boolean steps: - method: col_vals_gt columns: amount value: 0 on_violation: warn ``` ## Patterns ### Source-only pipeline ```python pipeline = pb.Pipeline(source=source_contract) result = pipeline.run(data=df, transform=lambda d: d) ``` ### Target-only pipeline ```python pipeline = pb.Pipeline(target=target_contract) result = pipeline.run(data=raw_df, transform=my_transform) ``` ### Accessing validation reports ```python result = pipeline.run(data=df, transform=transform) if not result.passed: if not result.source_passed: result.source_validation.get_tabular_report() if not result.target_passed: result.target_validation.get_tabular_report() ```
SKILL LAYOUT
scan-and-profile/
├── SKILL.md
└── references/
├── datascan-reference.md
└── schema-inference.md
SKILL.md
--- name: scan-and-profile description: > Profile and scan datasets with Pointblank before writing validation rules. Covers DataScan for column-level statistics, Schema inference with schema_from_tbl(), missing values analysis with missing_vals_tbl(), and table previewing. Use when exploring a new dataset or understanding data distributions before validation. license: MIT compatibility: Requires Python >=3.10, pointblank installed. metadata: author: rich-iannone version: "1.0" tags: - data-profiling - data-scan - schema - data-exploration - missing-values --- # Scan and Profile Skill for profiling datasets before writing validation rules. Understanding your data's shape, types, distributions, and missingness patterns helps you write targeted, effective validation plans. ## Quick start ```python import pointblank as pb # Scan a dataset scan = pb.DataScan(data=df, tbl_name="orders") scan.get_tabular_report() # rich HTML summary ``` ## Skill directory structure ``` skills/scan-and-profile/ +-- SKILL.md <- This file +-- references/ +-- datascan-reference.md <- DataScan details and output +-- schema-inference.md <- Schema inference and construction ``` ## When to use what | I want to... | Use | | -------------------------------- | --------------------------------------- | | Get a full column-level profile | `DataScan` | | See column types and basic stats | `DataScan.get_tabular_report()` | | Export profile as JSON | `DataScan.to_json()` | | Infer a schema from data | `schema_from_tbl()` | | Infer a schema with constraints | `Schema.from_table()` | | See a quick preview of the table | `preview()` | | Analyze missing values | `missing_vals_tbl()` | | Get row/column counts | `get_row_count()`, `get_column_count()` | ## Core concepts ### DataScan `DataScan` produces a comprehensive profile of every column in a dataset: ```python scan = pb.DataScan(data=df, tbl_name="monthly_sales") # View as HTML table scan.get_tabular_report() # Include sample data in the report scan.get_tabular_report(show_sample_data=True) # Access raw summary data scan.summary_data # Export to JSON json_str = scan.to_json() scan.save_to_json("profile_output.json") ``` The report includes per-column: - Data type - Count of non-null values - Missingness (count and percentage) - Distinct value count - Negative / zero / positive value counts (numeric) - Descriptive statistics (mean, median, std, min, max) - Quantiles (Q1, Q3, IQR) ### Shortcut: col_summary_tbl For a quick column summary without creating a DataScan object: ```python pb.col_summary_tbl(data=df, tbl_name="orders") ``` ### Table preview Quick visual preview of the first and last rows: ```python pb.preview(data=df, n_head=5, n_tail=5) # Customize pb.preview( data=df, columns_subset=["id", "name", "amount"], n_head=10, n_tail=3, limit=50, show_row_numbers=True, max_col_width=250, ) ``` ### Missing values analysis Dedicated analysis of missingness patterns: ```python # Basic missing values table pb.missing_vals_tbl(data=df) # As a heatmap pb.missing_vals_tbl(data=df, as_heatmap=True) # With structured missingness definitions missing_specs = { "measurement": pb.MissingSpec( reasons={-999: "not collected", -1: "redacted"}, ), "notes": pb.MissingSpec( reasons={"N/A": "not applicable"}, ), } pb.missing_vals_tbl(data=df, missing=missing_specs) ``` ### Schema inference Infer a schema from an existing table: ```python # Basic inference (column names and types) schema = pb.schema_from_tbl(df) print(schema.get_column_list()) print(schema.get_dtype_list()) # With constraint inference schema = pb.Schema.from_table( df, infer_constraints=True, # infer value ranges, sets, etc. categorical_threshold=20, # columns with <= 20 distinct values detect_presets=True, # detect email, URL, etc. patterns sample_size=None, # sample rows for inference (None=all) ) ``` ### Constructing schemas manually ```python # From keyword arguments schema = pb.Schema(id="Int64", name="String", amount="Float64") # From a dictionary schema = pb.Schema({"id": "Int64", "name": "String"}) # From a list of tuples schema = pb.Schema([("id", "Int64"), ("name", "String")]) # Column names only (type checking skipped) schema = pb.Schema(["id", "name", "amount"]) ``` ### Schema inspection ```python schema.get_column_list() # ["id", "name", "amount"] schema.get_dtype_list() # ["Int64", "String", "Float64"] ``` ### Quick counts ```python pb.get_row_count(df) # number of rows pb.get_column_count(df) # number of columns ``` ## Workflows ### Profiling a new dataset 1. Load or connect to the data. 2. Run `pb.preview(data)` for a quick look. 3. Run `pb.DataScan(data=df).get_tabular_report()` for full stats. 4. Run `pb.missing_vals_tbl(data=df)` to understand missingness. 5. Infer a schema: `schema = pb.schema_from_tbl(df)`. 6. Use the profile to inform validation rules. ### From profile to validation plan 1. Profile the data with `DataScan`. 2. Note columns with high missingness -- add `col_pct_null` checks. 3. Note columns with few distinct values -- add `col_vals_in_set`. 4. Note numeric ranges -- add `col_vals_between` checks. 5. Infer schema and use in `col_schema_match`. 6. Build the validation plan with the `write-validation` skill. ### Comparing profiles over time ```python scan_today = pb.DataScan(data=today_df) scan_yesterday = pb.DataScan(data=yesterday_df) # Compare by exporting to JSON scan_today.save_to_json("profile_today.json") scan_yesterday.save_to_json("profile_yesterday.json") ``` ## Gotchas 1. **DataScan reads the full table.** For large datasets, consider sampling first. 2. **Schema type names are backend-specific.** Polars uses `"Int64"`, Pandas uses `"int64"`. Use `schema_from_tbl()` to get the right names automatically. 3. **`schema_from_tbl` infers from current data.** If the data has unexpected types (e.g., string column with numbers), the inferred schema reflects that. 4. **`missing_vals_tbl` only shows null by default.** Pass `MissingSpec` definitions to include sentinel values. 5. **`preview()` returns a GT table object.** In notebooks it renders automatically; in scripts, you may need to display it. ## Related skills | Skill | When to use it | | ---------------- | -------------------------------------------- | | pointblank | Full Validate workflow overview | | write-validation | Build validation plans from profile insights | | generate-data | Create synthetic data matching a schema |
references/datascan-reference.md
# DataScan reference ## Constructor ```python pb.DataScan( data: Any, # DataFrame, Ibis table, or file path tbl_name: str | None = None, ) ``` ## Properties | Property | Type | Description | |----------------|--------|--------------------------------------| | `summary_data` | `dict` | Raw column-level statistics | ## Methods | Method | Returns | Description | |------------------------------------------|----------|---------------------------------| | `get_tabular_report(show_sample_data=False)` | `GT` | HTML summary table | | `to_json()` | `str` | JSON string of profile | | `save_to_json(output_file)` | `None` | Write JSON to file | ## Column statistics in summary_data For each column, the summary includes: | Statistic | Description | |-----------------|------------------------------------------| | `dtype` | Column data type | | `n_non_null` | Count of non-null values | | `n_null` | Count of null values | | `pct_null` | Percentage null | | `n_distinct` | Count of distinct values | | `pct_distinct` | Percentage distinct | | `n_negative` | Count of negative values (numeric) | | `n_zero` | Count of zero values (numeric) | | `n_positive` | Count of positive values (numeric) | | `mean` | Arithmetic mean (numeric) | | `median` | Median value (numeric) | | `std` | Standard deviation (numeric) | | `min` | Minimum value | | `max` | Maximum value | | `q1` | First quartile (numeric) | | `q3` | Third quartile (numeric) | ## Shortcut function ```python pb.col_summary_tbl(data=df, tbl_name="my_table") ``` Equivalent to `DataScan(data, tbl_name).get_tabular_report()`. ## missing_vals_tbl ```python pb.missing_vals_tbl( data: Any, missing: dict[str, MissingSpec] | None = None, as_heatmap: bool = False, ) -> GT ``` Analyzes null and missing values across all columns. Returns a GT table showing missingness counts and patterns. With `as_heatmap=True`, renders a visual heatmap of missingness across rows and columns. ## preview ```python pb.preview( data: Any, columns_subset: list[str] | None = None, n_head: int = 5, n_tail: int = 5, limit: int = 50, show_row_numbers: bool = True, max_col_width: int = 250, min_tbl_width: int = 500, incl_header: bool | None = None, ) -> GT ``` ## Utility functions ```python pb.get_row_count(data: Any) -> int pb.get_column_count(data: Any) -> int ```
references/schema-inference.md
# Schema inference reference ## schema_from_tbl ```python pb.schema_from_tbl( tbl: Any, *, infer_constraints: bool = True, categorical_threshold: int = 20, detect_presets: bool = True, sample_size: int | None = None, ) -> Schema ``` Creates a Schema from an existing table with optional constraint inference. ### Parameters | Parameter | Default | Description | |--------------------------|---------|-------------------------------------| | `infer_constraints` | `True` | Infer value ranges, allowed sets | | `categorical_threshold` | `20` | Max distinct values for categorical | | `detect_presets` | `True` | Detect email, URL, phone patterns | | `sample_size` | `None` | Rows to sample (None = all) | ## Schema.from_table Class method with the same parameters: ```python schema = pb.Schema.from_table( df, infer_constraints=True, categorical_threshold=20, detect_presets=True, sample_size=1000, ) ``` ## Schema constructor ```python # Keyword arguments pb.Schema(id="Int64", name="String", amount="Float64") # Dictionary pb.Schema({"id": "Int64", "name": "String"}) # List of tuples pb.Schema([("id", "Int64"), ("name", "String")]) # Column names only pb.Schema(["id", "name", "amount"]) # From existing table pb.Schema(tbl=df) ``` ## Schema methods | Method | Returns | Description | |---------------------------------|----------------|------------------------------| | `get_column_list()` | `list[str]` | Column names | | `get_dtype_list()` | `list[str]` | Data type strings | | `get_schema_coerced(to=None)` | `Schema` | Coerced to a target backend | | `generate(n=100, ...)` | `DataFrame` | Generate synthetic data | ## Common dtype strings by backend | Concept | Polars | Pandas | |-----------|-------------|--------------| | Integer | `Int8/16/32/64`, `UInt8/16/32/64` | `int8/16/32/64`, `uint8/16/32/64` | | Float | `Float32/64` | `float32/64` | | String | `String` | `object`, `string` | | Boolean | `Boolean` | `bool` | | Date | `Date` | `datetime64[ns]` | | Datetime | `Datetime` | `datetime64[ns]` | | Duration | `Duration` | `timedelta64[ns]` | ## Using inferred schema in validation ```python schema = pb.schema_from_tbl(df) validation = ( pb.Validate(data=new_df) .col_schema_match( schema=schema, complete=True, # all columns must be present in_order=True, # column order must match full_match_dtypes=True, ) .interrogate() ) ``` ### col_schema_match parameters | Parameter | Default | Description | |----------------------------|---------|-------------------------------------| | `schema` | required | Schema object to match against | | `complete` | `True` | All schema columns must exist | | `in_order` | `True` | Column order must match | | `case_sensitive_colnames` | `True` | Column name comparison | | `case_sensitive_dtypes` | `True` | Dtype string comparison | | `full_match_dtypes` | `True` | Exact dtype match required |
SKILL LAYOUT
generate-data/
├── SKILL.md
└── references/
├── field-reference.md
└── presets-reference.md
SKILL.md
--- name: generate-data description: > Generate synthetic datasets with Pointblank using Schema and field classes. Covers IntField, FloatField, StringField, BoolField, DateField, DatetimeField, and more. Supports presets (name, email, address, etc.), country-specific data, nullable columns, unique constraints, and profile_fields() for person data. Use when creating test data, fixtures, or synthetic datasets for validation testing. license: MIT compatibility: Requires Python >=3.10, pointblank installed. metadata: author: rich-iannone version: "1.0" tags: - data-generation - synthetic-data - test-data - schema - faker --- # Generate Data Skill for creating synthetic datasets from schema definitions and field specifications. Useful for testing validation rules, creating fixtures, generating demo data, and prototyping pipelines. ## Quick start ```python import pointblank as pb schema = pb.Schema( id=pb.int_field(min_val=1, max_val=10000, unique=True), name=pb.string_field(preset="name"), email=pb.string_field(preset="email"), age=pb.int_field(min_val=18, max_val=95), score=pb.float_field(min_val=0.0, max_val=100.0, precision=2), active=pb.bool_field(p_true=0.8), ) df = schema.generate(n=1000, seed=42) ``` ## Skill directory structure ``` skills/generate-data/ +-- SKILL.md <- This file +-- references/ +-- field-reference.md <- All field types and parameters +-- presets-reference.md <- Available string presets ``` ## When to use what | I want to... | Use | | ----------------------------------- | ------------------------------------- | | Generate a dataset from a schema | `schema.generate()` | | Generate without creating a Schema | `pb.generate_dataset()` | | Define integer columns | `int_field()` | | Define float columns | `float_field()` | | Define string columns with patterns | `string_field(pattern=)` | | Define string columns with presets | `string_field(preset=)` | | Define boolean columns | `bool_field()` | | Define date columns | `date_field()` | | Define datetime columns | `datetime_field()` | | Define time columns | `time_field()` | | Define duration columns | `duration_field()` | | Add person profile fields | `profile_fields()` | | Generate country-specific data | `generate(country="DE")` | | Make columns nullable | `nullable=True, null_probability=0.1` | | Ensure unique values | `unique=True` | | Use a custom generator function | `generator=my_func` | ## Core concepts ### Schema-based generation Define columns using field classes, then generate: ```python schema = pb.Schema( order_id=pb.int_field(min_val=1, max_val=99999, unique=True), product=pb.string_field(allowed=["Widget A", "Widget B", "Gadget"]), quantity=pb.int_field(min_val=1, max_val=100), price=pb.float_field(min_val=0.99, max_val=999.99, precision=2), shipped=pb.bool_field(p_true=0.7), order_date=pb.date_field(min_date="2024-01-01", max_date="2024-12-31"), ) df = schema.generate(n=500, seed=42, output="polars") ``` ### generate() parameters | Parameter | Default | Description | | ---------- | ---------- | --------------------------------------- | | `n` | `100` | Number of rows to generate | | `seed` | `None` | Random seed for reproducibility | | `output` | `"polars"` | Output format: `"polars"` or `"pandas"` | | `country` | `"US"` | Country code for locale-aware data | | `shuffle` | `True` | Shuffle rows after generation | | `weighted` | `True` | Use weighted distributions | ### generate_dataset() convenience function ```python df = pb.generate_dataset(schema, n=500, seed=42) ``` ### Nullable columns Any field type supports nulls: ```python pb.int_field(min_val=0, max_val=100, nullable=True, null_probability=0.1) pb.string_field(preset="email", nullable=True, null_probability=0.05) ``` ### Unique constraints Ensure all generated values are distinct: ```python pb.int_field(min_val=1, max_val=10000, unique=True) pb.string_field(preset="email", unique=True) ``` ### Allowed values (categorical) Restrict to a specific set of values: ```python pb.int_field(allowed=[1, 2, 3, 5, 8, 13]) pb.float_field(allowed=[0.5, 1.0, 1.5, 2.0]) pb.string_field(allowed=["low", "medium", "high"]) ``` ### String patterns Generate strings matching a pattern: ```python pb.string_field(pattern=r"[A-Z]{3}-\d{4}") # "ABC-1234" pb.string_field(pattern=r"INV-\d{6}") # "INV-003847" pb.string_field(pattern=r"[a-z]{5,10}") # random lowercase ``` ### String presets Use built-in presets for realistic data: ```python pb.string_field(preset="name") # full names pb.string_field(preset="email") # email addresses pb.string_field(preset="address") # street addresses pb.string_field(preset="city") # city names pb.string_field(preset="phone_number") # phone numbers pb.string_field(preset="company") # company names pb.string_field(preset="job") # job titles pb.string_field(preset="url") # URLs pb.string_field(preset="uuid4") # UUIDs pb.string_field(preset="iban") # IBANs pb.string_field(preset="ssn") # SSNs ``` Presets produce country-specific data when `country` is set. ### Profile fields Generate person-related fields as a group: ```python fields = pb.profile_fields( set="standard", # "standard" or "extended" split_name=True, # first_name + last_name vs full name include=None, # specific fields to include exclude=None, # specific fields to exclude prefix=None, # prefix for field names ) schema = pb.Schema( id=pb.int_field(min_val=1, max_val=99999, unique=True), **fields, ) df = schema.generate(n=100, country="US") ``` ### Custom generators Supply your own generator function: ```python import random def custom_sku(): return f"SKU-{random.randint(1000, 9999)}" schema = pb.Schema( sku=pb.string_field(generator=custom_sku), ) ``` ### Country-specific generation Over 100 countries supported: ```python # German names, addresses, phone numbers df = schema.generate(n=100, country="DE") # Japanese df = schema.generate(n=100, country="JP") # Brazilian df = schema.generate(n=100, country="BR") ``` ## Workflows ### Creating test data for validation rules 1. Define the schema matching your production table. 2. Use field constraints to generate realistic ranges. 3. Add some nullable columns to test null handling. 4. Generate the dataset. 5. Run your validation plan against it. ```python schema = pb.Schema( id=pb.int_field(min_val=1, max_val=10000, unique=True), amount=pb.float_field(min_val=-10, max_val=1000, precision=2), status=pb.string_field(allowed=["active", "inactive", "INVALID"]), email=pb.string_field(preset="email", nullable=True, null_probability=0.1), ) test_df = schema.generate(n=500, seed=42) validation = ( pb.Validate(data=test_df) .col_vals_gt(columns="amount", value=0) .col_vals_in_set(columns="status", set=["active", "inactive"]) .col_vals_not_null(columns="email") .interrogate() ) ``` ### Generating fixtures from an existing table ```python # Infer schema from real data schema = pb.Schema.from_table( production_df, infer_constraints=True, categorical_threshold=20, ) # Generate synthetic version fixture = schema.generate(n=100, seed=1) ``` ## Gotchas 1. **`unique=True` needs a large enough range.** If `max_val - min_val` < `n`, generation will fail for integer fields. 2. **Only one of `preset`, `pattern`, `allowed` per StringField.** They are mutually exclusive. 3. **Presets require `faker` to be installed.** Install with `pip install pointblank[faker]` or `pip install faker`. 4. **`seed` makes generation reproducible** but the same seed with different `n` produces different data (not a prefix of larger). 5. **`output` only supports `"polars"` and `"pandas"`.** For other formats, convert after generation. 6. **`null_probability=0` with `nullable=True`** generates no nulls. Set the probability to get actual null values.
references/field-reference.md
# Field reference All field types for synthetic data generation. ## Common parameters (all field types) | Parameter | Type | Default | Description | |--------------------|--------------------|----------|------------------------------| | `nullable` | `bool` | `False` | Allow null values | | `null_probability` | `float` | `0.0` | Fraction of nulls (0.0-1.0) | | `unique` | `bool` | `False` | All values must be distinct | | `generator` | `Callable \| None` | `None` | Custom generator function | ## IntField / int_field() ```python pb.int_field( min_val: int | None = None, max_val: int | None = None, allowed: list[int] | None = None, dtype: str = "Int64", # Int8/16/32/64, UInt8/16/32/64 nullable=False, null_probability=0.0, unique=False, generator=None, ) ``` ## FloatField / float_field() ```python pb.float_field( min_val: float | None = None, max_val: float | None = None, allowed: list[float] | None = None, precision: int | None = None, # decimal places dtype: str = "Float64", # Float32 or Float64 nullable=False, null_probability=0.0, unique=False, generator=None, ) ``` ## StringField / string_field() ```python pb.string_field( min_length: int | None = None, max_length: int | None = None, pattern: str | None = None, # regex pattern to match preset: str | None = None, # named preset (e.g., "email") allowed: list[str] | None = None, nullable=False, null_probability=0.0, unique=False, generator=None, ) ``` Only one of `preset`, `pattern`, or `allowed` may be set. ## BoolField / bool_field() ```python pb.bool_field( p_true: float = 0.5, # probability of True nullable=False, null_probability=0.0, unique=False, generator=None, ) ``` ## DateField / date_field() ```python pb.date_field( min_date: str | date | None = None, # "2024-01-01" or date() max_date: str | date | None = None, nullable=False, null_probability=0.0, unique=False, generator=None, ) ``` ## DatetimeField / datetime_field() ```python pb.datetime_field( min_date: str | datetime | None = None, max_date: str | datetime | None = None, nullable=False, null_probability=0.0, unique=False, generator=None, ) ``` ## TimeField / time_field() ```python pb.time_field( min_time: str | time | None = None, max_time: str | time | None = None, nullable=False, null_probability=0.0, unique=False, generator=None, ) ``` ## DurationField / duration_field() ```python pb.duration_field( min_duration: str | timedelta | None = None, max_duration: str | timedelta | None = None, nullable=False, null_probability=0.0, unique=False, generator=None, ) ``` ## profile_fields() ```python pb.profile_fields( set: str = "standard", # "standard" or "extended" split_name: bool = True, # first_name + last_name vs name include: list[str] | None = None, exclude: list[str] | None = None, prefix: str | None = None, ) -> dict[str, StringField] ``` Returns a dict of StringField objects suitable for `**`-unpacking into `Schema()`. Standard set includes: name (or first_name + last_name), email, phone_number, address, city, country. Extended set adds: company, job, url, and more.
references/presets-reference.md
# String presets reference Available preset values for `string_field(preset="...")`. Presets generate realistic, locale-aware data using Faker under the hood. ## Person | Preset | Example output | |-----------------|----------------------------| | `name` | "John Smith" | | `first_name` | "John" | | `last_name` | "Smith" | | `prefix` | "Mr." | | `suffix` | "Jr." | ## Contact | Preset | Example output | |-----------------|----------------------------| | `email` | "john.smith@example.com" | | `phone_number` | "+1-555-123-4567" | | `url` | "https://example.com" | ## Address | Preset | Example output | |-----------------|----------------------------| | `address` | "123 Main St, Apt 4" | | `city` | "New York" | | `state` | "California" | | `zipcode` | "90210" | | `country` | "United States" | | `street_address`| "123 Main Street" | ## Business | Preset | Example output | |-----------------|----------------------------| | `company` | "Acme Corporation" | | `job` | "Software Engineer" | | `catch_phrase` | "Innovative solutions" | ## Internet | Preset | Example output | |-----------------|----------------------------| | `user_name` | "jsmith42" | | `domain_name` | "example.com" | | `ipv4` | "192.168.1.1" | | `ipv6` | "2001:db8::1" | | `mac_address` | "00:1A:2B:3C:4D:5E" | ## Identifiers | Preset | Example output | |-----------------|----------------------------| | `uuid4` | "a1b2c3d4-e5f6-..." | | `iban` | "DE89 3704 0044 0532 ..." | | `ssn` | "123-45-6789" | | `license_plate` | "ABC-1234" | ## Text | Preset | Example output | |-----------------|----------------------------| | `text` | "Lorem ipsum dolor..." | | `sentence` | "The quick brown fox." | | `paragraph` | "Lorem ipsum dolor sit..." | | `word` | "lorem" | ## Finance | Preset | Example output | |---------------------|------------------------| | `credit_card_number` | "4111111111111111" | | `currency_code` | "USD" | | `cryptocurrency_code`| "BTC" | ## Country-specific behavior Presets produce locale-appropriate data based on the `country` parameter passed to `generate()`: ```python schema = pb.Schema( name=pb.string_field(preset="name"), city=pb.string_field(preset="city"), phone=pb.string_field(preset="phone_number"), ) # US data us_df = schema.generate(n=100, country="US") # German data de_df = schema.generate(n=100, country="DE") # Japanese data jp_df = schema.generate(n=100, country="JP") ``` Over 100 country codes are supported, using standard ISO 3166-1 alpha-2 codes.