Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

### What this adds

### Assessment requirement served
### Requirement served

### Docs updated

Expand Down
15 changes: 7 additions & 8 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# Contributing

This is an assessment submission repo for Elevent Group (ERP Collection
Reporting Workflow) — not an open-source project accepting outside
contributions. This file documents the actual working conventions this
repo follows, for anyone reviewing or continuing the work.
This repo is not currently accepting outside contributions. This file
documents the actual working conventions it follows, for anyone reviewing
or continuing the work.

## Workflow

Expand Down Expand Up @@ -31,7 +30,7 @@ anything under `backend/app/collections/`.

## Pull requests

Each PR follows the template: what it adds, which assessment requirement
it serves, which docs were updated, verification commands run, and known
gaps carried to the next phase. Self-review before merge; run
`/ponytail-review` on the diff first.
Each PR follows the template: what it adds, which requirement it serves,
which docs were updated, verification commands run, and known gaps carried
to the next phase. Self-review before merge; run `/ponytail-review` on the
diff first.
45 changes: 22 additions & 23 deletions MASTER_PLAN.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# ERP Collection Reporting Workflow: Master Plan v3
Assessment for Elevent Group. Supersedes Blueprint v1 and v2.
Companion document: `QA_PREP.md` (the twenty questions this design answers).
# ERP Collection Reporting Workflow: Project Plan
Companion document: `QA_PREP.md` (a design-rationale FAQ this plan answers).
---
## 1. Goal, restated from the source of truth
The email lists deliverables. The **README sheet inside the workbook** defines the
Expand All @@ -16,18 +15,18 @@ Non-negotiables extracted from the workbook:
| Payments after report date do not reduce the position | Date filter before aggregation |
| **Do not silently delete problematic rows** | Every exclusion emits an exception row |
| Blank Region derived from Region_Mapping via State | Enrichment step, never a blank output |
Graded on: business understanding, validation approach, exception handling, code
structure, responsible AI use, clarity of explanation.
**This is an MVP for a presentation, not a platform.** Optimise for what a
reviewer can see, click, and understand in fifteen minutes.
Design priorities: business understanding, validation approach, exception
handling, code structure, responsible AI use, clarity of explanation.
**This is an MVP for a walkthrough, not a platform.** Optimise for what
someone can see, click, and understand in fifteen minutes.
---
## 2. Two deadlines, two scopes
| Milestone | Deadline | Scope |
|---|---|---|
| **Submission** | 2 days | Phases 0 to 8. Code, README, working API, minimal UI |
| **Presentation** | Later, date TBC | Phases 9 to 13. They hand over a second workbook live |
## 2. Two phases of scope: core and extended
| Milestone | Scope |
|---|---|
| **Core** | Phases 0 to 8. Code, README, working API, minimal UI |
| **Extended** | Phases 9 to 13. Handling a second, differently-shaped workbook live |
Design every phase so that stopping at the end of it still leaves a demoable
system. Never end a day mid-phase.
system. Never leave a phase half-finished.
---
## 3. The second-workbook problem
At the presentation they will hand over a different file. Three ways that file
Expand Down Expand Up @@ -58,7 +57,7 @@ after that is your diff, which is exactly what a reviewer wants to see.
The frontend that was dead weight in v2 is now an asset, since a team needs to
view results without reading logs. Keep it.
```
backend/app/collections/ # the entire assessment service, one folder
backend/app/collections/ # the entire collections service, one folder
__init__.py
config.py # report date, thresholds, LLM settings
contracts.py # CanonicalCustomer/Invoice/Payment/RegionMap
Expand Down Expand Up @@ -102,10 +101,10 @@ backend/app/collections/ # the entire assessment service, one folder
docs/ # see section 5
fixtures/
tests/
frontend/src/routes/collections/ # the assessment UI
frontend/src/routes/collections/ # the collections UI
runs.tsx run-detail.tsx exceptions.tsx summary.tsx upload.tsx mapping.tsx
```
One folder, one concern, no assessment code leaking into the template's own
One folder, one concern, no collections code leaking into the template's own
modules. A reviewer can `ls backend/app/collections` and see the whole design.
Wire the router into the template's `app/api/main.py` with a single include line.
That one-line touch is the only template file you modify.
Expand All @@ -114,7 +113,7 @@ That one-line touch is the only template file you modify.
Six files, strict ownership. A fact lives in exactly one file; others link to it.
| File | Owns | Never contains |
|---|---|---|
| `README.md` (service root) | **The assessment deliverable.** What it does, how to run, business rules, assumptions, AI tooling used, validation performed | Architecture internals, endpoint schemas |
| `README.md` (service root) | **The primary deliverable.** What it does, how to run, business rules, assumptions, AI tooling used, validation performed | Architecture internals, endpoint schemas |
| `docs/ARCHITECTURE.md` | Components, data flow, why each decision was made, trade-offs, what breaks at scale | How to run, endpoint payloads |
| `docs/API.md` | Every endpoint: method, path, params, response schema, status codes, error codes | Business rationale, setup |
| `docs/RULES.md` | The exception rule catalogue. Single source of truth for E001 to E014 | Anything not a rule |
Expand Down Expand Up @@ -241,7 +240,7 @@ One MR per phase. MR description template lives in
```markdown
## Phase N: <name>
### What this adds
### Assessment requirement served
### Requirement served
### Docs updated
- [ ] README.md - [ ] ARCHITECTURE.md - [ ] API.md
- [ ] RULES.md - [ ] DEVELOPMENT.md - [ ] CHANGELOG.md
Expand All @@ -259,13 +258,13 @@ SemVer, tagged on merge to `main`.
| `v0.2.0` | Domain core |
| `v0.3.0` | Validation |
| ... | one minor per phase |
| `v1.0.0` | Submission-ready |
| `v1.0.0` | Core scope complete |
| `v1.1.0`+ | Presentation hardening |
`CHANGELOG.md` updated in the same MR, never retrofitted.
---
## 9. Phases
Each phase: branch, build, docs, `/ponytail-review`, MR, tag, merge.
### Submission scope
### Core scope
**Phase 0: Foundation** `v0.1.0`
Clone template, init commit. Add `collections` package skeleton and docs
skeleton. Docker Compose up with Postgres. Health endpoint returns green.
Expand Down Expand Up @@ -303,13 +302,13 @@ Batched by rule code. Cause, impact, suggested fix, owner. `auto_fixable`
hardcoded false. Guarded and cached.
*Done when:* every exception row carries an explanation, and the explainer never
invents an ID absent from its input.
**Phase 8: Frontend** `v0.9.0` then `v1.0.0` on submission
**Phase 8: Frontend** `v0.9.0` then `v1.0.0` on core-scope completion
Five routes: runs list, run detail with timeline, exceptions table with filters,
summary with block banner, upload. Template's shadcn/ui components throughout.
*Done when:* someone who has never seen the terminal can upload a file and read
every output.
**Submission cut line. Everything above ships in two days.**
### Presentation scope
**Core scope cut line — everything above is the core build.**
### Extended scope
**Phase 9: Exports** `v1.1.0`
CSV per report, PDF management pack. Download buttons on every view.
**Phase 10: Multi-workbook** `v1.2.0`
Expand Down
14 changes: 7 additions & 7 deletions QA_PREP.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Twenty Questions They Will Ask
# Design Rationale: Twenty Questions and Answers

Each entry: the question, the answer, and the design decision that makes the
answer true. If a design decision here is not in `MASTER_PLAN.md`, one of the two
documents is wrong.
Each entry: a question worth asking about this design, the answer, and the
design decision that makes the answer true. If a design decision here is not
in `MASTER_PLAN.md`, one of the two documents is wrong.

---

Expand Down Expand Up @@ -204,9 +204,9 @@ with a different owner.

**19. Why did you use the official FastAPI template rather than starting clean?**

It supplies the parts that are not being assessed and would otherwise consume the
It supplies the parts that aren't the point of this project and would otherwise consume the
timeline: Postgres with SQLModel and Alembic, Docker Compose, auth, CI, and a
React frontend with shadcn/ui. All assessment code lives in one folder,
React frontend with shadcn/ui. All collections code lives in one folder,
`app/collections`, with a single line wiring the router in. The initial commit is
the unmodified template, so every subsequent commit is our own work and the diff
is the deliverable.
Expand All @@ -228,7 +228,7 @@ AR platforms and where E007 actually comes from.

---

## Two you should hope they ask
## Two worth hoping someone asks

**How did you use AI to build this, not just inside it?**

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@ for the full command reference.
## Repo layout

```
backend/app/collections/ the entire assessment service — one folder,
backend/app/collections/ the entire collections service — one folder,
one concern, see MASTER_PLAN.md section 4
frontend/src/routes/collections/ the assessment UI
frontend/src/routes/collections/ the collections UI
docs/CHANGELOG.md this project's own history
MASTER_PLAN.md design and phase-by-phase build plan
QA_PREP.md companion doc: the questions MASTER_PLAN.md answers
Expand Down
49 changes: 20 additions & 29 deletions backend/app/collections/README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# ERP Collection Reporting Workflow

