#!/usr/bin/env python3 """ BOQ PDF -> Excel + CSV python3 run.py tender.pdf # writes out/BOQ_.xlsx + .csv python3 run.py tender.pdf --report # also prints the validation report """ from __future__ import annotations import argparse import json import os import re import sys from boq_engine.extract import extract from boq_engine.populate import flatten, populate_template, to_csv from boq_engine.validate import validate def safe(name: str) -> str: return re.sub(r"[^A-Za-z0-9]+", "_", name).strip("_")[:60] def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("pdf") ap.add_argument("-o", "--outdir", default="out") ap.add_argument("--contract", default="") ap.add_argument("--title", default="BILL OF QUANTITIES") ap.add_argument("--vat", type=float, default=15.0) ap.add_argument("--report", action="store_true") ap.add_argument("--json", dest="json_out") args = ap.parse_args() base = safe(os.path.splitext(os.path.basename(args.pdf))[0]) os.makedirs(args.outdir, exist_ok=True) xlsx = os.path.join(args.outdir, f"{base}.xlsx") csvp = os.path.join(args.outdir, f"{base}.csv") jsonp = args.json_out or os.path.join(args.outdir, f"{base}.json") print(f"reading {args.pdf}") tree = extract(args.pdf) rep = validate(tree) stats = populate_template(tree, xlsx, title=args.title, contract=args.contract, vat_pct=args.vat) nrows = to_csv(tree, csvp) with open(jsonp, "w", encoding="utf-8") as fh: json.dump({"tree": tree, "validation": rep.as_dict()}, fh, indent=1, default=str) print(f"\n priced lines {stats['lines']}") print(f" groups {stats['groups']}") print(f" 'Rate only' {stats['rate_only']}") print(f" odd units {stats['unknown_units']} (highlighted in the workbook)") print(f" csv rows {nrows}") print(f" validation {len(rep.errors)} error(s), {len(rep.warnings)} warning(s)") if rep.errors or args.report: print("\nVALIDATION") for i in rep.issues: if i.severity == "info" and not args.report: continue mark = {"error": "ERROR", "warn": "WARN ", "info": "INFO "}[i.severity] print(f" {mark} {i.code}") print(f" {i.where}") print(f" {i.detail}") print(f"\nwrote {xlsx}\nwrote {csvp}\nwrote {jsonp}") return 1 if rep.errors else 0 if __name__ == "__main__": sys.exit(main())