{
  "schema_version": 1,
  "diagram_type": "architecture",
  "meta": {
    "title": "Basketball Team Manager",
    "quality_profile": "showcase",
    "views": [
      {
        "id": "family-path",
        "label": "Parent opens the page",
        "focus": [
          "parents",
          "caddy",
          "site",
          "plan",
          "data"
        ],
        "note": "The page is a thin viewer over the Python plan."
      },
      {
        "id": "whatsapp-path",
        "label": "Message reaches the group",
        "focus": [
          "site",
          "bridge",
          "wa",
          "apprise"
        ],
        "note": "Two ways in: a button on the page, or a schedule from Apprise or n8n."
      },
      {
        "id": "data-refresh",
        "label": "Where the fixture data comes from",
        "focus": [
          "playhq",
          "fetch",
          "data",
          "plan"
        ],
        "note": "PlayHQ holds the draw. EDJBA season dates are entered once by hand."
      }
    ]
  },
  "components": [
    {
      "id": "parents",
      "type": "external",
      "label": "Parents",
      "sublabel": "phone browser",
      "pos": [
        40,
        100
      ],
      "size": [
        140,
        60
      ]
    },
    {
      "id": "caddy",
      "type": "security",
      "label": "Caddy .35",
      "sublabel": "TLS + basic auth",
      "pos": [
        270,
        100
      ],
      "size": [
        150,
        60
      ]
    },
    {
      "id": "site",
      "type": "backend",
      "label": "btm-web",
      "sublabel": "Go + HTMX .13:8420",
      "pos": [
        500,
        100
      ],
      "size": [
        160,
        60
      ]
    },
    {
      "id": "plan",
      "type": "backend",
      "label": "tools/plan.py",
      "sublabel": "roster and messages",
      "pos": [
        740,
        100
      ],
      "size": [
        170,
        60
      ]
    },
    {
      "id": "data",
      "type": "database",
      "label": "data/*.json",
      "sublabel": "players fixtures history",
      "pos": [
        990,
        100
      ],
      "size": [
        170,
        60
      ]
    },
    {
      "id": "apprise",
      "type": "external",
      "label": "Apprise / n8n",
      "sublabel": "scheduled posts",
      "pos": [
        40,
        340
      ],
      "size": [
        150,
        60
      ]
    },
    {
      "id": "bridge",
      "type": "backend",
      "label": "wa-bridge",
      "sublabel": "Node .13:8421",
      "pos": [
        500,
        340
      ],
      "size": [
        160,
        60
      ]
    },
    {
      "id": "wa",
      "type": "external",
      "label": "WhatsApp group",
      "sublabel": "team chat",
      "pos": [
        740,
        340
      ],
      "size": [
        160,
        60
      ]
    },
    {
      "id": "playhq",
      "type": "external",
      "label": "PlayHQ API",
      "sublabel": "draw venue opponent",
      "pos": [
        40,
        560
      ],
      "size": [
        160,
        60
      ]
    },
    {
      "id": "fetch",
      "type": "backend",
      "label": "fetch_fixtures.py",
      "sublabel": "scheduled refresh",
      "pos": [
        270,
        560
      ],
      "size": [
        170,
        60
      ]
    }
  ],
  "boundaries": [
    {
      "kind": "region",
      "label": "machine .13",
      "wraps": [
        "site",
        "plan",
        "data",
        "bridge",
        "fetch"
      ]
    },
    {
      "kind": "region",
      "label": "edge .35",
      "wraps": [
        "caddy"
      ]
    }
  ],
  "connections": [
    {
      "id": "parents-caddy",
      "from": "parents",
      "to": "caddy",
      "label": "HTTPS",
      "labelDx": 0,
      "labelDy": -16
    },
    {
      "id": "caddy-site",
      "from": "caddy",
      "to": "site",
      "label": "proxy",
      "labelDy": -16
    },
    {
      "id": "site-plan",
      "from": "site",
      "to": "plan",
      "label": "runs it",
      "labelDy": -16
    },
    {
      "id": "plan-data",
      "from": "plan",
      "to": "data",
      "label": "reads",
      "labelDy": -16
    },
    {
      "id": "site-bridge",
      "from": "site",
      "to": "bridge",
      "label": "POST /send",
      "labelDy": 62
    },
    {
      "id": "apprise-bridge",
      "from": "apprise",
      "to": "bridge",
      "label": "POST /send",
      "labelDy": -16
    },
    {
      "id": "bridge-wa",
      "from": "bridge",
      "to": "wa",
      "label": "linked device",
      "labelDy": -16
    },
    {
      "id": "fetch-playhq",
      "from": "fetch",
      "to": "playhq",
      "label": "GraphQL",
      "labelDy": 16
    },
    {
      "id": "fetch-data",
      "from": "fetch",
      "to": "data",
      "label": "writes",
      "toSide": "bottom"
    }
  ],
  "cards": [
    {
      "dot": "cyan",
      "title": "One source of truth",
      "items": [
        "Python owns the rules: rotation, training dates, message wording",
        "The Go app renders JSON from tools/plan.py and holds no logic",
        "Website, CSV, WhatsApp drafts all come from the same run"
      ]
    },
    {
      "dot": "emerald",
      "title": "WhatsApp path",
      "items": [
        "Bridge links the phone once as a linked device",
        "One POST /send with a group name and text",
        "Accepts form or JSON so Apprise and n8n can post without code"
      ]
    },
    {
      "dot": "amber",
      "title": "Weekly routine",
      "items": [
        "Thursday 3pm: training reminder, names the next game's timekeeper",
        "Friday 9am: refresh fixtures, 9:05am game details, 4pm re-post if changed",
        "Quiet weeks send a short note: no training, or no game this weekend",
        "n8n calls the same endpoints the page uses; failures alert by email and ntfy"
      ]
    }
  ]
}
