Outside validation that your financial system is sound — tells midsize financial institutions whether their books balance day to day, and when they don't, where to look first. Ships AWS QuickSight, self-hosted HTMX, and regulator-ready PDF surfaces from one L2 institution model.
Project description
Recon Generator
Outside validation that your financial system is sound
Do you have a financial system you need to know is consistent and reconcilable? Recon Generator is an independent validation tool for midsize financial institutions: it tells you whether your books balance day to day, and when they don't, it tells you where to look first.
Accounting is standard. Your institution is not. Recon Generator layers the two: standard double-entry invariants on top of the unique shape of your institution — your accounts, your rails, your multi-leg transfer templates, your bundling rules, your aging caps — so every way you actually move money is checked against the rules that govern it.
Built for the people who actually do the work
Reconciliation needs more than one perspective because no single role has the full picture. Recon Generator carries specialized surfaces for:
- Integrators — wiring the institution's shape into the tool (Studio editor, L2 Flow Tracing, Hygiene Exceptions).
- Trainers — shaping the demo and seeded scenarios so the dashboards exercise every path before going live (data-shaping panel, scope knobs, plant overlays).
- Operators — driving the L1 invariants daily, walking exceptions back to their cause (L1 Dashboard, Daily Statement, Today's Exceptions).
- Investigators / Executives — compliance-AML triage and board-cadence rollups built on the same base ledger (Investigation + Executives apps).
Every surface speaks your institution's vocabulary — account names, role labels, persona prose are all driven from the L2 institution YAML and substituted into the rendered output. Swap the L2, the language follows.
See it live. The bundled Sasquatch example institution (tests/l2/sasquatch_pr.yaml) renders into the full training-materials surface at chotchki.github.io/recon-gen — the same persona-driven handbooks, walkthroughs, and per-sheet explainers your operators would see against your own L2 instance. It's both demo and template: read it to evaluate the tool, fork it to start your own.
Not an ETL tool — but we'll help you wire one in
Recon Generator validates data; it doesn't move it. Your transactions and daily-balance feeds land in <prefix>_transactions and <prefix>_daily_balances (the Data Integration handbook documents the column contract) and Recon Generator reads from there.
Two ways we close the loop:
- Documented carefully, supported responsively. Wiring an upstream system into the L1 schema is real work — column mapping, type narrowing, metadata extraction, the supersession contract. The Data Integration handbook walks the path end-to-end, issues get a real response, and the Studio Deploy-changes pipeline carries an ETL hook so your existing extract can plug in without bolting code onto Recon Generator itself.
- Synthetic scenarios on top of your real data. Once your data is flowing, the test-data generator plants additional scenarios on top — drift events, overdraft breaches, stuck-pending aging, supersession trails, fanout patterns, anomaly spikes — so you can validate every L1 invariant without delaying go-live. Trainer knobs (
scope: full / uncovered_rails / exceptions_only / only_template,derive_balances) shape what gets generated.
Runs where you run
Three database backends — PostgreSQL 17+, Oracle 19c+, and SQLite 3.38+ — covering on-prem, cloud-managed, and zero-install integrator-laptop iteration.
Three runtime environments — pick what your auditors and analysts already trust:
- AWS QuickSight — managed BI, embeddable in your portal, role-aware permissions.
- Self-hosted HTMX web app — same dashboards, no AWS dependency, offline-iteration-friendly. Suitable for sensitive deployments that can't reach external SaaS.
- Regulator-ready PDF audit report — printable, cryptographically fingerprinted, optionally pyHanko-signed. Same source data as the dashboards (and an end-of-pipeline 4-way agreement test gates that they stay in agreement).
Heavily tested because trust is the product
You can't ship a reconciliation tool on "trust me." This tool ships with:
- Layered test gates that run in order — unit → DB → self-hosted app → deploy → API → browser — so a regression at layer N short-circuits before burning minutes on layer N+1.
- Strong typing throughout (Pyright strict on the core, NewType-wrapped identifiers, dataclass invariants), so an entire class of bug becomes a type error at the wiring site instead of a silent zero-row dashboard.
- Fuzz testing as a property axis — every test variant runs against random L2 institution shapes (
fuzz:Nfor N seeds, pinned viaf<seed>_..for repro), so the same invariants check against shapes nobody hand-wrote. - Deterministic, exhaustive test-data generation — your L2 institution shape drives positive and negative scenarios that the harness plants automatically: drift, overdraft, limit breach, stuck-pending, stuck-unbundled, supersession audit, fanout, anomaly spikes, money-trail chains. Each scenario is hash-locked per
(L2 instance, dialect). - Cross-runtime parity — the same scenario fans out into the QuickSight cell, the self-hosted cell, and the audit PDF, and a 4-way agreement test gates that all four agree on every L1 invariant violation set. A drift the dashboard shows must equal the drift the PDF prints.
CLI is organized as five artifact groups: recon-gen schema|data|json|docs|audit. Each artifact has apply/clean/test (plus a few extras); destructive operations default to emit and require --execute to actually run. The audit group also exposes a verify subcommand for recomputing a generated PDF's provenance fingerprint. Change the Python (or ask Claude), re-run json apply --execute, get a new dashboard.
Demo Docs
The demo ships with task-shaped handbooks deployed to GitHub Pages at chotchki.github.io/recon-gen.
- L1 Dashboard handbook — 11 sheets covering 5 baseline L1 invariants + 2 aging-watch invariants + supersession audit + per-account-day walk + raw posting ledger. Switch the L2 instance to switch the persona prose without touching dashboard code.
- L2 Flow Tracing handbook — Rails / Chains / Transfer Templates / L2 Hygiene Exceptions for L2 spec verification.
- Investigation handbook — Compliance / Investigation team flow. 4 walkthroughs, one per sheet's question.
- Executives handbook — board scorecard: account coverage, transaction volume, money moved.
- Data Integration handbook — how the Data Integration Team maps an upstream system into
<prefix>_transactions+<prefix>_daily_balances, validates the load, and extends the metadata contract. - Audit Reconciliation Report handbook — regulator-ready PDF generated by
recon-gen audit apply; covers the L1 invariants, embeds a provenance fingerprint, optionally auto-signs via pyHanko.
Source lives in src/recon_gen/docs/ (shipped with the wheel — extract with recon-gen docs export -o ./somewhere/); rebuild locally with recon-gen docs serve.
Why this exists
The customer for these reports doesn't know exactly what they want yet. Rather than click through the QuickSight console and lose the work when requirements change, everything is generated from code and deployed idempotently (delete-then-create). Iteration is one command.
The four apps
L1 Dashboard — 11 tabs
The recommended path for new integrators. Configured by an L2 instance YAML — declare your institution once (accounts, rails, transfer templates, chains, limit schedules, per-rail aging caps), and the same dashboard renders against you. Switching the L2 instance switches the prose on every TextBox without touching dashboard code.
| Tab | What it shows |
|---|---|
| Getting Started | Welcome + L2 coverage inventory pulled from the L2 instance's prose. |
| Drift | Leaf + parent account balance drift detail tables. Right-click any row → Daily Statement for that account-day. |
| Drift Timelines | KPI for largest single-day drift + 2 LineCharts (one line per account_role) tracking Σ ABS(drift) over the visible date range. |
| Overdraft | KPI + violations table for internal accounts holding negative money at EOD. Right-click → Daily Statement. |
| Limit Breach | KPI + per-(account, day, transfer_type) breach table. Caps inlined from L2 LimitSchedules at schema-emit time. |
| Pending Aging | Stuck-Pending transactions past their rail's max_pending_age. KPI + 5-bucket horizontal aging bar + detail. Right-click → Transactions. |
| Unbundled Aging | Posted legs with bundle_id IS NULL past their rail's max_unbundled_age. Same KPI + bar + detail shape with 4 day-scale buckets. |
| Supersession Audit | Logical keys with multiple entry versions — the rewrite trail (Inflight / BundleAssignment / TechnicalCorrection). |
| Today's Exceptions | UNION across all 5 baseline invariant views scoped to the most recent business day. KPI + by-check bar + detail sorted by magnitude. |
| Daily Statement | Per-account-day walk: 5 KPIs (Opening / Debits / Credits / Closing / Drift) + every Money record posted that day. |
| Transactions | Raw posting ledger (<prefix>_current_transactions matview — supersession-aware). 5 dropdown filters for analyst-driven slicing. |
Reads from per-instance <prefix>_* views/matviews emitted by common.l2.emit_schema(instance). See L1 Invariants for the per-view contract + SHOULD-constraint motivation.
L2 Flow Tracing — 4 tabs
| Tab | What it shows |
|---|---|
| Getting Started | Welcome + roadmap of the three flow tabs below. |
| Rails | Postings explorer + per-rail firing counts + L2 declaration cascade. |
| Chains | Parent → child rail/template firings with per-chain SUM amounts. |
| Transfer Templates | Multi-leg transfer template firings + L2 hygiene exception list. |
Investigation — 5 tabs
| Tab | What it shows |
|---|---|
| Getting Started | Landing page — heading + roadmap of the four question-shaped sheets below. |
| Recipient Fanout | Who is receiving money from too many distinct senders? 3 KPIs (qualifying recipients / distinct senders / total inbound) + ranked table; threshold slider sets where "too many" starts. |
| Volume Anomalies | Which sender → recipient pair just spiked above its rolling baseline? Backed by inv_pair_rolling_anomalies matview (rolling 2-day SUM per pair + population z-score). KPI flagged-pair count + σ distribution chart + ranked table; σ slider gates KPI + table while the chart shows the full population. |
| Money Trail | Where did this transfer originate, and where does it go? Backed by inv_money_trail_edges matview (recursive WITH RECURSIVE walk over parent_transfer_id). Sankey as the headline + hop-by-hop table beside it; chain-root dropdown + max-hops + min-hop-amount controls. |
| Account Network | What does this account's money network look like, on either side? Two side-by-side directional Sankeys (inbound on the left, outbound on the right, anchor visually meeting in the middle) + touching-edges table. Walk-the-flow drill: right-click any table row or left-click any Sankey node to walk the anchor to the counterparty and re-render around the new center. |
Executives — 4 tabs
| Tab | What it shows |
|---|---|
| Getting Started | Landing page — heading + per-sheet highlights. |
| Account Coverage | Open vs Active account KPIs + bar chart by account_type + detail table. The Active KPI + Active bar carry a visual-pinned activity_count >= 1 filter so they read as "accounts that moved money in the period" while the Open KPI/bar count every row — same dataset, different scope. |
| Transaction Volume Over Time | Total transfers + average daily KPIs + daily stacked bar by transfer_type + per-type bar. Per-transfer pre-aggregation collapses multi-leg transfers so a 2-leg $100 movement counts as one $100 transfer, not two $200. |
| Money Moved | Gross + net amount KPIs + daily stacked bar by transfer_type + per-type bar. Net = inflows − outflows from the bank's perspective. |
Shared conventions
- Clickable cells look clickable. Accent-colored text = left-click drill; accent text on a pale tint background = right-click menu drill.
- Every sheet has a plain-language description; every visual has a subtitle. Coverage is asserted in unit + API e2e tests.
- All resources tagged
ManagedBy: recon-gen; extra tags viaextra_tagsin config.
Quick start
Prerequisites
- Python 3.13+
- An AWS account with QuickSight Enterprise enabled
- Either a pre-existing QuickSight datasource ARN or a PostgreSQL 17+ / Oracle 19c+ database URL for demo mode (the schema uses SQL/JSON path syntax —
JSON_VALUE/JSON_QUERY/JSON_EXISTS— supported on both engines)
Install from PyPI
For consumers — using a pre-existing QuickSight datasource ARN:
pip install recon-gen
For demo mode against PostgreSQL 17+ (requires psycopg2-binary):
pip install "recon-gen[demo]"
For demo mode against Oracle 19c+ (requires oracledb thin mode — no Oracle Instant Client install):
pip install "recon-gen[demo,demo-oracle]"
For demo mode against SQLite 3.38+ (no extra install — uses stdlib sqlite3 + the JSON1 functions that ship built-in since 3.38):
pip install recon-gen
The package was renamed from
quicksight-gentorecon-genin v11.0.0.pip install quicksight-gencontinues to work transparently for a 1-2 month grace period after the rename via a meta-package shim; switch torecon-genat your convenience.
Setup from source
The repo uses uv for env / lock management
(deterministic resolution from uv.lock). One command sets up .venv/
with every extra:
uv sync --all-extras
Then invoke tools directly via the venv (no source activate needed):
.venv/bin/pytest
.venv/bin/recon-gen --help
For a leaner install, swap --all-extras for the specific extras you
need: --extra dev (tests + pyright + boto3), --extra audit (PDF
report deps), --extra docs (mkdocs + macros), --extra demo /
--extra demo-oracle (DB drivers).
If you'd rather stick with pip, the standard PEP-621 path still works:
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
Configure
cp config.example.yaml config.yaml
Edit config.yaml:
aws_account_id: "123456789012"
aws_region: "us-east-2"
# Pre-existing QuickSight datasource ARN.
# Not required when demo_database_url is set (auto-derived).
datasource_arn: "arn:aws:quicksight:us-east-2:123456789012:datasource/your-datasource-id"
# Required: deployment identity (Z.C). `deployment_name` prefixes
# every QS resource ID; `db_table_prefix` prefixes every DB
# table/matview/dataset name. Both are required — no defaults.
deployment_name: "recon-prod"
db_table_prefix: "recon_prod"
# Optional: IAM principals granted permissions on generated resources.
principal_arns:
- "arn:aws:quicksight:us-east-1:123456789012:user/default/admin"
# Optional: additional tags on every generated resource
extra_tags:
Environment: production
Team: finance
# Optional: which database family for `data apply --execute` (default: postgres)
# dialect: "postgres" # or "oracle" or "sqlite"
# Optional: database URL for `data apply --execute` and friends
# Postgres:
# demo_database_url: "postgresql://user:pass@localhost:5432/quicksight_demo"
# Oracle (Easy Connect form, no scheme prefix):
# demo_database_url: "system/pass@localhost:1521/FREEPDB1"
# SQLite (file or in-memory; integrator-local iteration loop, no server):
# demo_database_url: "sqlite:///./demo.sqlite"
Theme is declared inline on the L2 institution YAML's
theme:block, not on the deploy config. When the L2 instance carries notheme:block, AWS QuickSight CLASSIC takes over at deploy.
All values can also be set via QS_GEN_-prefixed environment variables (e.g. QS_GEN_AWS_ACCOUNT_ID). Env vars override YAML.
Generate and deploy
# Generate JSON for all four bundled apps to out/
recon-gen json apply -c config.yaml -o out/
# Same emit, then deploy to AWS (delete-then-create, idempotent)
recon-gen json apply -c config.yaml -o out/ --execute
# Override the L2 instance (defaults to bundled spec_example)
recon-gen json apply -c config.yaml -o out/ --l2 run/sasquatch_pr.yaml --execute
json apply --execute polls async resources (analyses, dashboards) until they reach a terminal state. Resources with the ManagedBy: recon-gen tag that aren't in the current output aren't touched — clean those up explicitly:
recon-gen json clean # dry-run: list stale tagged resources
recon-gen json clean --execute # delete them
What you get
out/
theme.json
datasource.json # demo only (auto-derived)
investigation-analysis.json
investigation-dashboard.json
executives-analysis.json
executives-dashboard.json
l1-dashboard-analysis.json
l1-dashboard-dashboard.json
l2-flow-tracing-analysis.json
l2-flow-tracing-dashboard.json
datasets/
<deployment_name>-inv-*.json # 5 Investigation datasets
<deployment_name>-exec-*.json # 2 Executives datasets
<deployment_name>-l1-*.json # 14 L1 Dashboard datasets
<deployment_name>-l2ft-*.json # 2 L2 Flow Tracing datasets
<deployment_name>-*-app-info-*.json # 2 App Info datasets per app (8 total)
<deployment_name> comes from cfg.deployment_name (required field). Pick distinct values per environment (e.g. recon-staging vs recon-prod) so multiple deployments can coexist in the same QuickSight account without colliding.
Demo mode
A deterministic demo generator seeds the four apps end-to-end so you can see them work without wiring up real data. Every app feeds two per-prefix base tables — <db_table_prefix>_transactions (every money-movement leg) and <db_table_prefix>_daily_balances (per-account end-of-day snapshots), where <db_table_prefix> is cfg.db_table_prefix (required).
# Apply schema + seed to your demo database, then generate QuickSight JSON.
# Requires: demo_database_url + dialect in config.yaml and the matching
# extra installed (`[demo]` for Postgres, `[demo,demo-oracle]` for Oracle).
# Per-prefix DDL + seed are emitted at apply time using cfg.db_table_prefix.
recon-gen schema apply -c config.yaml --execute # tables + matviews
recon-gen data apply -c config.yaml --execute # 90-day baseline + plants
recon-gen data refresh -c config.yaml --execute # populate matviews
recon-gen json apply -c config.yaml -o out/ --execute # JSON + AWS deploy
recon-gen audit apply -c config.yaml --execute -o report.pdf # regulator-ready PDF (optional)
schema apply --execute creates the per-prefix base tables + matviews via common/l2/schema.py::emit_schema(l2_instance, prefix=cfg.db_table_prefix). data apply --execute inserts the L2-shape seed data (90-day baseline + every L1 SHOULD-violation plant + the Investigation fanout / volume / chain plants). data refresh --execute refreshes every dependent matview in dependency order. json apply --execute writes a datasource.json derived from the database URL (Type=POSTGRESQL or ORACLE, dispatched off dialect), generates all QuickSight JSON to out/, and deploys to AWS. audit apply --execute queries the per-prefix L1 invariant matviews and writes a regulator-ready PDF reconciliation report (cover, executive summary, per-invariant violation tables, per-account-day Daily Statement walks, sign-off block, cryptographic provenance fingerprint) — see the Audit Reconciliation Report handbook for the full reference. The account_type and transfer_type columns discriminate which app a row belongs to. See Schema_v6.md for the full feed contract, canonical type values, metadata key catalog, and ETL examples.
PostgreSQL 17+, Oracle 19c+, or SQLite 3.38+ required for schema apply --execute. PG + Oracle support the SQL/JSON path syntax (JSON_VALUE, JSON_QUERY, JSON_EXISTS) the schema uses for metadata JSON columns; SQLite uses the equivalent JSON1 functions (json_extract, json_valid) routed through the dialect helpers in common/sql/dialect.py. The portable subset forbids the Postgres-only ->> / -> / @> / ? operators and JSONB; on Oracle, also no named WINDOW clause and no TIMESTAMP WITH TIME ZONE in PK columns; on SQLite, matviews emit as CREATE TABLE … AS SELECT (refreshed by re-CREATE). See Schema_v6.md → Forbidden SQL patterns for the full constraint matrix.
Datasets are all Direct Query (no SPICE), so seed changes show up immediately after a fresh data apply --execute + data refresh --execute — no QuickSight-side refresh needed.
Demo scenarios
Two L2 institution YAMLs ship in tests/l2/:
spec_example.yaml— the persona-neutral default fixture. Generic accounts/rails/chains exercising every L2 primitive without naming a specific institution.sasquatch_pr.yaml— a flavored Sasquatch National Bank persona block carrying the curated demo narrative: SNB control accounts, merchant DDAs (Bigfoot Brews, Sasquatch Sips, etc.), Investigation anchor (Juniper Ridge LLC) with three converging scenarios (12-sender fanout cluster, Cascadia Trust Bank → Juniper anomaly spike, 4-hop layering chain through shell entities).
Pass --l2 tests/l2/sasquatch_pr.yaml (or your own) to switch the rendered handbook + demo data narrative without touching dashboard code.
Self-hosted renderer (Dashboards)
The four apps render two ways off the same L2 instance. The default is AWS QuickSight — json apply --execute pushes the JSON resource graph (above). The second is Dashboards (formerly "App 2"): a small self-hosted HTMX + d3 page server that reads the same database directly, with no AWS account involved.
pip install 'recon-gen[serve]'
recon-gen dashboards -c config.yaml # one process, all 4 apps + the handbook at /docs
# → http://127.0.0.1:8765/dashboards
It speaks all three SQL dialects (PostgreSQL / Oracle / SQLite); point demo_database_url at any of them. The schema + seed have to already be applied (schema apply --execute, data apply --execute, data refresh --execute) — Dashboards only reads. It's stateless: every GET re-runs the query, filter state round-trips as ?param_X=… query params (so the URL is the cache key), no auth/sessions — put it behind your own auth front on a network. All browser-side assets (htmx, d3, the filter widgets) ship inside the wheel — it runs offline.
Why two renderers: Dashboards is the offline-iteration loop (edit the L2 YAML / dataset SQL, refresh the page — no deploy cycle) and the renderer the in-progress Studio (recon-gen studio, the YAML editor + diagram + data-shaping orchestrator + ETL coverage) builds on. A 4-way cross-tool agreement test (scenario plants ⊆ direct matview SELECT == QuickSight == Dashboards, == audit PDF where it applies) gates the release, so "feature parity with QuickSight, minus the QuickSight bugs" is enforced, not just claimed. Full reference — what ships in the wheel, the maintainer recipes for bumping a vendored asset — in the handbook's Self-hosting the dashboards page.
Theming
Theme is declared inline on the L2 institution YAML's theme: block. When the L2 instance carries no theme: block, build_theme returns None and AWS QuickSight CLASSIC takes over at deploy (silent-fallback contract). The single DEFAULT_PRESET in common/theme.py is the in-canvas-accent fallback for apps when their L2 instance declares no theme — no registry, no CLI flag.
To customize the demo persona's brand: edit the theme: block on tests/l2/sasquatch_pr.yaml (or your own L2 YAML). See the ThemePreset dataclass in common/l2/theme.py for the full field list. Rich-text on the Getting Started sheets resolves the accent color to hex at generate time.
Project structure
src/recon_gen/
__main__.py # python -m recon_gen entry point
cli/ # Click CLI shell — schema | data | json | docs groups
__init__.py # main + group registration
schema.py / data.py / json.py / docs.py
_helpers.py # shared resolve_l2_for_demo / emit_to_target / connect_and_apply
_app_builders.py # per-app JSON-emit helpers
common/
config.py # Config dataclass + YAML/env loader
models.py # Dataclasses → AWS QuickSight API JSON
ids.py # Typed ID newtypes (SheetId / VisualId / FilterGroupId / ParameterName)
theme.py # DEFAULT_PRESET fallback + build_theme(cfg, theme | None)
persona.py # DemoPersona dataclass — generic skeleton; populated from L2 YAML
deploy.py # Python deploy (delete-then-create, async waiters)
cleanup.py # Tag-based cleanup of stale resources
dataset_contract.py # ColumnSpec / DatasetContract / build_dataset()
drill.py # Cross-app deep-link URL builder
clickability.py # Conditional-format helpers
rich_text.py # XML helpers for SheetTextBox.Content
probe.py # Playwright walker for deployed-dashboard error surfacing
tree/ # Typed tree primitives (Phase L). App / Analysis / Dashboard / Sheet,
# typed Visual subtypes, typed Filter wrappers, Parameter + Filter
# Controls, Drill actions, Datasets + Columns + Dim/Measure factories,
# CalcFields. Object-ref cross-references, auto-IDs, emit-time
# validation. All four apps are tree-built — see CLAUDE.md
# "Tree pattern" for the L1 / L2 / L3 layer model.
l2/ # L2 model: primitives, validate, loader, schema, seed,
# auto_scenario, derived, theme, topology
sql/dialect.py # Dialect enum (POSTGRES / ORACLE)
browser/ # Playwright helpers (helpers.py + ScreenshotHarness)
handbook/ # mkdocs-macros vocabulary + diagrams
sheets/app_info.py # populate_app_info_sheet — Info canary builder
apps/
l1_dashboard/ # 11 sheets, configured by L2 instance
l2_flow_tracing/ # 4 sheets — Rails / Chains / Templates / Hygiene
investigation/ # 5 sheets — fanout / anomalies / money trail / account network
executives/ # 4 sheets — coverage / volume / money moved
docs/ # Unified mkdocs site source — concepts/, handbook/, walkthroughs/,
# for-your-role/, scenario/, Schema_v6.md, _diagrams/, _macros/.
# Renders against any L2 instance via mkdocs-macros + HandbookVocabulary.
tests/ # Mirror the artifact split: tests/{schema,data,json,docs,unit,e2e}/
run_tests.sh # Layered test chain runner (unit → db → app2 → deploy → api → browser)
config.example.yaml
Tests
./run_tests.sh up_to=unit # ~20s, no DB / no AWS
./run_tests.sh up_to=db # full matrix (13 cells, parallel)
./run_tests.sh up_to=db --dialects=pg --targets=lo # pg-container only
./run_tests.sh up_to=browser # full chain through Playwright
./run_tests.sh up_to=api --variants=sp_pg_aw # single AW cell, API only
./run_tests.sh sweep --yes # cleanup orphan AWS resources
The runner enforces ordering — invoking layer N runs layers 1..N-1 first. See CLAUDE.md::Test sequencing for the full guide.
Coverage:
- Unit / integration: models, tags, config, CLI, demo determinism + scenario coverage (per-instance SHA256 seed-hash locks), tree primitives + validators, dataset builders, visual builders, filter groups, cross-reference validation (dataset ARNs, filter bindings, visual ID uniqueness, sheet scoping), explanation coverage, schema + seed SQL structure for both Postgres + Oracle.
- E2E: two layers gated by
QS_GEN_E2E=1.- API layer (boto3) — resource existence, status, dashboard structure (per-sheet visual counts, parameter / filter-group source-of-truth checks), dataset import health.
- Browser layer (Playwright WebKit, headless) — dashboard loads via pre-authenticated embed URL, sheet tabs, per-sheet visual counts + spot-checked titles, drill-downs, mutual-filter reconciliation tables, date-range filter narrowing, Show-Only-X toggles, Investigation slider + dropdown filters.
E2E tunables (env vars): QS_E2E_PAGE_TIMEOUT, QS_E2E_VISUAL_TIMEOUT, QS_E2E_USER_ARN, QS_E2E_IDENTITY_REGION. Failure screenshots land in tests/e2e/screenshots/<app>/ (gitignored).
Customising
Change the SQL
Edit the dataset builders in apps/<app>/datasets.py. Each dataset has a sql string and a DatasetContract (column name + type list) — unit tests assert the SQL projection matches the contract, so the contract is the safety net when rewriting.
The dataset SQL reads from two shared base tables (<prefix>_transactions, <prefix>_daily_balances) plus the L1 invariant + Investigation matviews. To wire your production data in, ETL into the same shape: see Schema_v6.md for column specifications, the canonical account_type / transfer_type values, the JSON metadata key catalog, and end-to-end ETL examples.
Add a visual or tab
- Open
apps/<app>/app.pyand find the relevant sheet's populator function. - Place the visual on a layout row:
row.add_kpi(...),row.add_table(...),row.add_bar_chart(...),row.add_sankey(...). Passtitle=,subtitle=, and the typedDim/Measureslots — the tree validates dataset / column references at emit time. - Subtitle is required — enforced at construction (
Visual.__post_init__raises on a blank subtitle), not by a separate test. - Run
pytest— typed cross-reference errors fail at the wiring site, not deep in the generated JSON.
Add a filter
Filters push to SQL — a <<$paramName>> placeholder in the dataset's CustomSql, not an analysis-level FilterGroup (a Phase Y change that converged the QuickSight and self-hosted renderers on the same query-level narrowing). So:
- Date filter: write the dataset SQL with a
{date_filter}slot in itsWHEREand callbuild_dataset(sql_template, CONTRACT, ..., app2_date_column="<table>.<col>")— it substitutes the slot per renderer (QuickSight's universal date control narrows QS; the self-hosted renderer gets aBETWEEN :date_from AND :date_tobind). - Categorical / slider filter: put
<<$pParamName>>directly in theWHERE; declare the analysis parameter and wire its control (ParameterDropDownControl/ slider) inapps/<app>/app.py. The dataset parameter, the analysis→dataset bridge, and the self-hosted renderer's filter spec are all auto-derived from that one control node. pytestwalks the tree and flags missing references at emit time; the dataset'sDatasetContractis the safety net when you edit the SQL.
(See CLAUDE.md → "Filter authoring" for the full pattern. Analysis-level FilterGroups are deprecated for filter intent — kept only for the universal date control and the rare highlight-without-narrowing case.)
Re-skin
Edit your L2 institution YAML's theme: block (or copy from tests/l2/sasquatch_pr.yaml for a worked example). Keys: theme_name, version_description, accent, primary_fg, link_tint, analysis_name_prefix. See common/l2/theme.py::ThemePreset for the full field contract.
Ask Claude
The codebase is intentionally easy to mutate. Ask Claude to add visuals, reshape the layout, adjust filters, update SQL for your schema, or add conditional formatting — it'll edit the Python and re-run tests.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file recon_gen-11.5.0.tar.gz.
File metadata
- Download URL: recon_gen-11.5.0.tar.gz
- Upload date:
- Size: 9.0 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
14be13ad5b2fae7545fd3e6a136e1d0bb4a326f78f58ea3fcc05cae23949f466
|
|
| MD5 |
715782d68736679088fb2b00c4e824d4
|
|
| BLAKE2b-256 |
f2a0ad2bc64fd0b76e29f8a4ec441a4fb3a997905ee5262ed35877d8e231aaac
|
Provenance
The following attestation bundles were made for recon_gen-11.5.0.tar.gz:
Publisher:
release.yml on chotchki/recon-gen
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
recon_gen-11.5.0.tar.gz -
Subject digest:
14be13ad5b2fae7545fd3e6a136e1d0bb4a326f78f58ea3fcc05cae23949f466 - Sigstore transparency entry: 1574111526
- Sigstore integration time:
-
Permalink:
chotchki/recon-gen@7e146f780ce9f93b0200db76f82f47a53f55191b -
Branch / Tag:
refs/tags/v11.5.0 - Owner: https://github.com/chotchki
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7e146f780ce9f93b0200db76f82f47a53f55191b -
Trigger Event:
push
-
Statement type:
File details
Details for the file recon_gen-11.5.0-py3-none-any.whl.
File metadata
- Download URL: recon_gen-11.5.0-py3-none-any.whl
- Upload date:
- Size: 9.1 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7c6c5e52987653e8783a40e858c433e9d014d8459236aa8d11110d2fafb151f1
|
|
| MD5 |
e47a522b0dfe22cdaaf718f563d1d45a
|
|
| BLAKE2b-256 |
92b55a176d2be18d53e861b34e8ff5b60ee9c2007710d76e672fef544365b049
|
Provenance
The following attestation bundles were made for recon_gen-11.5.0-py3-none-any.whl:
Publisher:
release.yml on chotchki/recon-gen
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
recon_gen-11.5.0-py3-none-any.whl -
Subject digest:
7c6c5e52987653e8783a40e858c433e9d014d8459236aa8d11110d2fafb151f1 - Sigstore transparency entry: 1574111652
- Sigstore integration time:
-
Permalink:
chotchki/recon-gen@7e146f780ce9f93b0200db76f82f47a53f55191b -
Branch / Tag:
refs/tags/v11.5.0 - Owner: https://github.com/chotchki
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7e146f780ce9f93b0200db76f82f47a53f55191b -
Trigger Event:
push
-
Statement type: