Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c9fc3f3
docs: inherit org community health files (#237)
rachelauryn May 14, 2026
ed8170f
feat(api): simplify create model request to name only (#238)
mrmaloof May 27, 2026
0465ca9
chore: bump edvise dependency to 1.0.0 (#241)
vishpillai123 May 27, 2026
eb9bb20
chore: creating dummy changlog.md file while we create semver / gitfl…
May 27, 2026
7066397
feat: trigger Databricks GCS→bronze sync after file validation (Edvis…
chapmanhk May 29, 2026
1846864
feat(api): validate Edvise uploads with repo schemas (#242)
chapmanhk May 29, 2026
663caaf
feat(webapp): establish pyproject.toml as canonical Edvise API versio…
mrmaloof Jun 8, 2026
327c97c
feat(eda): add clear_cache option to /eda endpoint (#233)
mrmaloof Jun 10, 2026
e139a48
feat: legacy school inference DB job trigger (#212)
vishpillai123 Jun 11, 2026
2c680c4
feat: add gen ai as 4th option in addition pdp / edvise / legacy in a…
vishpillai123 Jun 12, 2026
ac6160b
fix(models): derive PDP batch schema configs from institution schemas…
vishpillai123 Jun 15, 2026
387690c
ci: datakind shared workflows (#245)
mrmaloof Jun 15, 2026
a05d8d0
chore(release): sync develop with main (#251)
vishpillai123 Jun 17, 2026
c91d0ca
refactor(validation): remove API JSON schema validation from upload p…
chapmanhk Jun 22, 2026
41c9548
feat: hook up Edvise Schema (ES) inference to api (#253)
nm3224 Jun 23, 2026
da0572b
feat: creating `is_genai_institution` parameter to feed into genai/ed…
vishpillai123 Jun 24, 2026
563875e
feat: using `batch_id` parameter for subfolder naming convention duri…
vishpillai123 Jun 24, 2026
4c98cf3
feat: use batch parameters for run-inference endpoint for genAI/Edvis…
vishpillai123 Jun 25, 2026
f4e04d0
docs(db): add staging-verified shared schema contract for UI and API …
chapmanhk Jun 26, 2026
a338ae1
feat(api): expose model_run_id and model_version on RunInfo endpoints…
chapmanhk Jun 26, 2026
ef75b89
feat(api): mirror accepted_terms and invite_validated on AccountTable…
chapmanhk Jun 26, 2026
56c3fa4
fix: sync ES pipeline rename & allow greater flexibility with legacy/…
vishpillai123 Jun 26, 2026
239f2bd
fix(api): coerce Databricks model version to str for RunInfo response…
vishpillai123 Jun 29, 2026
06cff1f
chore(release): bump version
mrmaloof Jun 29, 2026
0868395
Merge branch 'main' into release/1.1.0
mrmaloof Jun 29, 2026
fa4e8bb
chore(release): update CHANGELOG
mrmaloof Jun 29, 2026
645420d
chore: bump edvise from 1.2.0 to 1.4.0
Jun 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## 1.1.0 (2026-06-29)
- feat: hook up Edvise Schema (ES) inference to the API (#253)
- feat: use batch parameters for run-inference endpoint (GenAI/Edvise/Legacy) (#257)
- feat: add `is_genai_institution` parameter to GenAI/Edvise inference job for SSoT (#254)
- feat: use `batch_id` for subfolder naming in GCS→bronze async job (#256)
- feat: expose `model_run_id` and `model_version` on RunInfo endpoints (#260)
- feat: mirror `accepted_terms` and `invite_validated` on `AccountTable` (#263)
- fix: sync ES pipeline rename and allow greater flexibility for legacy/GenAI/ES uploads (#262)
- fix: coerce Databricks model version to `str` for RunInfo responses (#264)
- refactor: remove API JSON schema validation from upload pipeline (#246)
- docs: add staging-verified shared DB schema contract for UI and API (#258)

## 1.0.0 (2026-06-16)
- feat: add GenAI as a fourth schema type (alongside PDP, Edvise, and Legacy) in API and uploads (#244)
- feat: legacy school inference Databricks job trigger (#212)
Expand Down
172 changes: 172 additions & 0 deletions docs/DB_SCHEMA_CONTRACT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# Database schema contract (edvise-ui + edvise-api)

Canonical DDL and ownership rules for the **shared Cloud SQL database** used by `edvise-ui` (Laravel) and `edvise-api` (FastAPI) in deployed environments.

**Staging verified:** 2026-06-24 against Cloud SQL database `all_tables` (staging instance). Evidence: workspace `github/docs/dbtables/` (`SHOW CREATE TABLE` CSV exports).

See also: migration plan in workspace `github/.cursor/docs/database_table_ownership.md`.

---

## Migration ownership

| Table(s) | DDL owner | Migration tool | Runtime writers | Runtime readers |
|----------|-----------|----------------|-----------------|-----------------|
| `users` | **edvise-ui** | Laravel | UI (primary), API (auth) | Both |
| `job` | **edvise-api** (Phase 1+) | Alembic | API | API, UI (read-only until Phase 1.5) |
| UI-only tables | **edvise-ui** | Laravel | UI | UI |
| API-only tables | **edvise-api** | Alembic | API | API |

**Rules:**

- API Alembic must **never** `CREATE TABLE users`.
- UI Laravel must **not** add new migrations for `job` after Phase 1 cutover (bootstrap migration may remain idempotent).
- Shared column changes require **paired PRs** (see plan doc).

---

## Staging verification summary (2026-06-24)

| Finding | Implication |
|---------|-------------|
| `users` has DB FK `inst_id` → `inst.id` ON DELETE CASCADE | Present on staging (likely from historical API `create_all`). UI migrations do not add it; **do not drop** without explicit decision. |
| `job.model_run_id` is `VARCHAR(255)` with FK to `model.id` | Matches API ORM. **No `ALTER` needed on staging** before Alembic stamp. |
| `job` lacks composite / `triggered_at` indexes from UI migration | Optional future Alembic migration for perf only — not blocking cutover. |
| `institutions` table absent | Safe to remove UI migration (PR 5). |
| `inst_custom_to_legacy_backup`, `schema_registry_custom_ext_backup` exist | Not in contract inventory; **exclude from Alembic** unless explicitly adopted. |

**Still required:** repeat `SHOW CREATE TABLE` / dump on **dev** `all_tables` before dev Alembic stamp (PR 11).

---

## Greenfield bootstrap order

1. **API** `alembic upgrade head` — creates `inst`, `model`, `job`, and all API-owned tables.
2. **UI** `php artisan migrate` — creates `users`, teams, and UI-only tables.
3. Seed institutions via API; users via UI registration.

---

## Shared table: `users`

**DDL owner:** edvise-ui (`database/migrations/`).
**ORM (API):** `AccountTable` in `src/webapp/database.py`.

### Canonical columns (staging `all_tables`, 2026-06-24)

| Column | Type (MySQL) | Nullable | Default | Notes |
|--------|--------------|----------|---------|-------|
| `id` | `CHAR(32)` | NO | — | PK; UUID without dashes on staging |
| `inst_id` | `CHAR(32)` | YES | NULL | Index; **FK → `inst.id` ON DELETE CASCADE on staging** |
| `name` | `VARCHAR(255)` | NO | — | |
| `email` | `VARCHAR(255)` | NO | — | UNIQUE |
| `invite_validated` | `TINYINT(1)` | NO | `0` | |
| `google_id` | `VARCHAR(255)` | YES | NULL | |
| `azure_id` | `VARCHAR(255)` | YES | NULL | |
| `email_verified_at` | `DATETIME` | YES | NULL | |
| `password` | `VARCHAR(255)` | NO | — | |
| `two_factor_secret` | `TEXT` | YES | NULL | |
| `two_factor_recovery_codes` | `TEXT` | YES | NULL | |
| `two_factor_confirmed_at` | `DATETIME` | YES | NULL | |
| `remember_token` | `VARCHAR(255)` | YES | NULL | Laravel migration allows 100; staging uses 255 |
| `current_team_id` | `CHAR(36)` | YES | NULL | |
| `access_type` | `VARCHAR(36)` | YES | NULL | |
| `accepted_terms` | `TINYINT(1)` | NO | `0` | API ORM must mirror |
| `created_at` | `DATETIME` | YES | `now()` on staging | |
| `updated_at` | `DATETIME` | YES | NULL | |

### Constraints (staging)

```sql
CONSTRAINT `users_ibfk_1` FOREIGN KEY (`inst_id`) REFERENCES `inst` (`id`) ON DELETE CASCADE
```

**Policy:** UI Laravel migrations must not rely on this FK for local SQLite. Staging/prod may retain it from API history. New UI migrations must not drop it without coordinated review.

**Source migrations (UI):** `2014_10_12_000000_create_users_table.php`, `2025_06_06_142209_add_accepted_terms_to_users_table.php`, `2025_08_24_210106_add_invite_validated_to_users_table.php`.

---

## Shared table: `job`

**DDL owner:** edvise-api (Alembic, Phase 1+).
**ORM (API):** `JobTable` in `src/webapp/database.py`.
**UI model:** `App\Models\Job` (read paths only until Phase 1.5).

### Canonical columns (staging `all_tables`, 2026-06-24)

| Column | Type (MySQL) | Nullable | Default | Notes |
|--------|--------------|----------|---------|-------|
| `id` | `BIGINT` | NO | AUTO_INCREMENT | PK; inference `run_id` |
| `model_id` | `CHAR(32)` | NO | — | FK → `model.id` ON DELETE CASCADE |
| `created_by` | `CHAR(32)` | NO | — | User UUID |
| `triggered_at` | `DATETIME` | NO | — | |
| `batch_name` | `VARCHAR(255)` | NO | — | |
| `output_filename` | `VARCHAR(255)` | YES | NULL | |
| `err_msg` | `VARCHAR(255)` | YES | NULL | |
| `completed` | `TINYINT(1)` | YES | NULL | |
| `output_valid` | `TINYINT(1)` | YES | NULL | |
| `model_run_id` | `VARCHAR(255)` | YES | NULL | |
| `model_version` | `VARCHAR(255)` | YES | NULL | |

### Constraints and indexes (staging)

```sql
PRIMARY KEY (`id`)
KEY `model_id` (`model_id`)
CONSTRAINT `job_ibfk_1` FOREIGN KEY (`model_id`) REFERENCES `model` (`id`) ON DELETE CASCADE
```

### Optional vs UI migration (not blocking)

| Item | UI Laravel migration | Staging | Action |
|------|----------------------|---------|--------|
| `model_run_id` length | `VARCHAR(150)` | `VARCHAR(255)` | **No action on staging** — already 255 |
| `model_id` FK | Not in UI migration | Present | **No action on staging** |
| Composite `(model_id, completed, output_valid)` | In UI migration | Absent | Optional Alembic index migration later |
| Index on `triggered_at` | In UI migration | Absent | Optional Alembic index migration later |

---

## Tables on staging outside Alembic scope

| Table | Notes |
|-------|-------|
| `inst_custom_to_legacy_backup` | One-off backup; do not include in baseline autogenerate |
| `schema_registry_custom_ext_backup` | One-off backup; do not include in baseline autogenerate |

---

## edvise-ui only tables (Laravel DDL)

Verified on staging 2026-06-24: `teams`, `team_user`, `team_invitations`, `personal_access_tokens`, `password_reset_tokens`, `sessions`, `failed_jobs`, `dk_api_tokens`, `data_dictionary`, `invites`, `migrations`.

**Removed in Phase 0:** `institutions` — never existed on staging; duplicate of API `inst`.

---

## edvise-api only tables (Alembic DDL)

Verified on staging 2026-06-24: `inst`, `apikey`, `account_history`, `file`, `batch`, `file_batch_association_table`, `model`, `schema_registry`, `job`.

After Phase 1: `alembic_version`.

---

## Change process checklist

### `users` column change

- [ ] Laravel migration (`edvise-ui`)
- [ ] `AccountTable` update (`edvise-api`)
- [ ] This contract updated
- [ ] Both READMEs accurate
- [ ] Deploy UI migrate before API deploy

### `job` column change

- [ ] Alembic migration (`edvise-api`)
- [ ] UI `Job` model updated if UI reads the field
- [ ] This contract updated
- [ ] No new Laravel `job` migration
- [ ] Deploy API `api-migrate` before UI deploy
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "src"
version = "1.0.0"
version = "1.1.0"
description = "School-agnostic lib for implementing Edvise workflows."
readme = "README.md"
requires-python = ">=3.12,<3.13"
Expand Down Expand Up @@ -30,7 +30,7 @@ dependencies = [
"mlflow~=2.22",
"cachetools",
"types-cachetools",
"edvise~=1.2.0",
"edvise~=1.4.0",
]

[project.urls]
Expand Down
18 changes: 16 additions & 2 deletions src/webapp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,30 @@ In the long-term, look into a way to have the API key --> token conversion be ha

All data is stored in MySQL databases for dev/staging/prod, these are databases in GCP's Cloud SQL. In the local environment, the database is sqlite. The main file you'll want to look at for database table definitions is [src/webapp/database.py](https://github.com/datakind/edvise-api/blob/develop/src/webapp/database.py).

**Schema contract:** Shared and owned tables are documented in [docs/DB_SCHEMA_CONTRACT.md](../docs/DB_SCHEMA_CONTRACT.md). Update that file whenever `users` or `job` columns change. Staging `all_tables` DDL was verified 2026-06-24 (evidence: workspace `github/docs/dbtables/`). Re-export **dev** before Alembic stamp (PR 11).

### Shared tables (same physical MySQL database as edvise-ui)

| Table | DDL owner | Notes |
|-------|-----------|-------|
| `users` | **edvise-ui** (Laravel) | API reads/writes via `AccountTable`; keep ORM in sync with UI migrations |
| `job` | **edvise-api** (Alembic, Phase 1+) | API writes; UI may read until Phase 1.5 |

### Greenfield database bootstrap

1. Run API migrations (`alembic upgrade head`) — API-owned tables including `inst`, `model`, `job`.
2. Run UI migrations (`php artisan migrate`) — `users` and UI-only tables.

At time of writing, the databases the API cares about and tracks, are as follows:

* Institution Table ("inst"): the institutions, including info about them like PDP ID if applicable, creator/creation time, etc.
* API Key Table ("apikey"): the API keys including access type, valid status (you can disable a key), etc.
* Account Table ("users"): **THIS TABLE IS (the only table) SHARED WITH THE FRONTEND**. This contains enduser email/password, access types, inst if applicable etc. Because this table is shared with the frontend, any changes to the table definition should be reflected in both the ORM handling the table in the frontend _and_ the backend. Note that intentionally, there's no way to create new users from the backend. This is because the backend only uses API keys to authenticate and also lacks some reqiured fields such as team id generation that is required by Laravel to use the user table. The frontend can directly create users in the table which the backend will be able to read.
* Account Table ("users"): **SHARED WITH THE FRONTEND** (DDL owned by Laravel in edvise-ui). This contains enduser email/password, access types, inst if applicable etc. Because this table is shared with the frontend, any changes to the table definition should be reflected in both the ORM handling the table in the frontend _and_ the backend. See [docs/DB_SCHEMA_CONTRACT.md](../../docs/DB_SCHEMA_CONTRACT.md). Note that intentionally, there's no way to create new users from the backend. This is because the backend only uses API keys to authenticate and also lacks some reqiured fields such as team id generation that is required by Laravel to use the user table. The frontend can directly create users in the table which the backend will be able to read.
* Account History Table ("account_history"): audit trail of certain events undertaken by users. TODO: interactions with this table largely remain unimplemented.
* File Table ("file"): tracks files
* Batch Table ("batch"): tracks batches
* Model Table ("model"): tracks models
* Job Table ("job"): tracks Databricks jobs, storing the per-run unique job_run_id. Status of the job is also partially tracked here. Note that failed jobs are currently indistinguishable from incomplete jobs.
* Job Table ("job"): **SHARED WITH THE FRONTEND** (DDL owned by API after Phase 1). Tracks Databricks inference runs (`run_id`, `model_run_id`). API writes; UI reads for some pages until Phase 1.5. See [docs/DB_SCHEMA_CONTRACT.md](../../docs/DB_SCHEMA_CONTRACT.md).

NOTE: naming convention is to use a singular descriptor for the table name, however, the "users" table has to follow Laravel's table naming convention, which has the users table called "users".

Expand Down
6 changes: 6 additions & 0 deletions src/webapp/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,12 @@ class AccountTable(Base):
access_type: Mapped[str | None] = mapped_column(
String(VAR_CHAR_LENGTH), nullable=True
)
accepted_terms: Mapped[bool] = mapped_column(
Boolean, nullable=False, default=False, server_default="0"
)
invite_validated: Mapped[bool] = mapped_column(
Boolean, nullable=False, default=False, server_default="0"
)
# profile_photo_path : Mapped[dict[str, str]] = mapped_column(String(VAR_CHAR_LENGTH), nullable=True)
created_at = mapped_column(
DateTime(timezone=True),
Expand Down
Loading
Loading