Spaces:
Sleeping
Sleeping
| """Generate the fictitious sample PDFs bundled with the app. | |
| Run from the repo root (or this directory): python sample_docs/generate_samples.py | |
| Every document carries a visible "SAMPLE - FICTITIOUS DATA" banner and | |
| footer. All company names, numbers, and amounts are invented. The bill | |
| of lading is re-rendered as an image-only page so it exercises the | |
| Tesseract OCR path; the other two keep their native text layer. | |
| """ | |
| import os | |
| import fitz | |
| OUT_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| PAGE_W, PAGE_H = 612, 792 # US Letter | |
| MARGIN = 54 | |
| INK = (0.13, 0.16, 0.22) # charcoal | |
| SUBTLE = (0.45, 0.50, 0.58) # slate | |
| ACCENT = (0.16, 0.35, 0.68) # deep blue | |
| BANNER_BG = (0.98, 0.93, 0.93) | |
| BANNER_INK = (0.65, 0.15, 0.15) | |
| RULE = (0.85, 0.87, 0.90) | |
| class Doc: | |
| """Tiny layout helper: a cursor-based text flow over one page.""" | |
| def __init__(self): | |
| self.pdf = fitz.open() | |
| self.page = self.pdf.new_page(width=PAGE_W, height=PAGE_H) | |
| self.y = MARGIN | |
| def banner(self): | |
| rect = fitz.Rect(0, 0, PAGE_W, 26) | |
| self.page.draw_rect(rect, color=None, fill=BANNER_BG) | |
| self.page.insert_text( | |
| (MARGIN, 17), | |
| "SAMPLE DOCUMENT - FICTITIOUS DATA FOR DEMONSTRATION ONLY - NOT REAL DATA", | |
| fontsize=8.5, fontname="hebo", color=BANNER_INK, | |
| ) | |
| self.y = 26 + 28 | |
| def header(self, company, doc_title): | |
| self.page.insert_text((MARGIN, self.y), company, fontsize=16, fontname="hebo", color=INK) | |
| self.y += 20 | |
| self.page.insert_text((MARGIN, self.y), doc_title, fontsize=11, fontname="helv", color=ACCENT) | |
| self.y += 12 | |
| self.page.draw_line((MARGIN, self.y), (PAGE_W - MARGIN, self.y), color=RULE, width=1) | |
| self.y += 22 | |
| def kv(self, label, value, x=MARGIN, label_w=150): | |
| self.page.insert_text((x, self.y), label, fontsize=9.5, fontname="hebo", color=SUBTLE) | |
| self.page.insert_text((x + label_w, self.y), value, fontsize=9.5, fontname="helv", color=INK) | |
| self.y += 16 | |
| def kv_pair_row(self, l1, v1, l2, v2): | |
| mid = PAGE_W / 2 + 10 | |
| self.page.insert_text((MARGIN, self.y), l1, fontsize=9.5, fontname="hebo", color=SUBTLE) | |
| self.page.insert_text((MARGIN + 120, self.y), v1, fontsize=9.5, fontname="helv", color=INK) | |
| self.page.insert_text((mid, self.y), l2, fontsize=9.5, fontname="hebo", color=SUBTLE) | |
| self.page.insert_text((mid + 110, self.y), v2, fontsize=9.5, fontname="helv", color=INK) | |
| self.y += 16 | |
| def section(self, title): | |
| self.y += 10 | |
| self.page.insert_text((MARGIN, self.y), title.upper(), fontsize=9, fontname="hebo", color=ACCENT) | |
| self.y += 6 | |
| self.page.draw_line((MARGIN, self.y), (PAGE_W - MARGIN, self.y), color=RULE, width=0.8) | |
| self.y += 16 | |
| def para(self, text, size=9.5): | |
| rect = fitz.Rect(MARGIN, self.y - 10, PAGE_W - MARGIN, self.y + 90) | |
| used = self.page.insert_textbox(rect, text, fontsize=size, fontname="helv", color=INK, lineheight=1.35) | |
| self.y += (90 - used) + 6 if used >= 0 else 60 | |
| def table(self, headers, rows, col_xs): | |
| for x, h in zip(col_xs, headers): | |
| self.page.insert_text((x, self.y), h, fontsize=8.5, fontname="hebo", color=SUBTLE) | |
| self.y += 5 | |
| self.page.draw_line((MARGIN, self.y), (PAGE_W - MARGIN, self.y), color=RULE, width=0.8) | |
| self.y += 13 | |
| for row in rows: | |
| for x, cell in zip(col_xs, row): | |
| self.page.insert_text((x, self.y), cell, fontsize=9, fontname="helv", color=INK) | |
| self.y += 15 | |
| self.y += 4 | |
| def watermark(self): | |
| self.page.insert_text( | |
| (PAGE_W / 2 - 150, PAGE_H / 2), "SAMPLE - NOT REAL DATA", | |
| fontsize=24, fontname="hebo", color=(0.88, 0.88, 0.90), fill_opacity=0.8, | |
| ) | |
| def footer(self): | |
| self.page.draw_line((MARGIN, PAGE_H - 46), (PAGE_W - MARGIN, PAGE_H - 46), color=RULE, width=0.8) | |
| self.page.insert_text( | |
| (MARGIN, PAGE_H - 32), | |
| "This sample document was generated for product demonstration. All names, numbers,", | |
| fontsize=7.5, fontname="helv", color=SUBTLE, | |
| ) | |
| self.page.insert_text( | |
| (MARGIN, PAGE_H - 22), | |
| "and amounts are fictitious. Any resemblance to real entities is coincidental.", | |
| fontsize=7.5, fontname="helv", color=SUBTLE, | |
| ) | |
| def save(self, name, as_scanned=False, dpi=170): | |
| self.footer() | |
| path = os.path.join(OUT_DIR, name) | |
| if as_scanned: | |
| # JPEG keeps the "scanned" page under ~300 KB; a raw PNG pixmap | |
| # embed produces a multi-megabyte file. | |
| pix = self.page.get_pixmap(dpi=dpi) | |
| jpeg_bytes = pix.tobytes("jpeg", jpg_quality=80) | |
| scanned = fitz.open() | |
| page = scanned.new_page(width=PAGE_W, height=PAGE_H) | |
| page.insert_image(fitz.Rect(0, 0, PAGE_W, PAGE_H), stream=jpeg_bytes) | |
| scanned.save(path) | |
| scanned.close() | |
| else: | |
| self.pdf.save(path) | |
| self.pdf.close() | |
| return path | |
| def construction_change_order(): | |
| d = Doc() | |
| d.banner() | |
| d.header("MERIDIAN BUILD GROUP (FICTITIOUS)", "Change Order Notice - CO-2026-014") | |
| d.kv_pair_row("Project:", "Falcon Ridge Distribution Ctr", "Date:", "06/12/2026") | |
| d.kv_pair_row("PO#", "4500187266", "SAP Document:", "4500187266") | |
| d.kv_pair_row("Contractor:", "Ironline Steel Erectors", "Contract Ref:", "C-2025-0092") | |
| d.section("Description of Change") | |
| d.para( | |
| "This change order documents a schedule slip of 9 working days on the " | |
| "Phase 2 structural package, driven by a material shortage of steel bar " | |
| "joists at the fabricator. The revised sequence results in a cost overrun " | |
| "of $48,750.00 against the structural steel budget line." | |
| ) | |
| d.section("Field Notes") | |
| d.para( | |
| "A safety violation was recorded on 06/10/2026 during joist staging " | |
| "(improper fall protection at the leading edge). A corrective action " | |
| "plan is required from the subcontractor prior to remobilization. " | |
| "Reference OSHA 1926.501 requirements." | |
| ) | |
| d.section("Cost Summary") | |
| d.table( | |
| ["Item", "Description", "Amount"], | |
| [ | |
| ["CO-014.1", "Extended general conditions (9 days)", "$21,600.00"], | |
| ["CO-014.2", "Joist re-sequencing and crane standby", "$18,900.00"], | |
| ["CO-014.3", "Expedited freight on replacement joists", "$8,250.00"], | |
| ["", "Total this change order", "$48,750.00"], | |
| ], | |
| [MARGIN, MARGIN + 70, PAGE_W - MARGIN - 90], | |
| ) | |
| d.kv("Status:", "Pending approval") | |
| d.watermark() | |
| return d.save("sample_construction_change_order.pdf") | |
| def accounting_tax_invoice(): | |
| d = Doc() | |
| d.banner() | |
| d.header("NORTHPORT ACCOUNTING SERVICES (FICTITIOUS)", "Client Invoice & Tax Statement") | |
| d.kv_pair_row("Invoice #", "INV-20461", "Invoice Date:", "04/02/2026") | |
| d.kv_pair_row("Client:", "Harbor & Vine LLC", "Due Date:", "05/02/2026") | |
| d.kv_pair_row("Tax Year:", "2025", "Terms:", "Net 30") | |
| d.section("Services") | |
| d.table( | |
| ["Code", "Description", "Amount"], | |
| [ | |
| ["TAX-1120S", "Federal S-corp return preparation (2025)", "$6,800.00"], | |
| ["TAX-ST", "State franchise & sales tax filings (4 qtrs)", "$3,200.00"], | |
| ["ADV-Q4", "Quarterly advisory - Q4 close support", "$2,400.00"], | |
| ], | |
| [MARGIN, MARGIN + 70, PAGE_W - MARGIN - 90], | |
| ) | |
| d.section("Totals") | |
| d.kv("Subtotal:", "$12,400.00", label_w=120) | |
| d.kv("Sales Tax (8.25%):", "$1,023.00", label_w=120) | |
| d.kv("Total Due:", "$13,423.00", label_w=120) | |
| d.section("Account Status") | |
| d.para( | |
| "This invoice is PAST DUE as of 06/02/2026. A late fee of 1.5% per " | |
| "month applies to the outstanding balance. The account has been " | |
| "placed on credit hold; new engagements are suspended until the " | |
| "balance is settled or a payment plan is approved." | |
| ) | |
| d.watermark() | |
| return d.save("sample_accounting_tax_invoice.pdf") | |
| def bill_of_lading(): | |
| d = Doc() | |
| d.banner() | |
| d.header("BLUEWATER FREIGHT LINES (FICTITIOUS)", "Straight Bill of Lading - Not Negotiable") | |
| # Single-column rows: this page ships as a scanned image, and OCR reads | |
| # wide two-column layouts as separate label/value blocks. | |
| d.kv("BOL Number:", "BOL-88213", label_w=130) | |
| d.kv("Reference Number:", "REF-102455", label_w=130) | |
| d.kv("PO#", "4600221944", label_w=130) | |
| d.kv("Ship Date:", "07/11/2026", label_w=130) | |
| d.kv("Delivery Date:", "07/18/2026", label_w=130) | |
| d.kv("Freight Terms:", "Prepaid", label_w=130) | |
| d.section("Shipper / Consignee") | |
| d.kv("Shipper:", "Cascade Fixture Works, 1200 Foundry Rd, Portsville") | |
| d.kv("Consignee:", "Falcon Ridge Distribution Ctr, Dock 14, Meridian") | |
| d.kv("Carrier:", "Bluewater Freight Lines, SCAC: BWFL") | |
| d.section("Freight Description") | |
| d.table( | |
| ["Units", "Package", "Description of Articles", "Class"], | |
| [ | |
| ["12", "Pallets", "Steel shelving components, banded", "70"], | |
| ["4", "Crates", "Conveyor drive assemblies", "85"], | |
| ], | |
| [MARGIN, MARGIN + 55, MARGIN + 130, PAGE_W - MARGIN - 60], | |
| ) | |
| d.section("Weights") | |
| d.kv("Gross Weight:", "18,420 lbs", label_w=110) | |
| d.kv("Net Weight:", "17,660 lbs", label_w=110) | |
| d.kv("Declared Value:", "$92,500.00", label_w=110) | |
| d.para( | |
| "Received, subject to the classifications and tariffs in effect on the " | |
| "date of issue. Shipper certifies the above materials are properly " | |
| "classified, described, packaged, marked and labeled." | |
| ) | |
| d.watermark() | |
| # Saved as an image-only page so it exercises the OCR path. | |
| return d.save("sample_bill_of_lading.pdf", as_scanned=True) | |
| GENERATORS = { | |
| "sample_construction_change_order.pdf": construction_change_order, | |
| "sample_accounting_tax_invoice.pdf": accounting_tax_invoice, | |
| "sample_bill_of_lading.pdf": bill_of_lading, | |
| } | |
| def ensure_samples(): | |
| """Generate any missing sample PDFs; return the directory they live in. | |
| The PDFs are not committed to git (Hugging Face rejects binary files | |
| outside LFS), so the Space builds them on first launch. Falls back to a | |
| temp directory if the app directory isn't writable. | |
| """ | |
| global OUT_DIR | |
| import tempfile | |
| missing = [n for n in GENERATORS if not os.path.exists(os.path.join(OUT_DIR, n))] | |
| if not missing: | |
| return OUT_DIR | |
| try: | |
| os.makedirs(OUT_DIR, exist_ok=True) | |
| with open(os.path.join(OUT_DIR, ".write_test"), "w") as f: | |
| f.write("ok") | |
| os.remove(os.path.join(OUT_DIR, ".write_test")) | |
| except OSError: | |
| OUT_DIR = os.path.join(tempfile.gettempdir(), "sample_docs") | |
| os.makedirs(OUT_DIR, exist_ok=True) | |
| missing = [n for n in GENERATORS if not os.path.exists(os.path.join(OUT_DIR, n))] | |
| for name in missing: | |
| GENERATORS[name]() | |
| return OUT_DIR | |
| if __name__ == "__main__": | |
| for fn in GENERATORS.values(): | |
| print("wrote", fn()) | |