Skip to content

Commit 86ae1ff

Browse files
authored
Merge pull request #417 from posit-dev/feat-add-skills
feat: package several skills with Pointblank
2 parents b69aace + d70f05e commit 86ae1ff

21 files changed

Lines changed: 3575 additions & 0 deletions

File tree

great-docs.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,29 @@ exclude:
112112
- config
113113
- assistant.MODEL_PROVIDERS
114114

115+
# Agent Skills
116+
# ------------
117+
# Multiple named skills for the documentation site. Each entry maps
118+
# a human-readable name to a SKILL.md file path. All skills appear
119+
# on the Skills page with a switcher bar and are placed in
120+
# .well-known/agent-skills/ for auto-discovery.
121+
skill:
122+
skills:
123+
- name: pointblank
124+
file: skills/pointblank/SKILL.md
125+
- name: write-validation
126+
file: skills/write-validation/SKILL.md
127+
- name: validate-yaml
128+
file: skills/validate-yaml/SKILL.md
129+
- name: draft-validation
130+
file: skills/draft-validation/SKILL.md
131+
- name: define-contracts
132+
file: skills/define-contracts/SKILL.md
133+
- name: scan-and-profile
134+
file: skills/scan-and-profile/SKILL.md
135+
- name: generate-data
136+
file: skills/generate-data/SKILL.md
137+
115138
# API Reference Structure
116139
# Organized to match the current quartodoc sections
117140
reference:

