# Landlord override e2e — verification & deploy checklist

**Created:** 2026-05-28
**Branch:** `feature/frontend_landlord_overrides` (this repo) + `feature/landlord_data` (Model repo)
**Plan:** [landlord-override-frontend-plan.md](./landlord-override-frontend-plan.md) · **ADR:** [0003-classifier-triggers-as-address-subtask.md](../adr/0003-classifier-triggers-as-address-subtask.md)

## Context for picking this up cold

The landlord-classifier e2e is **implemented across both repos but uncommitted**, and
**statically verified only** (frontend `tsc` 0 errors, `next lint` clean, backend
`py_compile` clean). It has **not** been run live — that needs the steps below
(migrations applied, SQS queue + env, FastAPI endpoint + lambda deployed with
OpenAI/S3/Postgres access). Two migration files are **generated but not applied**:
`0215_invert_column_mapping.sql` (data) and `0216_add_subtask_service.sql` (schema).

Work through the sections in order — each step's prerequisites come first.

---

## A. Before you start
- [ ] Use dev/preview first, not prod.
- [ ] Confirm `.env.local` DB creds (`DB_HOST/PORT/USERNAME/PASSWORD/NAME`) point at the target DB.
- [ ] **Back up** `column_mapping` — the 0215 inversion is one-shot/irreversible:
  ```sql
  CREATE TABLE _bak_bulk_mapping AS
  SELECT id, column_mapping FROM bulk_address_uploads WHERE column_mapping IS NOT NULL;
  ```

## B. Database migrations  ⚠️ read the gotcha
`0215` = data (inverts `header→field` → `field→header`); `0216` = schema (`ADD COLUMN sub_task.service`).

⚠️ package.json only has `migration:push` (`drizzle-kit push`). **`push` diffs schema and will NOT run the 0215 data `UPDATE`** — it would add the column but silently skip the inversion. Use `migrate`:
- [ ] ```bash
  npx drizzle-kit migrate      # runs 0215 then 0216 in order
  ```
  (If the team only uses `push`: run `push` for 0216, then execute `0215`'s SQL manually.)
- [ ] ⚠️ Run **0215 exactly once, on old-shape data**. Re-running re-inverts and corrupts. `migrate` guards via the journal; manual runs don't.
- [ ] Verify 0215 — values should now be headers:
  ```sql
  SELECT column_mapping FROM bulk_address_uploads WHERE column_mapping IS NOT NULL LIMIT 5;
  -- expect {"address_1":"Addr 1","postcode":"PCode", ...}
  ```
- [ ] Verify 0216:
  ```sql
  SELECT 1 FROM information_schema.columns
  WHERE table_name='sub_task' AND column_name='service';
  ```

## C. Backend deploy (Model service)
- [ ] Create an SQS queue for the classifier (e.g. `landlord-description-overrides`).
- [ ] Set **`LANDLORD_OVERRIDES_SQS_URL`** in the FastAPI env to that queue.
- [ ] Deploy FastAPI so `/v1/bulk-uploads/trigger-landlord-overrides` is live.
- [ ] Deploy the lambda (`applications/landlord_description_overrides`) + event-source mapping queue → lambda.
- [ ] Lambda env/IAM: `OPENAI_API_KEY`, Postgres creds, **S3 read on the original-upload bucket** (it reads `upload.s3Bucket/s3Key`, not `retrofit-data-dev`).

## D. Frontend deploy
- [ ] Deploy `assessment-model` with the new code (`FASTAPI_API_URL` / `FASTAPI_API_KEY` already set).

## E. Verify the UI (Column Remapper)
- [ ] Map-columns page shows **one row per field with a header dropdown**, split into **Address fields** + **Landlord description fields**.
- [ ] Leaving Address 1 / Postcode unset blocks submit.
- [ ] Two **address** fields → one column is blocked; the **same** column → Property Type + Built Form is allowed.
- [ ] Existing `mapping_complete` uploads open with their mapping intact (confirms 0215).

## F. Verify trigger → classify → persist
- [ ] Map ≥1 landlord-description field, click **Start address matching**.
- [ ] `sub_task` has two rows under the task: `service='address2uprn'` and `service='landlord_description_overrides'`.
- [ ] SQS message enqueued + lambda ran (CloudWatch).
- [ ] Rows appear with `source='classifier'`:
  ```sql
  SELECT description, value FROM landlord_property_type_overrides WHERE portfolio_id = <id> LIMIT 10;
  ```

## G. Verify the results view
- [ ] `/portfolio/<id>/landlord-overrides` lists `description → value` per category with a "classifier" badge. (No nav link yet — reach by URL.)

## H. Regression — address matching unaffected
- [ ] The same upload's address pipeline still emits the canonical CSV (`Address 1`/`postcode`) and combines normally.

## I. Watch-out (by design — ADR-0003 "accepted coupling")
- [ ] If the classifier subtask **fails**, the shared onboarding task goes FAILED and **"Run Combiner" is blocked**; the task only COMPLETEs once classification finishes. If painful, switch to the separate-task design in the ADR.

---

## Still open / not done
- [ ] Commit the work (this repo + Model repo, separately) — currently uncommitted.
- [ ] Nav link to `/portfolio/<id>/landlord-overrides` (reachable by URL only).
- [ ] User-edit write-back for overrides (deferred — Q7 "read-only this iteration").
