Files
sciagent/docs/research-project-cockpit-feature.md
T
Thinh Lam 688fac73e9
CI/CD / backend (push) Failing after 2m8s
CI/CD / frontend (push) Failing after 1m40s
CI/CD / deploy (push) Has been skipped
sciagent code + Gitea Actions CI/CD
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 09:38:30 +07:00

94 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Research-project proposals + PI cockpit (frontend_investigator)
_Added 2026-06-14. A new parallel domain (independent of the sáng-kiến / initiative flow): a Principal
Investigator submits a research-project proposal (Thuyết minh đề tài, Mẫu III.06-TM.ĐTUD), an admin
approves it, then the PI manages the project via a "cockpit" (members / datasets / models / assets /
tiến độ + audit)._
## Shape (one-line)
PI app `frontend_investigator` → fill proposal → submit → **admin approves in `frontend_admin`**
cockpit unlocks (auto-seeded from the proposal) → PI manages entities. Owner + admin authz; every
mutation writes an append-only audit row.
## Backend (`be0/`)
- **Migration `016_research_projects.sql`** — 7 tables:
- `research_projects` — the aggregate root. **The proposal row IS the project** across its lifecycle
(`status`: `draft → submitted → approved | rejected`). `content JSONB` holds the whole proposal
form blob; a few **extracted scalars** (`title/level/pi_name/period_months/budget_total`) are columns
for listing/overview. `code` (mã số) is null until approved. Review fields: `reviewed_by/at/note`.
- `research_project_{members,datasets,models,assets,milestones}` — normalized child entity tables
(FK → project, `ON DELETE CASCADE`), columns mirror the cockpit artifact's fields. Milestones use
`start_period`/`end_period` (the JSON keys `start`/`end` map to them).
- `research_project_audit` — append-only (BIGSERIAL; actor + action + subject + detail).
- Registered in **4 places** (the stage-together rule): both compose files' `docker-entrypoint-initdb.d`
+ `apply_initiative_migrations.py` (`_needs_research_projects_migration` + guarded block) + the `.sql`.
- **`src/research_routes.py`** — router mounted at `/api/v1/research/*` (now the **5th** extracted router:
auth · admin-audit · admin-user-profiles · templates · **research**). Endpoints:
- Proposals: `POST/GET /projects`, `GET/PUT/DELETE /projects/{id}`, `POST /projects/{id}/submit`,
`POST /projects/{id}/approve` (admin), `POST /projects/{id}/reject` (admin), `GET /projects/{id}/audit`.
- Cockpit entities: `GET/POST /projects/{id}/entities/{entity}`, `PUT/DELETE …/{item_id}`,
`GET /projects/{id}/cockpit` (one-shot bundle: project + 5 entity lists + recent audit).
- **Generic config-driven CRUD** (`_ENTITY_CONFIG`): one whitelist `_apply_fields` maps json_key→column
for all 5 entities (no column injection — sensitive cols absent from configs).
- **Authz (v1) = owner OR platform admin.** Read hides others' rows with 404; submit/update are
owner-only + draft-only; approve/reject are admin-only + submitted-only; entity mutations require the
project to be **approved** (409 otherwise = "cockpit unlocks on approval"). `credential_version` is
enforced by the global `main.py` middleware (routers use bare `decode_access_token_user_id`).
- **Seed-on-approve** — `_seed_cockpit_from_proposal` populates members (chủ nhiệm + thư ký +
thành viên) + milestones (tiến độ) from `content` so the cockpit opens "according to the proposal".
Best-effort + idempotent.
- Auth model: a **PI = any authenticated user who owns a project** (no new system role). Admin via the
existing allow-list mechanism (`AUTH_ADMIN_EMAILS` env, else the built-in default list in `auth_api.py`).
## Frontend
- **`frontend_investigator/`** — new Vite/React/TS app cloned from `frontend_user` (same design system by
construction: `index.css` + tailwind tokens, Inter/Merriweather, shared shadcn primitives, auth flow,
dashboard shell). Dev `:8083`→container `5175` (static IP `10.5.0.7`); prod `FE_INV_PORT`. Pages:
`ProjectsListPage` (Đề tài của tôi), `ProposalFormPage` (schema-driven form), `CockpitPage`.
Monorepo wiring: root `workspaces` += `frontend_investigator`; **all 4 Dockerfiles** COPY the new
manifest (npm ci/install resolves); both compose files mount it into the FE services.
- **`shared/src/lib/researchApi.ts`** — typed client for `/api/v1/research/*` (+ barrel export); used by
both `frontend_investigator` and `frontend_admin`.
- **Proposal form** (`components/proposal/`): `proposalSchema.ts` (the Mẫu III.06-TM.ĐTUD schema +
buildInitial/shouldShow/collectMissing) + `ProposalFormFields.tsx` (renderer on shared shadcn) +
`ProposalFormPage.tsx` (load/save-draft/submit; read-only for non-draft; approved → cockpit).
- **Cockpit** (`components/cockpit/`): `cockpitConfig.ts` (ENTITIES field defs + status tones) +
`CockpitWidgets.tsx` (Badge/Stat/Bar/EntityCard/EntityDrawer) + `CockpitPage.tsx` (bundle via TanStack
Query, tabbed Overview/entities/audit, CRUD mutations + cache invalidation, owner/admin-gated).
- **`frontend_admin`** — `ResearchReviewPage.tsx` (route `/research`, nav "Thẩm định đề tài", admin-gated):
submitted-proposals queue + detail dialog (content read-only) + approve (assign mã số) / reject (note).
## Tests
- `be0/tests/test_research_routes.py`**7 tests** (3 pure-unit scalar extraction + 4 DB integration:
lifecycle, reject/authz, entity CRUD+coercion+seeding+approved-gate+audit+bundle, malformed-content
seeding). Run: `docker exec be0 sh -lc 'cd /app && python -m unittest tests.test_research_routes'`.
- FE: `npm run typecheck` (×4 workspaces clean) + `npm run build -w frontend_investigator|frontend_admin`.
## Gotchas hit this session (blameless RCA)
1. **Migration COMMENT with semicolons** (016 v1) → the naive SQL splitter in
`apply_initiative_migrations.py` splits on `;` even inside string literals → `unterminated quoted
string`, the COMMENT failed (tables still created, since they commit before it). _5-why:_ the COMMENT
body contained `; ` separators. _Fix:_ rewrote without semicolons (periods) + stripped accents.
_Guard:_ already a documented rule (CLAUDE.md + reviewer memory) — keep COMMENT bodies semicolon/accent-free.
2. **Seeder crash on malformed content** (P2, reviewer-caught P1) — `for x in c.get(key) or []` only
guards falsy; a truthy non-list (PI-controlled JSONB, e.g. `{"tienDoThucHien": 5}`) → `TypeError`
500 on approve. _Fix (double-fault: code + test):_ `v = c.get(key); rows = v if isinstance(v, list) else []`
+ regression test `test_seeding_survives_malformed_content`.
## Commits (local `main`)
`63e8bec` P1 schema+lifecycle · `c10ce1b` P2 entity APIs+seeding · `b561db4` P3 scaffold ·
`d3e7daf` P4 form · `8d186a6` P5 cockpit · `93cf6bf` P6 admin review · `b80cb64` admin allow-list.
## Eval / run
- Admin account provisioned for E2E: `ththinh@ump.edu.vn` (added to the default policy-admin list).
- Dev: PI app `localhost:5175`, admin `localhost:5174` (host vite servers, proxy `/api`→be0:4402).
- **Not deployed** (push ≠ deploy; no CI). Prod needs `scripts/deploy-prod.sh` + `FE_INV_PORT` set, and
a deploy of the new `frontend_investigator` service.
## Follow-ups (not done)
- Bundle code-split (frontend_investigator ~1.6 MB — shared-deps bloat, same as frontend_user).
- Richer admin proposal view (currently a humanized key:value dump — moving `proposalSchema` into
`@ump/shared` would let the admin reuse the labeled renderer).
- Per-member RBAC (v2) — link cockpit members to real accounts + enforce the 5 project roles server-side.
- A `file-replace`/attachments story if proposals need evidence files (none yet).