Metadata-Version: 2.4
Name: watchfire
Version: 0.3.1
Summary: Static analysis for UK financial regulatory citations in Python code.
Project-URL: Homepage, https://github.com/OpenAfterHours/watchfire
Project-URL: Repository, https://github.com/OpenAfterHours/watchfire.git
Project-URL: Issues, https://github.com/OpenAfterHours/watchfire/issues
Project-URL: Changelog, https://github.com/OpenAfterHours/watchfire/blob/master/CHANGELOG.md
Author: OpenAfterHours
Maintainer: OpenAfterHours
License: Apache-2.0
License-File: LICENSE
Keywords: audit,banking,basel,citations,compliance,crr,pra,regulation,static-analysis
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: polars>=1.0.0
Requires-Dist: typer>=0.12.0
Provides-Extra: build
Requires-Dist: httpx>=0.27.0; extra == 'build'
Requires-Dist: lxml>=5.0.0; extra == 'build'
Requires-Dist: pypdf>=4.0.0; extra == 'build'
Provides-Extra: dev
Requires-Dist: pytest-benchmark>=4.0.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: ruff>=0.3.0; extra == 'dev'
Requires-Dist: ty; extra == 'dev'
Requires-Dist: zensical>=0.0.41; extra == 'dev'
Description-Content-Type: text/markdown

# watchfire

> [!IMPORTANT]
> *This package is still in development and is not production ready.*

Static analysis for UK financial regulatory citations in Python code.

`watchfire` lets you annotate Python functions with citations to UK
financial regulation — CRR, the PRA Rulebook, PRA Policy Statements,
PRA Supervisory Statements — and then check those citations against a
bundled, versioned snapshot of the rulebook. The intent is to make the
mapping from compliance code to the regulation it implements *executable*:
runs in CI, lives next to the code, and breaks the build when it drifts.

## Why this exists

Regulatory engineering teams in UK banks need an auditable trail from
every formula in their RWA / capital code back to a specific article of
the CRR or rule in the PRA Rulebook. Today that trail is produced by
hand in Word documents that drift the moment anyone changes the code.
`watchfire` puts the mapping where the code is, checks it on every commit,
and gives reviewers, auditors, and the PRA something verifiable to look
at instead of a stale spreadsheet.