The assessment deliverable for Elevent Group: an ERP collection
reporting service built on `fastapi/full-stack-fastapi-template`. This
file is the single source of truth for **what the service does, why it
is built this way, and how to run it**. Everything else — internals,
endpoints, the rule catalogue, local setup — is owned by one other file
each; see the map at the bottom rather than this file repeating them.

Status: **submission scope (Phases 0-8) complete**, plus several
presentation-scope extras (file download, email, dashboard, full
rebrand) already pulled forward. See
An ERP collection reporting service built on
`fastapi/full-stack-fastapi-template`. This file is the single source
of truth for **what the service does, why it is built this way, and
how to run it**. Everything else — internals, endpoints, the rule
catalogue, local setup — is owned by one other file each; see the map
at the bottom rather than this file repeating them.

Status: **core scope (Phases 0-8) complete**, plus several extended-scope
extras (file download, email, dashboard, full rebrand) already pulled
forward. See
[`../../../docs/CHANGELOG.md`](../../../docs/CHANGELOG.md) for the
exact, dated history of what shipped when.

Expand Down Expand Up @@ -117,7 +117,7 @@ the exceptions, read the summary — from a browser.
never left blank in the output.
- **The control gate**: exception rows ÷ invoice count. Over 5%, the
run is `BLOCKED`; at or under, it `PASSED`. The gate is never tuned
to pass — the given assessment file blocks at 47.2%, and that's the
to pass — the given sample file blocks at 47.2%, and that's the
correct output for that file, not a bug to chase away.

