Skip to content

Commit 470a82d

Browse files
Compliance Audit Copilot: token-budgeted 4-stage audit pipeline
Four independent LLM features behind a real audit workflow — checklist generation, observation optimizer (3 paraphrase options), AI severity scoring (0-1), and executive summary — each routed through a tested 8k token budget (token_budget.py) with map-reduce fallback for oversized documents/findings, not just a "keep it short" comment. Deliberately doesn't use LangGraph/LangChain (used in the other two portfolio projects): this is four linear, independent, structured-output calls behind a CRUD workflow, not a multi-step agentic loop, so an agent framework would be unjustified complexity for the task shape. A real off-by-one token-budget bug was caught by the test suite itself (BPE tokens merge differently at template/content concatenation boundaries) and fixed with a safety margin, not tighter arithmetic — documented in the README as a concrete example of the token-budget tests actually proving the constraint, not just asserting it. FastAPI + SQLAlchemy/SQLite backend, React + Vite + TS frontend, pymupdf4llm for PDF extraction. Verified end-to-end against the real NVIDIA API with a synthetic sample IT security policy (deliberately generic domain, not the banking/regulatory domain of the real system this pattern is inspired by, to avoid any employer-IP overlap) and via a full Playwright browser run. 38 tests, CI, MIT license.
0 parents  commit 470a82d

60 files changed

Lines changed: 5805 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
NVIDIA_API_KEY=nvapi-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
2+
NEMOTRON_MODEL=nvidia/nemotron-3-nano-30b-a3b
3+
NVIDIA_BASE_URL=https://integrate.api.nvidia.com/v1
4+
DATABASE_URL=sqlite:///./data/audit.db
5+
UPLOADS_DIR=data/uploads
6+
MAX_CONTEXT_TOKENS=8000
7+
RESERVED_FOR_COMPLETION=1024

.github/workflows/ci.yml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
backend:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- uses: actions/setup-python@v5
16+
with:
17+
python-version: "3.12"
18+
cache: "pip"
19+
20+
- name: Install dependencies
21+
run: pip install -r requirements.txt
22+
23+
- name: Lint
24+
run: ruff check .
25+
26+
- name: Test
27+
run: pytest -q
28+
29+
frontend:
30+
runs-on: ubuntu-latest
31+
defaults:
32+
run:
33+
working-directory: frontend
34+
steps:
35+
- uses: actions/checkout@v4
36+
37+
- uses: actions/setup-node@v4
38+
with:
39+
node-version: "22"
40+
cache: "npm"
41+
cache-dependency-path: frontend/package-lock.json
42+
43+
- name: Install dependencies
44+
run: npm ci
45+
46+
- name: Lint
47+
run: npm run lint
48+
49+
- name: Build (type-check + bundle)
50+
run: npm run build

