docs(claude): capture session's new patterns + gotchas
Three additions from this session's work: 1. Django ORM gotcha — PayrollAdjustment project double-attribution. Documents the Coalesce pattern that solved the Apr 2026 perf-pass double-count bug on Overtime adjustments. 2. Payroll dashboard query-count baselines — target ranges for / and the four /payroll/ tabs after the perf pass, plus the "spotting a regression" heuristic (>50% jump = N+1 reintroduced). 3. Profiling locally — Django Debug Toolbar — what it is, how it's triple-gated, how to use it for N+1 hunting. Flags that the package is already in requirements.txt so future sessions don't need to install it. Net: +35 lines, three new sections, no deletions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8f495064c3
commit
b43892f712
51
CLAUDE.md
51
CLAUDE.md
@ -99,6 +99,25 @@ outer queryset JOIN-free. See `_build_report_context` in `core/views.py` and
|
|||||||
`ReportContextFilterInflationTests` in `core/tests.py` for the reference
|
`ReportContextFilterInflationTests` in `core/tests.py` for the reference
|
||||||
implementation (commit f1e246c, Apr 2026).
|
implementation (commit f1e246c, Apr 2026).
|
||||||
|
|
||||||
|
## Django ORM gotcha — PayrollAdjustment project double-attribution
|
||||||
|
`PayrollAdjustment` has TWO project FKs: a direct `adj.project` and an
|
||||||
|
indirect `adj.work_log.project`. For every **Overtime** adjustment these
|
||||||
|
always point at the same project (see `price_overtime()` — it sets
|
||||||
|
BOTH). When rolling up "costs per project" you typically want the
|
||||||
|
OR-union — "adjustments where either FK points to project P".
|
||||||
|
|
||||||
|
- **Correct**: `Q(project_id__in=ids) | Q(work_log__project_id__in=ids)` filter
|
||||||
|
+ `.annotate(effective_project_id=Coalesce('project_id', 'work_log__project_id'))`
|
||||||
|
+ `.values('effective_project_id', ...).annotate(total=Sum('amount'))`.
|
||||||
|
Each row contributes to exactly ONE project.
|
||||||
|
- **WRONG**: two separate filtered querysets (one per FK) summed in
|
||||||
|
Python. Any row with BOTH FKs set (every Overtime) gets counted twice.
|
||||||
|
Bit us during the Apr 2026 perf pass — Coalesce fix is commit
|
||||||
|
`167c821`. Regression test: `PayrollDashboardAdjustmentAggregationTests`
|
||||||
|
in `core/tests.py`. See `payroll_dashboard()` in `core/views.py` for
|
||||||
|
the reference implementation on both the unpaid-outstanding card and
|
||||||
|
the paid-monthly stacked chart.
|
||||||
|
|
||||||
## PayrollAdjustment Type Handling
|
## PayrollAdjustment Type Handling
|
||||||
- **Bonus / Deduction** — standalone, require a linked Project
|
- **Bonus / Deduction** — standalone, require a linked Project
|
||||||
- **New Loan** — creates a `Loan` record (`loan_type='loan'`); has a "Pay Immediately" checkbox (checked by default) that auto-processes the loan (creates PayrollRecord, sends payslip to Spark, marks as paid). When unchecked, the loan sits in Pending Payments for the next pay cycle. Editing syncs loan amount/balance/reason; deleting cascades to Loan + unpaid repayments
|
- **New Loan** — creates a `Loan` record (`loan_type='loan'`); has a "Pay Immediately" checkbox (checked by default) that auto-processes the loan (creates PayrollRecord, sends payslip to Spark, marks as paid). When unchecked, the loan sits in Pending Payments for the next pay cycle. Editing syncs loan amount/balance/reason; deleting cascades to Loan + unpaid repayments
|
||||||
@ -114,6 +133,22 @@ The dashboard's outstanding amount uses **per-worker** checking, not per-log:
|
|||||||
- This correctly handles partially-paid logs (e.g., one worker paid, another not)
|
- This correctly handles partially-paid logs (e.g., one worker paid, another not)
|
||||||
- Unpaid adjustments: additive types increase outstanding, deductive types decrease it
|
- Unpaid adjustments: additive types increase outstanding, deductive types decrease it
|
||||||
|
|
||||||
|
## Payroll dashboard query-count baselines (post Apr 2026 perf pass)
|
||||||
|
Target ranges after `payroll_dashboard()` was optimized with batched
|
||||||
|
aggregates + `Prefetch(to_attr='active_workers_cached')` + Coalesce-based
|
||||||
|
project attribution (commits `61c485f` + `167c821`):
|
||||||
|
- `/` (admin dashboard) — ~15 queries
|
||||||
|
- `/payroll/?status=pending` — ~24
|
||||||
|
- `/payroll/?status=history` — ~24
|
||||||
|
- `/payroll/?status=loans` — ~25
|
||||||
|
- `/payroll/?status=adjustments` — ~32
|
||||||
|
|
||||||
|
If any of these jumps meaningfully (>50%) after a future change, an N+1
|
||||||
|
was reintroduced. Profile with Django Debug Toolbar (see Profiling
|
||||||
|
section below) to find it. The test suite does NOT have `assertNumQueries`
|
||||||
|
guards on these views — deliberate YAGNI for now, worth adding if
|
||||||
|
regressions become a pattern.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
```bash
|
```bash
|
||||||
# Local development (SQLite)
|
# Local development (SQLite)
|
||||||
@ -131,6 +166,22 @@ python manage.py check # System check
|
|||||||
USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests -v 2
|
USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests -v 2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Profiling locally — Django Debug Toolbar
|
||||||
|
Installed as a dev-only dependency in `requirements.txt` since Apr 2026.
|
||||||
|
Triple-gated in `config/settings.py`: only loads when **DEBUG=true AND
|
||||||
|
USE_SQLITE=true AND NOT running tests**. Never loads in production —
|
||||||
|
prod has neither flag, and the test-run gate exists because the toolbar
|
||||||
|
emits an E001 system-check error + breaks template rendering when
|
||||||
|
DEBUG=false (which Django forces during `manage.py test`).
|
||||||
|
|
||||||
|
To profile a page: start the dev server normally (`run_dev.bat` or
|
||||||
|
inline `USE_SQLITE=true DJANGO_DEBUG=true python manage.py runserver`),
|
||||||
|
log in as admin, navigate to any URL, click the toolbar tab on the
|
||||||
|
right edge. The **SQL panel** shows query count + highlights any
|
||||||
|
duplicate-query groups — the go-to tool for N+1 hunting. See the
|
||||||
|
"Payroll dashboard query-count baselines" section for expected
|
||||||
|
numbers on hot pages.
|
||||||
|
|
||||||
## Development Workflow
|
## Development Workflow
|
||||||
- Active development branch: `ai-dev` (PR target: `master`)
|
- Active development branch: `ai-dev` (PR target: `master`)
|
||||||
- Local dev uses SQLite: set `USE_SQLITE=true` environment variable
|
- Local dev uses SQLite: set `USE_SQLITE=true` environment variable
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user