The first real user is
[`OpenAfterHours/rwa_calculator`](https://github.com/OpenAfterHours/rwa_calculator)
— a UK CRR / Basel 3.1 credit-risk RWA library whose formulas need to
trace back to specific articles. If you're building something similar,
`watchfire` is for you.

## Install

```bash
uv add watchfire
# or
pip install watchfire
```

Python 3.11+.

## Quickstart

Decorate a function with the regulation it implements:

```python
from watchfire import cites


@cites("CRR Art. 153(1)(a)")
def corporate_rw(pd: float, lgd: float, maturity: float) -> float:
    """Risk-weight under the IRB approach for corporates."""
    ...
```

Add a `[tool.watchfire]` table to your `pyproject.toml`:

```toml
[tool.watchfire]
rulebook_version = "2024-07-09"
instruments = ["CRR", "PRA_RULEBOOK", "PS", "SS"]
source_paths = ["src"]
```

Run the checker:

```bash
$ uv run watchfire check
watchfire: checked 47 citation(s); no issues found.
```

If a citation fails to parse or points at something the bundled index
doesn't know about, `watchfire check` exits non-zero and prints a line
per finding with file, line number, and reason — suitable for CI:

```
src/myproj/sa.py:31: sovereign_rw: unknown_article: citation 'CRR Art. 999' points to CRR Article 999, which is not in the bundled rulebook index
watchfire: 1 failing finding(s), 0 unresolved, out of 12 resolved citation(s).
```

`@cites` is a no-op at runtime — it attaches the parsed citations to
the function as a `tuple[Citation, ...]` on `__watchfire__` and returns
the function unchanged. No wrapping, no overhead, nothing to debug.

Multiple `@cites` decorators stack when the same rule lives in more
than one instrument (for example a CRR article and its corresponding
PRA Policy Statement). The outermost decorator is the primary citation
and appears first in `__watchfire__`; `watchfire check` reports one
finding per decorator.

```python
@cites("CRR Art. 163")            # outer / primary
@cites("PS1/26, paragraph 163")  # inner / secondary
def apply_pd_floor(...): ...
```

## Traceability matrix

`watchfire matrix` is the reverse lookup: given a project full of
decorated functions, group every citation by its article and list the
functions that cite it. The output is intended as an audit deliverable —
attach it to a PR or commit it as a CI artifact.

```bash
$ uv run watchfire matrix
CRR Art. 4(1)(75)                        Definitions: corporate           1 site
  src/myproj/irb.py:21  is_corporate

CRR Art. 113                             SA risk weights                  1 site
  src/myproj/sa.py:6   calculate_sa_rwa

CRR Art. 153                             IRB risk weights                 1 site
  src/myproj/irb.py:7   corporate_rw     CRR Art. 153(1)(a)

SS1/23, paragraph 2.5                    (not in index)                   1 site
  src/myproj/irb.py:17  model_validation

watchfire matrix: 4 entries, 4 citation sites across 4 functions.
```

Useful flags:

- `--format {text,markdown,json}` — `markdown` produces a copy-paste
  table for PR comments; `json` is for downstream tooling.
- `--specificity {article,full}` — default `article` collapses
  sub-paragraph detail into one row per article. Use `full` for the
  audit-grade view that keeps `(1)(a)` separate from `(1)(b)`.
- `--instrument CRR --article 153` — narrow to one article. Answers
  "which functions cite Art. 153?".

`watchfire matrix` exits 0 unconditionally; it's informational. Parse
failures and unresolved citations are counted in the footer — fix them
with `watchfire check`.

## Citation grammar

The parser accepts canonical UK regulatory citation strings. The shape
that comes out the other side is a frozen `Citation` dataclass; see
`watchfire.Citation` for the field list.

| Input                              | Meaning                                                  |
| ---------------------------------- | -------------------------------------------------------- |
| `CRR Art. 153`                     | Whole article                                            |
| `CRR Article 153`                  | (alternate spelling)                                     |
| `CRR Art. 153(1)`                  | Paragraph                                                |
| `CRR Art. 153(1)(a)`               | Point                                                    |
| `CRR Art. 153(1)(a)(ii)`           | Sub-point                                                |
| `CRR Art. 4(1)(75)`                | Numeric point (CRR definitions)                          |
| `CRR Art. 92a`                     | Inserted article with letter suffix                      |
| `CRR Art. 153(1a)`                 | Inserted paragraph with letter suffix                    |
| `PRA Rulebook, Credit Risk, 3.2`   | Rulebook section                                         |
| `PS9/24`                           | PRA Policy Statement, whole document                     |
| `SS1/23, paragraph 2.5`            | Supervisory Statement with paragraph reference           |
| `PS1/26, paragraph 123B`           | Alphanumeric paragraph identifier                        |
| `PS1/26 Art. 111(1)(a)`            | PS-attached rulebook instrument with CRR-style article   |
| `Delegated Regulation 2018/171 Art. 3` | UK on-shored EU Delegated Regulation                 |

The keyword for an article accepts `Art`, `Art.`, `Article`, or `article`
in any case. Whitespace is normalised. Letter suffixes are accepted on
article numbers, on CRR paragraph numbers, and on dotted segments of
PS/SS paragraph identifiers; each segment must still start with digits.
Article suffixes are canonicalised to lowercase (`92A` parses as `92a`)
to match the bundled index.
PRA Rulebook section paths are digits-only. Anything that doesn't parse
is a `CitationParseError`, which `watchfire check` reports with the
offending input — these are code-review events, not silent skips.

## Configuration reference

```toml
[tool.watchfire]
# Snapshot of the rulebook to pin to. Decorators that omit `version=`
# inherit this pin. ISO-8601 date.
rulebook_version = "2024-07-09"

# Citation instruments allowed in this project. A citation whose
# instrument is not in this list is reported by `watchfire check`.
instruments = ["CRR", "PRA_RULEBOOK", "PS", "SS", "DELEGATED_REG"]

# Directories to walk when running `watchfire check` with no arguments.
source_paths = ["src"]
```

## Public API

```python
from watchfire import (
    Citation,            # frozen dataclass: instrument, article, paragraph, ...
    parse_citation,      # str -> Citation, raises CitationParseError
    CitationParseError,
    cites,               # the @cites decorator
)
```

Everything else (`watchfire.ast_walker`, `watchfire.index`, `watchfire.checks`,
`watchfire.cli`) is internal and may change between releases.

## Roadmap

`watchfire` v0.1 is intentionally a narrow vertical slice: get the
citation grammar right against real usage in `rwa_calculator`, ship the
decorator and CLI, then expand.

| Version | Adds                                                                          |
| ------- | ----------------------------------------------------------------------------- |
| v0.1    | Citation grammar, `@cites`, `watchfire check`, bundled CRR index               |
| v0.2    | `watchfire matrix` (traceability matrix), `watchfire stale` (rulebook diff)       |
| v0.3+   | Automated scraping of legislation.gov.uk + the PRA Rulebook                    |

If you have feedback on the citation grammar specifically, please open
an issue — the grammar is the foundation, and getting it wrong now is
much cheaper to fix than getting it wrong later.

## Licence

Apache 2.0. See `LICENSE`.
