Milestone 2: module/model docs (MD, Sphinx RST, PDF) and work log
74 source modules documented with extracted signatures, JSDoc params, imports, and call graphs. 10 first-class models (PriceRule through StatusResponse) have their own MD/RST/PDF. Sphinx HTML builds from docs/sphinx. Per-module PDFs in docs/modules-pdf and docs/models-pdf. Middleware gates 2–6 and 10 pass on MOCK_VERAE without NATS. Learned: RST includes are relative to the RST file; keep one PDF per module; Add Numbers remains the only push required tomorrow.
This commit is contained in:
parent
b814501441
commit
10c663cc0c
347 changed files with 17200 additions and 14 deletions
194
scripts/gen-model-docs.py
Normal file
194
scripts/gen-model-docs.py
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
#!/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<string, PriceRule>", "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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue