# CareCollect — running it

The short version: three cron jobs, two worker units, one preflight command,
and a backup you have actually restored.

## Deploy

```bash
git pull && composer-free  # there is no composer; nothing to install
php bin/preflight.php      # exits 1 if anything would bite. Gate the deploy on it.
mysql carecollect < sql/0NN_next.sql
systemctl reload php8.3-fpm
systemctl restart carecollect-worker@1 carecollect-worker@2 carecollect-notify@1
```

`bin/preflight.php` is the gate. It checks debug mode, the signing key, HTTPS
cookies, Redis, OPcache, partition headroom, worker backlog, rollup freshness,
HIS connector heartbeats, and whether the notification, payment and routing
providers are configured. On a production-shaped config with those unset it
exits 1 and says why.

`bin/audit.php` checks the code against the conventions this project claims:
no `SELECT *` on the order table, no date function wrapped around an indexed
column, CSRF on every browser POST, a `tenant_id`-leading index on every
tenant-scoped table, no credentials in the source, a primary key everywhere.
Run it in CI.

## The moving parts

| What | Where | If it stops |
|---|---|---|
| Web | php-fpm behind nginx | Obvious immediately |
| Workers | `carecollect-worker@{1,2}` | Notifications, webhooks and HIS pushes queue up silently — preflight catches it |
| Notify worker | `carecollect-notify@1` | Same, but a slow partner endpoint can't block route optimisation |
| Rollup | cron 00:20 | Reports show gaps and offer to rebuild the missing day |
| Slot horizon | cron 00:40 | Bookings run out 45 days ahead — silent until someone can't book |
| Partitions | cron monthly | Inserts pile into `pmax`; the big tables degrade slowly |
| Prune | cron 03:30 | Expired tokens and sessions accumulate |
| Backup | cron 01:00 | You find out when you need it |

Workers claim jobs with `SKIP LOCKED`, so running several in parallel is safe
and is the way to scale throughput.

## Load, measured

Against 12,089 orders, 24,078 items, 9,595 samples and 500,000 location pings
on a single small box with a 128 MB buffer pool:

| Path | ms |
|---|---|
| Dispatch board (200-order day) | 10 |
| Live map feed | 13 |
| Live map, 8 dispatchers concurrently | 13 each |
| Collections | 7 |
| Reports overview, 30 days | 4 |
| Productivity, 30 days | 6 |
| Revenue, 30 days | 13 |
| Public home page | 7 |
| Sitemap | 6 |
| Location ping intake | 840 points/sec |

The location endpoint is the one that scales with headcount rather than
patients: 840 points/sec is roughly 25,000 phlebotomists pinging every 30
seconds, which is more than the whole Caresoft base would ever field at once.

## What to watch

- **Worker backlog** — `SELECT COUNT(*) FROM job_queue WHERE status='queued'`.
  Steady is fine, growing is not.
- **Cash age, not cash total** — the collections screen sorts by how long
  someone has been carrying money. Two days is the number that matters.
- **HIS outbox blocked count** — unmapped tests hold pushes on purpose. If it
  is climbing, somebody needs to map codes.
- **Rejection rate by person and by tube type** — a rise in one person is
  technique; a rise in one tube type is handling.
- **Partition headroom** — preflight reports months remaining.

## Restore

The drill is at the bottom of `deploy/backup.sh`. Run it quarterly on a scratch
box and **write down how long it took** — that number is your real recovery
time and the only honest answer to "how long would we be down".

`location_ping` is excluded from the daily data dump: it is most of the volume
and none of what you need to run tomorrow. Breadcrumbs are evidence, not state.
If you need them for a dispute, take a separate monthly dump of that table.

## Scaling, in the order it will actually matter

1. **Redis** — sessions, slot counters, rate limits, live positions. Currently
   optional and preflight fails without it in production.
2. **A read replica** — `Db::replica()` already routes every report and export.
   Point it at a replica and month-end stops competing with the board.
3. **More workers** — `systemctl enable --now carecollect-worker@{3,4}`.
4. **Partition pruning** — `console.php partitions` drops beyond 90 days.
5. **Only then** think about splitting anything.
