TenderIQ / scripts /make_v6.py
JaydeepR's picture
Add presentation generation scripts, gitignore deck binaries
1b26bd8
#!/usr/bin/env python3
"""TenderIQ v6 — Infographic (PDF via reportlab)"""
import os
import math
from reportlab.pdfgen.canvas import Canvas
from reportlab.lib.pagesizes import A4, landscape
from reportlab.lib import colors
from reportlab.lib.units import cm
from reportlab.platypus import Paragraph
from reportlab.lib.styles import ParagraphStyle
W, H = landscape(A4)
# Palette
C_BG = colors.white
C_STR = colors.HexColor("#F8FAFC")
C_BLU = colors.HexColor("#2563EB")
C_GRN = colors.HexColor("#22C55E")
C_RED = colors.HexColor("#EF4444")
C_AMB = colors.HexColor("#F59E0B")
C_PUR = colors.HexColor("#8B5CF6")
C_TXT = colors.HexColor("#0F172A")
C_SUB = colors.HexColor("#64748B")
C_DIV = colors.HexColor("#E2E8F0")
M = 1.8 * cm
def para(c, text, x, y, width, font="Helvetica", size=12, color=C_TXT, leading=16, align="left"):
style = ParagraphStyle(
'p', fontName=font, fontSize=size, leading=leading, textColor=color,
alignment={"left": 0, "center": 1, "right": 2}.get(align, 0))
p = Paragraph(text.replace("\n", "<br/>"), style)
_, used = p.wrapOn(c, width, 1000)
p.drawOn(c, x, y - used)
return used
def circ_progress(c, cx, cy, r, pct, fg, bg=C_DIV, thickness=8):
"""Draw a circular progress arc."""
c.setStrokeColor(bg)
c.setLineWidth(thickness)
c.circle(cx, cy, r, fill=0, stroke=1)
c.setStrokeColor(fg)
c.setLineWidth(thickness)
extent = 360 * pct
c.arc(cx - r, cy - r, cx + r, cy + r, startAng=90, extent=-extent)
c.setFillColor(fg)
c.setFont("Helvetica-Bold", int(r * 0.5))
c.drawCentredString(cx, cy - r * 0.18, f"{int(pct * 100)}%")
def header_strip(c, title, n):
c.setFillColor(C_STR)
c.rect(0, H - 1.3 * cm, W, 1.3 * cm, fill=1, stroke=0)
c.setStrokeColor(C_BLU)
c.setLineWidth(2)
c.line(0, H - 1.3 * cm, W, H - 1.3 * cm)
c.setFont("Helvetica-Bold", 16)
c.setFillColor(C_BLU)
c.drawString(M, H - 1.0 * cm, title)
c.setFont("Helvetica", 10)
c.setFillColor(C_SUB)
c.drawRightString(W - M, H - 1.0 * cm, f"TenderIQ · CRPF Hackathon · {n} / 8")
def footer_line(c):
c.setStrokeColor(C_DIV)
c.setLineWidth(0.5)
c.line(M, 0.9 * cm, W - M, 0.9 * cm)
def big_num(c, val, label, cx, cy, color=C_BLU):
c.setFont("Helvetica-Bold", 52)
c.setFillColor(color)
c.drawCentredString(cx, cy, val)
c.setFont("Helvetica", 11)
c.setFillColor(C_SUB)
# All-caps label
for i, line in enumerate(label.split("\n")):
c.drawCentredString(cx, cy - 1.3 * cm - i * 0.45 * cm, line.upper())
def icon_label(c, icon, label, cx, cy, color=C_BLU, icon_sz=36, lbl_sz=11):
c.setFont("Helvetica-Bold", icon_sz)
c.setFillColor(color)
c.drawCentredString(cx, cy, icon)
c.setFont("Helvetica", lbl_sz)
c.setFillColor(C_TXT)
c.drawCentredString(cx, cy - 1.2 * cm, label)
def stage_row(c, icon, title, desc, y, color=C_BLU):
# Icon circle
c.setFillColor(color)
c.circle(M + 0.9 * cm, y + 0.45 * cm, 0.45 * cm, fill=1, stroke=0)
c.setFont("Helvetica-Bold", 14)
c.setFillColor(colors.white)
c.drawCentredString(M + 0.9 * cm, y + 0.3 * cm, icon)
c.setFont("Helvetica-Bold", 13)
c.setFillColor(color)
c.drawString(M + 1.8 * cm, y + 0.55 * cm, title)
c.setFont("Helvetica", 11)
c.setFillColor(C_TXT)
c.drawString(M + 1.8 * cm, y + 0.18 * cm, desc)
# ── Slide 1 ─────────────────────────────────────────────────────────────────
def s1(c):
c.setFillColor(C_BG)
c.rect(0, 0, W, H, fill=1, stroke=0)
# Left accent bar
c.setFillColor(C_BLU)
c.rect(0, 0, 0.6 * cm, H, fill=1, stroke=0)
# Large icon
c.setFont("Helvetica-Bold", 80)
c.setFillColor(C_BLU)
c.drawCentredString(W / 2, H / 2 + 2.2 * cm, "TenderIQ")
# Divider
c.setStrokeColor(C_DIV)
c.setLineWidth(1.5)
c.line(W / 2 - 6 * cm, H / 2 + 1.3 * cm, W / 2 + 6 * cm, H / 2 + 1.3 * cm)
c.setFont("Helvetica", 20)
c.setFillColor(C_TXT)
c.drawCentredString(W / 2, H / 2 + 0.55 * cm,
"Explainable AI for Government Tender Evaluation")
c.setFont("Helvetica", 13)
c.setFillColor(C_SUB)
c.drawCentredString(W / 2, H / 2 - 0.4 * cm, "CRPF HACKATHON · THEME 3")
c.setFont("Helvetica-Oblique", 13)
c.setFillColor(C_BLU)
c.drawCentredString(W / 2, H / 2 - 1.3 * cm,
"From days to minutes. Every decision traceable.")
footer_line(c)
c.showPage()
# ── Slide 2 ─────────────────────────────────────────────────────────────────
def s2(c):
c.setFillColor(C_BG)
c.rect(0, 0, W, H, fill=1, stroke=0)
header_strip(c, "The Problem with Manual Tender Evaluation", 2)
cy = H * 0.5
third = (W - 2 * M) / 3
callouts = [
("3–5", "days per tender\nevaluation", C_RED),
("≠", "inconsistent verdicts\nfrom evaluators", C_AMB),
("?", "no audit trail\nfor decisions", C_PUR),
]
for i, (val, lbl, col) in enumerate(callouts):
cx = M + i * third + third / 2
big_num(c, val, lbl, cx, cy, col)
# Divider
c.setStrokeColor(C_DIV)
c.setLineWidth(1)
c.line(M, cy - 2.2 * cm, W - M, cy - 2.2 * cm)
bullets = [
"• Mixed document formats: typed PDFs, scanned certificates, phone photographs",
"• Government procurement worth ₹50 lakh crore+ annually in India",
"• Project execution delays traced directly to procurement bottlenecks",
]
c.setFont("Helvetica", 12)
c.setFillColor(C_SUB)
for i, b in enumerate(bullets):
c.drawString(M, cy - 2.8 * cm - i * 0.55 * cm, b)
footer_line(c)
c.showPage()
# ── Slide 3 ─────────────────────────────────────────────────────────────────
def s3(c):
c.setFillColor(C_BG)
c.rect(0, 0, W, H, fill=1, stroke=0)
header_strip(c, "TenderIQ — Four Stages, End to End", 3)
# Four icons in a row with arrows
icons = ["PDF", "OCR", "EVAL", "LOG"]
labels = ["Extract", "OCR & Index", "Evaluate", "Review & Audit"]
descs = [
"DeepSeek LLM reads tender PDF\n→ structured JSON criteria",
"3-tier pipeline handles any\ndoc format → vector index",
"Vector search + LLM verdict\n+ safety rule",
"Human review queue\n+ full audit trail (CSV)",
]
colors_ = [C_BLU, C_PUR, C_GRN, C_AMB]
step = (W - 2 * M) / 4
cy = H * 0.52
for i, (ico, lbl, desc, col) in enumerate(zip(icons, labels, descs, colors_)):
cx = M + i * step + step / 2
# Icon circle
c.setFillColor(col)
c.circle(cx, cy + 1.0 * cm, 0.9 * cm, fill=1, stroke=0)
c.setFont("Helvetica-Bold", 9)
c.setFillColor(colors.white)
c.drawCentredString(cx, cy + 0.88 * cm, ico)
c.setFont("Helvetica-Bold", 14)
c.setFillColor(col)
c.drawCentredString(cx, cy - 0.25 * cm, lbl)
c.setFont("Helvetica", 11)
c.setFillColor(C_TXT)
for j, dl in enumerate(desc.split("\n")):
c.drawCentredString(cx, cy - 0.82 * cm - j * 0.45 * cm, dl)
if i < 3:
c.setFont("Helvetica-Bold", 18)
c.setFillColor(C_SUB)
c.drawCentredString(M + (i + 1) * step, cy + 0.9 * cm, "→")
# Callout
by = H * 0.19
c.setFillColor(C_STR)
c.setStrokeColor(C_BLU)
c.setLineWidth(1.5)
c.roundRect(M, by, W - 2 * M, 1.1 * cm, 4, fill=1, stroke=1)
c.setFont("Helvetica-BoldOblique", 13)
c.setFillColor(C_BLU)
c.drawCentredString(W / 2, by + 0.38 * cm,
'"Minutes, not days. Every verdict traceable to a document and page."')
footer_line(c)
c.showPage()
# ── Slide 4 ─────────────────────────────────────────────────────────────────
def s4(c):
c.setFillColor(C_BG)
c.rect(0, 0, W, H, fill=1, stroke=0)
header_strip(c, "System Architecture", 4)
# Vertical flow infographic
stages = [
("PDF", "Tender PDF + Bidder Documents (PDFs · scans · photos)", C_BLU),
("LLM", "DeepSeek LLM — Extract Criteria | 3-Tier OCR → Vector Index", C_PUR),
("EVAL", "DeepSeek LLM — Evaluate each criterion with combined confidence", C_GRN),
("AUD", "eligible / not_eligible | needs_review → Human Review Queue", C_AMB),
("DB", "SQLite Audit Log — every action with timestamp + payload", C_BLU),
]
top_y = H - 2.2 * cm
row_h = 1.2 * cm
for i, (ico, desc, col) in enumerate(stages):
y = top_y - i * row_h
stage_row(c, ico, "", desc, y - 0.1 * cm, col)
if i < len(stages) - 1:
# Connector
c.setStrokeColor(C_DIV)
c.setLineWidth(1.5)
c.line(M + 0.9 * cm, y - 0.1 * cm, M + 0.9 * cm, y - row_h + 0.5 * cm)
# Key facts side panel
fx = W / 2 + 1 * cm
c.setFillColor(C_STR)
c.setStrokeColor(C_DIV)
c.setLineWidth(1)
c.roundRect(fx, H * 0.2, W - M - fx, H * 0.6, 5, fill=1, stroke=1)
c.setFont("Helvetica-Bold", 13)
c.setFillColor(C_BLU)
c.drawString(fx + 0.4 * cm, H * 0.76, "Key Technical Facts")
facts = [
"Single-process Streamlit app",
"Streamlit Cloud / HuggingFace deployment",
"Local storage: SQLite + vector index",
"Only external dep: DeepSeek API",
]
c.setFont("Helvetica", 11)
c.setFillColor(C_TXT)
for i, f in enumerate(facts):
c.drawString(fx + 0.4 * cm, H * 0.68 - i * 0.65 * cm, f"• {f}")
footer_line(c)
c.showPage()
# ── Slide 5 ─────────────────────────────────────────────────────────────────
def s5(c):
c.setFillColor(C_BG)
c.rect(0, 0, W, H, fill=1, stroke=0)
header_strip(c, "Three-Tier OCR — Handling Any Document Format", 5)
tiers = [
("Tier 1\nPyMuPDF", "Typed PDF → Free, instant", 1.0, C_BLU),
("Tier 2\nTesseract", "Scan / image → Free, local", 0.60, C_PUR),
("Tier 3\nVision LLM", "Low confidence → API call", 0.95, C_AMB),
]
step = (W - 2 * M) / 3
cy = H * 0.55
for i, (tier, desc, pct, col) in enumerate(tiers):
cx = M + i * step + step / 2
# Circular progress
circ_progress(c, cx, cy + 0.5 * cm, 1.3 * cm, pct, col)
# Tier label
c.setFont("Helvetica-Bold", 13)
c.setFillColor(col)
for li, ln in enumerate(tier.split("\n")):
c.drawCentredString(cx, cy - 1.35 * cm - li * 0.45 * cm, ln)
c.setFont("Helvetica", 11)
c.setFillColor(C_TXT)
c.drawCentredString(cx, cy - 2.55 * cm, desc)
if i < 2:
c.setFont("Helvetica-Bold", 18)
c.setFillColor(C_SUB)
c.drawCentredString(M + (i + 1) * step, cy + 0.5 * cm, "→")
# Demo callout
dy = H * 0.19
c.setFillColor(C_STR)
c.setStrokeColor(C_AMB)
c.setLineWidth(2)
c.roundRect(M, dy, W - 2 * M, 2.2 * cm, 4, fill=1, stroke=1)
c.setFont("Helvetica-Bold", 12)
c.setFillColor(C_AMB)
c.drawString(M + 0.5 * cm, dy + 1.78 * cm, "Demo Scenario")
c.setFont("Helvetica", 11)
c.setFillColor(C_TXT)
demo = ("Bidder C submits a blurry, rotated CA certificate scan. "
"Tesseract reads it at ~55% confidence. Vision LLM transcribes the turnover correctly. "
"Combined confidence = 0.58 → routed to human review. "
"Borderline evidence requires a human — this is intentional.")
para(c, demo, M + 0.5 * cm, dy + 1.55 * cm, W - 2 * M - 1 * cm, size=11, color=C_TXT, leading=16)
footer_line(c)
c.showPage()
# ── Slide 6 ─────────────────────────────────────────────────────────────────
def s6(c):
c.setFillColor(C_BG)
c.rect(0, 0, W, H, fill=1, stroke=0)
header_strip(c, "Every Decision is Explainable and Auditable", 6)
half = (W - 2 * M - 0.8 * cm) / 2
lx = M
rx = M + half + 0.8 * cm
top = H - 2.0 * cm
panel_h = 4.2 * cm
for px, title, lines, col in [
(lx, "Criterion-Level Verdicts", [
"Each (bidder × criterion) pair shows:",
"• Which criterion was checked",
"• Document and page providing evidence",
"• Value extracted (e.g. 'INR 6.2 Cr')",
"• OCR tier that read the document",
"• Combined confidence score (0–100%)",
"• Plain-English reason",
], C_BLU),
(rx, "Audit Trail", [
"Every action logged with:",
"• UTC timestamp",
"• Action type (6 types incl. criteria_extracted,",
" vision_ocr_invoked, precomputed_fallback_used)",
"• Model version & Actor",
"• Full payload JSON",
"• Exportable as CSV",
], C_BLU),
]:
c.setFillColor(C_STR)
c.setStrokeColor(col)
c.setLineWidth(1.5)
c.roundRect(px, top - panel_h, half, panel_h, 4, fill=1, stroke=1)
c.setFont("Helvetica-Bold", 13)
c.setFillColor(col)
c.drawString(px + 0.4 * cm, top - 0.5 * cm, title)
c.setFont("Helvetica", 11)
c.setFillColor(C_TXT)
for i, ln in enumerate(lines):
c.setFillColor(C_SUB if i == 0 else C_TXT)
c.drawString(px + 0.4 * cm, top - 1.0 * cm - i * 0.45 * cm, ln)
# Safety rule
sy = top - panel_h - 0.5 * cm - 1.9 * cm
c.setFillColor(colors.HexColor("#FFFBEB"))
c.setStrokeColor(C_AMB)
c.setLineWidth(2.5)
c.roundRect(M, sy, W - 2 * M, 1.9 * cm, 4, fill=1, stroke=1)
c.setFont("Helvetica-Bold", 14)
c.setFillColor(colors.HexColor("#92400E"))
c.drawString(M + 0.5 * cm, sy + 1.52 * cm, "The Safety Rule:")
c.setFont("Helvetica", 12)
c.setFillColor(C_TXT)
rule = ("If combined confidence is 0.55–0.80 AND verdict is not_eligible, "
"the verdict is automatically downgraded to needs_review. "
"A bidder is NEVER silently disqualified at medium confidence.")
para(c, rule, M + 0.5 * cm, sy + 1.3 * cm, W - 2 * M - 1 * cm, size=12, color=C_TXT, leading=16)
footer_line(c)
c.showPage()
# ── Slide 7 ─────────────────────────────────────────────────────────────────
def s7(c):
c.setFillColor(C_BG)
c.rect(0, 0, W, H, fill=1, stroke=0)
header_strip(c, "Demo: Three Bidders, Three Outcomes", 7)
bidders = [
("ELIGIBLE", C_GRN, colors.HexColor("#D1FAE5"),
"Bidder A\nApex Constructions Pvt. Ltd.",
["C1 Turnover: INR 6.37 Cr avg — PASS",
"C2 Projects: 5 completed (CRPF) — PASS",
"C3 GST: Active GSTIN — PASS",
"C4 ISO 9001:2015: Valid June 2027 — PASS",
"All typed PDFs, confidence ≥ 93%"]),
("NOT ELIGIBLE", C_RED, colors.HexColor("#FEE2E2"),
"Bidder B\nBuildRight Enterprises",
["C1 Turnover: INR 1.5 Cr avg — FAIL",
"Below required minimum of INR 5 Cr",
"C2–C4: All pass",
"Auto-disqualified, conf 95%"]),
("NEEDS REVIEW", C_AMB, colors.HexColor("#FEF3C7"),
"Bidder C\nShree Constructions & Services",
["C1: Blurry scan → Vision LLM",
"INR 5.4 Cr, combined conf 0.58",
"Safety rule → needs_review",
"C2: 3 projects (borderline)",
"C3–C4: Pass"]),
]
step = (W - 2 * M - 2 * cm) / 3
top = H - 2.0 * cm
panel_h = 4.3 * cm
for i, (verdict, col, bgc, name, bullets) in enumerate(bidders):
cx = M + i * (step + 1 * cm)
c.setFillColor(bgc)
c.setStrokeColor(col)
c.setLineWidth(2)
c.roundRect(cx, top - panel_h, step, panel_h, 5, fill=1, stroke=1)
# Outcome icon text
c.setFont("Helvetica-Bold", 24)
c.setFillColor(col)
c.drawCentredString(cx + step / 2, top - 0.6 * cm, verdict)
# Divider
c.setStrokeColor(col)
c.setLineWidth(1)
c.line(cx + 0.3 * cm, top - 0.9 * cm, cx + step - 0.3 * cm, top - 0.9 * cm)
# Name
c.setFont("Helvetica-Bold", 11)
c.setFillColor(C_TXT)
for li, nl in enumerate(name.split("\n")):
c.drawCentredString(cx + step / 2, top - 1.3 * cm - li * 0.38 * cm, nl)
# Bullets
c.setFont("Helvetica", 11)
c.setFillColor(C_TXT)
for j, b in enumerate(bullets):
c.drawString(cx + 0.3 * cm, top - 2.2 * cm - j * 0.42 * cm, b)
# Metric strip
metrics = [("5", "Criteria\nextracted"), ("15", "Bidder docs\nprocessed"),
("15", "LLM eval\ncalls"), ("1", "Vision OCR\ninvocations"),
("1", "Human review\nitems"), ("20+", "Audit\nentries")]
sy = H * 0.16
c.setFillColor(C_STR)
c.setStrokeColor(C_DIV)
c.setLineWidth(1)
c.roundRect(M, sy, W - 2 * M, 1.5 * cm, 4, fill=1, stroke=1)
mstep = (W - 2 * M) / len(metrics)
for i, (val, lbl) in enumerate(metrics):
mx = M + i * mstep + mstep / 2
c.setFont("Helvetica-Bold", 20)
c.setFillColor(C_BLU)
c.drawCentredString(mx, sy + 0.9 * cm, val)
c.setFont("Helvetica", 9)
c.setFillColor(C_SUB)
for li, ln in enumerate(lbl.split("\n")):
c.drawCentredString(mx, sy + 0.42 * cm - li * 0.28 * cm, ln.upper())
footer_line(c)
c.showPage()
# ── Slide 8 ─────────────────────────────────────────────────────────────────
def s8(c):
c.setFillColor(C_BG)
c.rect(0, 0, W, H, fill=1, stroke=0)
header_strip(c, "Stack, Impact & What's Next", 8)
# Icon grid for future work (6 items, 3×2)
future = [
("MULTI", "Multi-tender workspace\nsame bidder pool, multiple tenders"),
("GEM", "GeM portal API integration\nlive tender ingestion"),
("RANK", "Automated bidder ranking\nwith weighted scoring"),
("LLM+", "LayoutLM for complex\nfinancial tables in scans"),
("TEAM", "Multi-evaluator workflow\nwith role-based approval"),
("AUDIT", "Audit PDF export for\nprocurement oversight"),
]
colors_f = [C_BLU, C_GRN, C_PUR, C_AMB, C_RED, C_BLU]
gw = (W / 2 - M - 1 * cm) / 3
gh = 1.8 * cm
for i, ((ico, desc), col) in enumerate(zip(future, colors_f)):
row = i // 3
col_i = i % 3
gx = M + col_i * (gw + 0.4 * cm)
gy = H - 2.4 * cm - row * (gh + 0.5 * cm)
c.setFillColor(C_STR)
c.setStrokeColor(col)
c.setLineWidth(1)
c.roundRect(gx, gy - gh, gw, gh, 3, fill=1, stroke=1)
c.setFont("Helvetica-Bold", 9)
c.setFillColor(col)
c.drawString(gx + 0.25 * cm, gy - 0.5 * cm, ico)
c.setFont("Helvetica", 10)
c.setFillColor(C_TXT)
for li, ln in enumerate(desc.split("\n")):
c.drawString(gx + 0.25 * cm, gy - 0.95 * cm - li * 0.38 * cm, ln)
# Tech stack (right half)
stack = [
("UI / Orchestration", "Streamlit 1.39"),
("LLM", "DeepSeek API"),
("OCR Tiers", "PyMuPDF · Tesseract · Vision LLM"),
("Retrieval", "all-MiniLM-L6-v2"),
("Validation", "Pydantic v2"),
("Audit", "SQLite — Exportable CSV"),
("Deploy", "Streamlit Cloud / HuggingFace"),
]
sx = W / 2 + 0.5 * cm
sw = W - M - sx
c.setFont("Helvetica-Bold", 13)
c.setFillColor(C_BLU)
c.drawString(sx, H - 2.1 * cm, "Technology Stack")
for i, (comp, tech) in enumerate(stack):
ry = H - 2.75 * cm - i * 0.55 * cm
c.setFillColor(C_STR if i % 2 == 0 else C_BG)
c.rect(sx, ry - 0.38 * cm, sw, 0.5 * cm, fill=1, stroke=0)
c.setFont("Helvetica", 10)
c.setFillColor(C_SUB)
c.drawString(sx + 0.2 * cm, ry, comp)
c.setFillColor(C_TXT)
c.drawString(sx + sw * 0.42, ry, tech)
# Impact callout
iy = H * 0.15
c.setFillColor(C_BLU)
c.roundRect(M, iy, W - 2 * M, 1.3 * cm, 5, fill=1, stroke=0)
c.setFont("Helvetica-Bold", 13)
c.setFillColor(colors.white)
c.drawCentredString(W / 2, iy + 0.82 * cm,
"3–5 days → minutes. Every verdict traceable to a document, page, and model version.")
c.setFont("Helvetica", 11)
c.drawCentredString(W / 2, iy + 0.38 * cm,
"Built in one hackathon session. Deployable today.")
footer_line(c)
c.showPage()
def main():
os.makedirs("deck", exist_ok=True)
out = "deck/TenderIQ_v6_infographic.pdf"
c = Canvas(out, pagesize=landscape(A4))
s1(c); s2(c); s3(c); s4(c)
s5(c); s6(c); s7(c); s8(c)
c.save()
size = os.path.getsize(out)
print(f"OK: Saved {out} ({size:,} bytes, 8 slides)")
if __name__ == "__main__":
main()