The full 14-rule exception catalogue — every condition, every dataset
Expand Down Expand Up @@ -243,10 +243,10 @@ instead of raw JSON keeps a prompt small enough for a 4B-parameter
model to handle its context window reliably, instead of truncating or
losing structure on a large, verbose payload. It's wired and tested
today, reserved for the schema-mapping role that reads a *renamed*
workbook (presentation-scope, not required for this submission) —
being honest about that is more useful to a reviewer than overstating
what's active. What's non-negotiable either way, submission or not, is
the boundary it's built under and never crosses:
workbook (extended scope, not yet part of the core build) — being
honest about that is more useful to a reviewer than overstating what's
active. What's non-negotiable either way is the boundary it's built
under and never crosses:

```
ALLOWED workbook -> anydoc -> markdown -> LLM context
Expand Down Expand Up @@ -292,24 +292,15 @@ console errors throughout.

## Screenshots

`images/` in this folder holds the walkthrough screenshots referenced
below — upload, the run-detail timeline, an expanded exception, and the
management summary. Add them there under the filenames listed in
[`images/README.md`](images/README.md) and this section will render
them inline:

```
![Upload](images/upload.png)
![Run detail — blocked](images/run-detail-blocked.png)
![Exception expanded](images/exception-expanded.png)
![Summary](images/summary.png)
```
Walkthrough screenshots — the dashboard, upload, the run-detail timeline
with its AI-written analysis, the exceptions list, and the mobile views —
live in the [root README](../../../README.md#screenshots).

## Technology used

| Layer | Choice | Why |
|---|---|---|
| API framework | FastAPI (unmodified `fastapi/full-stack-fastapi-template` base) | Supplies everything not being assessed — Postgres/SQLModel/Alembic, Docker Compose, CI, a React frontend — so every commit on top of the init commit is this project's own work, and the diff is the deliverable |
| API framework | FastAPI (unmodified `fastapi/full-stack-fastapi-template` base) | Supplies everything outside this project's own scope — Postgres/SQLModel/Alembic, Docker Compose, CI, a React frontend — so every commit on top of the init commit is this project's own work, and the diff is the deliverable |
| Data model | SQLModel + Alembic | One ORM layer for both the Pydantic-style models and the SQL schema, with real migrations |
| Money/dates | `Decimal` / `date`, never `float`/`datetime.now()` | Financial figures can't tolerate binary floating-point rounding, and a report date has to be reproducible, not clock-dependent |
| Workbook parsing | `openpyxl` (no pandas) | Typed cell access straight into typed records — no second, looser dtype-inference path for money to travel through |
Expand All @@ -333,5 +324,5 @@ rather than repeating it:
| [`docs/DEVELOPMENT.md`](docs/DEVELOPMENT.md) | Local setup, every test/lint command, git workflow, and how this codebase was actually built with agent tooling |
| [`docs/DEMO.md`](docs/DEMO.md) | The live presentation runbook |
| [`../../../docs/CHANGELOG.md`](../../../docs/CHANGELOG.md) | The dated, phase-by-phase history of everything that shipped |
| [`../../../QA_PREP.md`](../../../QA_PREP.md) | Twenty rehearsed questions a reviewer is likely to ask, answered against this exact codebase |
| [`../../../QA_PREP.md`](../../../QA_PREP.md) | Twenty design-rationale questions and answers, grounded in this exact codebase |
| [`../../../MASTER_PLAN.md`](../../../MASTER_PLAN.md) | The original design and phase plan this build followed |
6 changes: 3 additions & 3 deletions backend/app/collections/docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Owns: components, data flow, why each decision was made, trade-offs, what
breaks at scale. Does not own: how to run anything (see `DEVELOPMENT.md`),
endpoint payloads (see `API.md`).

Status: **Phase 8 complete -- submission scope (Phases 0-8) done.** Populated phase by phase as components land.
Status: **Phase 8 complete -- core scope (Phases 0-8) done.** Populated phase by phase as components land.

## Components

Expand Down Expand Up @@ -143,7 +143,7 @@ itself.
`control/gate.py`'s `evaluate_gate` is a pure function: invoice count in,
`GateResult` out. It is deliberately never tuned to pass —
`dataset_a_original.xlsx` blocks (47.2%), and that is the correct,
expected output for the given assessment file, not a bug to chase away.
expected output for the given sample file, not a bug to chase away.

QA_PREP.md Q8 flags that the brief's wording, "exception records over
invoice records," is ambiguous: one invoice can carry more than one
Expand Down Expand Up @@ -358,7 +358,7 @@ path rather than an open-ended "would need more work."

**Scheduler: in-process → Celery Beat / a Kubernetes CronJob.**
`scheduler.py` is currently a stub for a single-process APScheduler job
(Phase 11, presentation scope). APScheduler works correctly with one
(Phase 11, extended scope). APScheduler works correctly with one
worker process; the moment there is more than one (any real horizontal
scale-out), two workers can both pick up the same trigger unless
coordination is added. Celery Beat or a CronJob hitting the API is the
Expand Down
2 changes: 1 addition & 1 deletion backend/app/collections/docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Owns: local setup, commands, git workflow, commit format, testing,
release process. Does not own: business logic (see `README.md`).

Status: **Phase 8 complete — submission scope (Phases 0-8) done.**
Status: **Phase 8 complete — core scope (Phases 0-8) done.**

## Local setup

Expand Down
2 changes: 1 addition & 1 deletion backend/app/collections/docs/RULES.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ needs investigation, not an automatic guess.
`Invoice.invoice_amount`.
**Fires on:** INV-1007 — invoice amount 88,000; PAY-2008 (30,000) +
PAY-2028 (80,000) = 110,000, an overpayment of 22,000.
**Not on the assessment's required list** — added because the data
**Not on the original required list** — added because the data
called for it (see QA_PREP.md Q5).
**Excludes nothing further:** `compute_outstanding` already floors at
zero, so no negative/credit figure leaks into the overdue report; this
Expand Down
15 changes: 0 additions & 15 deletions backend/app/collections/images/README.md

This file was deleted.

6 changes: 3 additions & 3 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [0.9.0] - 2026-08-31

**Submission scope (MASTER_PLAN.md Phases 0-8) complete.**
**Core scope (MASTER_PLAN.md Phases 0-8) complete.**

### Added

Expand Down Expand Up @@ -271,7 +271,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
amounts, invalid/missing GSTIN, payment-to-invoice customer mismatch,
payment after report date, payment before invoice date, Cancelled/Credit
Note status, duplicate source system reference, and overpayment (E014,
added beyond the assessment's required list).
added beyond the original required list).
- `docs/RULES.md`: complete 14-rule catalogue, one entry per code, with
condition, dataset A trigger, exclusion behavior, and rationale.
- Test suite additions under `backend/app/collections/tests/` (65 new
Expand All @@ -293,7 +293,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- `backend/app/collections/fixtures/dataset_a_original.xlsx`: the given
assessment workbook, committed as a reproducible fixture.
sample workbook, committed as a reproducible fixture.
- `openpyxl` runtime dependency for reading xlsx workbooks with typed cells.
- `contracts.py`: Decimal/date-typed canonical dataclasses (`CanonicalCustomer`,
`CanonicalInvoice`, `CanonicalPayment`, `RegionMap`, `CanonicalDataset`).
Expand Down
2 changes: 1 addition & 1 deletion postman/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ API returns, every request and test script actually ran.
2. **Runs > Create Run - Dataset A (Blocks)** — click into the Body tab,
click **Select File** next to `file`, choose
`backend/app/collections/fixtures/dataset_a_original.xlsx` (or your
own copy of the assessment workbook). Send. The test script saves the
own copy of the sample workbook). Send. The test script saves the
returned run's id into `{{run_id}}` automatically — nothing else to
configure.
3. Open **Reports** folder, run every request top to bottom against that
Expand Down
Loading