#!/usr/bin/env python3 """Write first-class data-model MD + RST + PDF sheets.""" from __future__ import annotations from pathlib import Path from reportlab.lib.pagesizes import letter from reportlab.lib.styles import getSampleStyleSheet from reportlab.lib.units import inch from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer ROOT = Path(__file__).resolve().parents[1] MD = ROOT / "docs" / "models" RST = ROOT / "docs" / "sphinx" / "models" PDF = ROOT / "docs" / "models-pdf" MODELS = { "PriceRule": { "src": "packages/zappier/src/pricing.ts", "kind": "discriminated union", "fields": [ ("kind", "'free' | 'fixed' | 'variable'", "Which list-price formula"), ("fixedCents", "number?", "When kind=fixed, cents per call"), ("baseCents", "number?", "When kind=variable, base cents"), ("perKbCents", "number?", "When kind=variable, per KB metadata"), ("perMbCents", "number?", "When kind=variable, per MB attachments"), ], "used_by": ["quoteCall", "meter", "admin rate card UI", "portal API & pricing tab"], "returns_in": "RateCard.endpoints values", }, "TierConfig": { "src": "packages/zappier/src/pricing.ts", "kind": "interface", "fields": [ ("id", "string", "free | pro | business or custom"), ("name", "string", "Display name"), ("multiplier", "number", "Applied once to list cents"), ("monthlyCreditCents", "number", "Included usage before Stripe/invoice"), ("defaultRule", "PriceRule?", "Price for unlisted operationIds"), ], "used_by": ["quoteCall", "applyMonthlyCredit", "admin tiers", "portal"], "returns_in": "PricingStore.getTiers()", }, "RateCard": { "src": "packages/zappier/src/pricing.ts", "kind": "interface", "fields": [("endpoints", "Record", "Keys are OpenAPI operationIds")], "used_by": ["quoteCall", "admin PUT /admin/api/endpoints/:id"], "returns_in": "PricingStore.getRateCard()", }, "Quote": { "src": "packages/zappier/src/pricing.ts", "kind": "interface", "fields": [ ("endpointId", "string", "operationId priced"), ("listCents", "number", "Before multiplier"), ("totalCents", "number", "round(list * multiplier)"), ("breakdown.baseCents", "number", "Variable base"), ("breakdown.metadataCents", "number", "KB component"), ("breakdown.attachmentCents", "number", "MB component"), ], "used_by": ["meter() sets res.locals.quote; JSON body.quote on /v1/*"], "returns_in": "quoteCall(...) → Quote", }, "CallUsage": { "src": "packages/zappier/src/pricing.ts", "kind": "interface", "fields": [ ("metadataBytes", "number", "UTF-8 length of metadata JSON"), ("attachmentBytes", "number", "Sum of uploaded file sizes"), ], "used_by": ["meter()", "quoteCall()"], "returns_in": "constructed in meter middleware", }, "Customer": { "src": "packages/zappier/src/auth.ts", "kind": "interface", "fields": [ ("id", "string", "cust_…"), ("name", "string", "Display"), ("tierId", "string", "Matches TierConfig.id"), ("apiKey", "string", "x-api-key value"), ("multiplierOverride", "number?", "Beats tier multiplier"), ("email", "string?", "Portal login"), ], "used_by": ["apiKeyAuth", "portal", "invoices", "usage.summaryFor"], "returns_in": "CustomerRepo.findByApiKey / findById", }, "AddNumbersResult": { "src": "packages/verae-activate/creates/add_numbers.js", "kind": "Zapier create output", "fields": [ ("number1", "number", "First addend (finite)"), ("number2", "number", "Second addend (finite)"), ("sum", "number", "number1 + number2"), ("mode", "'local' | 'hosted'", "local = Zapier cloud arithmetic; hosted = POST /v1/add"), ], "used_by": ["Zapier Add Numbers action", "optional zappier POST /v1/add"], "returns_in": "perform(z, bundle) → single object (creates never return arrays)", }, "TimestampRequest": { "src": "docs/00-sources/veraetime-openapi.yaml", "kind": "OpenAPI schema", "fields": [ ("data", "string (required)", "Payload to hash/timestamp"), ("hashAlg", "string?", "Defaults SHA256"), ], "used_by": ["POST /api/timestamp", "middleware timestampService.create"], "returns_in": "request body only", }, "TimestampResponse": { "src": "docs/00-sources/veraetime-openapi.yaml", "kind": "OpenAPI schema", "fields": [("jobId", "string", "UUID of async job")], "used_by": ["Zapier Create Timestamp"], "returns_in": "HTTP 202 JSON", }, "StatusResponse": { "src": "docs/00-sources/veraetime-openapi.yaml", "kind": "OpenAPI schema", "fields": [ ("id", "string", "jobId"), ("status", "'pending' | 'completed' | 'failed'", "Job state"), ("error", "string?", "If failed"), ("result", "string?", "Certificate if completed"), ("completedAt", "string date-time?", ""), ("metadata", "object?", "blockIndex, timestamp, certificate, block, chain"), ], "used_by": ["GET /api/status/{jobId}", "Find Job Status search"], "returns_in": "HTTP 200 JSON", }, } def body(name: str, spec: dict) -> str: rows = "\n".join(f"| `{n}` | `{t}` | {d} |" for n, t, d in spec["fields"]) return f"""# {name} **Kind:** {spec['kind']} **Defined in:** `{spec['src']}` ## Fields | Field | Type | Meaning | |-------|------|---------| {rows} ## Who constructs it {spec['returns_in']} ## Who consumes it {spec['used_by'] if isinstance(spec['used_by'], str) else ', '.join(f'`{x}`' for x in spec['used_by'])} ## Related modules See `docs/modules/` for the functions that take these types as parameters and the values they return. """ def main() -> None: MD.mkdir(parents=True, exist_ok=True) RST.mkdir(parents=True, exist_ok=True) PDF.mkdir(parents=True, exist_ok=True) styles = getSampleStyleSheet() for name, spec in MODELS.items(): text = body(name, spec) (MD / f"{name}.md").write_text(text, encoding="utf-8") (RST / f"{name}.rst").write_text( f"{name}\n{'=' * len(name)}\n\n.. include:: {name}.md\n :literal:\n", encoding="utf-8", ) (RST / f"{name}.md").write_text(text, encoding="utf-8") pdf_path = PDF / f"{name}.pdf" doc = SimpleDocTemplate(str(pdf_path), pagesize=letter, leftMargin=0.7 * inch, rightMargin=0.7 * inch) story = [] for line in text.splitlines(): if line.startswith("# "): story.append(Paragraph(line[2:].replace("&", "&"), styles["Title"])) elif line.startswith("## "): story.append(Spacer(1, 8)) story.append(Paragraph(line[3:].replace("&", "&"), styles["Heading2"])) elif line.strip(): story.append(Paragraph(line.replace("&", "&").replace("|", " · ").replace("`", ""), styles["BodyText"])) else: story.append(Spacer(1, 4)) doc.build(story) print(f"wrote {len(MODELS)} models") if __name__ == "__main__": main()