BudgetMan is a FastAPI app with a web portal that:
- accepts a BOM CSV,
- scrapes each product URL for price,
- captures screenshots,
- generates a PDF costing report.
The report now uses a single budget section (Components) instead of multiple grouped sections.
- CSV BOM upload from browser UI.
- Club profile selection from
static/clubs.json. - Scraping with Playwright (Chromium) and multiple fallback extractors.
- Manual CSV price fallback for blocked or unsupported pages.
- PDF with cover page, single budget table, and detailed screenshot annex.
- Python
- FastAPI
- Playwright +
playwright-stealth - ReportLab
main.py FastAPI app entrypoint
routers/bom.py API endpoint for report generation
services/parser.py CSV parsing and validation
services/scraper.py Product page scraping and screenshots
services/pdf_generator.py PDF report rendering
services/club_profiles.py Club profile normalization/lookup
static/index.html Web UI
static/app.js Frontend logic
static/clubs.json Club profile data for UI and backend selection
- Python 3.10+
- Chromium dependencies (installed by Playwright)
python -m venv venv./venv/Scripts/Activate.ps1source venv/bin/activateInstall dependencies:
pip install -r requirements.txt
python -m playwright install chromiumRun server:
uvicorn main:app --reload --port 8000Open:
http://localhost:8000/(portal)http://localhost:8000/docs(Swagger UI)
Multipart form-data fields:
file(required): BOM CSV file.club_profile_id(optional): profile ID fromstatic/clubs.json.project_name(optional): title shown on report cover.
Response:
application/pdffile namedBOM_Costing_Report.pdf.
Example (curl on macOS/Linux):
curl -X POST http://localhost:8000/generate-costing \
-F "file=@sample_bom.csv" \
-F "club_profile_id=aeromodelling" \
-F "project_name=Bipedal Bot" \
--output BOM_Costing_Report.pdfExample (curl.exe on Windows PowerShell):
curl.exe -X POST "http://localhost:8000/generate-costing" `
-F "file=@sample_bom.csv" `
-F "club_profile_id=aeromodelling" `
-F "project_name=Bipedal Bot" `
--output BOM_Costing_Report.pdfRequired headers:
nameurlquantity
Optional headers:
- any additional columns are preserved in parsed data,
- manual price fallback columns:
price,unit_price,cost,estimated_price.
Example:
name,url,quantity,unit_price,notes
MQ2 Gas Sensor,https://robu.in/product/mq-2-mq2-smoke-gas-lpg-butane-hydrogen-gas-sensor-detector-module/,1,,Primary gas sensor
Special Sensor,https://example.com/protected-page,3,12.75,Manual price fallbackNotes:
quantitydefaults to1when invalid.- rows missing
nameorurlare skipped. - category/group fields are not used for report sectioning.
Frontend and backend both rely on profile IDs, and the backend loads normalized profile fields.
Current JSON shape:
{
"profiles": [
{
"id": "aeromodelling",
"club_name": "Aeromodelling Club",
"secretary": {
"name": "Nalin Angrish",
"entry_number": "2023MEB1360",
"phone_number": ""
},
"representative": {
"name": "Kian Sparrow",
"entry_number": "2024EPB1268",
"phone_number": ""
}
}
]
}Logo files should exist under static/clubs/ and follow /static/clubs/<clubid>.png.
The BOST logo is loaded from /static/bost.png on both sides of the cover.
Generated report contains:
- Cover page with logos, club title, project name, and officer blocks.
- Budget section with a single
Componentstable and grand total. - Detailed list of components with screenshots.
For each product URL, scraping tries:
- common CSS selectors,
- metadata tags,
- full-page text regex,
- manual CSV fallback price columns.
- This project does not bypass bot protection (Cloudflare/challenges).
- If scraping fails, provide manual price in CSV.
- Prefer official supplier APIs when available.
Build:
docker build -t budgetman .Run:
docker run --rm -p 8000:8000 budgetmanOpen http://localhost:8000/.
- Push repository to GitHub.
- Create a new Render Web Service from the repo.
- Render detects
Dockerfileautomatically. - Deploy without a custom start command.
The container command already uses PORT:
uvicorn main:app --host 0.0.0.0 --port ${PORT:-8000}
Only CSV files accepted.: ensure filename ends with.csv.CSV missing required columns: includename,url,quantityheaders.- Empty report data: verify CSV rows include both
nameandurl. - Missing prices: add
unit_priceorpricein CSV for blocked pages.