Thinking global, living local

How to use uco-ulo-tracking: chain-of-custody, integrity flags and EPR checks for used cooking and lubricating oil

October 29, 2026

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

Skip to article content
Text Size

Revised · v1.0.0 · what changed

22backend tests passed in 10.7 s
21REST endpoints (supply chain, compliance, dashboard)
18integrity flags raised on the seeded data, across 14 check types
91hash-chained ledger entries, verified intact

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
Not for.
  • 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

RequirementWhyHow to get it
Python 3.10 or newerThe 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, httpxBackend runtime and test suitepip install -r backend/requirements.txt
Node.js and npm (only for the front end)run_app.sh starts Vite on port 5173nodejs.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:

ToolWhat you needBest for
GitHubFree account to fork or star; git to clone, or the green Code button, then Download ZIP, or open in CodespacesGetting the code and keeping up with updates
Claude CodeClaude subscription or API key; run claude inside the cloned folderLetting the AI read and run the whole repo
Gemini CLIGoogle account; run gemini inside the cloned folderThe same hands-on workflow with Gemini
Claude.ai, Gemini or ChatGPT in the browserA 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 firstEditing and extending the code
git clone https://github.com/herrrickshaw/uco-ulo-tracking.git
cd uco-ulo-tracking
claude      # or: gemini

Install

  1. Clone the repository
    git clone https://github.com/herrrickshaw/uco-ulo-tracking.git
    cd uco-ulo-tracking/backend
  2. 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

MethodHow it works
Run the API aloneuvicorn 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 rulesEdit 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 seedbackend/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 assistantGive 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.

Revision history.
  • 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.

Umashankar Triplicane Dwarakanathan
Contact Us
Umashankar Triplicane Dwarakanathan
Investment Promotion & Energy-Sector Leader · Chennai, Tamil Nadu, India
LinkedIn → GitHub → Email +91 78273 81696
How this site works

Data-led analysis of India's trade, currency and industrial policy. Every article is built from primary official sources, and every figure links back to the release, table or filing it came from.

Sources. DGCI&S TradeStat (imports/exports, HSN-wise) · PIB (government press releases, January 2017 to today, refreshed daily) · RBI (circulars, balance of payments) · MoSPI (CPI/WPI, IIP) · PARIVESH (environmental clearances) · CCIL (bond yields) · BIS (policy rates) · SEBI, NSE/BSE and SEC filings for company data.

Interpretation. Figures carry their vintage and retrieval date; estimates and press-reported numbers are labelled as such; where sources disagree, both are shown. Corrections are made visibly, never silently. Articles are written with AI assistance from the cited sources — AI-generated text can misstate figures even when working from real material, so verify any number that matters to a decision against the linked primary source.

footer

Browse all articles by topic

Every piece on this blog, grouped. Or read the full index.

Agriculture & FertilisersAI ToolsChemicalsClimate & CarbonEnergy & FuelsGas & LNGImport SubstitutionIndustrial PolicyMarkets & FinanceMobility & EVPrices & InflationTextilesTrade & Tariffs

Each topic is a live archive page that updates itself as pieces are labelled. It replaces a hand-kept list that had fallen 18 articles behind.