bid-tabulator — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited bid-tabulator (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Processes multiple subcontractor bid PDFs for a scope of work and produces an Excel comparison workbook. Extracts each bid's data as-submitted — the engineer handles normalization and alignment after reviewing discrepancies and contacting subcontractors as needed.
[MATH ERROR: line items sum to $X, bid states $Y].Ask the user for:
If project context is available (.construction/ directory), read project.yaml for project name/number to include in the workbook header.
THIS SKILL → /bid-evaluator → user confirms → /subcontract-writerThis skill is the entry point of the bid pipeline. It produces the tabulated data that /bid-evaluator consumes for analysis.
Bid Tabulation Progress:
- [ ] Step 1: Gather inputs (bid PDFs, scope)
- [ ] Step 2: Read first bid to discover structure
- [ ] Step 3: Process all bids
- [ ] Step 4: Generate comparison Excel
- [ ] Step 5: Present summary to engineer
- [ ] Step 6: Write graph entryOpen the first bid PDF to understand what data is available:
Try pdfplumber first. If text extraction returns meaningful content (>50 chars per page), use text mode. Otherwise fall back to vision.
Vision fallback for scanned bids:
${CLAUDE_SKILL_DIR}/../../bin/construction-python ${CLAUDE_SKILL_DIR}/../../scripts/pdf/rasterize_page.py BID.pdf 1 --dpi 200 --output bid_page.pngFrom the first bid, identify what data fields are present. Common bid data:
# Bidder info
company_name: ""
contact_name: ""
contact_phone: ""
contact_email: ""
bid_date: ""
bid_validity_period: ""
# Financial
base_bid_amount: ""
line_items: # Every line item as an object:
- spec_section: "" # CSI section if shown (e.g., "09 65 19")
description: "" # Original description verbatim
qty: null # Quantity (numeric or null if lump sum)
unit: "" # Unit as written (SF, LF, EA, LS, etc.)
unit_price: null # Per-unit cost (numeric — extract or calculate)
extended_price: null # Line total (qty × unit_price, or lump sum amount)
notes: "" # Flags, clarifications
alternates: [] # Alternate pricing: [{name, description, amount}]
allowances: [] # Allowances included
# Terms
scope_inclusions: [] # What the bid explicitly includes
scope_exclusions: [] # What the bid explicitly excludes
qualifications: [] # Conditions, assumptions, caveats
schedule_duration: "" # Proposed duration if stated
payment_terms: "" # Net 30, etc.
bond_included: false # Whether bid/performance bond is included
insurance_confirmed: falseLine item extraction rules:
unit_price = extended_price / qty.qty: null, unit: "LS", unit_price: null, extended_price: <amount>.spec_section may not always be present — extract it if the bid references CSI section numbers.Present the discovered structure to the user before processing remaining bids: "I found these data fields in the first bid: [list]. Should I extract all of these, or add/remove any?"
Process each bid PDF individually. For each bid:
[unclear: possibly $45,000 or $45/SF].construction/bid_tab/bids/{company_name_slug}.jsonState persistence — write progress after each bid:
# .construction/bid_tab/extraction_state.yaml
scope: "Division 09 - Finishes"
total_bids: 12
processed: 5
current_bid: "Smith_Drywall.pdf"
errors: []Important: Each bid's line items, descriptions, and amounts must be recorded exactly as submitted. Different bidders may describe the same work differently, use different units, or break out scope differently. This is expected and intentional — the engineer will reconcile after review.
${CLAUDE_SKILL_DIR}/../../bin/construction-python ${CLAUDE_SKILL_DIR}/scripts/bid_comparison_to_xlsx.py \
--data .construction/bid_tab/bids/ \
--scope "Division 09 - Finishes" \
--project "Project Name" \
--output "Bid_Comparison_Div09.xlsx"Excel workbook structure:
Tab 1 — Comparison Summary:
Tab 2 — Per-Bidder Detail Tabs (one tab per bidder):
Tab 3 — Exclusions & Qualifications:
Tab 4 — Scope Gaps (flagged):
After generating the Excel:
"I extracted data from [N] bids for [scope]. Here's the summary:
The Excel file is at [path]. All line items are extracted as-submitted — you'll want to review the Scope Gaps tab and contact subs to clarify any discrepancies before finalizing your comparison."
${CLAUDE_SKILL_DIR}/../../bin/construction-python ${CLAUDE_SKILL_DIR}/../../scripts/graph/write_finding.py \
--type "bid_tabulation_complete" \
--title "Bid comparison: {scope} — {N} bidders" \
--output-file "Bid_Comparison_{scope_slug}.xlsx" \
--data '{"scope": "Division 09", "bidder_count": 12, "low_bid": 450000, "high_bid": 680000}'Check for .construction/bid_tab/extraction_state.yaml. If status: in_progress, resume from the next unprocessed bid.
Never overwrite an existing bid comparison. If a file exists at the target location, save with a versioned name (e.g., Bid_Comparison_Div09_v2.xlsx).
${CLAUDE_SKILL_DIR}/../../bin/construction-python${CLAUDE_SKILL_DIR}/../../scripts/pdf/rasterize_page.py${CLAUDE_SKILL_DIR}/scripts/bid_comparison_to_xlsx.py${CLAUDE_SKILL_DIR}/../../scripts/graph/write_finding.py~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.