A FastAPI + SQLite backend (with a React front end) that tracks used cooking oil and used lubricating oil from generator to buyer: manifests, lot mass balance, processing yields, a SHA-256 hash-chained ledger, fraud heuristics and India's used-oil EPR arithmetic. This post runs the tests, starts the API on seeded mock data and reads the compliance endpoints.
Open-Source Tools · How-To · Energy & fuels models
How to use uco-ulo-tracking: chain-of-custody, integrity flags and EPR checks for used cooking and lubricating oil
Revised
· v1.0.0 · what changed
Code: github.com/herrrickshaw/uco-ulo-tracking
Watch the walkthrough
Watch on YouTube: youtu.be/mVYgWein8Xo
Why it is useful
- Aggregators, re-refiners, biodiesel and SAF feedstock buyers, and compliance teams get a working reference model of a UCO/ULO custody chain: generator, collection manifest, lot, processing batch, dispatch and shipment, each with the quality fields (TPC, FFA, water, PCB, chlorine) that regulators care about.
- It replaces spreadsheet mass balances and ad-hoc fraud checks with eleven named integrity heuristics (suspiciously fresh UCO, generator over capacity, duplicate manifest, lapsed registration, mass balance, implausible yield, PCB hazard and more) and a tamper-evident ledger.
- The used-oil EPR module encodes the HOWM 2023 amendment arithmetic (producer target ladder 5/10/20/20/40/40%, Y-2 basis, certificate weightage 1.0 re-refining vs 0.25 co-processing, purchase cap of liability + 10%) so a producer's shortfall is computed, not eyeballed. Every threshold is an editable default served at /api/compliance/parameters.
- Evidence from the run: 22 tests passed; the API seeded six months of mock data on first start and reported 73.9 t UCO and 30.5 t ULO collected, 18 high-severity flags (all planted deliberately in the seed), a 91-entry ledger with intact hash chain, and one of two EPR producers short by 15.68 t of certificates.
What it is for
The backend (backend/app) is a FastAPI service over SQLite with routers for supply (generators, collectors, collections, lots, batches, dispatches, shipments), compliance (integrity, EPR, certificates, recycler supply, returns, ledger, GHG, parameters) and a dashboard summary. On first start it seeds mock data with deliberately planted anomalies so each check has something to show. The front end (frontend/, React + Vite + Tailwind) renders the same data; it was not run for this post.
It is a sister project to saf-monitoring-system and a reference implementation for how UCO (a HEFA/biodiesel feedstock) and ULO (a hazardous waste re-refined to base oil) could be tracked in India. The README links a hosted front-end-only demo and a narrated walkthrough.
Regulatory numbers in backend/app/calculations/constants.py are editable defaults, several marked VERIFY or PLACEHOLDER. This post and the tool are not legal or regulatory advice.
Who it is for
- UCO and ULO aggregators, re-refiners and biofuel feedstock buyers designing a traceability system
- Compliance and sustainability analysts who want to see India's used-oil EPR rules as code
- Developers building a chain-of-custody app who want a small, tested FastAPI + SQLite starting point
- Filing anything with CPCB, an SPCB or FSSAI: this is not legal advice and the thresholds must be reconciled with the official notifications first
- Certified GHG accounting: the savings figures are indicative RED-style defaults, not CORSIA or RED III certified values
- Treating a flag as a finding: the integrity checks are prompts to investigate, and the ledger is tamper-evident on one node only
What you need
| Requirement | Why | How to get it |
|---|---|---|
| Python 3.10 or newer | The code uses PEP 604 type hints; run_app.sh checks for 3.10+ | python.org; this post used Python 3.15.1 |
| FastAPI, SQLAlchemy, Pydantic, uvicorn, pytest, httpx | Backend runtime and test suite | pip install -r backend/requirements.txt |
| Node.js and npm (only for the front end) | run_app.sh starts Vite on port 5173 | nodejs.org; not used in this post |
Get the code and an AI assistant
You do not have to be a programmer. Get the repository, give it to an AI assistant, and ask in plain language. Any of these routes works:
| Tool | What you need | Best for |
|---|---|---|
| GitHub | Free account to fork or star; git to clone, or the green Code button, then Download ZIP, or open in Codespaces | Getting the code and keeping up with updates |
| Claude Code | Claude subscription or API key; run claude inside the cloned folder | Letting the AI read and run the whole repo |
| Gemini CLI | Google account; run gemini inside the cloned folder | The same hands-on workflow with Gemini |
| Claude.ai, Gemini or ChatGPT in the browser | A chat account; upload README.md and the files you care about (or paste the output of git ls-files) | Understanding the project and asking questions without installing anything |
| Other tools (Copilot Chat, Cursor, Codex CLI) | Open the cloned folder in the tool and point it at README.md first | Editing and extending the code |
git clone https://github.com/herrrickshaw/uco-ulo-tracking.git
cd uco-ulo-tracking
claude # or: gemini
Install
- Clone the repository
git clone https://github.com/herrrickshaw/uco-ulo-tracking.git cd uco-ulo-tracking/backend - Create a virtual environment and install the backend dependencies
python3 -m venv .venv && source .venv/bin/activate pip install -r requirements.txt
Quick start: commands and what you should see
Run the test suite
pytest -q
22 passed in about 11 s, with StarletteDeprecationWarning lines about httpx and the test client (harmless; see limits).
Start the API (seeds mock data on first run)
uvicorn app.main:app --port 8000
# interactive docs: http://127.0.0.1:8000/docs
Application startup complete. The first request seeds 8 generators, 7 collectors/processors, 12 lots, 12 batches, 14 shipments and 2 EPR producers.
Read the dashboard and the integrity flags
curl -s http://127.0.0.1:8000/api/dashboard/summary
curl -s http://127.0.0.1:8000/api/compliance/integrity
A summary with collected tonnes by material and output tonnes by product, ledger status and flag counts; then a list of 18 flags with check, ref, detail and severity.
See every regulatory threshold the model uses
curl -s http://127.0.0.1:8000/api/compliance/parameters
A JSON of editable defaults: FSSAI_TPC_LIMIT_PCT 25, UCO_SUSPICIOUSLY_FRESH_TPC_PCT 8, EPR_PRODUCER_PCT ladder 2024 to 2029, WEIGHTAGE re_refining 1.0 and co_processing 0.25, PURCHASE_CAP_MARGIN_PCT 10, EC_INR_PER_TONNE null, among others.
Sample outputs
Real output from a run on the date shown in the sources line; trimmed to fit.
Dashboard summary after first start (seeded mock data)
curl -s http://127.0.0.1:8000/api/dashboard/summary
{
"uco_collected_tonnes": 73.9,
"ulo_collected_tonnes": 30.5,
"output_tonnes_by_product": {"base_oil": 20.9, "biodiesel": 65.4},
"ledger_intact": true,
"ledger_entries": 91,
"integrity_flags_high": 18,
"integrity_flags_total": 18
}
All figures come from the deterministic mock seed (fixed random seed, seed date 20 October 2026). The generator and company names in the data are fictional.
Integrity flags: counts by check and four examples
curl -s http://127.0.0.1:8000/api/compliance/integrity
generator_over_capacity 4, duplicate_manifest 2, suspiciously_fresh_uco 1, collector_registration 1,
pcb_hazard_segregate 1, mass_balance 1, implausible_yield 1, dispatch_exceeds_output 1,
vehicle_not_in_nhwts 1, vehicle_no_gps 1, transit_variance 1, ca1_has_storage 1,
ca1_container_too_large 1, ca1_out_of_radius 1
{"check": "suspiciously_fresh_uco", "ref": "collection M90001", "detail": "TPC 3.1% < 8.0% -- possible fresh/blended oil", "severity": "high"}
{"check": "generator_over_capacity", "ref": "Spice Route Restaurant 2026-09", "detail": "1600 L collected vs plausible max 900 L from declared purchases", "severity": "high"}
{"check": "collector_registration", "ref": "collection M90003", "detail": "Lapsed Pickup Co registration missing/expired on 2026-09-10", "severity": "high"}
{"check": "pcb_hazard_segregate", "ref": "collection M90004", "detail": "ULO acceptance threshold exceeded", "severity": "high"}
The fresh-oil check is the classic UCO-fraud signature: total polar compounds far below what used frying oil shows. The over-capacity check compares litres collected with 60% of declared monthly purchases. Each is a prompt to investigate, not a finding.
Used-oil EPR position for the two seeded producers, FY2026-27
curl -s http://127.0.0.1:8000/api/compliance/epr
producer type basis_fy basis_t target required_t certificates_t shortfall_t max_purchasable_t compliant
Meridian Lubricants P3 2024-25 78.4 20.0% 15.68 16.30 0.00 17.25 true
Coastal Base Oils P1 2024-25 125.4 20.0% 25.08 9.40 15.68 27.59 false
FY2026-27 target 20% of FY2024-25 sales (Y-2 basis); max purchasable = liability + 10%. Reformatted from the JSON list for width; values unchanged.
Ledger head and lot mass balance
curl -s http://127.0.0.1:8000/api/compliance/ledger
curl -s http://127.0.0.1:8000/api/lots
entries: 91 intact: true problems: []
head_hash: 9331f5d33338bd2ba8afcfc687302436d6f9e203cac148c64a34eff60e4f7755
{"lot_code": "UCO-202609", "material": "UCO", "n_collections": 9, "received_kg": 21222.6, "processed_kg": 20585.9, "unprocessed_kg": 636.7, "balanced": true}
{"lot_code": "ULO-202609", "material": "ULO", "n_collections": 4, "received_kg": 6170.4, "processed_kg": 7095.9, "unprocessed_kg": -925.5, "balanced": false}
The ULO lot processed more than it received, which is the planted mass_balance anomaly. The ledger hash chain detects edits to logged records on this node; anchor the head hash externally for stronger guarantees.
Ways to use it
| Method | How it works |
|---|---|
| Run the API alone | uvicorn app.main:app --port 8000 from backend/, then use the Swagger UI at /docs or curl the endpoints. |
| Full app | ./run_app.sh starts the backend on 8000 and the Vite front end on 5173 (needs npm; not run here). |
| Adapt the rules | Edit backend/app/calculations/constants.py; the values are served at /api/compliance/parameters so the front end and your reports stay in sync. |
| Replace the mock seed | backend/app/seed/mock_data.py only runs when the Generator table is empty; swap it for your own loader or POST collections, batches, dispatches and shipments through the API. |
| Through an AI assistant | Give it README.md and backend/app/calculations/ and ask it to explain or extend a check. |
Sample prompts for your AI assistant
Copy, paste and edit the parts in [brackets].
- Read backend/app/calculations/integrity.py and list every check, its threshold and where that threshold comes from in constants.py; mark which are regulation and which are analytical choices.
- Explain the used-oil EPR calculation in backend/app/calculations/epr.py for a producer with [120] t of FY2024-25 sales and [10] t of re-refining certificates, and show the FY2026-27 shortfall.
- Add a new integrity check that flags collections whose FFA % is above [15]% and write a pytest for it in backend/tests/test_calculations.py.
- Write a Python script that POSTs a month of collections from [my CSV] to /api/collections and then prints the new flags from /api/compliance/integrity.
- Compare the thresholds in constants.py with docs/QUALITY_REQUIREMENTS.md and list the ones marked VERIFY or PLACEHOLDER that I must confirm before real use.
- Explain how the SHA-256 ledger in backend/app/calculations/ledger.py chains entries and what an attacker with database access could still do.
Limits and honest caveats
- Everything shown is seeded mock data with planted anomalies; no real generators, collectors or producers. The front end was not run for this post.
- The test run printed 93 warnings, including StarletteDeprecationWarning that httpx with the Starlette test client is deprecated. Tests still pass, but a future Starlette or httpx release could break them.
- Regulatory defaults are editable placeholders. The README itself flags: the >50 L/day RUCO record-keeping threshold is known only via a press notice; the EPR conversion factor is set per recycler by CPCB and the default is a placeholder; Environmental Compensation (INR/t) is not captured; GHG defaults are indicative, not certified.
- Fraud heuristics (fresh-oil TPC floor of 8%, 60% recoverable fraction) are analytical choices, not regulation. A flag is a prompt to investigate.
- SQLite and a single node: the ledger is tamper-evident, not tamper-proof. See the end of docs/PLATFORM_REVIEW.md for what is not built yet. Not legal advice.
Sources: the uco-ulo-tracking repo (README, backend/app/, backend/app/seed/mock_data.py, backend/app/calculations/constants.py), with pytest and uvicorn run in a throwaway virtualenv on 29 October 2026 from a fresh clone; endpoint outputs captured with curl against the seeded SQLite database; front end not run.
- v1.0.0 — 30 October 2026 — first published.
About this article: Researched, written and edited by Umashankar Triplicane Dwarakanathan, with AI research assistance; every figure is meant to trace to the primary source cited. See the Editorial Policy for how sourcing, AI use and corrections work.