.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.env
2+
*.env
3+
!.env.example
4+
__pycache__/
5+
*.py[cod]
6+
.venv/
7+
venv/
8+
.pytest_cache/
9+
.ruff_cache/
10+
.DS_Store
11+
*.db
12+
*.sqlite3
13+
data/uploads/*
14+
!data/uploads/.gitkeep

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Aditya Patil
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
.PHONY: install api frontend-install frontend-dev test lint
2+
3+
install:
4+
python3.12 -m venv .venv
5+
. .venv/bin/activate && pip install -r requirements.txt
6+
7+
api:
8+
uvicorn backend.main:app --host 127.0.0.1 --port 8001
9+
10+
frontend-install:
11+
cd frontend && npm install
12+
13+
frontend-dev:
14+
cd frontend && npm run dev
15+
16+
test:
17+
pytest -q
18+
19+
lint:
20+
ruff check .

README.md

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# Compliance Audit Copilot
2+
3+
A four-stage, **token-budgeted** audit workflow: upload policy documents,
4+
get an AI-generated checklist of audit questions, write raw observations
5+
and get them rewritten into professional phrasing, get an AI severity
6+
score per finding, and generate an executive summary across the whole
7+
audit. Every LLM call — in every feature — is routed through a real,
8+
tested 8,000-token budget with automatic map-reduce fallback for oversized
9+
input, not a "keep it short" comment.
10+
11+
![Checklist item with three paraphrase options and an AI severity score](assets/observation_severity.png)
12+
13+
![Generated executive summary synthesizing all findings](assets/executive_summary.png)
14+
15+
## The four features
16+
17+
1. **Checklist generation** — upload a PDF (extracted via `pymupdf4llm`),
18+
get back a structured list of specific, answerable audit questions.
19+
2. **Observation optimizer** — write a raw, informal observation; get three
20+
professionally-phrased alternatives to choose from.
21+
3. **AI severity scoring** — a continuous 0.0–1.0 severity rating per
22+
observation, with a short rationale, relative to its question.
23+
4. **Executive summary** — synthesizes every question, observation, and
24+
severity score in the audit into one coherent summary for leadership.
25+
26+
## Token budgeting — the actual point of this project
27+
28+
`token_budget.py` enforces a hard 8,000-token context budget
29+
(`MAX_CONTEXT_TOKENS`), with ~7,000 tokens reserved for input after
30+
accounting for the expected completion. Every feature routes its prompt
31+
construction through it:
32+
33+
- **Observation optimizer & severity scoring** operate on one question +
34+
one observation — comfortably within budget by construction, but still
35+
routed through the same `fit_to_budget` guard defensively (a
36+
pathologically long raw observation shouldn't be able to blow the budget).
37+
- **Checklist generation & executive summary** are the two places a real
38+
audit can genuinely exceed the budget (a long policy document; a large
39+
number of findings). Both fall back to **map-reduce**: split into
40+
token-sized chunks, process each chunk independently, then merge/combine
41+
the partial results into one final output — all individually
42+
budget-checked, including the merge/combine step itself.
43+
44+
This isn't asserted, it's tested: `tests/test_checklist.py` and
45+
`tests/test_summary.py` each feed in a deliberately oversized input and
46+
assert that **every single prompt actually sent to the LLM** — across every
47+
map call and the final reduce call — measured with `tiktoken`, fits the
48+
budget. That's the proof the constraint is real, not just a docstring.
49+
50+
### A real off-by-one bug this caught
51+
52+
Early on, a test failed with the prompt at *6,977 tokens against a 6,976
53+
budget* — off by exactly one token. The cause: BPE tokens can merge
54+
differently at the boundary where a prompt template and inserted content
55+
join, so `count(template) + count(content)` isn't always exactly
56+
`count(template.format(content))`. The fix wasn't tighter arithmetic, it
57+
was accepting that boundary effects exist: `content_budget()` reserves a
58+
small safety margin (`BOUNDARY_SAFETY_MARGIN`) specifically for this,
59+
found by a test catching a real violation, not by inspection.
60+
61+
## Architecture
62+
63+
- **Direct LLM calls, not an agent framework.** `llm.py` / `llm_json.py`
64+
wrap a plain OpenAI-compatible client against the NVIDIA NIM endpoint.
65+
This system is four independent, linear, structured-output calls behind
66+
a CRUD workflow — not a multi-step agentic loop — so pulling in
67+
LangGraph/LangChain (used in the other two portfolio projects) would be
68+
unjustified complexity for this task shape. Right tool for the job, not
69+
a hammer looking for a nail.
70+
- **Structured output, verified not trusted.** `llm_json.py` prompts for
71+
strict JSON, extracts it with a balanced-brace parser that tolerates
72+
markdown fences and surrounding prose, validates against a Pydantic
73+
schema, and retries once with corrective feedback on failure — the same
74+
"don't just trust the model's JSON" lesson learned (and re-learned)
75+
across all three portfolio projects.
76+
- **Real persistence.** SQLAlchemy + SQLite — audits, documents, checklist
77+
items, and observations survive a restart; this is a system, not a demo
78+
script that forgets everything on refresh.
79+
80+
```mermaid
81+
flowchart LR
82+
UI[React UI] -->|REST| API[FastAPI]
83+
API --> DB[(SQLite)]
84+
API --> PDF[pymupdf4llm]
85+
API --> Budget[token_budget.py]
86+
Budget --> LLM[NVIDIA Nemotron]
87+
subgraph Features
88+
C[checklist.py]
89+
O[observation.py]
90+
S[severity.py]
91+
Sum[summary.py]
92+
end
93+
API --> Features
94+
Features --> Budget
95+
```
96+
97+
## A note on originality
98+
99+
This project deliberately mirrors a pattern (checklist → observation →
100+
severity → summary) similar to production audit-automation work. To avoid
101+
any overlap with employer IP, this is an independent implementation built
102+
from scratch, applied to a different domain (generic IT security /
103+
operational compliance, not banking/regulatory), using entirely synthetic
104+
sample documents — no real organization's data or proprietary logic.
105+
106+
## Getting started
107+
108+
```bash
109+
git clone <your-fork-url>
110+
cd compliance-audit-copilot
111+
cp .env.example .env
112+
# edit .env and add your NVIDIA_API_KEY (free key: https://build.nvidia.com)
113+
114+
python3.12 -m venv .venv
115+
source .venv/bin/activate
116+
pip install -r requirements.txt
117+
118+
# terminal 1 — backend
119+
uvicorn backend.main:app --host 127.0.0.1 --port 8001
120+
121+
# terminal 2 — frontend
122+
cd frontend
123+
npm install
124+
npm run dev
125+
```
126+
127+
Open http://localhost:5173 (proxies `/api` to the backend on `:8001`).
128+
129+
## Testing
130+
131+
```bash
132+
pytest -q # 38 tests
133+
ruff check .
134+
135+
cd frontend && npm run build && npm run lint
136+
```
137+
138+
- `tests/test_token_budget.py` — counting, truncation, chunking, and the
139+
boundary safety margin.
140+
- `tests/test_llm_json.py` — JSON extraction (fences, embedded prose) and
141+
the validate-then-retry-once structured output flow.
142+
- `tests/test_pdf_extraction.py` — real extraction from a generated PDF.
143+
- One test file per feature, each with an oversized-input case proving the
144+
map-reduce path actually engages and stays under budget.
145+
- `tests/test_api.py` — the full workflow (create → upload → generate
146+
checklist → observe → optimize → select → score → summarize) through the
147+
real FastAPI app and a real (temporary) SQLite database, LLM mocked.
148+
149+
## Project structure
150+
151+
```
152+
compliance-audit-copilot/
153+
├── config.py # settings, including token budget constants
154+
├── token_budget.py # the core differentiator — see above
155+
├── llm.py, llm_json.py # NVIDIA client + verified structured output
156+
├── pdf_extraction.py # pymupdf4llm wrapper
157+
├── models.py, db.py # SQLAlchemy models + session management
158+
├── features/
159+
│ ├── checklist.py # + map-reduce for oversized documents
160+
│ ├── observation.py
161+
│ ├── severity.py
162+
│ └── summary.py # + map-reduce for oversized findings
163+
├── backend/main.py # FastAPI REST endpoints
164+
├── frontend/src/
165+
│ ├── pages/{AuditList,AuditDetail}.tsx
166+
│ └── components/{ChecklistItemCard,DocumentUpload,ExecutiveSummary,...}.tsx
167+
├── tests/
168+
└── .github/workflows/ci.yml
169+
```
170+
171+
## Known limitations
172+
173+
- Token counting uses `tiktoken`'s `cl100k_base` encoding as a standard
174+
proxy — Nemotron doesn't publish a public tiktoken-compatible encoding,
175+
but this is close enough in practice for budgeting purposes.
176+
- SQLite is fine for a portfolio demo; a real multi-user deployment would
177+
need a proper server-based database and auth.
178+
- Regenerating a checklist replaces the previous one (and, via cascade, its
179+
observations) — simplest consistent behavior for a demo, not full
180+
version history.
181+
182+
## License
183+
184+
MIT — see [LICENSE](LICENSE).

assets/executive_summary.png

206 KB
Loading

assets/observation_severity.png

334 KB
Loading

backend/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)