skills/define-contracts/SKILL.md

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
---
2+
name: define-contracts
3+
description: >
4+
Define data contracts and pipeline validation with Pointblank.
5+
Covers Contract, Step, Schema, Pipeline, and PipelineResult for
6+
enforcing structural and semantic expectations at data boundaries.
7+
Use when setting up source/target contracts, pipeline validation,
8+
or contract serialization to YAML.
9+
license: MIT
10+
compatibility: Requires Python >=3.10, pointblank installed.
11+
metadata:
12+
author: rich-iannone
13+
version: "1.0"
14+
tags:
15+
- data-contracts
16+
- pipeline-validation
17+
- schema
18+
- data-quality
19+
---
20+
21+
# Define Contracts
22+
23+
Skill for defining data contracts that enforce expectations at the
24+
boundaries of data pipelines. A contract declares what a dataset
25+
must look like (schema) and what properties it must satisfy (steps),
26+
along with metadata about ownership and violation behavior.
27+
28+
## Quick start
29+
30+
```python
31+
import pointblank as pb
32+
33+
contract = pb.Contract(
34+
name="orders-source",
35+
direction="source",
36+
schema=pb.Schema(
37+
order_id="Int64",
38+
amount="Float64",
39+
status="String",
40+
),
41+
steps=[
42+
pb.Step("col_vals_gt", columns="amount", value=0),
43+
pb.Step("col_vals_not_null", columns="order_id"),
44+
pb.Step("col_vals_in_set", columns="status",
45+
set=["pending", "shipped", "delivered"]),
46+
],
47+
on_violation="raise",
48+
)
49+
50+
# Validate data against the contract
51+
validation = contract.validate(data=df)
52+
```
53+
54+
## Skill directory structure
55+
56+
```
57+
skills/define-contracts/
58+
+-- SKILL.md <- This file
59+
+-- references/
60+
+-- contract-reference.md <- Contract, Step, on_violation details
61+
+-- pipeline-reference.md <- Pipeline, PipelineResult details
62+
```
63+
64+
## When to use what
65+
66+
| I want to... | Use |
67+
| ------------------------------------- | ---------------------- |
68+
| Declare expected table structure | `Schema` |
69+
| Declare a semantic check as a step | `Step` |
70+
| Bundle schema + steps into a contract | `Contract` |
71+
| Validate data at pipeline ingestion | `Pipeline` with source |
72+
| Validate data after transformation | `Pipeline` with target |
73+
| Validate both source and target | `Pipeline` with both |
74+
| Serialize a contract to YAML | `contract.to_yaml()` |
75+
| Load a contract from YAML | `Contract.from_yaml()` |
76+
| Warn on violation without stopping | `on_violation="warn"` |
77+
| Raise an exception on violation | `on_violation="raise"` |
78+
| Log violations silently | `on_violation="log"` |
79+
80+
## Core concepts
81+
82+
### Contract
83+
84+
A `Contract` bundles:
85+
86+
- **name** -- identifier for the contract
87+
- **direction** -- `"source"` (incoming data) or `"target"` (output)
88+
- **schema** -- expected column names and types
89+
- **steps** -- list of `Step` objects defining semantic checks
90+
- **on_violation** -- what to do when validation fails
91+
92+
```python
93+
contract = pb.Contract(
94+
name="customer-data",
95+
direction="source",
96+
schema=pb.Schema(
97+
id="Int64",
98+
name="String",
99+
email="String",
100+
age="Int32",
101+
),
102+
steps=[
103+
pb.Step("col_vals_not_null", columns="id"),
104+
pb.Step("col_vals_gt", columns="age", value=0),
105+
pb.Step("col_vals_regex", columns="email",
106+
pattern=r".+@.+\..+"),
107+
pb.Step("rows_distinct", columns_subset=["id"]),
108+
],
109+
version="1.0",
110+
owner="data-team",
111+
consumers=["analytics", "ml-pipeline"],
112+
description="Customer master data contract",
113+
on_violation="raise",
114+
)
115+
```
116+
117+
### Step
118+
119+
A `Step` is a declarative representation of a validation method call:
120+
121+
```python
122+
pb.Step("col_vals_gt", columns="amount", value=0)
123+
pb.Step("col_vals_between", columns="score", left=0, right=100)
124+
pb.Step("col_vals_in_set", columns="status", set=["a", "b", "c"])
125+
pb.Step("col_vals_not_null", columns="id")
126+
pb.Step("rows_distinct", columns_subset=["id"])
127+
pb.Step("col_schema_match", schema=my_schema, complete=True)
128+
pb.Step("row_count_match", count=1000, tol=50)
129+
```
130+
131+
The `method` argument is any Validate method name. All remaining
132+
keyword arguments are passed to that method.
133+
134+
### Schema
135+
136+
Define expected table structure:
137+
138+
```python
139+
# From keyword arguments
140+
schema = pb.Schema(id="Int64", name="String", age="Int32")
141+
142+
# From a dictionary
143+
schema = pb.Schema({"id": "Int64", "name": "String"})
144+
145+
# From a list of tuples
146+
schema = pb.Schema([("id", "Int64"), ("name", "String")])
147+
148+
# Column names only (no type checking)
149+
schema = pb.Schema(["id", "name", "age"])
150+
151+
# Infer from an existing table
152+
schema = pb.schema_from_tbl(df)
153+
schema = pb.Schema.from_table(df, infer_constraints=True)
154+
```
155+
156+
### Validating against a contract
157+
158+
```python
159+
# Returns a Validate object (already interrogated)
160+
validation = contract.validate(data=df)
161+
162+
# Check results
163+
validation.all_passed()
164+
validation.get_tabular_report()
165+
```
166+
167+
Or convert to a Validate object for further customization:
168+
169+
```python
170+
v = contract.to_validate(data=df)
171+
# Add more steps if needed
172+
v = v.col_vals_gt(columns="extra_col", value=0)
173+
v = v.interrogate()
174+
```
175+
176+
### on_violation behavior
177+
178+
| Value | Behavior |
179+
| --------- | ------------------------------------ |
180+
| `"warn"` | Print a warning message (default) |
181+
| `"raise"` | Raise an exception if any step fails |
182+
| `"log"` | Log the violation silently |
183+
184+
### Pipeline
185+
186+
A `Pipeline` orchestrates source and target contract validation
187+
around a data transformation:
188+
189+
```python
190+
source_contract = pb.Contract(
191+
name="raw-orders",
192+
direction="source",
193+
schema=pb.Schema(id="Int64", amount="Float64"),
194+
steps=[pb.Step("col_vals_not_null", columns="id")],
195+
on_violation="raise",
196+
)
197+
198+
target_contract = pb.Contract(
199+
name="clean-orders",
200+
direction="target",
201+
schema=pb.Schema(id="Int64", amount="Float64", is_valid="Boolean"),
202+
steps=[
203+
pb.Step("col_vals_gt", columns="amount", value=0),
204+
pb.Step("col_vals_not_null", columns="is_valid"),
205+
],
206+
on_violation="warn",
207+
)
208+
209+
pipeline = pb.Pipeline(
210+
source=source_contract,
211+
target=target_contract,
212+
label="Order cleaning pipeline",
213+
short_circuit=True, # skip target if source fails
214+
)
215+
216+
def transform(df):
217+
return df.with_columns(is_valid=pl.col("amount") > 0)
218+
219+
result = pipeline.run(data=raw_df, transform=transform)
220+
```
221+
222+
### PipelineResult
223+
224+
```python
225+
result.passed # True if both source and target passed
226+
result.source_passed # True if source contract passed
227+
result.target_passed # True if target contract passed
228+
result.source_validation # Validate object for source
229+
result.target_validation # Validate object for target
230+
result.transform_output # the transformed data
231+
result.get_report() # summary report string
232+
```
233+
234+
### Serialization
235+
236+
```python
237+
# Save contract to YAML
238+
contract.to_yaml("contracts/orders-source.yaml")
239+
240+
# Load contract from YAML
241+
contract = pb.Contract.from_yaml("contracts/orders-source.yaml")
242+
243+
# Dictionary round-trip
244+
d = contract.to_dict()
245+
contract = pb.Contract.from_dict(d)
246+
247+
# Pipeline serialization
248+
pipeline.to_yaml("pipelines/order-cleaning.yaml")
249+
pipeline = pb.Pipeline.from_yaml("pipelines/order-cleaning.yaml")
250+
```
251+
252+
## Workflows
253+
254+
### Setting up a new contract
255+
256+
1. Profile the data with `pb.DataScan(data=df)` to understand its
257+
shape, types, and distributions.
258+
2. Infer a starting schema: `schema = pb.schema_from_tbl(df)`.
259+
3. Define steps for the semantic rules your domain requires.
260+
4. Choose `on_violation` based on criticality.
261+
5. Test with `contract.validate(data=df)`.
262+
6. Serialize to YAML for version control.
263+
264+
### Adding contracts to an existing pipeline
265+
266+
1. Define source and target contracts.
267+
2. Wrap the transformation in a `Pipeline`.
268+
3. Use `short_circuit=True` to skip the transform when source
269+
validation fails.
270+
4. Check `result.passed` to gate downstream processing.
271+
272+
### Evolving contracts over time
273+
274+
When schema or rules change:
275+
276+
1. Update the schema and steps in the contract YAML.
277+
2. Bump the `version` field.
278+
3. Test against representative data.
279+
4. Communicate changes to `consumers`.
280+
281+
## Gotchas
282+
283+
1. **`direction` is metadata, not enforcement.** It documents intent
284+
but doesn't change validation behavior.
285+
2. **`on_violation="raise"` stops execution.** Use `"warn"` or
286+
`"log"` when you want to continue despite failures.
287+
3. **`short_circuit=True` skips target validation** if source
288+
validation fails. Set to `False` to always run both.
289+
4. **Schema type strings are backend-specific.** Use the dtype names
290+
from your backend (e.g., `"Int64"` for Polars, `"int64"` for
291+
Pandas).
292+
5. **`to_validate()` does not call `interrogate()`.** Call it yourself
293+
if you add steps. Use `validate()` for automatic interrogation.
294+
6. **Steps reference method names as strings.** Typos in method names
295+
surface at validation time, not at contract creation.

0 commit comments

Comments
 (0)