{
  "schema_version": 1,
  "diagram_type": "architecture",
  "meta": {
    "title": "Ordermatic Runtime Architecture",
    "output": "ordermatic-runtime.architecture.html",
    "quality_profile": "showcase",
    "viewBox": [1200, 720],
    "views": [
      {
        "id": "po-lifecycle",
        "label": "PO lifecycle",
        "focus": ["users", "cdn", "lb", "api", "validation", "worker"],
        "note": "From inbound email to reviewed, extracted, validated, and submitted ERP order."
      },
      {
        "id": "data-flow",
        "label": "Data + assets",
        "focus": ["cdn", "s3", "api", "cache", "llm", "db", "dagster", "typesense", "validation"],
        "note": "PDFs, LLM extraction, Typesense matching, structured state, and data pipelines."
      },
      {
        "id": "human-review",
        "label": "Human review",
        "focus": ["auth", "lb", "api", "validation", "typesense", "worker"],
        "note": "CSR corrections, Typesense lookups, validation, and approval gates."
      },
      {
        "id": "catalog-sync",
        "label": "Catalog & search",
        "focus": ["worker", "dagster", "s3", "typesense", "lb"],
        "note": "ERP data feeds Dagster, which writes R2/Iceberg, runs dbt, and syncs Typesense for the webapp."
      }
    ]
  },
  "components": [
    {
      "id": "users",
      "type": "external",
      "label": "Customer",
      "sublabel": "send PDF PO",
      "pos": [40, 300],
      "size": [120, 60]
    },
    {
      "id": "auth",
      "type": "security",
      "label": "CSR",
      "sublabel": "review & submit",
      "pos": [40, 110],
      "size": [120, 64],
      "tag": "Astro UI"
    },
    {
      "id": "cdn",
      "type": "cloud",
      "label": "Email Worker",
      "sublabel": "Cloudflare routing",
      "pos": [250, 300],
      "size": [130, 60]
    },
    {
      "id": "lb",
      "type": "backend",
      "label": "Ordermatic Webapp",
      "sublabel": "Astro SSR / Hono",
      "pos": [460, 300],
      "size": [130, 60]
    },
    {
      "id": "api",
      "type": "messagebus",
      "label": "Temporal Server",
      "sublabel": "workflows & queues",
      "pos": [670, 300],
      "size": [130, 60]
    },
    {
      "id": "cache",
      "type": "backend",
      "label": "Extraction Worker",
      "sublabel": "Python / LLM",
      "pos": [670, 150],
      "size": [130, 60]
    },
    {
      "id": "db",
      "type": "database",
      "label": "PostgreSQL",
      "sublabel": "Drizzle + Iceberg catalog",
      "pos": [880, 300],
      "size": [130, 60]
    },
    {
      "id": "llm",
      "type": "external",
      "label": "LLM Providers",
      "sublabel": "Gemini / Claude",
      "pos": [880, 150],
      "size": [130, 60]
    },
    {
      "id": "s3",
      "type": "cloud",
      "label": "Cloudflare R2",
      "sublabel": "PDFs + bronze lake",
      "pos": [250, 440],
      "size": [130, 60],
      "tag": "object store"
    },
    {
      "id": "validation",
      "type": "backend",
      "label": "TS Temporal Worker",
      "sublabel": "validate & submit",
      "pos": [670, 440],
      "size": [130, 60],
      "tag": "TypeScript"
    },
    {
      "id": "worker",
      "type": "external",
      "label": "ERP Systems",
      "sublabel": "P21 / Acumatica / ...",
      "pos": [880, 440],
      "size": [130, 60]
    },
    {
      "id": "dagster",
      "type": "backend",
      "label": "Dagster",
      "sublabel": "DLT → dbt → sensors",
      "pos": [250, 580],
      "size": [130, 60],
      "tag": "data orchestration"
    },
    {
      "id": "typesense",
      "type": "database",
      "label": "Typesense",
      "sublabel": "search index",
      "pos": [460, 580],
      "size": [130, 60]
    }
  ],
  "boundaries": [
    {
      "kind": "region",
      "label": "Ordermatic Cloud Runtime",
      "wraps": [
        "cdn",
        "lb",
        "api",
        "cache",
        "db",
        "s3",
        "validation",
        "worker",
        "dagster",
        "typesense"
      ]
    },
    { "kind": "security-group", "label": "Tenant boundary", "wraps": ["lb", "api"] }
  ],
  "connections": [
    {
      "id": "inbound-mail",
      "from": "users",
      "to": "cdn",
      "label": "MIME email",
      "variant": "emphasis"
    },
    {
      "id": "csr-review",
      "from": "auth",
      "to": "api",
      "label": "review/submit",
      "variant": "security",
      "fromSide": "right",
      "toSide": "top",
      "via": [
        [620, 142],
        [620, 246],
        [735, 246]
      ]
    },
    { "id": "email-to-webapp", "from": "cdn", "to": "lb" },
    {
      "id": "raw-pdf-to-r2",
      "from": "cdn",
      "to": "s3",
      "label": "raw PDF",
      "variant": "dashed",
      "fromSide": "bottom",
      "toSide": "top",
      "labelDy": 58
    },
    { "id": "webapp-to-temporal", "from": "lb", "to": "api" },
    {
      "id": "extraction-run",
      "from": "api",
      "to": "cache",
      "label": "extract PDF",
      "fromSide": "top",
      "toSide": "bottom",
      "labelDy": -68
    },
    {
      "id": "llm-call",
      "from": "cache",
      "to": "llm",
      "label": "LLM extraction",
      "variant": "dashed"
    },
    { "id": "sql-state", "from": "api", "to": "db", "label": "SQL" },
    {
      "id": "enqueue-submit",
      "from": "api",
      "to": "validation",
      "label": "validate/submit",
      "variant": "dashed",
      "fromSide": "bottom",
      "toSide": "top",
      "labelDy": 58
    },
    { "id": "submit-to-erp", "from": "validation", "to": "worker", "label": "submit order" },
    {
      "id": "validation-search",
      "from": "validation",
      "to": "typesense",
      "label": "match",
      "fromSide": "bottom",
      "toSide": "right",
      "via": [[735, 610]],
      "labelAt": [650, 602]
    },
    {
      "id": "webapp-search",
      "from": "lb",
      "to": "typesense",
      "label": "search queries",
      "fromSide": "bottom",
      "toSide": "top",
      "labelDy": 24
    },
    {
      "id": "dagster-index",
      "from": "dagster",
      "to": "typesense",
      "label": "products/customers",
      "fromSide": "right",
      "toSide": "left",
      "labelAt": [420, 654]
    },
    {
      "id": "dagster-bronze",
      "from": "dagster",
      "to": "s3",
      "label": "bronze lake",
      "fromSide": "top",
      "toSide": "bottom",
      "labelDy": -58
    },
    {
      "id": "bronze-sensor",
      "from": "s3",
      "to": "dagster",
      "label": "bronze → dbt",
      "variant": "dashed",
      "fromSide": "right",
      "toSide": "left",
      "via": [
        [420, 470],
        [420, 570],
        [230, 570],
        [230, 610]
      ],
      "labelAt": [460, 540]
    },
    {
      "id": "erp-to-dagster",
      "from": "worker",
      "to": "dagster",
      "label": "sync source",
      "fromSide": "bottom",
      "toSide": "bottom",
      "via": [
        [945, 700],
        [315, 700]
      ]
    },
    {
      "id": "webapp-to-dagster",
      "from": "lb",
      "to": "dagster",
      "label": "trigger sync",
      "fromSide": "top",
      "toSide": "top",
      "via": [
        [525, 240],
        [400, 240],
        [400, 560],
        [315, 560]
      ],
      "labelAt": [800, 240]
    }
  ],
  "cards": [
    {
      "dot": "cyan",
      "title": "Order pipeline",
      "items": [
        "Cloudflare Email Worker ingests PDFs; Temporal orchestrates extraction, matching, and submission",
        "TS Temporal Worker matches lines in Typesense and submits to ERP; CSRs review in the Astro/Hono webapp"
      ]
    },
    {
      "dot": "violet",
      "title": "Catalog & search",
      "items": [
        "Dagster syncs ERP products, customers, and images to Iceberg on R2, then runs dbt and sensors to update Typesense",
        "Typesense powers typo-tolerant product, customer, and address search for matching and the webapp"
      ]
    }
  ]
}
