Skip to content

deva-ships/tender-tally

Repository files navigation

TenderTally

CI License: MIT

Turn a pile of vendor tenders for one building job into a single Excel file that compares every line item side by side, highlights the lowest rate per item, and ranks the vendors from cheapest to dearest.

TenderTally ships as a Claude skill called tender-comparison. The same scripts also run on their own from the command line.

The problem

An architect or contractor invites several vendors to bid on the same job. Each vendor sends back a tender. The trouble is that every vendor formats it differently. They name items differently ("800x800 vitrified tile flooring" vs "Vitrified tiles 800x800mm"), group them differently, quote in a different order, and some hand-write the rates on a scanned page with no text layer. Comparing them by hand is slow and error prone.

TenderTally lines them up item by item, keeps nothing hidden, and tells you who is cheapest for each item and overall.

What you get

One .xlsx file with a Summary sheet and one comparison sheet per job. Each comparison sheet has:

  • one row per item, grouped under section headings
  • a Rate column and an Amount column for every vendor
  • the lowest rate in each row highlighted green, with a "Lowest bidder" column
  • items a vendor did not quote flagged, never dropped
  • a totals row, a net payable row that accounts for blanket discounts, and an L1 to Ln ranking with the winner in gold

Example TenderTally comparison output

The image above is built from the fictional example data. The openable file is at tender-comparison/examples/Example Comparison (anonymized).xlsx.

How it works

 tenders (PDF/scan/xlsx/doc)
        │  render_tender.py        render scanned pages to images, read the rates
        ▼
 tender JSON per job              one master item list + every vendor's rates
        │  cross_check.py          GATE: each vendor's rates must tie to their stated total
        ▼
 build_comparison.py              JSON -> formatted Excel
        │  self_check.py           GATE: re-read the file, verify every number and highlight
        ▼
 Tender Comparison.xlsx

The two gates are the point. Nothing gets built until the extracted rates reconcile with each vendor's own grand total, and nothing gets presented until a second pass re-derives every amount, highlight, formula, and ranking from the source.

Use it as a Claude skill

Claude Code: copy the tender-comparison/ folder into your skills directory (for a project, .claude/skills/tender-comparison/). Then ask Claude to compare your tenders and point it at the folder of files.

claude.ai: run ./scripts/package_skill.sh to build tender-comparison.zip, then upload it under Settings, Capabilities, Skills.

The skill's instructions live in tender-comparison/SKILL.md, with detail in tender-comparison/reference/.

Use it standalone

python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

cd tender-comparison

# 1. render scanned tenders to page images, then read the rates into a JSON per job
python scripts/render_tender.py --indir "input" --outdir "pages" --dpi 200

# 2. gate: confirm each vendor's rates tie to their stated total
python scripts/cross_check.py job1.json job2.json

# 3. build the Excel comparison
python scripts/build_comparison.py --out "Tender Comparison.xlsx" job1.json job2.json

# 4. gate: validate the built file
python scripts/self_check.py --xlsx "Tender Comparison.xlsx" job1.json job2.json

The tender JSON schema is documented in reference/output-format.md, with a full worked pair under examples/.

Correctness

Extraction from a scan is the weak link in any tool like this, so TenderTally leans on checks rather than trust:

  • cross_check.py adds up rate times quantity for each vendor and compares it to the total the vendor wrote. A mismatch means a misread number, and it stops the run. A genuine slip in the vendor's own arithmetic is recorded with a note, because the quoted rate governs.
  • self_check.py re-reads the saved workbook and re-derives everything: every amount equals rate times quantity, every green highlight is the true row minimum, the net payable formulas are correct, the ranking is sorted, and no item was dropped or duplicated.
  • tests/ includes fault-injection tests that corrupt a workbook on purpose and assert the checker catches it. A validator that never fails would be useless.

What no tool can catch on its own is a rate read wrong that still happens to tie out. The cross-check is the main defence, and the ground truth is always the original tender.

Testing

pip install -r requirements-dev.txt
pytest

Project structure

tender-comparison/          the Claude skill (this is what you upload)
├── SKILL.md                what the skill does and the step-by-step workflow
├── reference/              extraction, item matching, output format, edge cases
├── scripts/                render, cross_check, build_comparison, self_check
└── examples/               a fictional worked pair + a pre-built example .xlsx
tests/                      pytest suite, including fault-injection tests
scripts/package_skill.sh    build the upload zip
.github/workflows/ci.yml    tests + pipeline smoke test on every push

Privacy

Real tenders contain real vendor names and live bid prices. This repo commits none of that. The .gitignore excludes input/, output/, sample/, and .private/, and the bundled examples are fictional. Keep it that way.

License

MIT. See LICENSE.

About

Compare vendor tenders item by item and export a ranked Excel comparison. Claude skill + CLI.

Topics

Resources

License

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors