#!/usr/bin/env python3 """ JingTian-Tracker Sample Document Generator Generates 7 realistic sample documents for testing the JingTian document processing pipeline. Uses pre-generated content pools (no LLM needed at runtime). Usage: python generate_samples.py --output ./outputs python generate_samples.py --output ./outputs --seed 42 Documents generated: 1. DOCX - Letter TO client (Client/{name}/) 2. PDF - Scanned letter FROM client (Client/{name}/) 3. PNG - Email screenshot FROM client (Client/{name}/) 4. PDF - IPD filing receipt (Admin/IPD e-filing/) 5. DOCX - Internal memo (Admin/General Matter/) 6. XLSX - Invoice schedule (Billing/Draft Bills/) 7. PDF - TM registry record (IP/) + Copies 2 real PDFs to IP/ """ import argparse import json import random import shutil import sys from datetime import datetime, timedelta from pathlib import Path # Document generators from generators.docx_letter_to_client import generate as gen_letter_to from generators.pdf_scanned_letter import generate as gen_scanned_letter from generators.image_email import generate as gen_email_image from generators.pdf_filing_receipt import generate as gen_filing_receipt from generators.docx_memo import generate as gen_memo from generators.xlsx_invoice import generate as gen_invoice from generators.pdf_tm_record import generate as gen_tm_record DATA_DIR = Path(__file__).parent / "data" REAL_PDFS_DIR = Path(__file__).parent / "real_pdfs" def load_pools(): """Load all data pools.""" pools = {} for name in ["clients", "trademarks", "names", "content_pools"]: path = DATA_DIR / f"{name}.json" if not path.exists(): print(f"Error: {path} not found. Run generate_content_pools.py first.") sys.exit(1) pools[name] = json.loads(path.read_text(encoding="utf-8")) return pools def random_future_date(min_days=30, max_days=180): """Generate a random date in the future.""" delta = timedelta(days=random.randint(min_days, max_days)) return (datetime.now() + delta).strftime("%d-%m-%Y") def random_tm_number(): """Generate a realistic 9-digit TM number.""" prefix = random.choice(["306", "307"]) suffix = str(random.randint(100000, 999999)) return prefix + suffix def sanitize_dirname(name): """Sanitize a string for use as a directory/file name.""" # Replace dots at end (Windows issue), replace spaces with underscores name = name.replace(" ", "_") name = name.rstrip(".") # Remove other problematic chars for ch in ["<", ">", ":", '"', "/", "\\", "|", "?", "*"]: name = name.replace(ch, "") return name def pick_client(pools): """Pick a random client with contact info.""" client = random.choice(pools["clients"]["clients"]) return client def pick_trademark(pools): """Pick a random trademark from the real data.""" tm = random.choice(pools["trademarks"]["trademarks"]) return tm def get_body(item): """Extract body text from a content pool item (str or dict with 'body' key).""" if isinstance(item, dict): return item.get("body", str(item)) return str(item) def fill_template(template, replacements): """Fill placeholders in a template string.""" result = get_body(template) if not isinstance(template, str) else template for key, val in replacements.items(): result = result.replace(f"{{{key}}}", str(val)) return result def generate_all(output_dir, pools): """Generate all 7 documents and copy real PDFs.""" manifest = { "generated_at": datetime.now().isoformat(), "documents": [], } firm = pools["names"]["firm"] ipd = pools["names"]["ipd"] content = pools["content_pools"] # ── 1. DOCX: Letter TO client ────────────────────────────────── client1 = pick_client(pools) tm1 = pick_trademark(pools) deadline1 = random_future_date(30, 120) body1 = random.choice(content["letter_to_client_bodies"]) closing1 = random.choice(content["letter_closings"]) replacements1 = { "deadline": deadline1, "tm_number": tm1["number"], "tm_text": tm1["text"], "client_name": client1["name"], "contact_person": client1["contact_person"], } client_dir = output_dir / "Client" / sanitize_dirname(client1["name"]) client_dir.mkdir(parents=True, exist_ok=True) fname1 = f"Letter_Re_TM{tm1['number']}.docx" gen_letter_to( output_path=client_dir / fname1, firm=firm, client=client1, body=fill_template(body1, replacements1), closing=fill_template(closing1, replacements1), ref_number=f"JT/{datetime.now().year}/{random.randint(1000, 9999)}", date=datetime.now().strftime("%d %B %Y"), re_line=f"Trademark Application No. {tm1['number']} - {tm1['text']}", ) manifest["documents"].append( { "filename": fname1, "path": f"Client/{sanitize_dirname(client1['name'])}/{fname1}", "type": "Letter to Client", "format": "docx", "expected_extraction": { "document_type": "Client Correspondence", "deadline": deadline1, "client": client1["name"], "tm_number": tm1["number"], }, } ) print(f" [1/7] DOCX letter to client: {fname1}") # ── 2. PDF: Scanned letter FROM client ───────────────────────── client2 = pick_client(pools) tm2 = pick_trademark(pools) deadline2 = random_future_date(14, 90) body2 = random.choice(content["letter_from_client_bodies"]) has_deadline2 = "{deadline}" in body2 replacements2 = { "deadline": deadline2, "tm_number": tm2["number"], "tm_text": tm2["text"], "client_name": client2["name"], "contact_person": client2["contact_person"], } client2_dir = output_dir / "Client" / sanitize_dirname(client2["name"]) client2_dir.mkdir(parents=True, exist_ok=True) fname2 = f"Client_Instructions_{sanitize_dirname(client2['name'])}.pdf" gen_scanned_letter( output_path=client2_dir / fname2, from_name=client2["contact_person"], from_company=client2["name"], from_address=client2["address"], to_name=firm["attorney"]["name"], to_firm=firm["name"], body=fill_template(body2, replacements2), date=datetime.now().strftime("%d %B %Y"), ) manifest["documents"].append( { "filename": fname2, "path": f"Client/{sanitize_dirname(client2['name'])}/{fname2}", "type": "Letter from Client (Scanned)", "format": "pdf_scanned", "ocr_required": True, "expected_extraction": { "document_type": "Client Instructions", "deadline": deadline2 if has_deadline2 else None, "client": client2["name"], "tm_number": tm2["number"] if "{tm_number}" in body2 else None, }, } ) print(f" [2/7] PDF scanned letter from client: {fname2}") # ── 3. PNG: Email FROM client ────────────────────────────────── client3 = pick_client(pools) tm3 = pick_trademark(pools) deadline3 = random_future_date(7, 60) email_body = random.choice(content["email_from_client_bodies"]) email_subject = random.choice(content["email_subjects"]) has_deadline3 = "{deadline}" in email_body replacements3 = { "deadline": deadline3, "tm_number": tm3["number"], "tm_text": tm3["text"], "client_name": client3["name"], "contact_person": client3["contact_person"], } client3_dir = output_dir / "Client" / sanitize_dirname(client3["name"]) client3_dir.mkdir(parents=True, exist_ok=True) fname3 = f"Email_{sanitize_dirname(client3['name'])}_{datetime.now().strftime('%Y%m%d')}.png" gen_email_image( output_path=client3_dir / fname3, from_email=client3.get( "email", f"info@{client3['name'].lower().replace(' ', '')}.com" ), from_name=client3["contact_person"], to_email="benjamin.choi@jingtian.com", to_name=firm["attorney"]["name"], subject=fill_template(email_subject, replacements3), body=fill_template(email_body, replacements3), date=datetime.now().strftime("%A, %d %B %Y %H:%M"), ) manifest["documents"].append( { "filename": fname3, "path": f"Client/{sanitize_dirname(client3['name'])}/{fname3}", "type": "Email from Client (Screenshot)", "format": "png", "ocr_required": True, "expected_extraction": { "document_type": "Client Email", "deadline": deadline3 if has_deadline3 else None, "client": client3["name"], "tm_number": tm3["number"] if "{tm_number}" in email_body or "{tm_number}" in email_subject else None, }, } ) print(f" [3/7] PNG email screenshot: {fname3}") # ── 4. PDF: IPD Filing Receipt ───────────────────────────────── client4 = pick_client(pools) tm_number4 = random_tm_number() tm_text4 = random.choice( [ client4["name"].split()[0].upper(), random.choice(["NOVA", "APEX", "STELLAR", "ZENITH", "PRIMEX", "VANTAGE"]), ] ) filing_date = datetime.now().strftime("%d-%m-%Y") response_deadline4 = random_future_date(60, 120) nice_class = random.choice(list(pools["trademarks"]["nice_classes"].keys())) filing_dir = output_dir / "Admin" / "IPD e-filing" filing_dir.mkdir(parents=True, exist_ok=True) fname4 = f"Filing_Receipt_{tm_number4}.pdf" gen_filing_receipt( output_path=filing_dir / fname4, ipd=ipd, tm_number=tm_number4, tm_text=tm_text4, applicant=client4["name"], applicant_address=client4["address"], agent=firm["name"], agent_address=firm["address"], filing_date=filing_date, response_deadline=response_deadline4, nice_class=nice_class, class_description=pools["trademarks"]["nice_classes"][nice_class], ) manifest["documents"].append( { "filename": fname4, "path": f"Admin/IPD e-filing/{fname4}", "type": "IPD Filing Receipt", "format": "pdf_native", "expected_extraction": { "document_type": "Filing Receipt", "deadline": response_deadline4, "client": client4["name"], "tm_number": tm_number4, }, } ) print(f" [4/7] PDF filing receipt: {fname4}") # ── 5. DOCX: Internal Memo ───────────────────────────────────── memo_body = random.choice(content["memo_bodies"]) clients_for_memo = random.sample( pools["clients"]["clients"], min(3, len(pools["clients"]["clients"])) ) tms_for_memo = random.sample( pools["trademarks"]["trademarks"], min(3, len(pools["trademarks"]["trademarks"])), ) memo_replacements = {} memo_deadlines = [] nice_classes_list = list(pools["trademarks"]["nice_classes"].keys()) for i in range(3): dl = random_future_date(14 + i * 30, 60 + i * 60) memo_deadlines.append(dl) memo_replacements[f"tm_number_{i + 1}"] = ( tms_for_memo[i]["number"] if i < len(tms_for_memo) else random_tm_number() ) # Use TM number as the mark reference (Chinese text causes rendering issues in memos) tm_text = tms_for_memo[i]["text"] if i < len(tms_for_memo) else "N/A" tm_num = ( tms_for_memo[i]["number"] if i < len(tms_for_memo) else random_tm_number() ) # If text is CJK, show as "No. XXXXXXX (text)", otherwise just the text memo_replacements[f"tm_text_{i + 1}"] = ( tm_text if tm_text.isascii() else f"No. {tm_num}" ) memo_replacements[f"deadline_{i + 1}"] = dl memo_replacements[f"client_{i + 1}"] = ( clients_for_memo[i]["name"] if i < len(clients_for_memo) else "Various" ) memo_replacements[f"class_{i + 1}"] = random.choice(nice_classes_list) memo_replacements[f"contact_person_{i + 1}"] = ( clients_for_memo[i]["contact_person"] if i < len(clients_for_memo) else "N/A" ) # Also fill generic placeholders memo_replacements["deadline"] = memo_deadlines[0] memo_replacements["contact_person"] = clients_for_memo[0]["contact_person"] memo_replacements["client_name"] = clients_for_memo[0]["name"] memo_dir = output_dir / "Admin" / "General Matter" memo_dir.mkdir(parents=True, exist_ok=True) fname5 = f"Memo_{datetime.now().strftime('%Y%m%d')}_{random.randint(100, 999)}.docx" gen_memo( output_path=memo_dir / fname5, firm=firm, body=fill_template(memo_body, memo_replacements), date=datetime.now().strftime("%d %B %Y"), subject="Upcoming Trademark Deadlines - Action Required", ) manifest["documents"].append( { "filename": fname5, "path": f"Admin/General Matter/{fname5}", "type": "Internal Memo", "format": "docx", "expected_extraction": { "document_type": "Internal Memo", "deadlines": memo_deadlines, "tm_numbers": [ r.get(f"tm_number_{i + 1}") for i, r in enumerate([memo_replacements] * 3) ], }, } ) print(f" [5/7] DOCX internal memo: {fname5}") # ── 6. XLSX: Invoice Schedule ────────────────────────────────── invoice_clients = random.sample( pools["clients"]["clients"], min(6, len(pools["clients"]["clients"])) ) invoice_tms = random.sample( pools["trademarks"]["trademarks"], min(6, len(pools["trademarks"]["trademarks"])), ) invoice_descs = content["invoice_descriptions"] invoice_rows = [] for i in range(min(6, len(invoice_clients))): cls = random.choice(list(pools["trademarks"]["nice_classes"].keys())) desc = fill_template(random.choice(invoice_descs), {"class": cls}) due = random_future_date(14, 90) amount = random.randint(5, 150) * 1000 invoice_rows.append( { "client": invoice_clients[i]["name"], "matter_ref": f"JT/{datetime.now().year}/{random.randint(1000, 9999)}", "tm_number": invoice_tms[i]["number"] if i < len(invoice_tms) else random_tm_number(), "description": desc, "amount_hkd": amount, "due_date": due, "status": random.choice(["Draft", "Sent", "Overdue", "Paid"]), } ) billing_dir = output_dir / "Billing" / "Draft Bills" billing_dir.mkdir(parents=True, exist_ok=True) fname6 = f"Invoice_Schedule_{datetime.now().strftime('%Y')}Q{(datetime.now().month - 1) // 3 + 1}.xlsx" gen_invoice( output_path=billing_dir / fname6, rows=invoice_rows, firm=firm, ) manifest["documents"].append( { "filename": fname6, "path": f"Billing/Draft Bills/{fname6}", "type": "Invoice Schedule", "format": "xlsx", "expected_extraction": { "document_type": "Invoice Schedule", "deadlines": [r["due_date"] for r in invoice_rows], "clients": [r["client"] for r in invoice_rows], }, } ) print(f" [6/7] XLSX invoice schedule: {fname6}") # ── 7. PDF: TM Registry Record ───────────────────────────────── tm7 = pick_trademark(pools) client7 = pick_client(pools) status_type = random.choice(pools["trademarks"]["statuses"]) # Some statuses imply deadlines has_implicit_deadline = status_type in [ "Application Opposed", "Examined - First Examination Report Issued", "Examined - Further Examination Report Issued", ] ip_dir = output_dir / "IP" ip_dir.mkdir(parents=True, exist_ok=True) fname7 = f"TM_Record_{tm7['number']}.pdf" gen_tm_record( output_path=ip_dir / fname7, ipd=ipd, tm_number=tm7["number"], tm_text=tm7["text"], status=status_type, nice_class=str(tm7["classes"][0]) if tm7["classes"] else "5", class_description=pools["trademarks"]["nice_classes"].get( str(tm7["classes"][0]), "General goods and services" ), applicant=client7["name"], applicant_address=client7["address"], agent=firm["name"], agent_address=firm["address"], filing_date=tm7.get("filing_date", datetime.now().strftime("%d-%m-%Y")), publication_date=( datetime.now() - timedelta(days=random.randint(30, 180)) ).strftime("%d-%m-%Y"), ) manifest["documents"].append( { "filename": fname7, "path": f"IP/{fname7}", "type": "TM Registry Record", "format": "pdf_native", "expected_extraction": { "document_type": "Trademark Registry Record", "deadline": None, "deadline_note": "Implicit deadline based on status" if has_implicit_deadline else "No deadline", "client": client7["name"], "tm_number": tm7["number"], "status": status_type, }, } ) print(f" [7/7] PDF TM registry record: {fname7}") # ── Copy real PDFs ───────────────────────────────────────────── if REAL_PDFS_DIR.exists(): for pdf in REAL_PDFS_DIR.glob("*.pdf"): dest = ip_dir / pdf.name shutil.copy2(pdf, dest) manifest["documents"].append( { "filename": pdf.name, "path": f"IP/{pdf.name}", "type": "Real Document (not generated)", "format": "pdf", "expected_extraction": None, } ) print(f" [+] Copied real PDF: {pdf.name}") # ── Save manifest ────────────────────────────────────────────── llm_dir = output_dir / "_LLM" llm_dir.mkdir(parents=True, exist_ok=True) manifest_path = llm_dir / "manifest.json" manifest_path.write_text( json.dumps(manifest, indent=2, ensure_ascii=False), encoding="utf-8", ) print(f"\n Manifest saved to {manifest_path}") return manifest def main(): parser = argparse.ArgumentParser(description="Generate JingTian sample documents") parser.add_argument( "--output", "-o", default=str(Path(__file__).parent / "outputs"), help="Output directory (default: ./outputs)", ) parser.add_argument( "--seed", "-s", type=int, default=None, help="Random seed for reproducible runs" ) args = parser.parse_args() if args.seed is not None: random.seed(args.seed) print(f"Using seed: {args.seed}") output_dir = Path(args.output) output_dir.mkdir(parents=True, exist_ok=True) print(f"Generating JingTian sample documents...") print(f"Output: {output_dir}\n") pools = load_pools() manifest = generate_all(output_dir, pools) print(f"\nDone! Generated {len(manifest['documents'])} documents.") if __name__ == "__main__": main()