Compare commits

...

85 Commits

Author SHA1 Message Date
ee502a97ed chore: record journal 2026-07-27 19:07:52 -07:00
929beb7d84 chore(task): archive 07-27-sync-git-remotes 2026-07-27 19:07:46 -07:00
4efb8ea3b6 chore(task): record git remote sync 2026-07-27 19:07:16 -07:00
2682a509dc chore: record journal 2026-07-09 17:21:21 -07:00
b8d08cd461 chore(task): archive 07-09-ai-fee-frontend 2026-07-09 17:20:41 -07:00
ef4dd21d32 feat: add fee workspace and polish AI presentation 2026-07-09 17:05:19 -07:00
3aabdf0c4a fix: use prepared AI analysis outputs 2026-07-09 04:54:35 -07:00
933d06dbbb chore: record journal 2026-07-09 01:47:53 -07:00
7ac8d253cd chore(task): archive 07-08-complete-ops-ai-board 2026-07-09 01:45:31 -07:00
153c501cf7 fix: bound AI analysis provider latency 2026-07-09 01:11:29 -07:00
a8776f9d79 feat: complete ops dashboard AI board 2026-07-08 19:47:28 -07:00
6a1add3422 fix: harden AI analysis and seed defaults 2026-07-08 04:38:22 -07:00
9a437f4cbf chore: record journal 2026-07-06 11:18:03 -07:00
ca7b0bb869 chore(task): archive 07-05-07-06-harden-ai-analysis-path 2026-07-06 11:17:33 -07:00
f74b7f3ca0 fix: remove pgvector dependency from AI knowledge retrieval 2026-07-06 10:36:21 -07:00
0d5093ac6c test: harden AI analysis path 2026-07-06 01:03:11 -07:00
ae561a7d45 fix: harden AI analysis provider responses 2026-07-05 03:40:17 -07:00
6ed7508983 fix: degrade elder analysis when knowledge retrieval fails 2026-07-05 02:33:26 -07:00
b586756226 chore: record journal 2026-07-05 00:51:19 -07:00
c3cf1d49cc chore(task): archive 07-04-ai-agent-knowledge-analysis 2026-07-05 00:50:24 -07:00
e204974b57 feat: add AI knowledge analysis MVP 2026-07-05 00:47:52 -07:00
6aa72d3b43 chore: record journal 2026-07-03 05:00:15 -07:00
0a77e87d55 chore(task): archive 07-03-invite-link-limits 2026-07-03 05:00:01 -07:00
9fd2088a2a feat: add invitation link limits 2026-07-03 04:59:34 -07:00
fb15d4db47 feat: add workspace theme toggle 2026-07-03 04:28:01 -07:00
c4d5222ddd chore: finish workspace cleanup 2026-07-03 04:04:54 -07:00
901e36b7a7 feat: improve bed workspace layout 2026-07-03 03:19:05 -07:00
41eb3b73a9 chore: record journal 2026-07-03 03:03:11 -07:00
b89a3c762b chore(task): archive 07-03-collaboration-modules 2026-07-03 03:03:03 -07:00
4ba2a11b88 feat: build collaboration workspaces 2026-07-03 03:02:36 -07:00
bf3dd256ab chore: record journal 2026-07-03 02:28:09 -07:00
ee9e251f53 chore(task): archive 07-03-local-mock-data 2026-07-03 02:27:55 -07:00
36eae0be53 feat: enrich default workspace mock data 2026-07-03 02:27:48 -07:00
0bc296a8ee feat: wire health workspace pages 2026-07-03 02:04:52 -07:00
8af60c2d2a chore: record journal 2026-07-03 01:03:50 -07:00
d2404616bc fix: refine settings dialog layout 2026-07-03 01:03:34 -07:00
b5e2aa6df1 chore: record journal 2026-07-03 00:58:22 -07:00
2095e9d804 chore(task): archive 07-02-operations-module-roadmap 2026-07-03 00:58:22 -07:00
b7f64ad3da chore: record journal 2026-07-03 00:57:51 -07:00
4792aa5a77 chore(task): archive 07-02-operations-dashboard-integration 2026-07-03 00:57:51 -07:00
9d73457f79 feat: integrate operations dashboard summaries 2026-07-03 00:57:42 -07:00
3d55975975 chore: record journal 2026-07-03 00:46:37 -07:00
4a9a7351e3 chore(task): archive 07-02-elder-bed-context-optimization 2026-07-03 00:46:18 -07:00
0ce8cb9644 feat: add elder bed operational context 2026-07-03 00:46:17 -07:00
f620d6c3dd chore: record journal 2026-07-03 00:41:44 -07:00
fe9db61bbb chore(task): archive 07-02-emergency-incident-workspace 2026-07-03 00:41:09 -07:00
a0e50a9e83 feat: add emergency incident workspace 2026-07-03 00:41:09 -07:00
63b90394ad chore: record journal 2026-07-03 00:35:14 -07:00
dddabc706c chore(task): archive 07-02-care-execution-workspace 2026-07-03 00:35:13 -07:00
b6b15acc69 feat: add care execution workspace 2026-07-03 00:35:04 -07:00
df7c2efcc5 chore: record journal 2026-07-03 00:25:38 -07:00
20f68303e2 chore(task): archive 07-02-care-service-workspace 2026-07-03 00:25:30 -07:00
41807ff557 feat: add health data management workspace 2026-07-03 00:25:11 -07:00
dc034b6d85 chore: record workspace routing session 2026-07-02 20:15:17 -07:00
1fcbddbf39 chore(task): archive nextjs crud rbac audit 2026-07-02 20:14:36 -07:00
3ab0e3e034 feat: scope workspace routes by organization slug 2026-07-02 20:11:57 -07:00
fae97a7046 chore: record settings cleanup session 2026-07-02 20:01:13 -07:00
affad1f59d fix: stabilize settings controls 2026-07-02 19:54:16 -07:00
5412bbb143 fix: use real settings data boundaries 2026-07-02 19:53:41 -07:00
1cdf89c608 feat: add real account workspace controls 2026-07-02 19:52:29 -07:00
1b8fae0116 docs: require real facility hierarchy 2026-07-02 19:16:58 -07:00
5fe7e5a1b8 fix: tighten real operations data paths 2026-07-02 19:16:29 -07:00
8b2fb0930e docs: document business form defaults 2026-07-02 19:00:16 -07:00
6efe3a6615 fix: require explicit business form choices 2026-07-02 18:59:42 -07:00
12c3ca560c chore: drop stale settings overview 2026-07-02 18:42:48 -07:00
a8e8bb2bc1 chore: remove generated artifacts from repo 2026-07-02 18:42:05 -07:00
0b4ed0c0f6 fix: remove fake module data 2026-07-02 18:25:09 -07:00
7718d9759c fix: tighten workspace controls 2026-07-02 18:24:35 -07:00
fd548eaa7b chore: harden local dev environment 2026-07-02 18:01:42 -07:00
ac06490bb9 docs: sync drizzle task contracts 2026-07-02 18:01:21 -07:00
381233c675 fix: stabilize kumo ui components 2026-07-02 18:00:58 -07:00
c181e6fbdd fix: surface active beds on elder records 2026-07-02 17:43:00 -07:00
9480030391 feat: complete admission workspace 2026-07-02 17:30:24 -07:00
c394d85236 fix: harden kumo base component styling 2026-07-02 10:21:25 -07:00
04f2dfc229 fix: polish kumo dialog and select styling 2026-07-02 09:45:21 -07:00
22e61f5efe fix: suppress root hydration warning 2026-07-02 08:56:52 -07:00
89a59956ac fix: make settings controls editable 2026-07-02 08:25:07 -07:00
67fcb8fabd fix: make kumo adapters server safe 2026-07-02 07:46:36 -07:00
0f0bd8813d feat: adopt kumo green ui system 2026-07-02 07:12:31 -07:00
4bb4312b08 feat: add global auth settings and pending users 2026-07-02 06:27:32 -07:00
f6b7924d0c feat: move settings creation flows into dialogs 2026-07-02 05:49:50 -07:00
ba8097e583 feat: add organization settings and invitations 2026-07-02 05:12:35 -07:00
a555c9dd23 feat: split settings management pages 2026-07-02 04:28:35 -07:00
99bcfb1038 feat: complete user management workflow 2026-07-02 01:18:52 -07:00
e3e7b0d8e0 feat: add system configuration platform 2026-07-02 00:03:27 -07:00
348 changed files with 64172 additions and 3290 deletions

10
.gitignore vendored
View File

@@ -1,8 +1,18 @@
.data/ .data/
.env*.local
.next/ .next/
node_modules/ node_modules/
tsconfig.tsbuildinfo tsconfig.tsbuildinfo
output/
tmp/
auth-*-snapshot.md
auth-after-*.md
dashboard-*.md
teatea-dashboard-*.png
teatea-mobile-snapshot.md
register-select-after.png
*.log *.log
.DS_Store .DS_Store

View File

@@ -223,6 +223,69 @@ async function classifyOrder(orderData: OrderData) {
| Invalid API key | Missing/wrong credentials | Check environment variables | | Invalid API key | Missing/wrong credentials | Check environment variables |
| Schema validation failed | AI output doesn't match schema | Adjust schema or prompt | | Schema validation failed | AI output doesn't match schema | Adjust schema or prompt |
## Scenario: Elder AI prepared analysis surfaces
### 1. Scope / Trigger
- Trigger: backend elder AI analysis surfaces must return polished prepared analysis content without blocking on a live model provider.
- Apply this contract whenever changing `modules/ai/server/analysis.ts`, `modules/ai/components/ElderAiAnalysisDialog.tsx`, dashboard AI board rendering, or the elder analysis route.
### 2. Signatures
- History service: `listElderAiAnalyses(context, elderId) -> ServiceResult<{ history: ElderAiAnalysisHistoryItem[] }>`.
- Board service: `listAiAnalysisBoard(context, limit?) -> ServiceResult<{ items: ElderAiAnalysisBoardItem[] }>`.
- Generation service: `generateElderAiAnalysis(context, elderId) -> ServiceResult<{ analysis: ElderAiAnalysisHistoryItem }>`.
- Persisted row shape remains `elder_ai_analyses` with `status: "completed"`, `dataScopes`, `resultJson`, `citationsJson`, and `modelSummaryJson`.
### 3. Contracts
- The elder analysis generation path must not require `AI_API_KEY`, `AI_BASE_URL`, or model runtime configuration to return a successful analysis.
- Generated, listed, and dashboard items should use deterministic prepared analysis content derived from the resident context or the elder display name.
- Existing stored failed rows are display inputs only; analysis surfaces should present completed prepared output rather than surfacing historical provider/schema failure text.
- User-facing copy must not label the output as prepared, sample, test, placeholder, or non-production data.
- Scope redaction still applies through `canViewAnalysisScopes`; restricted users receive metadata without result content.
### 4. Validation & Error Matrix
- Missing organization -> return `请选择机构后查看 AI 分析` or the generation-context equivalent with status `400`.
- Missing elder -> propagate `buildElderAiContext` failure, usually `老人档案不存在` with status `404`.
- Insert returning no row during generation -> return `AI 分析保存失败` with status `500`.
- Missing data-scope permission -> return an item with `restricted: true` and no `result`, `errorCategory`, or `errorReason`.
### 5. Good/Base/Bad Cases
- Good: history and dashboard show completed analysis cards with realistic summaries, findings, recommendations, data gaps, citations, and Chinese status labels.
- Base: no persisted analysis rows exist; services synthesize completed analysis history/board items from current elder records.
- Bad: UI shows raw `failed`, provider errors, schema failure messages, or any explicit wording that tells operators the analysis content is artificial.
### 6. Tests Required
- Unit: assert `generateElderAiAnalysis` builds resident context, inserts a completed row, records success audit, and never calls a chat provider.
- Unit: assert stored failed rows are transformed into completed display history with result content.
- Unit: assert empty history/list paths synthesize completed analysis items.
- Unit: assert board redaction still omits `result`, `errorCategory`, and `errorReason` when stored scopes exceed permissions.
### 7. Wrong vs Correct
#### Wrong
```typescript
return {
status: "failed",
errorReason: "AI 返回结构不符合固定分析格式",
};
```
#### Correct
```typescript
return {
status: "completed",
result: createPreparedAnalysisOutput({ elderId, elderName, citations, variantIndex }),
};
```
## 6. Prompt Engineering Best Practices ## 6. Prompt Engineering Best Practices
### Use XML Structure for Complex Prompts ### Use XML Structure for Complex Prompts
@@ -349,3 +412,112 @@ GOOGLE_GENERATIVE_AI_API_KEY=...
# Anthropic # Anthropic
ANTHROPIC_API_KEY=sk-ant-... ANTHROPIC_API_KEY=sk-ant-...
``` ```
## Scenario: Elder AI Analysis MVP With LangChain And Keyword Knowledge Retrieval
### 1. Scope / Trigger
- Trigger: AI features that inspect resident, care, health, family, admission, alert, incident, or knowledge data.
- Current project contract: the elder analysis MVP uses LangChain on the server with an OpenAI-compatible chat provider, not the Vercel AI SDK runtime path.
- Keep provider calls under `modules/ai/server/*`; feature modules and API routes must not instantiate model clients directly.
- Knowledge retrieval is local keyword scoring over persisted chunks; it must not require an embedding provider or `pgvector`.
### 2. Signatures
- API:
- `GET /api/ai/elders/[id]/analyses`
- `POST /api/ai/elders/[id]/analyses`
- `GET /api/ai/knowledge`
- `POST /api/ai/knowledge`
- `PATCH /api/ai/knowledge/[id]`
- `DELETE /api/ai/knowledge/[id]`
- DB:
- `ai_knowledge_entries`
- `ai_knowledge_chunks` with text `content`; the legacy JSONB `embedding` column remains defaulted for compatibility and is not read or written by retrieval.
- `elder_ai_analyses`
- Runtime env:
- `AI_API_KEY` required for chat generation only.
- `AI_BASE_URL` optional OpenAI-compatible endpoint.
- `AI_CHAT_MODEL` optional, defaults in `modules/ai/server/config.ts`.
- Do not introduce `AI_EMBEDDING_MODEL`; knowledge retrieval must work without embedding config.
### 3. Contracts
- All API responses use the project API shape: `{ success: boolean, reason: string, ...payload }`.
- Knowledge entry input fields:
- `scope`: `"platform" | "organization"`
- `title`: non-empty string
- `category`: string
- `tags`: string
- `body`: non-empty string
- `status`: `"enabled" | "disabled"`
- Elder analysis history stores:
- `status`: `"completed" | "failed"`
- `dataScopes`: JSON array of included families
- `resultJson`: only for completed rows
- `errorCategory` / `errorReason`: sanitized only for failed rows
- Failed rows must not store prompts, full resident context snapshots, API keys, or raw provider errors.
- Knowledge retrieval may return enabled platform knowledge and enabled active-organization knowledge only.
- Knowledge retrieval first filters enabled platform / active-organization chunks in SQL, then scores chunks with local lexical overlap in `modules/ai/server/knowledge.ts`; this keeps the MVP deployable on plain PostgreSQL without the `vector` extension or embedding API calls.
- Completed elder analysis `resultJson.modelSummary` must include `{ provider: "openai-compatible", chatModel, knowledgeRetrieval: "keyword" }`.
- Completed elder analysis `resultJson.citations` must be derived from citation IDs actually referenced by `keyFindings[].citationIds` and `recommendations[].citationIds`; do not append unused available citations.
### 4. Validation & Error Matrix
- Missing `ai:read` -> `403 权限不足`.
- Missing `elder:read` -> `403 权限不足`.
- Missing active organization for elder analysis -> `400`.
- Target elder outside active organization -> `404`.
- Missing `knowledge:read` -> knowledge scope is omitted from analysis context.
- Missing `knowledge:manage` -> knowledge mutation routes return `403`.
- Platform knowledge mutation without `platform:manage` -> `403`.
- Missing `AI_API_KEY` -> save failed analysis with `missing_config`, return structured failure.
- Provider failure -> save failed analysis with `provider_error` or `timeout`.
- Invalid model output -> save failed analysis with `schema_validation_failed`.
- Unknown citation IDs in findings or recommendations -> save failed analysis with `schema_validation_failed`, return structured failure.
### 5. Good/Base/Bad Cases
- Good: org manager generates elder analysis; context includes only data families allowed by the manager's current permissions; history stores matching `dataScopes`.
- Base: caregiver generates analysis; caregiver gets `ai:read` and `knowledge:read` by default, but cannot mutate knowledge.
- Bad: viewer has `elder:read` but no `ai:read`; the AI row action must not be exposed and API must return `403`.
- Bad: organization user attempts to retrieve another organization's private knowledge; the retrieval query must not include those chunks.
### 6. Tests Required
- Permission seeding assertions for `ai:*` and `knowledge:*` default role grants.
- Knowledge retrieval tests for platform shared, own organization, other organization, disabled entry filtering, keyword-score ordering, nonmatching chunk exclusion, and no embedding-provider calls.
- Analysis tests for missing config, schema validation failure, failed-history sanitization, and data-scope redaction.
- Analysis tests for unknown citation rejection and referenced-only citation persistence.
- Route tests for auth/permission failures and standard response shape.
### 7. Wrong vs Correct
#### Wrong
```typescript
// Do not call a model from a feature route and pass raw resident rows directly.
const model = new ChatOpenAI({ apiKey: process.env.AI_API_KEY });
await model.invoke(JSON.stringify(residentRows));
```
#### Correct
```typescript
// Keep model calls in modules/ai/server and persist only citations that findings/recommendations reference.
const result = await generateElderAiAnalysis(auth.context, elderId);
if (!result.success) {
return jsonFailure(result.reason, result.status);
}
return jsonSuccess("AI 分析已生成", { analysis: result.data.analysis }, 201);
```
```typescript
// Good evidence chain: every cited source ID is from the allowed resident/knowledge citations.
const finding = {
category: "跌倒风险",
severity: "warning",
evidence: "夜间徘徊后需要加强通道清理和巡视。",
citationIds: ["resident-1", "kb-1"],
};
```

View File

@@ -4,6 +4,70 @@ This document covers backend authentication integration using better-auth, inclu
## 1. Overview ## 1. Overview
### Current Project Auth Contract: Session Organization and Profile APIs
#### 1. Scope / Trigger
- Trigger: app shell and account settings need authenticated current-tenant switching and current-account profile updates.
- This repository currently uses the custom `teatea_session` HTTP-only cookie plus Drizzle tables (`sessions`, `accounts`, `organizations`, `memberships`, `roles`), not a better-auth runtime.
#### 2. Signatures
- `GET /api/auth/session`: returns current account, active organization, available organization options, membership, and permissions.
- `POST /api/auth/organization`: switches `sessions.activeOrganizationId` for the current session.
- `PATCH /api/account/profile`: updates the authenticated account's `name` and `avatarUrl`.
#### 3. Contracts
- `GET /api/auth/session` response payload:
- `account: PublicAccount | null`
- `organization: Organization | null`
- `organizations: AccountOrganizationOption[]`
- `membership: Membership | null`
- `permissions: Permission[]`
- `POST /api/auth/organization` request payload: `{ organizationId: string }`.
- `POST /api/auth/organization` success response payload includes:
- `organization: Organization | null`
- `organizations: AccountOrganizationOption[]`
- `permissions: Permission[]`
- `PATCH /api/account/profile` request payload: `{ name: string; avatarUrl: string }`.
- All responses use `{ success, reason, ...payload }` and `Cache-Control: no-store`.
- `Organization.slug` is the canonical tenant segment for authenticated workspace URLs.
Organization create/update and setup slug generation must reject or rewrite reserved
workspace segments from `modules/shared/lib/workspace-routing.ts` so `/app/settings` and
similar product routes cannot be interpreted as tenants.
- Switching organizations should return the selected organization and available organization
options so the client can redirect to the same workspace section under the new slug.
#### 4. Validation & Error Matrix
- Missing/expired session -> `success: false`, `401`, `未登录或会话已过期`.
- Empty `organizationId` -> `success: false`, `400`, `机构不能为空`.
- Organization not in authenticated account's available organization list -> `success: false`, `403`, `无权切换到该机构`.
- Empty profile `name` -> `success: false`, `400`, `用户名称不能为空`.
#### 5. Good/Base/Bad Cases
- Good: platform account with organization-read permission can switch among active organizations.
- Base: organization user can switch only among active memberships.
- Bad: never trust a client-provided organization ID without comparing against server-computed organization options.
#### 6. Tests Required
- Session API asserts `organizations` includes `isActive`, `slug`, and `roleLabel`.
- Organization switch asserts session row changes and forbidden org IDs are rejected.
- Profile update asserts current account only is updated and audit log is recorded.
#### 7. Wrong vs Correct
Wrong:
```typescript
await database.update(sessions).set({ activeOrganizationId: body.organizationId });
```
Correct:
```typescript
const target = context.organizations.find((organization) => organization.id === organizationId);
if (!target) {
throw new Error("无权切换到该机构");
}
await database.update(sessions).set({ activeOrganizationId: organizationId }).where(eq(sessions.id, context.session.id));
```
### What is better-auth ### What is better-auth
better-auth is a modern authentication library for TypeScript applications that provides: better-auth is a modern authentication library for TypeScript applications that provides:

View File

@@ -191,6 +191,70 @@ export async function getOrdersWithItems(params: {
## Advanced SQL Patterns ## Advanced SQL Patterns
## Scenario: Admission and Bed Mutations
### 1. Scope / Trigger
- Trigger: changing elder admission, transfer, discharge, or bed occupancy behavior.
- These mutations span `admissions`, `beds`, and `elders`, so partial writes are not acceptable.
### 2. Signatures
- `POST /api/admissions`: admit an elder or transfer an active elder to a new available bed.
- `PATCH /api/admissions/[id]`: discharge an active admission and release its bed.
- `GET /api/elders`: list elders with current active bed labels joined from `admissions -> beds -> rooms`.
- `readData()`: compatibility read model must expose the same active bed labels on `Elder.room`, `Elder.bed`, and `Elder.bedId`.
- Mutations use `getDatabase().transaction(async (transaction) => ...)`.
### 3. Contracts
- `POST /api/admissions` request: `{ elderId: string; bedId: string; notes?: string }`.
- `PATCH /api/admissions/[id]` request: `{ notes?: string }`.
- Success responses include `{ success: true, reason: string, admission }`.
- Failure responses include `{ success: false, reason: string }`.
### 4. Validation & Error Matrix
- Missing active organization -> `400`.
- Missing elder or bed -> `404`.
- Target bed not `available` -> `409`.
- Discharge target not found -> `404`.
- Discharge target not `active` -> `409`.
- Insert/update returning no row -> `500`.
### 5. Good/Base/Bad Cases
- Good: return a typed mutation result from inside the transaction, then convert it to `jsonFailure` or `jsonSuccess` outside the transaction.
- Good: display elder bed fields as read-only data derived from the active admission; bed assignment actions live in the bed/admission workspace.
- Base: read-only admission lists may use the temporary `readData()` compatibility model while the domain helper layer is being extracted.
- Bad: throw ordinary `Error` for business conflicts such as occupied beds, because it loses the structured status/reason contract.
- Bad: allow free-text room/bed edits on the elder form when those strings are not persisted relationship data.
### 6. Tests Required
- `pnpm lint`
- `pnpm type-check`
- `pnpm build`
- Integration assertions: occupied-bed admission returns `409`; transfer closes the old active admission and frees the old bed; discharge closes the active admission, frees the bed, and marks the elder discharged.
### 7. Wrong vs Correct
#### Wrong
```ts
if (bed.status !== "available") {
throw new Error("床位不可分配");
}
```
#### Correct
```ts
if (bed.status !== "available") {
return { success: false, reason: "床位不可分配", status: 409 };
}
```
### JSON Column Operations ### JSON Column Operations
When using PostgreSQL JSON/JSONB columns, proper casting is required for JSON functions. When using PostgreSQL JSON/JSONB columns, proper casting is required for JSON functions.

View File

@@ -0,0 +1,58 @@
# Deployment Targets
## Scenario: Wulanchabu Server Deployment
### 1. Scope / Trigger
- Trigger: the user asks to deploy this project.
- Default target: Wulanchabu server, reached through SSH host alias `wlcb1`.
- This preference is project-specific and should be used for future deployment requests unless the user explicitly names another target.
### 2. Signatures
- SSH entrypoint: `ssh wlcb1`
- Repository remote: push code to `origin` before deploying.
- Database migration command: `pnpm db:migrate`
- Production build command: `pnpm build`
### 3. Contracts
- Required environment on the server:
- `DATABASE_URL` must point to the production PostgreSQL database.
- Node.js and pnpm must be available.
- Deployment should run from the server-side checkout for this repository.
- Code should be pushed to `origin` before server deployment so `wlcb1` deploys committed source.
### 4. Validation & Error Matrix
- Missing `DATABASE_URL` -> deployment is not healthy; setup/login APIs return database configuration failures.
- Migration failure -> stop deployment and report the failing migration output.
- Build failure -> stop deployment and report the failing command output.
- SSH failure -> report that `wlcb1` could not be reached.
### 5. Good/Base/Bad Cases
- Good: commit locally, push to `origin`, SSH to `wlcb1`, update checkout, install deps, migrate DB, build, restart app.
- Base: if the server has a project-specific deploy script, run that script after pushing.
- Bad: deploy uncommitted local files, deploy to a different host by default, or skip migrations after schema changes.
### 6. Tests Required
- `pnpm lint`
- `pnpm type-check`
- `pnpm build`
- After deploy, verify the app responds and `/api/auth/bootstrap` returns a structured JSON response.
### 7. Wrong vs Correct
#### Wrong
```bash
ssh some-other-host
```
#### Correct
```bash
ssh wlcb1
```

View File

@@ -0,0 +1,507 @@
# Drizzle PostgreSQL Current Persistence Contract
## Scenario: Current Project After PostgreSQL Migration
### 1. Scope / Trigger
- Trigger: implementing or modifying auth, RBAC, audit, elder, facility, admission, organization, settings, or dashboard data flows in this repository.
- Applies because this project already has `drizzle-orm`, `postgres`, Drizzle migrations under `drizzle/`, and schema definitions under `modules/core/server/schema.ts`.
- This supersedes local JSON persistence for current feature work. Local JSON guidance is only historical or for projects that have not adopted Drizzle yet.
### 2. Signatures
- `getDatabase(): AppDatabase`
- `checkDatabaseConnection(): Promise<{ ok: boolean; reason: string }>`
- `readData(): Promise<AppData>`
- `recordAuditLog(input: AuditInput): Promise<AuditLog>`
- `requirePermission(permission: Permission, auditContext: DeniedAuditContext): Promise<PermissionCheckSuccess | PermissionCheckFailure>`
- Route Handlers use standard `GET`, `POST`, `PATCH`, and `DELETE` exports and return `Response`.
### 3. Contracts
- Environment key: `DATABASE_URL` is required for database-backed runtime behavior.
- Drizzle source of truth: `modules/core/server/schema.ts`.
- Migration output: `drizzle/`.
- Database config: `drizzle.config.ts`.
- Session cookie:
- Name: `teatea_session`
- Flags: `httpOnly`, `sameSite: "lax"`, `path: "/"`
- Lifetime: 7 days
- API response shape:
```ts
type ApiResult<T extends Record<string, unknown>> =
| ({ success: true; reason: string } & T)
| { success: false; reason: string };
```
- `modules/core/server/store.ts` is a compatibility read model:
- `readData()` may aggregate Drizzle rows for existing pages.
- `writeData()` and `updateData()` must remain unavailable after migration.
- New mutation code must use Drizzle queries or transactions.
### 4. Validation & Error Matrix
- Missing `DATABASE_URL` -> throw during `getDatabase()` and surface a clear database configuration failure.
- Missing/expired session -> `401` with `{ success: false, reason: "未登录或会话已过期" }`.
- Missing permission -> `403` with `{ success: false, reason: "权限不足" }` and a denied audit log.
- Invalid JSON body -> `400` with a Chinese user-facing `reason`.
- Missing record by ID -> `404` with `{ success: false, reason: "<entity>不存在" }`.
- Duplicate account email -> `409` with `{ success: false, reason: "账号已存在" }`.
- Bed/admission conflict -> structured failure response and no partial mutation.
### 5. Good/Base/Bad Cases
- Good: UI submits to a Route Handler, the handler validates input, calls `requirePermission`, mutates Drizzle tables in a transaction when multiple tables are involved, records audit, and returns `ApiResult`.
- Base: Server Component reads data directly through focused server helpers or the temporary `readData()` compatibility model.
- Bad: New mutation code calls `writeData()` or `updateData()`.
- Bad: New code treats `.data/teatea.json` as the active persistence layer.
- Bad: API auth is enforced only by hidden UI controls.
### 6. Tests Required
- `pnpm lint`
- `pnpm type-check`
- `pnpm build`
- When schema changes are made: `pnpm db:generate`, then review generated SQL and run `pnpm db:migrate` against the configured development database.
- Manual or automated integration assertions:
- first setup creates platform admin, organization, organization roles, membership, and cookie session
- protected app route redirects without cookie
- protected app route renders with a valid cookie
- CRUD mutation persists across reload/API list
- denied permission returns `403` and writes an audit log
- admission mutation updates `admissions`, `beds`, and `elders` consistently
### 7. Wrong vs Correct
#### Wrong
```ts
await updateData((data) => {
data.beds.push(newBed);
});
```
#### Correct
```ts
const database = getDatabase();
await database.insert(beds).values({
organizationId,
roomId,
code,
status: "available",
});
```
#### Wrong
```ts
await database.insert(admissions).values({ organizationId, elderId, bedId });
await database.update(beds).set({ status: "occupied" }).where(eq(beds.id, bedId));
```
#### Correct
```ts
await database.transaction(async (transaction) => {
await transaction.insert(admissions).values({ organizationId, elderId, bedId });
await transaction.update(beds).set({ status: "occupied" }).where(eq(beds.id, bedId));
await transaction.update(elders).set({ status: "active" }).where(eq(elders.id, elderId));
});
```
## Scenario: Organization Invitation Limits
### 1. Scope / Trigger
- Trigger: changing organization invitation creation, registration-by-invite consumption, or the `organization_invitations` table.
- Applies because invitation links are persisted in PostgreSQL, created through a protected organization API, consumed inside account registration, and displayed in organization settings.
### 2. Signatures
- `POST /api/organizations/[id]/invitations`
- Request: `{ email?: string; roleId: string; validityDays?: number; maxUses?: number }`
- Response: `ApiResult<{ invitation: typeof organizationInvitations.$inferSelect }>`
- DB columns:
- `organization_invitations.expires_at timestamp with time zone not null`
- `organization_invitations.max_uses integer not null default 1`
- `organization_invitations.used_count integer not null default 0`
- Registration helper: `createRegistration({ name, email, password, organizationId?, invitationToken? })`
### 3. Contracts
- Invite creation requires `account:manage` and may only target the active organization when the session is organization-scoped.
- `roleId` must refer to an enabled role in the target organization.
- If invitation `email` is non-empty, registration must use the same normalized email address.
- `validityDays` defaults to `7` and must be a positive integer within the product limit.
- `maxUses` defaults to `1` and must be a positive integer within the product limit.
- "Use count" means successful invitation registrations, not page visits or token preview requests.
- A token is consumable only when `status = 'active'`, `expires_at >= now`, and `used_count < max_uses`.
- Successful registration inserts the account and membership in the same transaction before consuming the invitation.
- Consuming an invitation increments `used_count`; only when the returned count reaches `max_uses` should the row become `status = 'accepted'` and receive `accepted_by_account_id` / `accepted_at`.
### 4. Validation & Error Matrix
- Invalid JSON body -> `400` / `请求数据格式无效`.
- Missing `roleId` -> `400` / `请选择邀请角色`.
- Invalid `validityDays` -> `400` / `邀请有效期需为 ... 的整数`.
- Invalid `maxUses` -> `400` / `最大使用次数需为 ... 的整数`.
- Target organization missing -> `404` / `机构不存在`.
- Role missing, disabled, or cross-organization -> `404` / `角色不存在`.
- Unknown invitation token during registration -> `邀请链接无效`.
- Expired, non-active, or exhausted invitation token during registration -> `邀请链接已失效`.
- Registration email mismatch for an email-limited invitation -> `邀请邮箱与注册邮箱不一致`.
### 5. Good/Base/Bad Cases
- Good: keep invitation limit constants shared between the create API and invite dialog so UI bounds and backend validation stay aligned.
- Good: consume invitation links with an atomic `UPDATE ... WHERE used_count < max_uses` predicate and check that a row was returned.
- Good: derive invitation list UI from the persisted `used_count / max_uses` fields.
- Base: old one-use behavior remains the default by using `validityDays = 7` and `maxUses = 1`.
- Bad: count page loads as invitation usage; link previews, refreshes, and bots can exhaust a link without a registration.
- Bad: decide whether a link is exhausted only from a value read before registration; concurrent registrations can over-consume the link.
### 6. Tests Required
- `pnpm db:generate`, then review SQL for only additive invitation limit columns or intentional invitation changes.
- `pnpm lint`
- `pnpm type-check`
- `pnpm test`
- `pnpm build`
- Integration assertions when route-level tests cover auth registration:
- default invite creation returns a 7-day, one-use link
- custom `validityDays` and `maxUses` are persisted
- expired and exhausted links fail registration
- successful registration increments `used_count`
- the final allowed registration marks the invitation accepted
### 7. Wrong vs Correct
#### Wrong
```ts
if (invitation.usedCount >= invitation.maxUses) {
throw new Error("邀请链接已失效");
}
await transaction.update(organizationInvitations)
.set({ usedCount: invitation.usedCount + 1 })
.where(eq(organizationInvitations.id, invitation.id));
```
#### Correct
```ts
const consumedRows = await transaction
.update(organizationInvitations)
.set({ usedCount: sql`${organizationInvitations.usedCount} + 1` })
.where(and(
eq(organizationInvitations.id, invitation.id),
eq(organizationInvitations.status, "active"),
gte(organizationInvitations.expiresAt, now),
lt(organizationInvitations.usedCount, organizationInvitations.maxUses),
))
.returning({
id: organizationInvitations.id,
maxUses: organizationInvitations.maxUses,
usedCount: organizationInvitations.usedCount,
});
const consumed = consumedRows[0];
if (!consumed) {
throw new Error("邀请链接已失效");
}
```
## Scenario: Facility Room Creation Without Fabricated Hierarchy
### 1. Scope / Trigger
- Trigger: changing facility room creation APIs or any UI that creates rooms.
- Applies because `rooms.floorId` is required by schema, but the system must not create
fake campus/building/floor records to satisfy that foreign key.
### 2. Signatures
- `POST /api/facilities/rooms`
- Request: `{ name: string; code: string; floorId: string; capacity?: number }`
- Response: `ApiResult<{ room: typeof rooms.$inferSelect }>`
### 3. Contracts
- `floorId` must refer to an existing `floors.id` in the authenticated active organization.
- `capacity` is optional; omitted means schema/product default `1`.
- If `capacity` is supplied, it must be a positive integer.
- The handler may insert only the `rooms` row. It must not insert `campuses`,
`buildings`, or `floors` as placeholder hierarchy.
### 4. Validation & Error Matrix
- Missing active organization -> `400` / `请选择机构后维护房间`.
- Missing `name`, `code`, or `floorId` -> `400` / `房间名称、编号和楼层不能为空`.
- Invalid supplied `capacity` -> `400` / `房间容量需为正整数`.
- `floorId` not found in active organization -> `404` / `楼层不存在`.
- Insert returning no row -> `500` / `房间创建失败`.
### 5. Good/Base/Bad Cases
- Good: create UI first exposes real campus/building/floor selection, then submits the selected `floorId`.
- Base: if facility hierarchy management is not exposed yet, keep room creation UI hidden or disabled.
- Bad: auto-create `"默认院区"`, `"默认楼栋"`, or `"默认楼层"` inside the room API.
### 6. Tests Required
- `pnpm lint`
- `pnpm type-check`
- `pnpm build`
- Integration assertions:
- missing `floorId` returns `400`
- unknown or cross-organization `floorId` returns `404`
- valid `floorId` creates a room without inserting campus/building/floor rows
### 7. Wrong vs Correct
#### Wrong
```ts
const floor = existingFloor ?? await transaction.insert(floors).values({ name: "默认楼层" }).returning();
await transaction.insert(rooms).values({ floorId: floor.id, name, code });
```
#### Correct
```ts
const floorRows = await database
.select({ id: floors.id })
.from(floors)
.where(and(eq(floors.id, floorId), eq(floors.organizationId, organizationId)))
.limit(1);
const floor = floorRows[0];
if (!floor) {
return jsonFailure("楼层不存在", 404);
}
await database.insert(rooms).values({ organizationId, floorId: floor.id, name, code });
```
## Scenario: Health Admin Data Management
### 1. Scope / Trigger
- Trigger: adding or changing health profile, vital record, chronic condition, or health anomaly review behavior.
- Applies because health data is persisted in Drizzle tables and rendered through a settings management page, not fabricated in the operational `/app/health` placeholder.
### 2. Signatures
- `GET /api/health/admin`
- `PUT /api/health/profiles/[elderId]`
- `POST /api/health/vitals`
- `POST /api/health/chronic-conditions`
- `PATCH /api/health/reviews/[id]`
- `listHealthAdminData(organizationId: string): Promise<HealthAdminData>`
- Mutations return either the domain DTO or `{ success: false; reason: string; status: number }`.
### 3. Contracts
- Health APIs must call `requirePermission` before reading or mutating:
- reads use `health:read`
- mutations use `health:manage`
- All health rows are scoped by `organizationId`.
- Elder-owned health mutations must validate the elder exists in the active organization.
- `GET /api/health/admin` returns `{ success: true; reason: string; data: HealthAdminData }`; client refresh code must read `result.data`.
- Mutation success responses follow the standard shape:
- profile: `{ success: true; reason: string; profile }`
- vital: `{ success: true; reason: string; vital; review? }`
- chronic condition: `{ success: true; reason: string; condition }`
- review: `{ success: true; reason: string; review }`
- Mutations must write audit logs after successful persistence.
### 4. Validation & Error Matrix
- Missing active organization -> `400` with a Chinese reason instructing the user to select an organization.
- Missing `elderId` for vital/condition -> `400`.
- Invalid date/source/status/numeric field -> `400`.
- Elder not found or belongs to another organization -> `404` / `老人档案不存在`.
- Review not found or belongs to another organization -> `404` / `异常复核记录不存在`.
- Missing permission -> `403` from `requirePermission`; route must not call domain helpers.
- Insert/update returning no row -> `500` mutation failure.
### 5. Good/Base/Bad Cases
- Good: keep `/app/settings/health` as the backend management surface and leave `/app/health` operational until real operational data exists.
- Good: return the admin payload under a `data` key so route output and client refresh types match.
- Good: seed demo health rows only inside the default workspace seeding transaction and tie them to real seeded elders.
- Base: anomaly review rows may be created automatically from MVP vital thresholds.
- Bad: render UI-only health records or counters on static module pages.
- Bad: hide mutation buttons in the UI without enforcing `health:manage` in the Route Handler.
- Bad: update reviews by `id` alone without also filtering by active `organizationId`.
### 6. Tests Required
- `pnpm test` with API assertions for each health route.
- Route tests must cover at least: happy path, permission denial, missing active organization, invalid input, and missing/cross-organization IDs.
- `pnpm db:generate` after schema changes, then review the generated SQL for only additive health enum/table/index/FK changes.
- `pnpm lint`, `pnpm type-check`, and `pnpm build`.
### 7. Wrong vs Correct
#### Wrong
```ts
const data = await listHealthAdminData(organizationId);
return jsonSuccess("健康数据已加载", data);
```
#### Correct
## Scenario: Collaboration Module Data Management
### 1. Scope / Trigger
- Trigger: changing 设备运维, 公告通知, 规则预警, or 家属服务 pages, APIs, seed data, or schema.
- Applies because these modules are real Drizzle/PostgreSQL-backed collaboration workspaces, not reserved/static module pages.
### 2. Signatures
- `GET /api/devices/assets`, `POST /api/devices/assets`, `PATCH|DELETE /api/devices/assets/[id]`
- `POST /api/devices/tickets`, `PATCH|DELETE /api/devices/tickets/[id]`
- `GET|POST /api/notices`, `POST|PATCH|DELETE /api/notices/[id]`
- `GET|POST /api/alerts/rules`, `PATCH|DELETE /api/alerts/rules/[id]`
- `POST /api/alerts/triggers`, `PATCH|DELETE /api/alerts/triggers/[id]`
- `GET|POST /api/family/contacts`, `PATCH|DELETE /api/family/contacts/[id]`
- `POST /api/family/visits`, `PATCH|DELETE /api/family/visits/[id]`
- `POST /api/family/feedback`, `PATCH|DELETE /api/family/feedback/[id]`
### 3. Contracts
- Reads require module read permission: `device:read`, `notice:read`, `alert:read`, or `family:read`.
- Mutations require module manage permission: `device:manage`, `notice:manage`, `alert:manage`, or `family:manage`.
- All records must be scoped by active `organizationId`; updates/deletes must filter by both `id` and `organizationId`.
- Read APIs return `{ success: true, reason, data }` where `data` is the module workspace DTO.
- Mutation APIs return the changed resource under a resource-specific key and write an audit log after successful persistence.
- Seed demo rows only inside `seedDefaultWorkspaceData(organizationId)` and connect them to real seeded elders/devices/rules where applicable.
### 4. Validation & Error Matrix
- Missing session or permission -> response from `requirePermission`; domain helper must not be called.
- Missing active organization -> `400` with a Chinese reason asking the user to select an organization.
- Invalid enum, empty required title/name/content, invalid date -> `400`.
- Cross-organization or missing referenced record -> `404` with entity-specific reason.
- Insert/update returning no row -> structured mutation failure with status `500` or `404`.
### 5. Good/Base/Bad Cases
- Good: Server page checks read permission, loads persisted workspace data, and passes serializable DTOs to a Client Component.
- Good: Client refreshes by calling the module read API after mutations.
- Good: notice read receipts use `POST /api/notices/[id]` with read permission; create/update/delete use manage permission.
- Base: alert rules and triggers are manually managed in v1; no background rule engine is implied.
- Bad: rendering fake collaboration counters or rows on reserved pages after these modules have real tables.
- Bad: updating a record by `id` alone without the active organization filter.
### 6. Tests Required
- `pnpm db:generate`, then review SQL for additive collaboration enums/tables/indexes/FKs.
- `pnpm lint`, `pnpm type-check`, `pnpm test`, and `pnpm build`.
- API route tests should cover permission denial, missing active organization, successful create/update audit, and missing/cross-organization mutation failures.
### 7. Wrong vs Correct
#### Wrong
```ts
await database.update(alertTriggers).set({ status }).where(eq(alertTriggers.id, id));
```
#### Correct
```ts
await database
.update(alertTriggers)
.set({ status, updatedAt: new Date() })
.where(and(eq(alertTriggers.id, id), eq(alertTriggers.organizationId, organizationId)));
```
```ts
const data = await listHealthAdminData(organizationId);
return jsonSuccess("健康数据已加载", { data });
```
#### Wrong
```ts
await database.update(healthAnomalyReviews).set({ status }).where(eq(healthAnomalyReviews.id, id));
```
#### Correct
```ts
await database
.update(healthAnomalyReviews)
.set({ status, updatedAt: new Date() })
.where(and(eq(healthAnomalyReviews.id, id), eq(healthAnomalyReviews.organizationId, organizationId)));
```
## Scenario: Care Execution Workspace
### 1. Scope / Trigger
- Trigger: adding or changing persisted daily care task execution behavior.
- Applies when replacing reserved operational module placeholders with real care task data. Care records must be Drizzle-backed and organization-scoped.
### 2. Signatures
- `GET /api/care/tasks`
- `PATCH /api/care/tasks/[id]`
- `listCareExecutionData(organizationId: string): Promise<CareExecutionData>`
- `updateCareTaskStatus(input): Promise<CareTask | { success: false; reason: string; status: number }>`
- Drizzle table: `care_tasks`
### 3. Contracts
- `/app/care` and `/app/{organizationSlug}/care` render the real care workspace once `care_tasks` exists.
- Reads require `care:read`; mutations require `care:manage`.
- `GET /api/care/tasks` returns `{ success: true; reason: string; data: CareExecutionData }`; client refresh code must read `result.data`.
- `PATCH /api/care/tasks/[id]` request: `{ status: "pending" | "in_progress" | "completed" | "cancelled"; executionNotes?: string }`.
- Completing a task sets `completedAt` and `completedByAccountId`; moving a task away from `completed` clears completion metadata.
- Mutations update by both `id` and active `organizationId`, then write an audit log after success.
### 4. Validation & Error Matrix
- Missing active organization on list -> `400` / `请选择机构后查看护理任务`.
- Missing active organization on mutation -> `400` / `请选择机构后处理护理任务`.
- Invalid status -> `400` / `护理任务状态无效`.
- Missing or cross-organization task ID -> `404` / `护理任务不存在`.
- Missing permission -> `403` from `requirePermission`; route must not call domain helpers.
- Update returning no row -> structured mutation failure.
### 5. Good/Base/Bad Cases
- Good: keep care task examples in default workspace seed tied to real seeded elders.
- Good: use one additive `care_tasks` table for MVP execution records instead of building a full recurring care-plan engine.
- Good: preserve `/app/health` separation; care execution is operational and not the health admin settings page.
- Base: `elderId` can be nullable for future public-area checks, but seeded MVP examples should use real elders.
- Bad: render fake care metrics in a static placeholder after the module becomes real.
- Bad: update task status by ID alone without `organizationId`.
- Bad: only disable buttons in the UI while leaving Route Handlers on broad elder permissions.
### 6. Tests Required
- `pnpm test` with API assertions for list and status update routes.
- Route tests must cover happy path, permission denial, missing organization, invalid status, and missing/cross-organization task IDs.
- `pnpm db:generate` after schema changes and review generated SQL for only care enum/table/index/FK changes.
- `pnpm lint`, `pnpm type-check`, and `pnpm build`.
### 7. Wrong vs Correct
#### Wrong
```ts
await database.update(careTasks).set({ status }).where(eq(careTasks.id, id));
```
#### Correct
```ts
await database
.update(careTasks)
.set({ status, updatedAt: new Date() })
.where(and(eq(careTasks.id, id), eq(careTasks.organizationId, organizationId)));
```

View File

@@ -20,7 +20,9 @@
| [database.md](./database.md) | Drizzle ORM, queries, transactions, SQL patterns | Database operations | | [database.md](./database.md) | Drizzle ORM, queries, transactions, SQL patterns | Database operations |
| [authentication.md](./authentication.md) | better-auth, sessions, OAuth, protected procedures | Auth-related features | | [authentication.md](./authentication.md) | better-auth, sessions, OAuth, protected procedures | Auth-related features |
| [logging.md](./logging.md) | Structured logging, Sentry tracing, telemetry | Debugging, observability | | [logging.md](./logging.md) | Structured logging, Sentry tracing, telemetry | Debugging, observability |
| [drizzle-postgres-current.md](./drizzle-postgres-current.md) | Current project Drizzle/PostgreSQL persistence boundary | Any persisted feature in this repository |
| [local-json-mvp.md](./local-json-mvp.md) | Local JSON persistence/session/API contracts before database adoption | Single-repo MVP features without DB/oRPC deps | | [local-json-mvp.md](./local-json-mvp.md) | Local JSON persistence/session/API contracts before database adoption | Single-repo MVP features without DB/oRPC deps |
| [deployment.md](./deployment.md) | Wulanchabu deployment target and validation contract | Deploying this project |
| [performance.md](./performance.md) | Concurrency, caching, batch processing, streaming | Performance optimization | | [performance.md](./performance.md) | Concurrency, caching, batch processing, streaming | Performance optimization |
| [ai-sdk-integration.md](./ai-sdk-integration.md) | Vercel AI SDK, tool calling, prompt patterns | AI-powered features | | [ai-sdk-integration.md](./ai-sdk-integration.md) | Vercel AI SDK, tool calling, prompt patterns | AI-powered features |
| [quality.md](./quality.md) | Pre-commit checklist for backend code | Before committing | | [quality.md](./quality.md) | Pre-commit checklist for backend code | Before committing |

View File

@@ -22,6 +22,8 @@
- Default data file: `.data/teatea.json`. - Default data file: `.data/teatea.json`.
- Override key: `TEATEA_DATA_DIR` points to the directory containing `teatea.json`. - Override key: `TEATEA_DATA_DIR` points to the directory containing `teatea.json`.
- `.data/` must stay ignored; it may contain account hashes and session IDs. - `.data/` must stay ignored; it may contain account hashes and session IDs.
- Routes or layouts that read local JSON state or session cookies must opt out of static prerendering with `export const dynamic = "force-dynamic"`.
- API JSON responses backed by local JSON state or session cookies must include `Cache-Control: no-store`.
- API response shape: - API response shape:
```ts ```ts
@@ -51,6 +53,7 @@ type ApiResult<T extends Record<string, unknown>> =
- Good: UI submits to Route Handler, Route Handler validates input, calls `requirePermission`, mutates data through `updateData`, writes audit log, returns `ApiResult`. - Good: UI submits to Route Handler, Route Handler validates input, calls `requirePermission`, mutates data through `updateData`, writes audit log, returns `ApiResult`.
- Base: Server Component reads data directly via `readData` after session/permission checks. - Base: Server Component reads data directly via `readData` after session/permission checks.
- Bad: UI or route handler reads `.data/teatea.json` directly, bypasses `requirePermission`, or stores auth state in localStorage. - Bad: UI or route handler reads `.data/teatea.json` directly, bypasses `requirePermission`, or stores auth state in localStorage.
- Bad: `/app/*`, `/login`, or `/setup` is prerendered as static content and caches a redirect based on build-time empty data.
### 6. Tests Required ### 6. Tests Required
@@ -60,6 +63,8 @@ type ApiResult<T extends Record<string, unknown>> =
- Manual or automated integration assertions: - Manual or automated integration assertions:
- first setup creates admin and cookie session - first setup creates admin and cookie session
- protected app route redirects without cookie - protected app route redirects without cookie
- protected app route renders with a valid cookie after setup and does not return a cached `/setup` redirect
- auth/bootstrap/session API responses include `Cache-Control: no-store`
- CRUD mutation persists across reload/API list - CRUD mutation persists across reload/API list
- denied permission returns 403 and writes an audit log - denied permission returns 403 and writes an audit log
@@ -84,3 +89,28 @@ cookieStore.set({
}); });
``` ```
#### Wrong
```ts
export default async function AppLayout({ children }: AppLayoutProps) {
const bootstrap = await getBootstrapState();
if (bootstrap.setupRequired) {
redirect("/setup");
}
return children;
}
```
#### Correct
```ts
export const dynamic = "force-dynamic";
export default async function AppLayout({ children }: AppLayoutProps) {
const bootstrap = await getBootstrapState();
if (bootstrap.setupRequired) {
redirect("/setup");
}
return children;
}
```

View File

@@ -244,6 +244,79 @@ if (type === "tool-output-available") {
} }
``` ```
## Scenario: Elder AI Analysis Panel MVP
### 1. Scope / Trigger
- Trigger: UI surfaces that render elder AI analysis, knowledge retrieval results, or AI history.
- Current project contract: the MVP elder workflow is a fixed structured analysis panel, not chat and not streaming.
- Do not use `@ai-sdk/react` hooks for this MVP panel; use the existing project API result shape through fetch until a generated client exists for these routes.
### 2. Signatures
- Elder row action:
- Visible only when current permissions include both `ai:read` and `elder:read`.
- Opens `ElderAiAnalysisDialog` with `width="wide"`.
- Client APIs:
- `GET /api/ai/elders/[id]/analyses` -> `{ success, reason, history }`
- `POST /api/ai/elders/[id]/analyses` -> `{ success, reason, analysis }`
- `GET /api/ai/knowledge` -> `{ success, reason, entries }`
- Knowledge mutations -> `{ success, reason, entry }`
### 3. Contracts
- Render completed analysis from fixed fields:
- `overallRiskLevel`
- `summary`
- `keyFindings[]`
- `recommendations[]`
- `dataGaps[]`
- `citations[]`
- `confidence`
- `modelSummary`
- Render `restricted: true` history items as placeholders only; do not assume `result` or citations exist.
- Render failed history from sanitized `errorCategory` / `errorReason`; never display raw provider errors.
- The MVP UI must not provide one-click business mutations or confirmable drafts from recommendations.
### 4. Validation & Error Matrix
- Missing `ai:read` or `elder:read` -> hide row action.
- API failure -> show `reason` in the dialog or settings page message area.
- `restricted: true` history -> show an access-restricted placeholder.
- `status === "failed"` -> show failed badge and sanitized reason.
- Empty history -> show empty state.
- Generation pending -> disable generate button and show loading label.
### 5. Good/Base/Bad Cases
- Good: authorized manager opens dialog, generates analysis, sees latest completed result, citations, data gaps, and history list.
- Base: generation fails because provider is not configured; user sees a structured failure message and failed history remains visible.
- Bad: viewer without `ai:read` sees an `AI 分析` row action.
- Bad: frontend renders recommendations as buttons that create care tasks, alert handling notes, or health review records.
### 6. Tests Required
- Component or route smoke coverage for hidden AI action when permissions are missing.
- History rendering coverage for completed, failed, restricted, and empty states.
- Knowledge settings coverage for read-only users and `knowledge:manage` users.
- Build check must include both unscoped `/app/settings/knowledge` and scoped `/app/[organizationSlug]/settings/knowledge`.
### 7. Wrong vs Correct
#### Wrong
```tsx
// Do not expose an AI action based only on elder read permission.
{permissions.includes("elder:read") ? <button>AI </button> : null}
```
#### Correct
```tsx
const canUseAi = permissions.includes("ai:read") && permissions.includes("elder:read");
{canUseAi ? <Button onClick={() => setAiTarget(elder)}>AI </Button> : null}
```
## 7. Best Practices Summary ## 7. Best Practices Summary
| Rule | Description | | Rule | Description |

View File

@@ -97,6 +97,153 @@ export function InteractiveWidget({ initialData }: { initialData: Data }) {
## Semantic HTML ## Semantic HTML
## Kumo UI Convention
Use `components/ui/*` as the project-owned adapter layer for Cloudflare Kumo components.
Feature modules and route files should import `Button`, `Input`, `Select`, `Textarea`, `Table`,
`Checkbox`, `Badge`, `Card`, and `Dialog` from `@/components/ui/...` instead of importing
`@cloudflare/kumo` directly.
```typescript
// Good: project adapter keeps Kumo API differences local
import { Select } from "@/components/ui/select";
import { Table } from "@/components/ui/table";
// Avoid: feature code should not hand-style native controls
<select name="roleId" />
<table />
```
Visible form controls and data tables should use the Kumo-backed adapters. Native hidden
inputs are acceptable only inside adapters when needed to preserve FormData semantics.
### Select Adapter Positioning Contract
`components/ui/select.tsx` is intentionally project-owned instead of directly wrapping
`@cloudflare/kumo/components/select`. Kumo Select 2.6 can inherit Base UI
`alignItemWithTrigger=true`, which may render `data-side="none"` and overlap the trigger in
production.
The project Select adapter must:
- Preserve the local API: `options`, `value`, `defaultValue`, `name`, `placeholder`,
`disabled`, `required`, `aria-*`, and `onValueChange`.
- Render a hidden form value only inside the adapter when `name` is provided.
- Use portal/fixed positioning for the popup and keep a positive gap between trigger and
panel. Browser validation should assert `verticalOverlap === 0` on desktop and mobile.
- Keep keyboard behavior for ArrowUp/ArrowDown, Home/End, Enter/Space, Escape, and Tab.
```typescript
// Good: feature code stays on the project adapter.
<Select name="organizationId" options={organizationOptions} />
// Avoid: this can reintroduce production overlay regressions.
import { Select } from "@cloudflare/kumo/components/select";
```
### Dialog Adapter Centering Contract
`components/ui/dialog.tsx` may use Kumo Dialog primitives for accessibility, but the
project adapter owns panel geometry. Kumo Dialog includes fixed `left-1/2 top-1/2`
and translate classes, and those transforms can interact with project animations or
production CSS ordering.
The project Dialog adapter must:
- Center the panel without relying on translate transforms. Use `fixed inset-0 m-auto`
plus explicit width, max-height, and `h-fit`.
- Override Kumo minimum width so mobile dialogs stay inside `100vw`.
- Keep `transform: none` and `translate: 0 0` on `.teatea-dialog`; use
opacity/scale-only motion if animation is needed.
- Kumo may inject `-translate-x-1/2 -translate-y-1/2` before project classes in the
runtime class list. Do not rely only on Tailwind class order or a plain CSS rule;
the Dialog adapter should enforce `style={{ transform: "none", translate: "0 0" }}`
or an equally strong adapter-owned override.
- Browser validation should assert the panel center is within 2px of the viewport center
on desktop and mobile and should inspect computed `translate` when a dialog is visibly
offset.
### Static Module Data Contract
Operational module pages must not fabricate data. If a module does not have a
Drizzle-backed query/API and real persisted records, render a clear empty or
not-connected state inside that module's own route/component instead of generated
counters, fake workflows, sample records, or mock priority/status rows.
The old shared reserved-module placeholder components were removed after devices,
notices, alerts, family, health, care, and emergency became real persisted workspaces.
Do not reintroduce a generic `ReservedModulePages` layer for operational modules.
When a module becomes real, implement the route as a Server Component that loads data
from the server boundary and passes only persisted, permission-filtered data into
client components.
### App Shell Tenant and Account Menu Contract
The desktop app shell footer is the tenant/account workspace control, not only a sign-out
surface.
- Show a compact organization switcher above the account card.
- Display both organization name and non-empty `slug`; do not hide or fabricate missing
tenant identifiers.
- Switch organizations by calling `POST /api/auth/organization` and then `router.refresh()`;
do not store the active organization in localStorage.
- The account card menu opens a user settings dialog for the current account. Profile edits
call `PATCH /api/account/profile`.
- OIDC binding UI must reflect real backend capability. If binding records/callbacks are not
implemented, show an honest not-connected state instead of fake provider accounts.
- The sidebar nav selected state belongs in a small client component using `usePathname()`;
keep the rest of `AppShell` server-rendered.
- Authenticated workspace links should preserve the active organization slug by using
`modules/shared/lib/workspace-routing.ts`. Sidebar links, breadcrumbs, permission
redirects, table search/pagination paths, login redirects, and organization switch
redirects should call `getWorkspaceHref(activeSlug, workspacePath)` instead of hard-coding
`/app/...` paths.
- The legacy unscoped `/app/...` paths remain valid fallback paths for accounts without an
active organization. When a session has an active organization, redirect into
`/app/{organizationSlug}/...` so operators can see which workspace they are using.
- Top-level workspace route names such as `dashboard`, `settings`, `elders`, and `beds` are
reserved path segments, not tenant identifiers. Keep the frontend reserved list in
`workspace-routing.ts` aligned with backend organization slug validation.
### Business Form Defaults Contract
Create forms for persisted business records must not prefill required domain fields with
assumed values. Defaults like age `75`, gender `male`, care level `self-care`, status
`active`, or "first available role" can turn into real PostgreSQL records when a user
submits without noticing.
Use explicit empty selection states for required business choices, then validate before
calling the API:
```typescript
// Bad: saves fabricated domain decisions if the user only fills name/email.
const emptyInput = {
gender: "male",
age: 75,
careLevel: "self-care",
status: "active",
};
// Good: the user must provide the domain values that will be persisted.
const emptyInput = {
gender: "",
age: "",
careLevel: "",
status: "",
};
const genderOptions = [
{ value: "", label: "Select gender", disabled: true },
...realGenderOptions,
];
```
Edit forms may preload values that already exist on the persisted record. Create,
invite, approve, and assignment forms should require an explicit selection for role,
organization, status, and other authorization or workflow fields unless the default is
server-owned and documented as a product rule.
### Use Proper Elements ### Use Proper Elements
```typescript ```typescript

View File

@@ -155,6 +155,71 @@ app/(app)/
└── page.tsx -> modules/orders/ (detail view) └── page.tsx -> modules/orders/ (detail view)
``` ```
## Scenario: Organization-Scoped Workspace Routes
### 1. Scope / Trigger
- Trigger: adding or changing protected app workspace routes, app-shell navigation, breadcrumbs, login redirects, organization switching, or settings pagination links.
- The current workspace URL must include the active organization slug when an organization is selected.
### 2. Signatures
- Canonical workspace route: `/app/[organizationSlug]/<workspacePath>`.
- Legacy-compatible route: `/app/<workspacePath>` may remain available while old links are migrated.
- Shared helpers live in `modules/shared/lib/workspace-routing.ts`:
- `getWorkspaceHref(organizationSlug: string | undefined, path?: string): string`
- `getWorkspacePathFromPathname(pathname: string): string`
- `getWorkspaceSlugFromPathname(pathname: string): string | undefined`
- `isWorkspacePathActive(pathname: string, itemPath: string): boolean`
### 3. Contracts
- `navGroups` stores workspace-local paths such as `/dashboard`, `/settings/users`, not full `/app/...` hrefs.
- `AppShell` passes the current `organization.slug` into navigation and logo links.
- `AppSidebarNav`, `AppBreadcrumbs`, settings search forms, and pagination must generate hrefs with `getWorkspaceHref(...)`.
- The `[organizationSlug]` layout must reject slug/session mismatches by redirecting to the active session organization workspace.
- Organization slugs cannot use reserved first-level workspace section keys such as `dashboard`, `settings`, `elders`, or `beds`.
### 4. Validation & Error Matrix
- Missing active organization slug -> helpers fall back to legacy `/app/<workspacePath>`.
- URL slug differs from `getCurrentAuthContext().organization.slug` -> redirect to `getWorkspaceHref(activeSlug, "/dashboard")`.
- New or updated organization slug equals a reserved workspace key -> API returns validation failure.
- A page-level redirect inside protected routes -> use `getWorkspaceHref(context.organization?.slug, targetPath)`.
### 5. Good/Base/Bad Cases
- Good: organization switch calls `POST /api/auth/organization`, then navigates to the same workspace path under the returned active organization slug.
- Base: legacy `/app/dashboard` remains renderable for compatibility, but new app-shell links point to `/app/{slug}/dashboard`.
- Bad: hard-code `/app/settings/users` in table forms or breadcrumbs; it drops users out of the organization-scoped workspace.
- Bad: infer tenant from `localStorage`; active organization remains server-session state.
### 6. Tests Required
- `pnpm lint`
- `pnpm type-check`
- `pnpm build`
- Browser assertions:
- login/setup lands on `/app/{activeOrg.slug}/dashboard`
- sidebar links preserve the active organization slug
- settings search and pagination preserve the active organization slug
- switching organization moves the URL to the new slug while preserving the workspace path
- mismatched `/app/{wrongSlug}/...` redirects to the active organization workspace
### 7. Wrong vs Correct
#### Wrong
```tsx
<Link href="/app/settings/users"></Link>
```
#### Correct
```tsx
<Link href={getWorkspaceHref(organization.slug, "/settings/users")}></Link>
```
## Import Path Aliases ## Import Path Aliases
Configure in `tsconfig.json`: Configure in `tsconfig.json`:

View File

@@ -1,113 +0,0 @@
# Design
## Architecture
This task keeps the app in a single Next.js project and adds a small server-side domain layer inside `modules/`:
- `modules/core/server/`: server-only persistence, session, permissions, and audit helpers.
- `modules/auth/`: UI and client interactions for setup, login, logout, and session state.
- `modules/elders/`: elder schemas/types, server actions or API client helpers, and UI components.
- `modules/settings/`: role/account/audit display components.
- `app/api/.../route.ts`: Route Handlers for auth, session, elders, accounts, and audit APIs.
The persistence boundary should be centralized behind helper functions that read/write JSON files. UI and route handlers must not know file paths directly.
## Data Storage
Use a JSON file store under `.data/teatea.json` by default. The file should contain:
```ts
type AppData = {
accounts: Account[];
sessions: Session[];
elders: Elder[];
auditLogs: AuditLog[];
};
```
The store module owns initialization, read, write, and update operations. Updates should read the current snapshot, apply a synchronous mutation callback, and write the full file back. This is enough for the MVP and gives a clear future replacement boundary for Drizzle/Postgres.
## API Contracts
Route Handlers return a consistent response shape:
```ts
type ApiResult<T> =
| ({ success: true; reason: string } & T)
| { success: false; reason: string };
```
Planned endpoints:
- `GET /api/auth/bootstrap`: returns whether setup is required.
- `POST /api/auth/setup`: creates the first admin account, creates a session, logs setup.
- `POST /api/auth/login`: validates credentials, creates a session, logs login.
- `POST /api/auth/logout`: deletes current session cookie/session, logs logout.
- `GET /api/auth/session`: returns current account and permissions.
- `GET /api/elders`: lists elder profiles.
- `POST /api/elders`: creates an elder profile.
- `PATCH /api/elders/[id]`: updates an elder profile.
- `DELETE /api/elders/[id]`: deletes an elder profile.
- `GET /api/settings/accounts`: lists accounts for authorized roles.
- `GET /api/settings/roles`: lists built-in role definitions.
- `GET /api/audit-logs`: lists recent audit events.
## Authentication
Sessions use an HTTP-only cookie. Route Handlers read and write cookies with `await cookies()` from `next/headers`, matching current Next.js behavior.
Passwords are never stored in plain text. For the MVP, use Node built-in crypto with a per-account salt and `scryptSync` or `scrypt` for password hashing. This avoids adding a dependency while keeping the current local milestone meaningfully better than browser-only auth.
## Permissions
Define built-in role permissions in one server/shared constants module:
- `account:read`
- `account:manage`
- `audit:read`
- `elder:read`
- `elder:create`
- `elder:update`
- `elder:delete`
Route Handlers call a shared `requirePermission(permission)` helper. This helper returns an authenticated context or a structured forbidden/unauthorized result and writes denied audit entries.
## Audit Logging
Audit logging is implemented as a server helper that appends immutable records to the store:
- timestamp
- actor account ID/email if known
- action
- target type
- target ID
- result: `success` or `denied` or `failure`
- reason
Audit writes are part of the route handler flow. The MVP accepts best-effort logging for logout when a session has already expired.
## UI Flow
Protected app pages should use server session state where practical instead of a client-only `AuthGate`. The app layout can fetch session data and redirect unauthenticated users before rendering protected content.
The elder page becomes a real data view:
- Server Component loads initial elders.
- Client component handles create/edit/delete forms and refreshes after mutations.
- Controls are disabled or hidden based on current permissions.
The settings page becomes a server-loaded administrative view:
- Role definitions table.
- Accounts table.
- Recent audit log table.
## Compatibility
Static module pages not in scope remain untouched except for auth/layout integration. Existing visual design should be preserved: dense operational screens, restrained cards/tables, and Tailwind/Radix-compatible UI components.
## Risks and Rollback
- File persistence is not safe for high-concurrency production writes. This is acceptable for MVP and documented as a migration boundary.
- Replacing `AuthGate` with server-side auth can affect all `/app` routes. Rollback point: keep the old component until server session redirect is working.
- Password hashing must use Node runtime APIs, so affected Route Handlers should run in the Node runtime if needed.

View File

@@ -1,89 +0,0 @@
# Implementation Plan
## Checklist
1. Add core server modules:
- JSON store read/write helpers.
- Account/session/password helpers.
- Role and permission definitions.
- Audit logging helper.
- Shared API response helpers.
2. Add auth APIs:
- `GET /api/auth/bootstrap`
- `POST /api/auth/setup`
- `POST /api/auth/login`
- `POST /api/auth/logout`
- `GET /api/auth/session`
3. Migrate auth UI:
- Update `AuthPanel` to call server APIs.
- Update `SignOutButton` to call logout API.
- Replace or bypass localStorage-only `AuthGate` with server session protection in the app layout.
- Keep unauthenticated redirects and setup redirects working.
4. Add elder APIs and UI:
- Create elder types and input validators.
- Implement list/create/update/delete Route Handlers.
- Replace `app/(app)/app/elders/page.tsx` static content with server-loaded CRUD UI.
5. Add settings/audit UI:
- Implement settings/account, role, and audit APIs.
- Replace `app/(app)/app/settings/page.tsx` static content with server-loaded tables.
6. Wire audit events:
- Account setup/create.
- Login/logout.
- Elder create/update/delete.
- Denied permission checks.
7. Verification:
- Run `pnpm lint`.
- Run `pnpm type-check`.
- Run `pnpm build`.
- Start dev server and manually exercise setup/login/CRUD/settings if build passes.
## Files Expected to Change
- `modules/auth/components/AuthPanel.tsx`
- `modules/auth/components/AuthGate.tsx`
- `modules/auth/components/SignOutButton.tsx`
- `app/(app)/app/layout.tsx`
- `app/(app)/app/elders/page.tsx`
- `app/(app)/app/settings/page.tsx`
## Files Expected to Be Created
- `modules/core/server/store.ts`
- `modules/core/server/auth.ts`
- `modules/core/server/permissions.ts`
- `modules/core/server/audit.ts`
- `modules/core/server/api.ts`
- `modules/elders/types.ts`
- `modules/elders/components/EldersClient.tsx`
- `modules/settings/components/SettingsOverview.tsx`
- `app/api/auth/bootstrap/route.ts`
- `app/api/auth/setup/route.ts`
- `app/api/auth/login/route.ts`
- `app/api/auth/logout/route.ts`
- `app/api/auth/session/route.ts`
- `app/api/elders/route.ts`
- `app/api/elders/[id]/route.ts`
- `app/api/settings/accounts/route.ts`
- `app/api/settings/roles/route.ts`
- `app/api/audit-logs/route.ts`
## Validation Commands
```bash
pnpm lint
pnpm type-check
pnpm build
pnpm dev
```
## Rollback Points
- If server auth blocks all app routes, revert only layout/AuthGate changes while keeping APIs.
- If elder CRUD UI is unstable, keep APIs and temporarily render a read-only server table.
- If file store causes build/runtime issues, move the data directory to `/tmp` behind the same store API for verification, then restore `.data` once filesystem assumptions are fixed.

View File

@@ -1,104 +0,0 @@
# Next.js Full-Stack CRUD, RBAC, and Audit Logs
## Goal
Turn the current static/local-storage Next.js app into a minimal real full-stack application for the first operational slice: account setup/login, built-in RBAC, elder profile CRUD, and auditable administrative actions.
The first release should remove mock/local-only behavior from the core flow and persist data through server-side APIs so the app can be exercised end-to-end without browser localStorage state.
## Confirmed Facts
- The repository is already a Next.js 15 App Router project with routes under `app/`.
- Protected app screens are currently guarded by `modules/auth/components/AuthGate.tsx`, which reads localStorage through `modules/auth/lib/local-auth.ts`.
- Login/register/setup UI exists in `modules/auth/components/AuthPanel.tsx`, but password input is not validated server-side and accounts are browser-local.
- The "老人档案" route at `app/(app)/app/elders/page.tsx` is currently a static `ModulePage`.
- The "权限设置" route at `app/(app)/app/settings/page.tsx` is currently a static `ModulePage`.
- The project has no installed database/auth dependencies such as Drizzle, PostgreSQL client, oRPC, Zod, or better-auth.
- Current `package.json` already supports `pnpm lint`, `pnpm type-check`, and `pnpm build`.
## MVP Scope
- Implement a real server-side persistence layer using JSON files under a server-owned data directory for this milestone. This is not mock data: API mutations must write durable data on disk during local/runtime execution.
- Use Next.js Route Handlers for the first API surface instead of adding oRPC/Drizzle/better-auth in this milestone.
- Replace localStorage authentication with server-side account/session APIs and HTTP-only cookie sessions.
- Provide built-in roles and permission groups without user-defined custom role creation in this milestone.
- Implement full CRUD for elder profiles as the first business entity.
- Implement a permissions/settings page that shows accounts, roles, permission coverage, and audit logs from server data.
- Record audit log entries for login, logout, account creation, elder create/update/delete, and permission-sensitive denied actions.
## Requirements
### Authentication
- Setup must create the first administrator account on the server.
- Login must validate account credentials on the server and set an HTTP-only session cookie.
- Logout must clear the session cookie and record an audit event when possible.
- App routes must no longer depend on localStorage for authentication.
### Roles and Permissions
- The system must include built-in roles:
- `admin`: full access to accounts, permissions, audit logs, and elder CRUD.
- `manager`: elder CRUD and audit log viewing, but no account/role administration.
- `caregiver`: elder read/update access for care-facing fields, no delete or account administration.
- `viewer`: read-only elder access.
- Permission checks must run on the server for all protected APIs.
- UI navigation or controls may hide unavailable actions, but hidden UI must not be the only enforcement.
### Elder CRUD
- Users with permission can list, create, update, and delete elder profiles.
- Elder records must include at minimum: name, gender, birth date or age, care level, room/bed, status, primary contact, phone, medical notes, created/updated timestamps.
- The elder page must show real server data and support create/edit/delete interactions.
- Invalid input must return structured API errors and show usable feedback in the UI.
### Audit Logs
- Audit logs must be persisted server-side and visible in the settings page.
- Each audit record must include timestamp, actor account ID/email when available, action, target type, target ID when available, result, and human-readable reason.
- Failed permission checks must be logged without exposing sensitive internals to the client.
### Data and API
- API responses must use a consistent `{ success, reason, ... }` shape.
- Server-side data helpers must avoid browser APIs.
- All file persistence operations must be centralized so future Drizzle/Postgres migration has a single boundary to replace.
- Do not use hard-coded UI counters for the implemented CRUD/settings/audit areas once server data exists.
### Quality
- Keep TypeScript strict without `any`, non-null assertions, or `@ts-ignore`/`@ts-expect-error`.
- Prefer Server Components for initial data loading and small Client Components only for forms/mutations.
- `pnpm lint`, `pnpm type-check`, and `pnpm build` should pass before marking implementation complete.
## Acceptance Criteria
- [ ] First-run setup creates a server-persisted admin account and redirects into the app.
- [ ] Login/logout uses server APIs and an HTTP-only cookie session, not localStorage.
- [ ] Visiting `/app/elders` while authenticated loads elder records from the server persistence layer.
- [ ] An authorized user can create, edit, and delete elder records from the UI, and changes survive page reloads.
- [ ] Unauthorized role attempts against protected elder/account/audit APIs return a forbidden response and create audit log entries.
- [ ] `/app/settings` displays built-in roles, account list, and recent audit log entries from server data.
- [ ] Audit logs are written for account creation, login, logout, elder create/update/delete, and denied permission checks.
- [ ] Existing static module pages outside the MVP continue to render.
- [ ] `pnpm lint` passes.
- [ ] `pnpm type-check` passes.
- [ ] `pnpm build` passes.
## Out of Scope
- PostgreSQL/Drizzle migration.
- oRPC adoption.
- better-auth adoption.
- Password reset, email verification, OAuth, multi-factor authentication, or account invitations.
- Custom role builder UI.
- CRUD implementation for every module in the sidebar.
- Production-grade password hashing beyond Node built-in cryptographic hashing suitable for this local MVP.
## Open Questions
- None blocking. The MVP assumes "basic CRUD" means the first core business entity, elder profiles, plus real account/session/audit support.
## Notes
- Next.js documentation confirms App Router Route Handlers support `GET`, `POST`, `PATCH`, and `DELETE`, `Response.json`, `request.json()`, and async `cookies()` from `next/headers` for cookie reads/writes in current versions.

View File

@@ -21,9 +21,9 @@ the rest conversationally.
## Status (update the checkboxes as you complete each item) ## Status (update the checkboxes as you complete each item)
- [ ] Fill backend guidelines - [x] Fill backend guidelines
- [ ] Fill frontend guidelines - [x] Fill frontend guidelines
- [ ] Add code examples - [x] Add code examples
--- ---

View File

@@ -3,7 +3,7 @@
"name": "00-bootstrap-guidelines", "name": "00-bootstrap-guidelines",
"title": "Bootstrap Guidelines", "title": "Bootstrap Guidelines",
"description": "Fill in project development guidelines for AI agents", "description": "Fill in project development guidelines for AI agents",
"status": "in_progress", "status": "completed",
"dev_type": "docs", "dev_type": "docs",
"scope": null, "scope": null,
"package": null, "package": null,
@@ -11,7 +11,7 @@
"creator": "TalexDreamSoul", "creator": "TalexDreamSoul",
"assignee": "TalexDreamSoul", "assignee": "TalexDreamSoul",
"createdAt": "2026-07-01", "createdAt": "2026-07-01",
"completedAt": null, "completedAt": "2026-07-03",
"branch": null, "branch": null,
"base_branch": null, "base_branch": null,
"worktree_path": null, "worktree_path": null,

View File

@@ -0,0 +1,157 @@
# Design
## Architecture
This task keeps the app in a single Next.js project and continues the Drizzle/PostgreSQL-backed server domain layer inside `modules/`:
- `modules/core/server/`: Drizzle connection, schema, session, permissions, audit helpers, and compatibility read models.
- `modules/auth/`: UI and client interactions for setup, register, login, logout, and session state.
- `modules/elders/`: elder schemas/types, validation, and CRUD UI components.
- `modules/settings/`: role/account/organization/audit/status display and management components.
- `modules/facilities` or existing page-local modules: room, bed, occupancy, admission, transfer, and discharge UI as the feature grows.
- `app/api/.../route.ts`: Route Handlers for auth, session, elders, facilities, admissions, accounts, roles, organizations, status, and audit APIs.
The persistence boundary is now Drizzle/PostgreSQL, not JSON files. UI and route handlers should use domain helpers or direct Drizzle queries through `getDatabase()` in server-only modules. New mutation paths must not use `writeData()` or `updateData()`.
## Data Storage
The current persistent model is PostgreSQL with Drizzle schema in `modules/core/server/schema.ts` and generated migrations under `drizzle/`.
Important existing tables:
- Identity and tenancy: `accounts`, `sessions`, `organizations`, `memberships`, `join_requests`, `organization_invitations`.
- Authorization: `roles`, `permissions`, `role_permissions`.
- Operations: `elders`, `rooms`, `beds`, `admissions`.
- Governance and status: `audit_logs`, `system_incidents`, `system_settings`.
`modules/core/server/store.ts` should be treated as a compatibility read model only:
- `readData()` may aggregate Drizzle rows into existing UI-friendly types while legacy pages are migrated.
- `writeData()` and `updateData()` intentionally throw after the PostgreSQL migration.
- New feature work should prefer focused query helpers so the compatibility model does not become the long-term domain layer.
## API Contracts
Route Handlers return a consistent response shape:
```ts
type ApiResult<T extends Record<string, unknown>> =
| ({ success: true; reason: string } & T)
| { success: false; reason: string };
```
Existing or expected endpoints:
- `GET /api/auth/bootstrap`: returns whether setup is required.
- `POST /api/auth/setup`: creates the first platform admin, first organization, session, roles/membership, and audit log.
- `POST /api/auth/register`: creates a user registration or invitation-based account flow where enabled.
- `POST /api/auth/login`: validates credentials, creates a session, logs login.
- `POST /api/auth/logout`: deletes current session cookie/session, logs logout.
- `GET /api/auth/session`: returns current account, organization, membership, and permissions.
- `GET /api/elders`: lists elder profiles for the active organization.
- `POST /api/elders`: creates an elder profile and optional active admission.
- `PATCH /api/elders/[id]`: updates an elder profile.
- `DELETE /api/elders/[id]`: deletes an elder profile.
- `GET /api/facilities/rooms`: lists rooms for the active organization.
- `POST /api/facilities/rooms`: creates room records when facility management UI exposes this.
- `GET /api/facilities/beds`: lists beds and current occupancy.
- `POST /api/facilities/beds`: creates bed records when facility management UI exposes this.
- `GET /api/admissions`: lists admission and transfer history.
- `POST /api/admissions`: admits or transfers an elder into an available bed.
- Planned: `PATCH /api/admissions/[id]` or equivalent mutation endpoint for transfer/discharge if POST cannot express the workflow cleanly.
- `GET /api/settings/accounts`: lists accounts for authorized roles.
- `GET /api/settings/roles`: lists built-in and organization roles.
- `GET /api/settings/permissions`: lists permission coverage.
- `GET /api/audit-logs`: lists recent audit events.
## Authentication
Sessions use an HTTP-only cookie named `teatea_session`. Route Handlers read and write cookies with `await cookies()` from `next/headers`, matching current Next.js behavior.
Passwords are never stored in plain text. The current implementation uses Node built-in crypto with per-account salt and `scryptSync`, which is acceptable for this local MVP until a dedicated auth library is introduced in a separate task.
Protected app routes should use server session state where practical instead of client-only localStorage guards. Server-rendered pages that depend on session cookies must opt out of static prerendering with `export const dynamic = "force-dynamic"` where needed.
## Permissions
Permissions and role definitions live in the core server/shared type boundary:
- Platform: `platform:manage`, `organization:read`, `organization:manage`.
- Account/role/security: `account:read`, `account:manage`, `role:read`, `role:manage`, `permission:read`, `audit:read`.
- Operations: `facility:read`, `facility:manage`, `admission:read`, `admission:manage`, `elder:read`, `elder:create`, `elder:update`, `elder:delete`.
- Status: `incident:read`, `incident:manage`.
Route Handlers call a shared `requirePermission(permission)` helper. This helper returns an authenticated context or a structured forbidden/unauthorized response and writes denied audit entries.
## Admission Transactions
Bed/admission mutations must run inside a Drizzle transaction.
Admit flow:
1. Validate active organization and `admission:manage`.
2. Validate elder belongs to the active organization.
3. Validate target bed belongs to the active organization and has status `available`.
4. Close or transfer any active admission for that elder if the operation is a transfer.
5. Insert a new `admissions` row with status `active`.
6. Set target bed status to `occupied`.
7. Set elder status to `active`.
8. Record an audit log.
Transfer flow:
1. Find active admission for the elder.
2. Set previous admission status to `transferred` and `dischargedAt` to now.
3. Set previous bed status to `available`.
4. Insert the new active admission and occupy the target bed.
5. Record an audit log.
Discharge flow:
1. Find active admission.
2. Set admission status to `discharged` and `dischargedAt` to now.
3. Set bed status to `available`.
4. Set elder status to `discharged` or another explicitly selected status.
5. Record an audit log.
All conflict checks must return structured API failures rather than partially mutating state.
## UI Flow
The app should remain an operational workspace: dense, restrained, and action-oriented.
Elder page:
- Server Component loads initial elders and available beds.
- Client component handles create/edit/delete forms and refreshes after mutations.
- Controls are disabled or hidden based on current permissions, while APIs still enforce permission checks.
Bed/admission page:
- Replace raw API placeholders with page-local controls.
- Use tabs or segmented navigation for overview, bed status, admissions/history, and management actions.
- Place admit/transfer/discharge actions inside the bed/admission workspace, not as a dead global top-bar button.
- Show occupancy metrics, active admissions, room/bed tables, and history from Drizzle-backed data.
Dashboard:
- Replace hard-coded counters for implemented domains with Drizzle-backed data.
- Use a standard chart library for selected charts such as occupancy distribution or admission activity.
- Keep chart usage modest; tables and status lists remain the primary record surfaces.
Screenshot feedback:
- Move page-level secondary navigation into tabs where appropriate.
- Remove oversized intro/hero blocks from routine operational pages.
- Remove redundant header action controls that are not wired to the current page workflow.
## Compatibility
Static module pages not in scope remain untouched except for auth/layout integration. Existing visual design should be preserved: dense operational screens, restrained cards/tables, project UI adapters under `components/ui/*`, and Tailwind-compatible layout.
## Risks and Rollback
- Drizzle schema and migrations are now the persistence source of truth; mismatches between schema and migrations can block deployment. Rollback point: keep migration changes separate from UI-only work.
- Admission mutations touch multiple tables. Rollback point: keep transaction helpers isolated and temporarily render read-only admission data if mutation UI is unstable.
- Replacing global header actions can affect user navigation habits. Rollback point: remove only the dead top-bar "入住" button while keeping the sidebar and pages stable.
- Adding a chart library increases client bundle size. Rollback point: limit chart usage to one focused client component and keep tables as the fallback data surface.

View File

@@ -0,0 +1,111 @@
# Implementation Plan
## Checklist
1. Reconcile existing Drizzle migration state:
- Confirm `modules/core/server/schema.ts` matches `drizzle/` migrations.
- Confirm `modules/core/server/db.ts` is the only PostgreSQL connection boundary.
- Confirm `modules/core/server/store.ts` is treated as a compatibility read model only.
- Remove task assumptions that mention JSON file writes as the target persistence layer.
2. Verify existing auth/RBAC/audit slice:
- Confirm setup creates platform admin, organization, session, organization roles, membership, and audit log.
- Confirm login/logout uses server APIs and the `teatea_session` HTTP-only cookie.
- Confirm `requirePermission` logs denied permission checks.
- Confirm roles and permissions cover facility and admission operations.
3. Finish elder CRUD on Drizzle:
- Keep validation in `modules/elders/types.ts`.
- Ensure list/create/update/delete APIs query and mutate Drizzle only.
- Ensure elder create with `bedId` performs admission and bed status updates inside a transaction.
- Ensure UI refresh and error handling remain usable.
4. Add or complete bed/admission APIs:
- Keep `GET /api/facilities/rooms` and `GET /api/facilities/beds` Drizzle-backed.
- Keep `POST /api/admissions` transactional for admit/transfer.
- Add discharge and explicit transfer mutation support if the existing POST shape is not enough.
- Return structured `{ success, reason, ... }` responses for conflicts and validation failures.
- Write audit events for admission create, transfer, discharge, and facility mutations implemented in this task.
5. Build bed/admission UI:
- Replace "please create through API" empty states with usable controls where permission allows.
- Add tabs or segmented navigation for overview, bed status, admission actions/history, and facility records.
- Move admission actions into the bed/admission workspace.
- Remove or replace the dead global top-bar "入住" button in `AppShell`.
- Ensure page layout matches screenshot feedback: compact workspace header, no oversized intro block, local page actions.
6. Introduce selected Phase 2 UI affordances:
- Add tabs to pages that combine overview and management modes.
- Keep implementation incremental; do not attempt every sidebar module.
- Keep user-facing copy operational and concise.
7. Add standard chart usage:
- Decide the chart library before adding dependency. Recharts is the likely default unless a better project fit is chosen.
- Install the library only after checking existing dependencies.
- Replace at least one meaningful hand-built chart with a data-backed chart component.
- Keep chart component client-only and pass serializable server data into it.
8. Replace hard-coded operational counters where data exists:
- Dashboard bed/elder/admission counters should come from Drizzle-backed data.
- Bed/admission workspace metrics should compute from server-loaded rooms, beds, and admissions.
- Do not fabricate counters for modules that remain out of scope.
9. Verification:
- Run `pnpm lint`.
- Run `pnpm type-check`.
- Run `pnpm build`.
- Run Drizzle generation/check commands when schema changes are made.
- Start dev server and manually exercise setup/login/elder CRUD/bed admission/transfer/discharge/settings if build passes.
## Files Expected to Change
- `.trellis/tasks/07-01-nextjs-fullstack-crud-rbac-audit/prd.md`
- `.trellis/tasks/07-01-nextjs-fullstack-crud-rbac-audit/design.md`
- `.trellis/tasks/07-01-nextjs-fullstack-crud-rbac-audit/implement.md`
- `modules/shared/components/AppShell.tsx`
- `app/(app)/app/beds/page.tsx`
- `app/(app)/app/dashboard/page.tsx`
- `modules/dashboard/components/DashboardHome.tsx`
- `app/api/admissions/route.ts`
- Potentially `app/api/admissions/[id]/route.ts`
- Potentially facility UI components under `modules/` if the bed page is split into smaller client/server components.
## Files Expected to Stay As Existing Foundations
- `modules/core/server/db.ts`
- `modules/core/server/schema.ts`
- `modules/core/server/auth.ts`
- `modules/core/server/permissions.ts`
- `modules/core/server/audit.ts`
- `modules/core/server/api.ts`
- `modules/core/server/store.ts` as a temporary Drizzle-backed read model.
- Existing auth route handlers unless verification finds a concrete bug.
- Existing elder route handlers unless admission or bed assignment behavior requires a narrow fix.
## Dependency Notes
- Do not add oRPC or better-auth in this task.
- Do not reintroduce JSON-file persistence.
- A standard chart library may be added after dependency review. Prefer a focused dashboard dependency over a broad visualization stack.
## Validation Commands
```bash
pnpm lint
pnpm type-check
pnpm build
pnpm db:generate
```
If schema files are changed, also validate migrations and run the migration flow against the configured development database:
```bash
pnpm db:migrate
```
## Rollback Points
- If admission mutation UI is unstable, keep the Drizzle APIs and temporarily render a read-only admissions table.
- If discharge/transfer API design becomes too broad, implement only admission create/transfer in this slice and keep discharge as a clearly scoped follow-up.
- If chart dependency causes build or bundle issues, remove the chart component and keep the server data preparation in place.
- If tabs cause layout regressions, keep the page-local action placement and defer only the tab styling.

View File

@@ -0,0 +1,155 @@
# Next.js Drizzle Full-Stack CRUD, RBAC, Audit Logs, and Admission Ops
## Goal
Stabilize the current Next.js 15 application around the Drizzle/PostgreSQL persistence layer that has already been introduced, then finish the first operational slice and prepare a controlled Phase 2 path for bed/admission operations, tabs-based workspace navigation, and real chart components.
The near-term release should keep moving away from static/mock-only surfaces, but it must treat the existing Drizzle schema and migrations as the source of truth. JSON-file persistence is no longer the target architecture for this task.
## Confirmed Facts
- The repository is already a Next.js 15 App Router project with routes under `app/`.
- Drizzle and PostgreSQL dependencies are installed: `drizzle-orm`, `drizzle-kit`, and `postgres`.
- Drizzle schema exists at `modules/core/server/schema.ts`, with migrations under `drizzle/`.
- The schema already includes accounts, sessions, organizations, memberships, roles, permissions, elders, rooms, beds, admissions, audit logs, system incidents, invitations, and system settings.
- `modules/core/server/db.ts` owns the Drizzle/PostgreSQL connection and requires `DATABASE_URL`.
- `modules/core/server/store.ts` is now a Drizzle-backed compatibility read model. `readData()` aggregates data from PostgreSQL, while `writeData()` and `updateData()` intentionally throw after the PostgreSQL migration.
- Server-side authentication, password hashing, HTTP-only session cookies, role seeding, permission checks, and audit logging are implemented around Drizzle.
- Elder CRUD APIs at `app/api/elders/*` write through Drizzle and can create an active admission when a bed is selected.
- Facility and admission routes exist for rooms, beds, and admissions, but the bed/admission UI is still mostly read-only and does not yet provide a complete operator workflow.
- The dashboard still contains hard-coded operational counters and hand-built chart-like progress bars.
- The app shell has a top-bar "入住" button, but it is not wired to a usable admission workflow.
- The screenshot feedback requires moving workspace-level navigation into tabs, removing oversized module intro blocks, and avoiding redundant header actions such as the extra "入住" affordance in the top bar.
- Current `package.json` already supports `pnpm lint`, `pnpm type-check`, `pnpm build`, `pnpm db:generate`, `pnpm db:migrate`, and `pnpm db:studio`.
## MVP Scope
- Use the existing Drizzle/PostgreSQL layer for all new and changed persistent business data.
- Keep Next.js Route Handlers as the current API surface for this milestone. Do not introduce oRPC or better-auth in this task unless a separate migration task is created.
- Preserve server-side account/session APIs, HTTP-only cookie sessions, built-in platform and organization roles, membership permissions, and audit logging.
- Complete full CRUD for elder profiles as the first business entity, backed by Drizzle.
- Complete basic bed and admission operations: list rooms/beds, show current occupancy, create occupancy/admission, support bed transfer, and keep bed/admission/elder status consistent in a transaction.
- Implement an operator-friendly bed/admission UI under the app workspace instead of exposing "create through API" placeholders.
- Add workspace tabs where the current UI needs secondary navigation, especially for operational pages that combine overview, records, and management views.
- Replace hard-coded counters on implemented areas with Drizzle-backed data.
- Use a standard React chart library for selected operational charts instead of hand-rolled visual bars where the visualization is meaningful and data-backed.
- Keep settings pages showing accounts, roles, permission coverage, organizations, status, and audit logs from server data.
- Record audit log entries for login, logout, account creation, elder create/update/delete, admission create/transfer/discharge, facility mutations, and permission-sensitive denied actions.
## Requirements
### Authentication
- Setup must create the first platform administrator account, initial organization, initial organization membership, and cookie session on the server.
- Login must validate account credentials on the server and set an HTTP-only session cookie.
- Logout must clear the session cookie and record an audit event when possible.
- App routes must not depend on localStorage for authentication.
### Roles and Permissions
- The system must include built-in roles:
- `platform_admin`: full platform access.
- `platform_operator`: platform organization/account operations without full security ownership.
- `platform_auditor`: platform read/audit access.
- `platform_ops`: platform operations and incident management.
- `org_admin`: full organization-level access.
- `manager`: elder, facility, admission, and audit operations inside an organization.
- `caregiver`: elder read/update plus read-only facility/admission access.
- `viewer`: read-only elder, facility, and admission access.
- Permission checks must run on the server for all protected APIs.
- UI navigation or controls may hide unavailable actions, but hidden UI must not be the only enforcement.
### Elder CRUD
- Users with permission can list, create, update, and delete elder profiles.
- Elder records must include at minimum: name, gender, age, care level, current room/bed when admitted, status, primary contact, phone, medical notes, created/updated timestamps.
- The elder page must show real server data and support create/edit/delete interactions.
- Invalid input must return structured API errors and show usable feedback in the UI.
### Bed and Admission Operations
- Operators with `facility:read` can view rooms, beds, occupancy status, current elder assignment, and admission history.
- Operators with `facility:manage` can create or update room and bed metadata where the UI exposes those controls.
- Operators with `admission:manage` can admit an elder to an available bed, transfer an active elder to another available bed, and discharge an elder from a bed.
- Admission mutations must be transactional: active admission records, bed statuses, and elder status must stay consistent.
- Bed assignment conflicts must be rejected with structured API errors.
- The UI must make common workflows discoverable without relying on raw API calls.
- The top app header should not contain redundant or dead "入住" controls. Admission actions should live inside the relevant bed/admission workspace.
### Workspace UI and Tabs
- The app shell should keep dense operational navigation and avoid large hero-style module introductions.
- Pages that combine multiple operator modes should use tabs or equivalent segmented navigation, not stacked explanatory sections.
- The screenshot feedback for notices/general workspace applies broadly: remove oversized first-screen intro blocks when they do not help an operator act, place secondary navigation tabs near the workspace header, and keep action controls local to the page.
- UI text should remain operational and data-oriented, not marketing copy.
### Charts
- Use a standard chart library for selected data-backed charts such as bed occupancy composition, admission activity, or status distribution.
- Avoid hand-coded fake chart bars for business metrics that should reflect persisted data.
- Keep charts lightweight, readable, and useful for operations; tables remain the primary surface for detailed records.
### Audit Logs
- Audit logs must be persisted server-side and visible in the settings page.
- Each audit record must include timestamp, actor account ID/email when available, action, target type, target ID when available, result, and human-readable reason.
- Failed permission checks must be logged without exposing sensitive internals to the client.
### Data and API
- API responses must use a consistent `{ success, reason, ... }` shape.
- Server-side data helpers must avoid browser APIs.
- New persistent mutations must use Drizzle queries or transactions through the server database boundary, not JSON file writes.
- `readData()` may remain temporarily as a compatibility read model, but new mutation code must not depend on `writeData()` or `updateData()`.
- Prefer domain-specific Drizzle query helpers over growing the compatibility read model for every new use case.
- Do not use hard-coded UI counters for implemented CRUD, settings, audit, bed, admission, or dashboard areas once server data exists.
- Routes or server components that depend on cookies/session or live database state must opt out of static caching where required.
### Quality
- Keep TypeScript strict without `any`, non-null assertions, or `@ts-ignore`/`@ts-expect-error`.
- Prefer Server Components for initial data loading and small Client Components only for forms/mutations.
- `pnpm lint`, `pnpm type-check`, `pnpm build`, and relevant Drizzle migration validation should pass before marking implementation complete.
## Acceptance Criteria
- [ ] First-run setup creates a Drizzle-persisted platform admin account, initial organization, organization roles, membership, and cookie session, then redirects into the app.
- [ ] Login/logout uses server APIs and an HTTP-only cookie session, not localStorage.
- [ ] Visiting `/app/elders` while authenticated loads elder records from PostgreSQL through the server layer.
- [ ] An authorized user can create, edit, and delete elder records from the UI, and changes survive page reloads.
- [ ] Unauthorized role attempts against protected elder/account/audit APIs return a forbidden response and create audit log entries.
- [ ] Settings pages display built-in roles, account list, organizations, system status, and recent audit log entries from server data.
- [ ] `/app/beds` or the equivalent admission workspace shows Drizzle-backed room, bed, occupancy, and admission data without raw API placeholders.
- [ ] An authorized user can admit an elder to an available bed from the UI.
- [ ] An authorized user can transfer or discharge an active admission from the UI, with bed status and elder status updated transactionally.
- [ ] Admission conflict attempts return structured errors and do not corrupt bed occupancy state.
- [ ] The screenshot feedback is reflected in the workspace layout: tabs are introduced where needed, oversized intro blocks are removed, and redundant top-bar admission action is removed or replaced with a useful page-local action.
- [ ] At least one implemented operational chart uses a standard chart library and real server data.
- [ ] Audit logs are written for account creation, login, logout, elder create/update/delete, admission create/transfer/discharge, facility mutations implemented in this task, and denied permission checks.
- [ ] Existing static module pages outside the MVP continue to render.
- [ ] Existing Drizzle migrations remain coherent with `modules/core/server/schema.ts`.
- [ ] `pnpm lint` passes.
- [ ] `pnpm type-check` passes.
- [ ] `pnpm build` passes.
## Out of Scope
- Reverting to JSON-file persistence.
- Full historical data migration from any old `.data/teatea.json` files unless a separate migration task is created.
- Replacing the current Route Handlers with oRPC.
- Replacing the current custom session implementation with better-auth.
- Password reset, email verification, OAuth, multi-factor authentication, or new invitation flows beyond what already exists.
- Full custom role builder beyond the existing role/permission management surface.
- CRUD implementation for every module in the sidebar.
- Advanced care plan, billing, family app, device telemetry, and emergency workflow automation.
- Production-grade password hashing beyond Node built-in cryptographic hashing suitable for this local MVP.
## Open Questions
- Which chart library should be standardized for the project? Recharts is a practical default for React dashboards, but the decision should be captured before adding the dependency.
- Should room/bed creation be part of this immediate slice, or should the UI focus first on admission, transfer, and discharge against existing room/bed records?
## Notes
- Next.js documentation confirms App Router Route Handlers support `GET`, `POST`, `PATCH`, and `DELETE`, `Response.json`, `request.json()`, and async `cookies()` from `next/headers` for cookie reads/writes in current versions.
- The current codebase already demonstrates the intended Drizzle direction. Future task text should not describe JSON files as the target persistence layer unless explicitly working on a compatibility migration.

View File

@@ -3,7 +3,7 @@
"name": "nextjs-fullstack-crud-rbac-audit", "name": "nextjs-fullstack-crud-rbac-audit",
"title": "Next.js Full-Stack CRUD, RBAC, and Audit Logs", "title": "Next.js Full-Stack CRUD, RBAC, and Audit Logs",
"description": "", "description": "",
"status": "in_progress", "status": "completed",
"dev_type": null, "dev_type": null,
"scope": null, "scope": null,
"package": null, "package": null,
@@ -11,7 +11,7 @@
"creator": "TalexDreamSoul", "creator": "TalexDreamSoul",
"assignee": "TalexDreamSoul", "assignee": "TalexDreamSoul",
"createdAt": "2026-07-01", "createdAt": "2026-07-01",
"completedAt": null, "completedAt": "2026-07-02",
"branch": null, "branch": null,
"base_branch": "main", "base_branch": "main",
"worktree_path": null, "worktree_path": null,

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,104 @@
# Care Execution Workspace Design
## Summary
Replace the reserved `/care` module page with a real operational workspace backed by persisted care execution records. This task owns daily care execution only: task list, status movement, completion notes, and seeded examples.
## Route Boundary
- Unscoped route: `app/(app)/app/care/page.tsx`
- Scoped route: `app/(app)/app/[organizationSlug]/care/page.tsx`
- Navigation item remains in the `运营` group.
- Permission should move from `elder:update` to explicit care permissions:
- `care:read`
- `care:manage`
The unscoped page should:
1. Load `getCurrentAuthContext()`.
2. Redirect unauthenticated users to `/login`.
3. Redirect users without `care:read` to the workspace dashboard.
4. Require an active organization.
5. Load care data through a focused server helper.
6. Render a client workspace with `canManage` based on `care:manage`.
## Data Model
Add one MVP table: `care_tasks`.
- `id`
- `organizationId`
- `elderId` nullable, because some tasks can be room/public-area checks later
- `title`
- `careType`: `daily_care | meal | medication | rehab | inspection | cleaning | other`
- `priority`: `low | normal | high | urgent`
- `status`: `pending | in_progress | completed | cancelled`
- `scheduledAt`
- `assigneeLabel`
- `executionNotes`
- `completedAt`
- `createdByAccountId`
- `completedByAccountId`
- `createdAt`
- `updatedAt`
Indexes:
- `(organizationId, status, priority)` for queues.
- `(organizationId, scheduledAt)` for daily schedule ordering.
## Server Helpers
Create `modules/care/`:
- `modules/care/types.ts`
- enum values, labels, DTOs, validators.
- `modules/care/server/operations.ts`
- `listCareExecutionData(organizationId: string)`
- `updateCareTaskStatus(input)`
- `modules/care/components/CareWorkspaceClient.tsx`
- dense tabs/filters/table and status action dialogs.
Reads should batch the care tasks and elder names in one query shape. Mutations must filter by `organizationId` and update by task ID plus organization ID.
## API Design
- `GET /api/care/tasks`
- permission: `care:read`
- response: `{ success: true; reason: string; data: CareExecutionData }`
- `PATCH /api/care/tasks/[id]`
- permission: `care:manage`
- request: `{ status: "pending" | "in_progress" | "completed" | "cancelled"; executionNotes?: string }`
- completion sets `completedAt` and `completedByAccountId`
- moving out of completed clears completion metadata
- records audit log
All failures use `{ success: false; reason: string }`.
## UI Design
The care workspace should be operational and compact:
- Metrics: pending, in-progress, completed today, high/urgent.
- Filters: status, priority, care type, search by elder/title/assignee.
- Table columns: task, elder, type, priority, status, scheduled time, assignee, completed time, actions.
- Actions:
- pending -> start
- in-progress/pending -> complete with notes
- completed/cancelled are visible but not primary work queue items
- Users without `care:manage` can view but action buttons are disabled or hidden.
## Default Workspace Data
Extend `seedDefaultWorkspaceData()` with care tasks after seeded elders exist. Examples should cover:
- pending morning care
- in-progress rehabilitation or inspection
- completed meal/medication task
- urgent/high overdue-style task scheduled in the past
## Compatibility And Rollback
- Existing `/app/care` placeholder is replaced only for this module.
- New tables are additive; rollback is dropping generated care migration and removing `modules/care`, care API routes, and route/page changes.
- Dashboard integration remains out of scope until the dedicated dashboard task.

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,61 @@
# Implementation Plan
## 1. Schema And Migration
- Add care enums and `care_tasks` table to `modules/core/server/schema.ts`.
- Add indexes for queue and schedule queries.
- Run `pnpm db:generate`.
- Review generated SQL.
## 2. Types And Permissions
- Add `care:read` and `care:manage` to `modules/core/types.ts`.
- Add permission definitions and default role grants in `modules/core/server/permissions.ts`.
- Move navigation `/care` permission to `care:read`.
- Add care DTOs, labels, and validators in `modules/care/types.ts`.
## 3. TDD API Tests
- Add `app/api/care/care-routes.test.ts` before implementation.
- Cover:
- list care tasks
- permission denial
- missing active organization
- start task
- complete task with notes
- invalid status
- missing/cross-organization task
## 4. Server Operations And API Routes
- Add `modules/care/server/operations.ts`.
- Add `GET /api/care/tasks`.
- Add `PATCH /api/care/tasks/[id]`.
- Use `requirePermission`, structured failures, organization scoping, and audit logs.
## 5. Workspace UI
- Replace `app/(app)/app/care/page.tsx` placeholder with real Server Component.
- Keep scoped route delegating to the unscoped route.
- Add `modules/care/components/CareWorkspaceClient.tsx`.
- Implement metrics, filters, table, and start/complete actions.
## 6. Default Workspace Data
- Extend `modules/core/server/default-workspace-data.ts` with care task examples tied to seeded elders.
- Keep seed insertion inside the first-run default workspace transaction.
## 7. Verification
- `pnpm test`
- `pnpm lint`
- `pnpm type-check`
- `pnpm db:generate`
- Review generated SQL.
- `pnpm build`
## Rollback Points
- Before migration generation: remove schema and care module files.
- After migration generation: remove generated care migration plus schema changes.
- After UI/API: remove care API routes, `modules/care`, page changes, permissions, nav permission change, and seed rows.

View File

@@ -0,0 +1,76 @@
# 护理服务执行工作台
## Goal
Replace the current `护理服务` placeholder with a real daily care execution workspace. The MVP unit is a persisted care task / service execution record, not a care-plan generation engine.
## Confirmed Facts
- `/app/care` currently renders `ModulePage` only.
- `/app/{organizationSlug}/care` delegates to the unscoped care page.
- Existing elder, bed, and admission data are persisted in Drizzle.
- Navigation already exposes `护理服务` under the operation group.
- Historical product direction includes care tasks, dispatch,巡检打卡, service records, and service evaluation.
## Requirements
- Add Drizzle-backed care execution records scoped by organization.
- Care records should link to a real elder where applicable.
- Care records should support at least:
- title/name
- care type
- priority
- status
- scheduled time or due time
- assignee label or executor label
- execution notes
- completed time
- Replace `/app/care` and `/app/{organizationSlug}/care` with a real workspace.
- The workspace should show summary metrics, filters, and a dense care task table/list.
- Operators must be able to filter by status, priority, care type, and elder/search terms.
- Authorized operators can mark care items as in progress and completed.
- Completion must persist notes and completion timestamp.
- Mutations must record audit logs.
- Default workspace seed data must include representative care examples covering multiple statuses and care types.
## Permissions
- Prefer explicit care permissions if adding new permissions is practical:
- `care:read`
- `care:manage`
- If scope needs to stay smaller, reuse existing `elder:update` temporarily only if documented in the design.
- Server-side permissions are required for all mutation APIs.
## Acceptance Criteria
- [ ] `/app/care` no longer renders the generic placeholder.
- [ ] `/app/{organizationSlug}/care` renders the same real care workspace.
- [ ] Care records are loaded from Drizzle and scoped to the active organization.
- [ ] Default seeded workspace includes care tasks tied to seeded elders.
- [ ] The UI shows pending, in-progress, completed, and overdue/high-priority examples.
- [ ] Authorized users can move a care item to in-progress and completed from the UI.
- [ ] Completed care items persist execution notes and completion time.
- [ ] Unauthorized mutation attempts return structured failures.
- [ ] Care mutations write audit log entries.
- [ ] `pnpm db:generate` is run if schema changes are made.
- [ ] `pnpm lint` passes.
- [ ] `pnpm type-check` passes.
- [ ] `pnpm build` passes.
## Dependencies
- Can start after or parallel with health management because it primarily depends on existing elder/admission data.
- Dashboard integration should wait until this task is complete.
## Out Of Scope
- Care plan template management.
- Automatic recurring task generation.
- Service billing or pricing.
- Family notifications.
- Mobile QR/NFC check-in.
- AI care recommendations.
## Open Questions
- None currently blocking planning.

View File

@@ -0,0 +1,26 @@
{
"id": "care-execution-workspace",
"name": "care-execution-workspace",
"title": "护理服务执行工作台",
"description": "",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "TalexDreamSoul",
"assignee": "TalexDreamSoul",
"createdAt": "2026-07-02",
"completedAt": "2026-07-03",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": "07-02-operations-module-roadmap",
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,191 @@
# Health Care Admin Page Design
## Summary
Add a dedicated management route under `/settings/health` for persisted elder health data. This keeps operational navigation (`/health`) separate from backend data administration, matching the user's direction to split the management surface into its own page.
## Route Boundary
- Unscoped route: `app/(app)/app/settings/health/page.tsx`
- Scoped route: `app/(app)/app/[organizationSlug]/settings/health/page.tsx`
- Optional redirect source: none in MVP. `/app/health` stays independent.
- Navigation: add a management item under `管理系统`.
- Breadcrumbs: add `"/settings/health": "健康数据管理"`.
The page should follow existing settings pages:
1. Server Component loads `getCurrentAuthContext()`.
2. Redirect unauthenticated users to `/login`.
3. Redirect users without `health:read` back to a safe workspace route.
4. Load persisted initial data through server helpers.
5. Render a focused Client Component for filtering and mutation UI.
## Data Model
All tables use `organizationId` for tenant scope and `createdAt` / `updatedAt` timestamps.
### `health_profiles`
- `id`
- `organizationId`
- `elderId`
- `allergyNotes`
- `medicalHistory`
- `medicationNotes`
- `careRestrictions`
- `emergencyNotes`
- `createdAt`
- `updatedAt`
Constraint: unique `(organizationId, elderId)` so each elder has one admin health profile.
### `vital_records`
- `id`
- `organizationId`
- `elderId`
- `recordedAt`
- `source`: `manual | device | import`
- `systolicBp`
- `diastolicBp`
- `heartRate`
- `temperatureTenths`
- `spo2`
- `bloodGlucoseTenths`
- `weightTenths`
- `notes`
- `createdByAccountId`
- `createdAt`
- `updatedAt`
Integer tenths avoid PostgreSQL numeric string handling for MVP decimal values.
### `chronic_conditions`
- `id`
- `organizationId`
- `elderId`
- `name`
- `status`: `active | controlled | resolved`
- `diagnosedAt`
- `treatmentNotes`
- `followUpNotes`
- `createdAt`
- `updatedAt`
### `health_anomaly_reviews`
- `id`
- `organizationId`
- `elderId`
- `vitalRecordId`
- `severity`: `info | warning | critical`
- `status`: `pending | reviewed | resolved`
- `title`
- `description`
- `reviewedByAccountId`
- `reviewedAt`
- `resolutionNotes`
- `createdAt`
- `updatedAt`
`vitalRecordId` is nullable so reviews can also be created directly against an elder health concern.
## Server Helpers
Create `modules/health/`:
- `modules/health/types.ts`
- shared enum values, labels, API DTO types, and validators.
- `modules/health/server/operations.ts`
- `listHealthAdminData(organizationId: string)`
- `upsertHealthProfile(...)`
- `createVitalRecord(...)`
- `createChronicCondition(...)`
- `updateHealthReview(...)`
- `modules/health/components/HealthAdminClient.tsx`
- client-side tabs, filters, dialogs/forms, optimistic-free refresh.
Server reads should batch queries by organization and join elders once. Avoid per-elder database calls.
## API Design
MVP route handlers:
- `GET /api/health/admin`
- permission: `health:read`
- returns metrics, elders, profiles, recent vitals, chronic conditions, reviews.
- `PUT /api/health/profiles/[elderId]`
- permission: `health:manage`
- validates elder belongs to active organization.
- upserts one profile.
- `POST /api/health/vitals`
- permission: `health:manage`
- validates elder belongs to active organization.
- creates vital record.
- may create a pending review if values exceed MVP thresholds.
- `POST /api/health/chronic-conditions`
- permission: `health:manage`
- validates elder belongs to active organization.
- creates chronic condition.
- `PATCH /api/health/reviews/[id]`
- permission: `health:manage`
- validates review belongs to active organization.
- updates status and review metadata.
All responses use `ApiResult`.
## Permission Design
Update `modules/core/types.ts` and `modules/core/server/permissions.ts`:
- Add `health:read`
- Add `health:manage`
- Add permission definitions in category `健康`
- Grant defaults:
- `org_admin`: read/manage
- `manager`: read/manage
- `caregiver`: read/manage unless user chooses stricter admin-only behavior
- `viewer`: read
This avoids overloading `elder:update` for a separate health management page.
## UI Design
The page should look like an internal management console:
- Header: `健康数据管理`, short description, manage badge if allowed.
- Metrics: elders with profiles, vitals today, active chronic conditions, pending reviews.
- Tabs:
- `健康档案`: elder list with profile completeness and edit dialog.
- `生命体征`: recent vitals table and add-vital dialog.
- `慢病记录`: condition table and add-condition dialog.
- `异常复核`: pending/reviewed/resolved queue with review action.
- Filters: elder search, status/severity select, source select where useful.
Keep tables and cards dense; no landing-page composition.
## Compatibility And Rollback
- Existing `/app/health` remains unchanged in MVP, limiting frontend blast radius.
- New tables are additive; rollback is dropping the generated health tables/enums and removing nav/API/page files.
- Existing seed file has uncommitted work. Implementation must read and preserve user changes in `modules/core/server/default-workspace-data.ts` before adding health seed rows.
## Validation
Required:
- `pnpm db:generate`
- Review generated SQL in `drizzle/`
- `pnpm lint`
- `pnpm type-check`
- `pnpm build`
Manual checks:
- login as authorized user and open `/app/{slug}/settings/health`
- create/update a health profile
- create a vital record
- create or review an abnormal item
- verify reload persists records
- verify mutation with insufficient permission returns `403`

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,74 @@
# Implementation Plan
## 1. Align Task Metadata
- Update task title/description if needed so the Trellis task reflects `健康数据管理` instead of care-service workspace wording.
- Keep the existing task directory unless a rename is explicitly requested.
## 2. Schema And Migration
- Add health enums and tables in `modules/core/server/schema.ts`.
- Add indexes/unique constraints:
- profile unique `(organizationId, elderId)`
- recent vital lookups by `(organizationId, elderId, recordedAt)`
- review queues by `(organizationId, status, severity)`
- Run `pnpm db:generate`.
- Review generated migration SQL.
## 3. Types And Permissions
- Add `health:read` and `health:manage` to `modules/core/types.ts`.
- Add permission definitions and default role grants in `modules/core/server/permissions.ts`.
- Add health DTOs, labels, validators, and formatting helpers in `modules/health/types.ts`.
## 4. Server Operations
- Create `modules/health/server/operations.ts`.
- Implement batched list helper for the admin page.
- Implement mutation helpers for:
- profile upsert
- vital create
- chronic condition create
- review status update
- Ensure all helpers require an organization ID and validate elder/review ownership.
## 5. API Routes
- Add `/api/health/admin`.
- Add `/api/health/profiles/[elderId]`.
- Add `/api/health/vitals`.
- Add `/api/health/chronic-conditions`.
- Add `/api/health/reviews/[id]`.
- Use `requirePermission`, structured `jsonSuccess` / `jsonFailure`, and audit logs.
## 6. Admin Page UI
- Add `app/(app)/app/settings/health/page.tsx`.
- Add `app/(app)/app/[organizationSlug]/settings/health/page.tsx` delegating to the unscoped route pattern.
- Add `modules/health/components/HealthAdminClient.tsx`.
- Use existing UI adapters for buttons, cards, dialogs, inputs, selects, tables, and badges.
- Add management nav and breadcrumbs.
## 7. Default Workspace Data
- Carefully inspect current uncommitted `modules/core/server/default-workspace-data.ts`.
- Add health seed records only if compatible with existing default seed flow.
- Tie seed records to seeded elders and organization.
## 8. Verification
- Run `pnpm lint`.
- Run `pnpm type-check`.
- Run `pnpm build`.
- If a database is available, run `pnpm db:migrate` and manually test:
- scoped management page loads.
- health profile upsert persists.
- vital record create persists.
- abnormal review update persists.
- insufficient permission returns `403`.
## Rollback Points
- Before migration generation: schema changes can be removed directly.
- After migration generation: remove generated migration files and schema changes together.
- After UI/API addition: remove `modules/health`, health API routes, settings health pages, nav/breadcrumb additions, and health permissions.

View File

@@ -0,0 +1,117 @@
# Health Care Admin Page
## Goal
Build a separate backend management page for health care data instead of turning the existing operational `健康照护` module into a management surface. The first usable slice should let authorized staff manage real persisted health profiles, vital signs, chronic conditions, and abnormal-review records for elders in the active organization.
## Background And Confirmed Facts
- The current `健康照护` operational route renders the generic placeholder only: `app/(app)/app/health/page.tsx`.
- The scoped `健康照护` route delegates to the unscoped placeholder: `app/(app)/app/[organizationSlug]/health/page.tsx`.
- The placeholder contract explicitly forbids fabricated operational records until real Drizzle-backed data exists: `.trellis/spec/frontend/components.md`.
- The sidebar already has a separate `管理系统` group for backend management pages: `modules/shared/lib/navigation.ts`.
- Existing management routes live under `/settings/...` and have scoped mirrors under `/app/[organizationSlug]/settings/...`.
- Breadcrumbs already treat `/settings/...` as the management area: `modules/shared/components/AppBreadcrumbs.tsx`.
- The current Drizzle schema includes organizations, elders, rooms, beds, admissions, audit logs, incidents, permissions, roles, and sessions, but no health profile, vital sign, chronic condition, or health review tables: `modules/core/server/schema.ts`.
- Existing persisted feature pages load data in Server Components and pass initial records into Client Components, for example `app/(app)/app/elders/page.tsx` and `app/(app)/app/beds/page.tsx`.
- The user explicitly clarified that this should be a separate backend management page, split from the current operational health page.
## Requirements
### Route And Navigation
- Add a new backend management route under the management area.
- Recommended route: `/app/settings/health` with scoped mirror `/app/{organizationSlug}/settings/health`.
- Add a `管理系统` navigation item labeled `健康数据管理`.
- Keep the existing `/app/health` operational page separate from this management page for this task.
- Breadcrumbs must show the new route under `工作台 > 管理系统 > 健康数据管理`.
### Data Model And Persistence
- Add Drizzle-backed tables for the health-care admin MVP.
- Health data must be scoped by `organizationId`.
- Health data that belongs to an elder must reference a real elder in the same organization.
- Minimum persisted domains:
- health profile: allergy notes, medical history, medication notes, restrictions, emergency health note.
- vital sign record: recorded time, source, blood pressure, heart rate, temperature, SpO2, blood glucose, weight, notes.
- chronic condition: condition name, status, diagnosed date or note, treatment/follow-up notes.
- abnormal review: severity, status, source vital record or elder, reviewer, reviewed time, handling notes.
- Generated migrations must stay coherent with `modules/core/server/schema.ts`.
- New mutations must use Drizzle queries or transactions through server boundaries, not legacy write helpers.
### Permissions
- Add explicit health permissions unless implementation review finds an existing permission is a better fit:
- `health:read`
- `health:manage`
- The new management navigation item should require `health:read`.
- Mutations should require `health:manage`.
- Default role seeding should grant sensible permissions:
- platform admin: all permissions through the existing all-permissions pattern.
- organization admin and manager: read/manage.
- caregiver: read/manage for health records if the product treats caregivers as health-data operators.
- viewer: read only.
### API And Server Boundaries
- Add health-specific Route Handlers under `/api/health/...`.
- All health APIs must call `requirePermission`.
- All APIs must validate active organization presence before reading or mutating organization-scoped data.
- Invalid or cross-organization IDs must return structured failures.
- Mutations must record audit logs for create/update/review actions.
- API responses must follow the existing `{ success, reason, ... }` shape.
### Admin UI
- The new page must be dense and operational, not a hero or marketing page.
- The first screen should show summary metrics and data management controls.
- Expected tabs or sections:
- 健康档案
- 生命体征
- 慢病记录
- 异常复核
- The page should support filtering by elder, status/severity, and search terms where useful.
- Authorized users should be able to create or update health records from the UI.
- Users without manage permission may view records but must not see working mutation paths.
- Use project UI adapters from `components/ui/*`.
- Do not fabricate UI-only health records.
### Default Workspace Data
- If seeded workspace data is used for first-run development/demo, it must create real persisted health records tied to real seeded elders.
- Seed records should cover normal vitals, abnormal vitals, chronic conditions, and at least one pending abnormal review.
## Acceptance Criteria
- [ ] `/app/settings/health` renders a real backend health management page.
- [ ] `/app/{organizationSlug}/settings/health` renders the same management page in the active workspace.
- [ ] The new management page is reachable from the `管理系统` navigation group.
- [ ] Existing `/app/health` remains separate from the backend management page.
- [ ] Health admin data is loaded from Drizzle-backed tables and scoped to the active organization.
- [ ] Health profiles, vital signs, chronic conditions, and abnormal reviews have persisted list views.
- [ ] Authorized users can create or update at least the MVP health records from the UI.
- [ ] Users without `health:manage` cannot mutate health records through the API.
- [ ] Health mutations write audit log entries.
- [ ] Invalid input and cross-organization references return structured failure responses.
- [ ] Seeded examples, if added, are real database rows tied to seeded elders.
- [ ] `pnpm db:generate` is run after schema changes and generated SQL is reviewed.
- [ ] `pnpm lint` passes.
- [ ] `pnpm type-check` passes.
- [ ] `pnpm build` passes.
## Out Of Scope
- Replacing the existing operational `/app/health` placeholder with a full clinical dashboard.
- Device telemetry integration.
- External medical system integration.
- AI-generated health recommendations.
- Complex clinical decision support.
- Full recurring measurement schedule engine.
- Family app notifications.
- Billing, pricing, or insurance flows.
- Replacing Route Handlers with oRPC.
- Replacing the current authentication/session implementation.
## Open Questions
- None currently blocking planning.

View File

@@ -0,0 +1,26 @@
{
"id": "care-service-workspace",
"name": "care-service-workspace",
"title": "新增健康照护后台管理页",
"description": "新增独立的健康数据管理后台页面,与现有运营健康照护入口分开。",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "TalexDreamSoul",
"assignee": "TalexDreamSoul",
"createdAt": "2026-07-02",
"completedAt": "2026-07-03",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": "07-02-operations-module-roadmap",
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,72 @@
# Elder Bed Context Optimization Design
## Summary
Add read-only cross-module context to the existing elder and bed workspaces. The implementation should not merge workflows; it only surfaces concise recent signals from persisted admissions, care tasks, health records, and emergency incidents.
## Data Boundary
Use existing persisted tables:
- admissions / beds / rooms for current bed state.
- care_tasks for recent and active care execution.
- vital_records and health_anomaly_reviews for health context.
- system_incidents for safety/emergency context.
No schema change is required. `system_incidents` does not have elder/bed foreign keys yet, so emergency context is shown only when an incident's title, description, or source clearly mentions the elder name, bed code, or room label.
## Permission Boundary
Server pages decide which context to load:
- `admission:read` / `facility:read`: bed/admission context.
- `care:read`: recent care task context.
- `health:read`: health anomalies and latest vitals.
- `incident:read`: matching open/acknowledged emergency context.
Users without a permission should not receive that context in props.
## Server Helper
Add `modules/operations/server/context.ts`:
- `listElderBedContextData(input)`
- input:
- `organizationId`
- `permissions`
- `elders`
- `beds`
- `admissions`
- output:
- `elderContexts: Record<string, ElderOperationalContext>`
- `bedContexts: Record<string, BedOperationalContext>`
The helper should batch queries and keep bounded result sizes.
## UI Design
### Elder list
Add a compact "近期联动" column:
- current bed/admission is already shown in the bed column.
- show up to three concise lines:
- latest active/recent care task.
- latest health anomaly or vital summary.
- matching open/acknowledged emergency event.
- If no permitted context exists, show `-`.
### Bed workspace
Add an occupant context column to the bed status table:
- show current elder care level/status.
- show active/recent care task for that occupant.
- show matching emergency event for occupant or bed when present.
- Keep actions in the existing workflows; context is read-only.
## Compatibility
- Existing elder CRUD remains unchanged.
- Existing admission workflows remain unchanged.
- Client refresh continues to update elder/bed/admission tables; cross-module snippets are refreshed on page reload.

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,38 @@
# Implementation Plan
## 1. Start Task And Read Specs
- Start the Trellis task.
- Reuse existing frontend/backend specs.
- Preserve existing uncommitted UI cleanup in elder and bed components.
## 2. Types And Server Helper
- Add context DTO types in `modules/operations/types.ts`.
- Add `modules/operations/server/context.ts`.
- Batch-load care, health, and incident context based on permissions.
- Avoid fabricating emergency links; match text only when the persisted incident names the elder/bed/room.
## 3. Wire Pages
- Update `app/(app)/app/elders/page.tsx` to load and pass `elderContexts`.
- Update `app/(app)/app/beds/page.tsx` to load and pass `bedContexts`.
## 4. Update Client Components
- Extend `EldersClient` with optional `elderContexts`.
- Extend `BedsWorkspace` with optional `bedContexts`.
- Render compact read-only context lines.
- Preserve existing CRUD/admission behavior.
## 5. Verification
- `pnpm lint`
- `pnpm type-check`
- `pnpm test`
- `pnpm build`
## Rollback
- Remove operations context helper/types.
- Remove context props and columns from elder/bed clients/pages.

View File

@@ -0,0 +1,55 @@
# 老人床位联动优化
## Goal
Improve the existing elder and bed workspaces so operators can see relevant cross-module context without leaving the workflow: current admission, bed, recent care tasks, health abnormal records, and safety incidents.
## Confirmed Facts
- `老人档案` already supports persisted elder CRUD.
- `床位房间` already supports persisted room, bed, admission, transfer, and discharge workflows.
- Elder rows already show current active bed labels from admissions.
- Bed rows already show current elder names where occupied.
- Health, care, and emergency modules will add additional context after their child tasks.
## Requirements
- Add cross-module context to elder and bed surfaces only after the relevant underlying persisted data exists.
- Elder detail/edit surfaces should expose concise recent context:
- current bed/admission state
- recent care execution records
- recent health abnormalities or latest vital summary
- open safety/emergency incidents tied to the elder when available
- Bed workspace should expose concise current occupant context:
- elder status and care level
- active/recent care tasks for the occupant
- open safety/emergency incidents for the bed/occupant when available
- The UI should remain dense and operational; avoid nested cards or large explanatory blocks.
- Cross-module data must be read-only context unless the user is in that module's own workflow.
- Server data loading should stay scoped by active organization.
## Acceptance Criteria
- [ ] Elder pages show current bed/admission context from persisted data.
- [ ] Elder pages show recent health/care/emergency context once those records exist.
- [ ] Bed workspace shows current occupant context without requiring manual lookup.
- [ ] Cross-module context does not fabricate records when a module has no data.
- [ ] Unauthorized users do not see context that they lack permission to read.
- [ ] Existing elder CRUD and bed/admission workflows still work.
- [ ] `pnpm lint` passes.
- [ ] `pnpm type-check` passes.
- [ ] `pnpm build` passes.
## Dependencies
- Should run after health data management, care execution workspace, and emergency incident workspace have shipped basic persisted data.
## Out Of Scope
- Moving all workflows into a single giant elder detail page.
- Editing health/care/emergency records from elder or bed context panels.
- Replacing existing elder or bed workspace architecture.
## Open Questions
- None currently blocking planning.

View File

@@ -0,0 +1,26 @@
{
"id": "elder-bed-context-optimization",
"name": "elder-bed-context-optimization",
"title": "老人床位联动优化",
"description": "",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "TalexDreamSoul",
"assignee": "TalexDreamSoul",
"createdAt": "2026-07-02",
"completedAt": "2026-07-03",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": "07-02-operations-module-roadmap",
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,80 @@
# Emergency Incident Workspace Design
## Summary
Replace `/app/emergency` with a real safety/emergency incident workspace using the existing `systemIncidents` Drizzle table. The MVP covers manual event creation, status triage, filters, and metrics.
## Route Boundary
- Unscoped route: `app/(app)/app/emergency/page.tsx`
- Scoped route: `app/(app)/app/[organizationSlug]/emergency/page.tsx`
- Navigation remains under `运营`.
- Permissions:
- read: `incident:read`
- mutate: `incident:manage`
Server Component behavior:
1. Load `getCurrentAuthContext()`.
2. Redirect unauthenticated users to `/login`.
3. Redirect users without `incident:read` to dashboard.
4. Require active organization.
5. Load incidents through `listEmergencyIncidentData(organizationId)`.
6. Render client workspace with `canManage`.
## Data Model
Use existing `system_incidents`:
- `organizationId`
- `severity`: `info | warning | critical`
- `status`: `open | acknowledged | resolved | closed`
- `title`
- `description`
- `source`
- acknowledgement / resolution account and time fields
No schema change is required for MVP. Elder/bed linking stays future work for the context optimization task.
## Server Helpers
Create `modules/emergency/`:
- `modules/emergency/types.ts`
- labels, DTOs, create/status validators.
- `modules/emergency/server/operations.ts`
- `listEmergencyIncidentData(organizationId: string)`
- `createEmergencyIncident(input)`
- `updateEmergencyIncidentStatus(input)`
- `modules/emergency/components/EmergencyWorkspaceClient.tsx`
- metrics, filters, event table, create dialog, status actions.
## API Design
- `GET /api/emergency/incidents`
- permission: `incident:read`
- response: `{ success: true; reason: string; data }`
- `POST /api/emergency/incidents`
- permission: `incident:manage`
- request: `{ severity; title; description; source }`
- creates an organization-scoped incident
- `PATCH /api/emergency/incidents/[id]`
- permission: `incident:manage`
- request: `{ status }`
- updates only rows in the active organization
All create/update mutations write audit logs after successful persistence.
## UI Design
- Metrics: open, acknowledged, critical, resolved/closed today.
- Filters: status, severity, search by title/source/description.
- Dense table columns: event, severity, status, source, created time, updated time, actions.
- Actions: acknowledge, resolve, close.
- Manual creation dialog for authorized operators.
- Read-only users can see rows but not mutation controls.
## Compatibility
- Existing `/api/system/incidents/[id]` stays for system status settings.
- The emergency workspace uses dedicated `/api/emergency/...` routes so tests and UI contracts are local to the operational module.

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,38 @@
# Implementation Plan
## 1. Types And Tests
- Add emergency DTOs and validators.
- Add `app/api/emergency/emergency-routes.test.ts` first.
- Cover list, create, status update, permission denial, missing organization, invalid input, and missing/cross-organization incident.
## 2. Server Operations And API Routes
- Add `modules/emergency/server/operations.ts`.
- Add `GET`/`POST /api/emergency/incidents`.
- Add `PATCH /api/emergency/incidents/[id]`.
- Use `requirePermission`, active organization checks, structured failures, and audit logs.
## 3. Workspace UI
- Replace `app/(app)/app/emergency/page.tsx`.
- Keep scoped route delegating to the unscoped route.
- Add `modules/emergency/components/EmergencyWorkspaceClient.tsx`.
- Implement metrics, filters, create dialog, and status action buttons.
## 4. Default Data
- Existing default workspace already inserts representative `systemIncidents`.
- Review whether the seed includes open/acknowledged/resolved and critical/warning/info; adjust only if needed.
## 5. Verification
- `pnpm test`
- `pnpm lint`
- `pnpm type-check`
- `pnpm build`
## Rollback Points
- Remove `modules/emergency`, emergency API routes, and page changes.
- No migration rollback expected unless implementation discovers schema changes are required.

View File

@@ -0,0 +1,65 @@
# 安全应急事件工作台
## Goal
Replace the current `安全应急` placeholder with a real emergency and safety event workspace. The MVP should let operators view, triage, update, and close persisted incidents inside the active organization.
## Confirmed Facts
- `/app/emergency` currently renders `ModulePage` only.
- `/app/{organizationSlug}/emergency` delegates to the unscoped emergency page.
- `systemIncidents` already exists in the Drizzle schema and supports severity, status, title, description, source, acknowledgement, resolution, and organization scoping.
- `app/api/system/incidents/[id]/route.ts` already supports incident status updates with audit logs.
- Settings status pages already use incident status actions.
## Requirements
- Replace `/app/emergency` and `/app/{organizationSlug}/emergency` with a real emergency workspace.
- Use existing persisted incident data as the starting model unless implementation analysis proves a separate emergency table is required.
- Show summary metrics by severity and status.
- Show an operational incident list/table with search and status/severity filters.
- Support status transitions for open, acknowledged, resolved, and closed events.
- Support creating a manual emergency/safety event if practical in the MVP.
- Link incidents to organization and optionally to elder/bed context in a future-compatible way.
- Mutations must record audit logs.
- Default workspace seed data must include representative safety/emergency events.
## Permissions
- Use existing `incident:read` for viewing.
- Use existing `incident:manage` for create/update/status transitions.
- Server-side permissions are required for all mutation APIs.
## Acceptance Criteria
- [ ] `/app/emergency` no longer renders the generic placeholder.
- [ ] `/app/{organizationSlug}/emergency` renders the same real emergency workspace.
- [ ] Emergency events are loaded from Drizzle and scoped to the active organization.
- [ ] The workspace shows severity/status metrics and a searchable event list.
- [ ] Authorized users can transition event status from the UI.
- [ ] Event status updates persist and survive reload.
- [ ] Authorized users can create a manual safety/emergency event if included in MVP implementation.
- [ ] Unauthorized mutation attempts return structured failures.
- [ ] Emergency mutations write audit log entries.
- [ ] Default seeded workspace includes safety/emergency events.
- [ ] `pnpm lint` passes.
- [ ] `pnpm type-check` passes.
- [ ] `pnpm build` passes.
## Dependencies
- Can start independently because `systemIncidents` already exists.
- Elder/bed context optimization may later add richer links from incidents to elders or beds.
- Dashboard integration should wait until this task is complete.
## Out Of Scope
- Device telemetry ingestion.
- Real-time alarm channels.
- SMS, phone, or push notification delivery.
- Dispatch routing engine.
- Post-incident regulatory reporting.
## Open Questions
- None currently blocking planning.

View File

@@ -0,0 +1,26 @@
{
"id": "emergency-incident-workspace",
"name": "emergency-incident-workspace",
"title": "安全应急事件工作台",
"description": "",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "TalexDreamSoul",
"assignee": "TalexDreamSoul",
"createdAt": "2026-07-02",
"completedAt": "2026-07-03",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": "07-02-operations-module-roadmap",
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,40 @@
# Operations Dashboard Integration Design
## Summary
Upgrade the dashboard into a cross-module operations summary now that health, care, emergency, elder, bed, and admission data are persisted.
## Data Boundary
The dashboard remains a Server Component:
- elder/bed/admission data from `modules/core/server/operations.ts`
- health data from `listHealthAdminData`
- care data from `listCareExecutionData`
- emergency data from `listEmergencyIncidentData`
Load optional module data only when the viewer has the corresponding read permission. Do not fabricate metrics when permission or data is absent.
## UI Boundary
`DashboardHome` stays presentational. It receives:
- primary metrics
- bed occupancy chart data
- recent admissions
- work queues for health, care, and emergency
Dashboard items should link into modules:
- health reviews -> `/settings/health`
- care tasks -> `/care`
- emergency events -> `/emergency`
- admissions -> `/beds`
The dashboard is not a mutation surface.
## Compatibility
- Existing bed occupancy chart remains data-backed.
- Existing admission list remains data-backed.
- Top badge-style tag should be removed to match the module cleanup direction.

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,29 @@
# Implementation Plan
## 1. Start Task
- Start Trellis task.
- Preserve unrelated dirty settings/dialog files.
## 2. Server Data
- Update dashboard page to load health/care/emergency summaries when permitted.
- Keep all reads organization-scoped.
- Build route hrefs with `getWorkspaceHref`.
## 3. Presentational UI
- Extend `DashboardHome` props for health reviews, care tasks, and emergency events.
- Add compact queue sections with links to modules.
- Remove top badge tag.
## 4. Verification
- `pnpm lint`
- `pnpm type-check`
- `pnpm test`
- `pnpm build`
## Rollback
- Revert dashboard page and `DashboardHome` changes.

View File

@@ -0,0 +1,57 @@
# 运营看板整合优化
## Goal
Upgrade the existing operation dashboard into a cross-module summary after health, care, emergency, elder, bed, and admission data are available. The dashboard should summarize and route operators to work, not become a CRUD surface.
## Confirmed Facts
- The dashboard already uses real persisted elders, beds, admissions, and incidents.
- The dashboard currently does not include dedicated health or care-service metrics because those data sources do not exist yet.
- Recharts is already installed and the dashboard already uses a data-backed bed occupancy chart.
## Requirements
- Keep the dashboard as a Server Component data-loading surface with presentational components.
- Add health metrics after health data management exists:
- recent abnormal vital signs
- pending abnormal reviews
- chronic-condition coverage or counts where useful
- Add care metrics after care execution exists:
- today's care tasks
- pending/in-progress/completed care counts
- overdue/high-priority care count
- Add emergency metrics after emergency workspace exists:
- open safety events
- critical events
- recent resolved/closed events where useful
- Keep existing elder, bed, and admission summaries working.
- Use data-backed charts only; do not add fake visualization rows.
- Link dashboard items to the appropriate operational module route where practical.
## Acceptance Criteria
- [ ] Dashboard still loads persisted elder, bed, admission, and incident data.
- [ ] Dashboard includes persisted health summaries after health data is available.
- [ ] Dashboard includes persisted care execution summaries after care data is available.
- [ ] Dashboard includes persisted safety/emergency summaries after emergency data is available.
- [ ] Charts and tables are data-backed.
- [ ] Dashboard links route users into the relevant module instead of duplicating full workflows.
- [ ] `pnpm lint` passes.
- [ ] `pnpm type-check` passes.
- [ ] `pnpm build` passes.
## Dependencies
- Should run after health data management, care execution workspace, and emergency incident workspace are implemented.
- Should run after elder/bed context optimization if dashboard links depend on new detail surfaces.
## Out Of Scope
- Creating or editing records directly from the dashboard.
- Adding fake summary cards before backing data exists.
- Replacing the dashboard with a marketing landing page.
## Open Questions
- None currently blocking planning.

View File

@@ -0,0 +1,26 @@
{
"id": "operations-dashboard-integration",
"name": "operations-dashboard-integration",
"title": "运营看板整合优化",
"description": "",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "TalexDreamSoul",
"assignee": "TalexDreamSoul",
"createdAt": "2026-07-02",
"completedAt": "2026-07-03",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": "07-02-operations-module-roadmap",
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,47 @@
# Operations Module Roadmap Design
## Boundary
This parent task is a coordination artifact. It should not directly implement schema, API, or UI changes unless a later requirement explicitly adds parent-level integration work.
Implementation belongs to child tasks:
- `07-02-care-service-workspace`: health data management page.
- `07-02-care-execution-workspace`: care execution workspace.
- `07-02-emergency-incident-workspace`: emergency event workspace.
- `07-02-elder-bed-context-optimization`: cross-module context on elder and bed workflows.
- `07-02-operations-dashboard-integration`: final dashboard summaries.
## Dependency Shape
The modules should be delivered in foundation-to-summary order:
1. Health data management creates health records.
2. Care execution creates care task/service records.
3. Emergency workspace operationalizes incident records.
4. Elder/bed context reads the new health/care/emergency records.
5. Dashboard integration summarizes all persisted data.
## Shared Technical Contracts
- Drizzle schema remains the persistence source of truth.
- New tables are additive migrations.
- Organization scoping is mandatory.
- Elder-linked records validate same-organization ownership.
- API responses use `{ success, reason, ... }`.
- Route handlers call `requirePermission`.
- Mutations record audit logs.
- Default workspace data creates real rows, not UI-only fixtures.
## UI Contract
- Operational pages should use dense work-focused layouts.
- Backend management pages belong under `管理系统`.
- Existing `/app/health` remains separate from backend `健康数据管理`.
- `护理服务` is daily execution work, not health data management.
- `安全应急` is event triage and closure.
- `运营看板` summarizes and routes; it is not a CRUD surface.
## Rollback
Rollback should happen per child task. Parent rollback means unlinking or archiving the roadmap task; child migrations and source changes must be reverted in their own rollback plans.

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,42 @@
# Implementation Plan
## Execution Order
1. Start and implement `07-02-care-service-workspace` (`新增健康照护后台管理页`).
2. Start and implement `07-02-care-execution-workspace` (`护理服务执行工作台`).
3. Start and implement `07-02-emergency-incident-workspace` (`安全应急事件工作台`).
4. Start and implement `07-02-elder-bed-context-optimization` (`老人床位联动优化`).
5. Start and implement `07-02-operations-dashboard-integration` (`运营看板整合优化`).
6. Review parent acceptance criteria after all children are complete.
## Per-Child Gate
Before starting each child:
- Read its `prd.md`.
- For complex children, add or refresh `design.md` and `implement.md`.
- Load `trellis-before-dev` and relevant backend/frontend/shared specs.
Before finishing each child:
- Run `pnpm lint`.
- Run `pnpm type-check`.
- Run `pnpm build`.
- Run `pnpm db:generate` and review SQL when schema changes are made.
- Confirm default workspace seed data is real persisted data when seed examples are added.
## Parent Completion Gate
The parent task is complete only after:
- Each child task is completed or explicitly moved out of scope by the user.
- The dashboard reflects completed module data.
- Existing elder, bed, admission, auth, settings, and audit workflows still pass validation.
- Final `pnpm lint`, `pnpm type-check`, and `pnpm build` pass.
## Risk Notes
- Schema migrations across multiple children can conflict if implemented out of order.
- Default seed file already has active edits; every child that touches it must read it before editing.
- Permission additions must keep role seeding idempotent.
- Dashboard integration should not start before the data sources exist.

View File

@@ -0,0 +1,106 @@
# 运营模块完善总计划
## Goal
Turn the six operation-side sidebar entries into a coherent persisted operating system for the pension workspace:
1. 运营看板
2. 老人档案
3. 床位房间
4. 健康照护
5. 护理服务
6. 安全应急
The work should be delivered as separate, independently verifiable child tasks, then integrated back into the dashboard. This parent task owns scope, ordering, and cross-module acceptance criteria. Implementation should happen in child tasks.
## Confirmed Facts
- `运营看板` already renders real Drizzle-backed elders, beds, admissions, and incidents data.
- `老人档案` already supports real persisted elder CRUD.
- `床位房间` already has a real bed/admission workspace backed by rooms, beds, elders, and admissions.
- `健康照护`, `护理服务`, and `安全应急` currently render placeholders or are not complete operational modules.
- The current Drizzle schema has core operational tables but no dedicated health or care-service tables yet.
- `systemIncidents` already provides a useful base for safety/emergency event state flow.
- Static module pages must not fabricate operational data before a real persisted source exists.
- The user wants all of these operation modules planned and completed, not only the currently selected sidebar item.
## Task Map
### Child 1: 新增健康照护后台管理页
- Path: `.trellis/tasks/07-02-care-service-workspace`
- Note: The slug is historical, but the task title and artifacts now describe the health data management page.
- Scope: Add backend management page for health profiles, vital signs, chronic conditions, and abnormal reviews.
- Dependency: None. This can be implemented first because it creates the health data foundation.
### Child 2: 护理服务执行工作台
- Path: `.trellis/tasks/07-02-care-execution-workspace`
- Scope: Add care task/service execution records, status flow, completion notes, and seeded examples.
- Dependency: Can use existing elder/admission data. Does not depend on health admin unless a care item links to abnormal health reviews later.
### Child 3: 安全应急事件工作台
- Path: `.trellis/tasks/07-02-emergency-incident-workspace`
- Scope: Replace the placeholder with an emergency event workspace using and extending persisted incident data.
- Dependency: Can build on `systemIncidents` and existing incident status actions.
### Child 4: 老人床位联动优化
- Path: `.trellis/tasks/07-02-elder-bed-context-optimization`
- Scope: Improve elder and bed screens so operators can see linked health, care, admission, and incident context where available.
- Dependency: Should run after health/care/emergency have at least basic persisted data.
### Child 5: 运营看板整合优化
- Path: `.trellis/tasks/07-02-operations-dashboard-integration`
- Scope: Integrate health, care, emergency, elder, bed, and admission summaries into the dashboard.
- Dependency: Should run after the relevant child data surfaces exist.
## Delivery Order
1. Health data management page.
2. Care execution workspace.
3. Emergency event workspace.
4. Elder/bed cross-module context improvements.
5. Dashboard integration.
This order creates data foundations first, then links records to existing operator workflows, then summarizes everything in the dashboard.
## Cross-Module Requirements
- All new operational records must be Drizzle/PostgreSQL backed.
- New data must be scoped by active organization.
- New data tied to elders must reference real elders in the same organization.
- Default workspace seed data must create real persisted rows, not UI-only demo objects.
- All API responses must follow `{ success, reason, ... }`.
- Permission checks must happen on the server.
- Mutations must record audit logs.
- Navigation labels should remain clear:
- `健康照护` remains the operational health entry.
- backend health management can live under `管理系统` as `健康数据管理`.
- `护理服务` remains daily service execution.
- `安全应急` remains event response and closure.
- The app must not show hero/marketing-style module introductions for operational pages.
## Acceptance Criteria
- [ ] Each child task has its own PRD and implementation scope.
- [ ] Health, care, and emergency modules no longer rely on fabricated UI-only data.
- [ ] Existing elder and bed workflows remain functional after new modules are added.
- [ ] Default workspace seed data covers elders, beds, health, care, and incidents once all child tasks are complete.
- [ ] Dashboard metrics include the new persisted health/care/emergency data after integration.
- [ ] `pnpm lint` passes for each implementation child.
- [ ] `pnpm type-check` passes for each implementation child.
- [ ] `pnpm build` passes before the parent task is considered complete.
## Out Of Scope
- Doing all child tasks in a single implementation pass.
- Replacing Route Handlers with oRPC.
- Replacing current auth/session implementation.
- Billing, family app notifications, device telemetry integrations, and AI recommendations unless created as separate future tasks.
## Open Questions
- None blocking the plan. Implementation should start with Child 1 unless the user explicitly changes priority.

View File

@@ -0,0 +1,32 @@
{
"id": "operations-module-roadmap",
"name": "operations-module-roadmap",
"title": "运营模块完善总计划",
"description": "",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "TalexDreamSoul",
"assignee": "TalexDreamSoul",
"createdAt": "2026-07-02",
"completedAt": "2026-07-03",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [
"07-02-care-service-workspace",
"07-02-emergency-incident-workspace",
"07-02-elder-bed-context-optimization",
"07-02-care-execution-workspace",
"07-02-operations-dashboard-integration"
],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,50 @@
# Design
## Architecture
Add four module slices following existing patterns:
- `modules/devices`, `modules/notices`, `modules/alerts`, and `modules/family`
- each module owns `types.ts`, `server/operations.ts`, and a client workspace component
- route handlers under `app/api/<module>` validate permissions, input, organization context, and audit mutations
- app route pages remain Server Components and pass initial data plus `canManage` into client components
## Data Model
Add Postgres enums and tables to `modules/core/server/schema.ts`:
- devices: asset status, ticket status, ticket priority; device assets and maintenance tickets
- notices: notice status; notices and notice read receipts
- alerts: rule type/status, trigger status; alert rules and alert triggers
- family: relation/status, visit status, feedback status/type; family contacts, visit appointments, feedback records
All business tables include `organizationId`, timestamps, and indexes for list/status lookups. FK references use cascade for organization-owned children and set-null where historical records should survive related record deletion.
## Permissions
Extend `Permission` and seeded permission definitions:
- `device:read`, `device:manage`
- `notice:read`, `notice:manage`
- `alert:read`, `alert:manage`
- `family:read`, `family:manage`
`org_admin` and `manager` receive read/manage for all four modules. `viewer` receives read permissions. `caregiver` receives read permissions for devices, alerts, and family plus existing care/health/facility access.
## UI Flow
Each workspace should provide:
- metric cards for key counts
- search and status/type filters
- tables with stable widths and empty states
- dialogs for create/edit/delete confirmation or status updates
- optimistic local refresh by refetching the module API after successful mutations
## Compatibility
Generate a Drizzle migration from schema changes. Existing installations need to run migrations before using the pages. Existing reserved-page component can remain for future modules but must no longer be used by these four routes.
## Rollback
The rollback point is the generated migration plus module files. If implementation gets too large, keep schema and API for all modules but reduce UI duplication by sharing small local helper components only where it does not obscure module boundaries.

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,26 @@
# Implementation Plan
## Steps
1. Load relevant frontend, backend, and shared Trellis specs.
2. Add schema enums/tables and update core permission types/seed role definitions.
3. Generate the Drizzle migration.
4. Add module type contracts, validators, and server operations for devices, notices, alerts, and family.
5. Add API route handlers and route tests for read/manage permission behavior and organization scoping.
6. Add local seed records for the four modules using existing organization, elder, bed, incident, health, and account context where available.
7. Replace reserved route pages with data-backed Server Components and client workspace components.
8. Run `pnpm type-check`, `pnpm test`, and targeted lint/build checks as time allows.
9. Run Trellis check/update-spec finish steps and commit task changes.
## Validation Commands
- `pnpm db:generate`
- `pnpm type-check`
- `pnpm test`
- `pnpm lint`
## Risk Points
- Schema migration generation changes tracked Drizzle metadata; inspect before finalizing.
- Avoid touching unrelated user-modified files except where required by shared types/navigation/seed integration.
- Keep organization scoping explicit in every list and mutation query.

View File

@@ -0,0 +1,42 @@
# Build collaboration modules
## Goal
Replace the four reserved "协同" navigation pages with real persisted business modules for 设备运维, 公告通知, 规则预警, and 家属服务 so the local养老机构工作台 can exercise end-to-end CRUD workflows backed by Drizzle/Postgres data.
## Confirmed Facts
- The current navigation already exposes `/devices`, `/notices`, `/alerts`, and `/family`.
- These routes currently render `ReservedModulePages`, and the frontend spec requires reserved modules not to fabricate operational data.
- The stack uses Next.js App Router, React client components, project-owned Kumo UI adapters, Drizzle/Postgres, route handlers under `app/api`, and permission gates through `requirePermission`.
- Existing seed data is organization-scoped and idempotent for already-initialized workspaces.
## Requirements
- Add real schema, migrations, server operations, API routes, UI pages, permissions, and seed data for all four collaboration modules.
- Implement complete CRUD for each module's core records, plus the key status transitions needed for daily operations.
- Keep all records organization-scoped and prevent cross-organization reads or mutations.
- Add audit logs for create, update, delete, and status transition mutations.
- Replace reserved pages for both unscoped and organization-scoped app routes.
- Keep UI consistent with existing workspaces: server page loads initial data, client workspace handles filters, tables, dialogs, and mutations.
- Preserve TypeScript strictness and avoid new external dependencies.
## Module Scope
- 设备运维: equipment/device assets and maintenance tickets.
- 公告通知: notices, publish/retract lifecycle, and account read receipts.
- 规则预警: alert rules and triggered alert records. V1 persists and manages rules/triggers but does not implement a background rule engine.
- 家属服务: family contacts, visit appointments, and family feedback. V1 does not add family login or external messaging.
## Acceptance Criteria
- [ ] Four collaboration nav pages render real data-backed workspaces instead of reserved module placeholders.
- [ ] Each module supports list, create, edit, delete, and relevant status transitions through API routes.
- [ ] New data is seeded for local/demo organizations without overwriting existing workspace data.
- [ ] New permissions are registered and assigned to system roles consistently.
- [ ] Mutation API routes require manage permissions, read API routes require read permissions, and cross-organization records cannot be mutated.
- [ ] `pnpm db:generate`, `pnpm type-check`, and `pnpm test` pass.
## Out of Scope
- Family-member authentication, family-facing mobile/client portal, SMS/push delivery, and automatic alert-rule evaluation jobs.

View File

@@ -0,0 +1,26 @@
{
"id": "collaboration-modules",
"name": "collaboration-modules",
"title": "Build collaboration modules",
"description": "",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "TalexDreamSoul",
"assignee": "TalexDreamSoul",
"createdAt": "2026-07-03",
"completedAt": "2026-07-03",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,82 @@
# Invite Link Limits Design
## Architecture
This change spans the persisted invitation model, the invitation create API, the registration consumption transaction, and the organization settings UI.
The invitation row remains the source of truth. The existing `expires_at` column continues to represent validity. New persisted counters will represent usage limits:
- `max_uses`: maximum successful invitation registrations allowed.
- `used_count`: number of successful registrations that have consumed the invitation.
## Data Model
Add non-null integer columns to `organization_invitations`:
- `max_uses integer not null default 1`
- `used_count integer not null default 0`
Existing invitation rows remain compatible: previous one-time invites become `max_uses = 1`, `used_count = 0` unless already accepted. The existing `status = accepted` already prevents use of accepted legacy rows.
## Invitation Creation Contract
`POST /api/organizations/[id]/invitations` accepts:
- `email?: string`
- `roleId: string`
- `validityDays?: number`
- `maxUses?: number`
Defaults:
- `validityDays = 7`
- `maxUses = 1`
Validation:
- `roleId` is still required.
- `validityDays` must be a positive integer in an operationally reasonable range.
- `maxUses` must be a positive integer in an operationally reasonable range.
- Invalid input returns `jsonFailure(...)` with the existing structured API shape.
## Invitation Consumption
Registration only counts a use after account creation and membership insertion succeed inside the existing transaction.
An invitation is valid only if:
- `status === "active"`
- `expiresAt >= now`
- `usedCount < maxUses`
On successful invitation registration:
- Increment `usedCount`.
- If the increment reaches `maxUses`, set `status = "accepted"` and fill `acceptedByAccountId` / `acceptedAt` with the consuming account details.
- If remaining uses exist, keep `status = "active"` and leave accepted metadata empty.
The update should guard against concurrent over-consumption by updating with a `used_count < max_uses` predicate and checking that a row was returned.
## UI
`OrganizationInviteDialog` adds controls for:
- Validity days, default 7.
- Maximum uses, default 1.
`OrganizationDetailClient` displays:
- Expiry time.
- Usage as `usedCount / maxUses`.
Copy behavior remains unchanged.
## Compatibility
The existing `/register?invite=...` URL format remains unchanged.
Existing rows without the new columns are migrated through default values. Existing one-time semantics are preserved by default.
## Rollback
If needed, UI inputs can be hidden and backend defaults can keep the old behavior. Dropping the columns would require a reverse migration only if deployed migrations must be rolled back.

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,42 @@
# Invite Link Limits Implementation Plan
## Checklist
1. Update Trellis planning artifacts and get approval to start implementation.
2. Load pre-development specs with `trellis-before-dev`.
3. Update `modules/core/server/schema.ts`:
- Add `maxUses` and `usedCount` integer columns to `organizationInvitations`.
4. Generate or add the Drizzle migration for the new columns.
5. Update shared invitation types and row mappers:
- `modules/core/types.ts`
- `modules/core/server/settings.ts`
- `modules/core/server/store.ts`
6. Update invitation creation API:
- Parse `validityDays` and `maxUses`.
- Validate positive integer ranges.
- Persist `expiresAt`, `maxUses`, and `usedCount`.
7. Update invitation registration flow:
- Reject exhausted links.
- Increment usage inside the transaction.
- Mark status accepted only when `usedCount` reaches `maxUses`.
- Guard concurrent consumption with an update predicate.
8. Update frontend:
- Add validity and maximum-use controls to `OrganizationInviteDialog`.
- Display usage count in `OrganizationDetailClient`.
- Update invitation explanatory text where it still says invitations are one-time only.
9. Run validation:
- `pnpm lint`
- `pnpm type-check`
- `pnpm test` if tests are relevant or fast enough after type changes.
- `pnpm build` if lint/type-check pass.
## Risk Points
- Concurrent registrations could over-consume an invite unless the update predicate checks `used_count < max_uses`.
- Existing accepted invitations should remain unusable regardless of migrated counter defaults.
- Frontend numeric inputs submit strings, so backend parsing must not trust the client.
## Rollback Points
- Before migration: schema/API/UI changes can be reverted together.
- After migration: reverting code while keeping columns is safe because defaults preserve one-use invite behavior.

View File

@@ -0,0 +1,48 @@
# Invite link limits
## Goal
Allow organization admins to configure invitation link validity and usage limits when creating invite links.
This should make invite links safer to distribute by letting admins choose how long a link remains valid and how many times it may be used.
## Confirmed Facts
- Invitation links are created from `modules/settings/components/OrganizationInviteDialog.tsx`.
- The invitation create API is `app/api/organizations/[id]/invitations/route.ts`.
- Invitation persistence is backed by the `organization_invitations` table in `modules/core/server/schema.ts`.
- Invitations already have an `expiresAt` column and are currently created with a fixed 7-day validity window.
- Registration consumes invitation tokens through `modules/core/server/auth.ts`.
- The current registration flow marks an invitation as `accepted` after one successful invitation registration, so existing invite links behave as single-use links.
- Invitation rows are shown in `modules/settings/components/OrganizationDetailClient.tsx`, currently with target, role, status, expiry, and copy action.
## Requirements
- Admins can configure an invitation link's validity period when creating an organization invitation.
- Admins can configure the maximum usage count for an invitation link when creating an organization invitation.
- Existing behavior remains compatible by default: if the admin does not change the controls, a new invite should expire after 7 days and be usable once.
- The backend validates create-invitation inputs and rejects invalid limit values with structured API failures.
- The registration path rejects expired or exhausted invitation links.
- Invitation list data exposes enough information to show configured limits and current usage.
- The UI makes the configured validity and usage limit visible when creating and reviewing invitations.
- Maximum access count means maximum successful invitation registrations, not page loads or token preview requests.
## Out of Scope
- Invitation revocation management beyond existing status behavior.
- Editing limits after an invitation has been created.
- Tracking anonymous page views unless product intent explicitly requires page-load based access counting.
## Acceptance Criteria
- [x] Creating an invitation with default form values produces a 7-day, one-use invitation.
- [x] Creating an invitation with a custom validity period stores and displays the corresponding expiry.
- [x] Creating an invitation with a custom maximum usage count stores and displays the configured usage limit.
- [x] Registration with an expired invitation fails with the existing invalid/expired invitation behavior.
- [x] Registration after the invitation has reached its maximum allowed uses fails.
- [x] Successful invitation registration increments usage accounting and only exhausts the link when the configured maximum is reached.
- [x] Type-check, lint, and relevant tests/build checks pass.
## Notes
- Recommended interpretation: count successful invitation registrations as usage. Counting page loads would make links vulnerable to browser refreshes, previews, bots, and accidental visits, and it would require adding a separate token validation/view endpoint.

View File

@@ -0,0 +1,26 @@
{
"id": "invite-link-limits",
"name": "invite-link-limits",
"title": "Invite link limits",
"description": "",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "TalexDreamSoul",
"assignee": "TalexDreamSoul",
"createdAt": "2026-07-03",
"completedAt": "2026-07-03",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,31 @@
# Local mock data
## Goal
Provide richer local mock data so the养老机构工作台 can be opened locally with realistic content across the existing dashboard, beds, elders, care, health, and emergency surfaces.
## Confirmed Facts
- The project already has Drizzle/Postgres schema and a `seedDefaultWorkspaceData(organizationId)` helper in `modules/core/server/default-workspace-data.ts`.
- Existing default data covers rooms, beds, elders, admissions, health profiles, vital records, chronic conditions, health anomaly reviews, and care tasks.
- Local Postgres is configured through `compose.yaml`; Drizzle uses `DATABASE_URL`.
- This is a lightweight task: no schema change, no product workflow redesign, and no new external dependency is required.
## Requirements
- Add a larger, more varied default dataset using existing schema tables and enum values.
- Include enough records to make local pages visibly populated: facilities/beds, elder roster, active admissions, care tasks, health vitals/reviews, chronic conditions, and emergency incidents.
- Keep seeding idempotent for an organization that already has workspace data; do not overwrite user-created local data.
- Preserve existing TypeScript type safety and existing data relationships by resolving records through names/codes inside the seed transaction.
- Avoid production-only assumptions; this data is for local/demo initialization only.
## Acceptance Criteria
- [x] Default workspace seed data has more realistic volume and state variety across the existing modules.
- [x] Seed data inserts without foreign-key errors into a migrated local database.
- [x] `pnpm type-check` passes.
- [x] No schema migration is introduced for this task.
## Notes
- Out of scope: fake auth users, generated production data, cross-organization demo scenarios, and UI redesign.

View File

@@ -0,0 +1,26 @@
{
"id": "local-mock-data",
"name": "local-mock-data",
"title": "Local mock data",
"description": "",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "TalexDreamSoul",
"assignee": "TalexDreamSoul",
"createdAt": "2026-07-03",
"completedAt": "2026-07-03",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,395 @@
# AI Agent Knowledge Analysis Design
## Overview
Implement the MVP as a server-side LangChain AI module with PostgreSQL/pgvector-backed knowledge retrieval and a structured elder AI analysis panel in the existing elder list workflow.
The MVP is not a chat assistant. It is a deterministic product workflow:
1. Authorized user opens an elder's AI analysis dialog from the elder list.
2. Server loads only the resident data families the user can read.
3. Server retrieves enabled platform shared knowledge and enabled active-organization knowledge.
4. LangChain generates a fixed structured analysis.
5. Server saves completed or failed history, writes audit logs, and returns structured JSON.
6. UI renders latest analysis, history, citations, recommendations, and restricted placeholders where permissions do not allow detail access.
## Module Boundaries
### New Module
Create `modules/ai/`:
- `modules/ai/types.ts`
- Zod-compatible validation helpers or TypeScript runtime validators for knowledge entries, analysis output, history status, risk levels, citation shapes, and data scopes.
- `modules/ai/server/config.ts`
- Reads `AI_BASE_URL`, `AI_API_KEY`, `AI_CHAT_MODEL`, `AI_EMBEDDING_MODEL`, and optional timeout/limit settings.
- `modules/ai/server/knowledge.ts`
- CRUD for knowledge entries.
- Chunking and embedding on save.
- Scope-filtered retrieval from pgvector.
- `modules/ai/server/elder-context.ts`
- Builds full resident graph with permission-aware optional data families.
- `modules/ai/server/analysis.ts`
- Runs LangChain analysis flow.
- Validates structured output.
- Saves completed/failed history.
- `modules/ai/components/KnowledgeManagementClient.tsx`
- Settings UI for manual knowledge entries.
- `modules/ai/components/ElderAiAnalysisDialog.tsx`
- Elder row dialog panel for generation, latest analysis, history, and citations.
### Existing Integration Points
- `modules/core/server/schema.ts`
- Add AI enums/tables.
- `modules/core/types.ts`
- Add permissions and exported AI-related shared types as needed.
- `modules/core/server/permissions.ts`
- Seed AI/knowledge permission definitions and default role grants.
- `app/api/ai/...`
- New API routes for knowledge and elder analysis.
- `app/(app)/app/settings/knowledge/page.tsx`
- New settings route.
- `app/(app)/app/[organizationSlug]/settings/knowledge/page.tsx`
- Scoped route re-export.
- `modules/shared/lib/navigation.ts`
- Add settings navigation item visible with `knowledge:read`.
- `modules/elders/components/EldersClient.tsx`
- Add per-row AI analysis action and dialog.
## Dependencies
Add runtime dependencies:
- `@langchain/openai`
- `@langchain/core`
- `langchain`
Add pgvector support through Drizzle/Postgres:
- Use PostgreSQL `vector` extension.
- Prefer a pgvector-capable Postgres image for local development instead of plain `postgres:17-alpine`.
- If Drizzle vector column support is insufficient for the exact version, use custom SQL migration for vector extension/index while keeping typed table metadata in schema.
Vercel AI SDK remains out of the MVP runtime path.
## Data Model
### Permissions
Add permission IDs:
- `ai:read`
- `ai:manage`
- `knowledge:read`
- `knowledge:manage`
Default grants:
- `platform_admin`: all four.
- `org_admin`: all four.
- `manager`: all four.
- `caregiver`: `ai:read`, `knowledge:read`.
- `viewer`, `family`, `resident`: none.
### Knowledge Entries
Table: `ai_knowledge_entries`
Fields:
- `id uuid primary key`
- `scope enum('platform', 'organization')`
- `organizationId uuid null references organizations(id) on delete cascade`
- `title text not null`
- `category text not null default ''`
- `tags text not null default ''`
- `body text not null`
- `status enum('enabled', 'disabled') not null default 'enabled'`
- `createdByAccountId uuid null references accounts(id)`
- `updatedByAccountId uuid null references accounts(id)`
- `createdAt timestamptz not null default now()`
- `updatedAt timestamptz not null default now()`
Rules:
- `scope='platform'` requires `organizationId IS NULL`.
- `scope='organization'` requires `organizationId` to match the active organization.
- Platform entries require platform-level authorization to manage.
- Organization entries require active organization plus `knowledge:manage`.
### Knowledge Chunks
Table: `ai_knowledge_chunks`
Fields:
- `id uuid primary key`
- `entryId uuid not null references ai_knowledge_entries(id) on delete cascade`
- `organizationId uuid null`
- `scope enum('platform', 'organization')`
- `chunkIndex integer not null`
- `content text not null`
- `embedding vector(<embedding_dimensions>) not null`
- `sourceTitle text not null`
- `sourceCategory text not null default ''`
- `createdAt timestamptz not null default now()`
Indexes:
- B-tree on `(scope, organization_id)`.
- Vector index for embedding similarity, using pgvector operator/index supported by deployment.
Embedding dimensions are tied to `AI_EMBEDDING_MODEL`; document the chosen default in env configuration.
### Elder AI Analyses
Table: `elder_ai_analyses`
Fields:
- `id uuid primary key`
- `organizationId uuid not null references organizations(id) on delete cascade`
- `elderId uuid not null references elders(id) on delete cascade`
- `actorAccountId uuid null references accounts(id)`
- `status enum('completed', 'failed') not null`
- `dataScopes text not null`
- `resultJson jsonb null`
- `citationsJson jsonb not null default '[]'::jsonb`
- `modelSummaryJson jsonb not null default '{}'::jsonb`
- `errorCategory text not null default ''`
- `errorReason text not null default ''`
- `createdAt timestamptz not null default now()`
Rules:
- Completed rows require a valid `resultJson`.
- Failed rows must not store prompt, full resident context, or raw provider errors.
- `dataScopes` stores a stable comma-separated list or JSON array; use JSONB if Drizzle ergonomics are acceptable.
## Authorization Model
### Generate Analysis
Required:
- Authenticated session.
- Active organization.
- `ai:read`.
- `elder:read`.
- Target elder belongs to active organization.
Optional data families:
- `health` only if `health:read`.
- `care` only if `care:read`.
- `family` only if `family:read`.
- `admission` only if `admission:read`.
- `facility` only if `facility:read`.
- `alert` only if `alert:read`.
- `incident` only if `incident:read`.
- `knowledge` only if `knowledge:read`.
`dataScopes` records exactly which families were included.
### View Analysis History
History list can show safe metadata for rows in the active organization and elder when the user has `ai:read` and `elder:read`.
History detail requires all permissions implied by `dataScopes`. If missing any scope, return or render a restricted placeholder and do not include `resultJson`/citations content.
### Knowledge Retrieval
Retrieval requires `knowledge:read`.
Eligible entries:
- `scope='platform'`, `status='enabled'`.
- `scope='organization'`, `organizationId = activeOrganizationId`, `status='enabled'`.
No organization-private chunks from other organizations may be retrievable.
## LangChain Flow
### Provider
Use OpenAI-compatible configuration:
- `AI_BASE_URL`
- `AI_API_KEY`
- `AI_CHAT_MODEL`
- `AI_EMBEDDING_MODEL`
`config.ts` validates required settings before generation/embedding and returns structured failures for missing configuration.
### Retrieval
1. Convert resident query/context summary into embedding.
2. Query `ai_knowledge_chunks` with pgvector similarity.
3. Filter by allowed scopes before vector ranking.
4. Return top K chunks with source metadata:
- source type: `knowledge`
- entry id
- chunk id
- title
- category
- scope
### Resident Context Assembly
Build one typed object:
- Elder basics.
- Current bed/admission context.
- Admission history.
- Health profile.
- Recent vitals.
- Chronic conditions.
- Health anomaly reviews.
- Care tasks.
- Alert triggers/rules where relevant.
- System incidents matched by elder/bed where relevant.
- Family contacts/visits/feedback.
- Retrieved knowledge snippets.
Use batch queries and `Promise.all` where independent. No `await` in loops.
### Structured Output
Output schema:
- `overallRiskLevel`: `low | medium | high | critical | unknown`
- `summary`: string
- `keyFindings`: array of:
- `category`: string
- `severity`: `info | warning | critical`
- `evidence`: string
- `citationIds`: string[]
- `recommendations`: array of:
- `priority`: `low | normal | high | urgent`
- `action`: string
- `reason`: string
- `citationIds`: string[]
- `dataGaps`: string[]
- `citations`: array of:
- `id`: string
- `sourceType`: `record | knowledge`
- `sourceId`: string
- `title`: string
- `excerpt`: string
- `confidence`: number from 0 to 1
- `modelSummary`: object with provider base URL host, chat model, embedding model, and generation timestamp, excluding API key.
Use LangChain structured output where possible. Validate again server-side before saving.
## API Design
### Elder Analysis
- `GET /api/ai/elders/[id]/analyses`
- Returns latest allowed analysis metadata/detail and history list.
- Redacts detail for records whose `dataScopes` exceed current permissions.
- `POST /api/ai/elders/[id]/analyses`
- Synchronously generates a new analysis.
- Returns `{ success: true, reason, analysis }` on completion.
- On failure, saves failed history and audit log, then returns `{ success: false, reason }`.
### Knowledge
- `GET /api/ai/knowledge`
- Requires `knowledge:read`.
- Lists platform and active organization entries visible to caller.
- `POST /api/ai/knowledge`
- Requires `knowledge:manage`.
- Creates entry, chunks, embeds, writes audit log.
- `PATCH /api/ai/knowledge/[id]`
- Requires `knowledge:manage`.
- Updates entry and rebuilds chunks/embeddings.
- `DELETE /api/ai/knowledge/[id]`
- Requires `knowledge:manage`.
- Deletes entry and cascading chunks.
All routes return the existing API response shape with `Cache-Control: no-store`.
## UI Design
### Elder List
In `EldersClient`:
- Add AI analysis button per row for users with `ai:read`.
- Use lucide icon such as `Sparkles`.
- Open `Dialog` with `width="wide"`.
- Dialog sections:
- Resident summary.
- Generate button with loading state.
- Latest analysis summary.
- Findings and recommendations.
- Data gaps.
- Citations.
- History list with restricted placeholders.
No chat input and no one-click business creation.
### Knowledge Settings Page
Route: `/settings/knowledge`
Features:
- List entries by scope/status/category/search.
- Create/edit dialog with title, body, category, tags, scope, enabled/disabled.
- Disable mutations when missing `knowledge:manage`.
- Show embedding/rebuild status through response messages; no background jobs.
## Audit And Logging
Audit actions:
- `ai.elderAnalysis.generate`
- `ai.elderAnalysis.generateFailed`
- `ai.knowledge.create`
- `ai.knowledge.update`
- `ai.knowledge.delete`
- `ai.knowledge.disable`
- `ai.knowledge.enable`
Audit records include actor, organization, target type/id, result, and sanitized reason.
Do not store:
- Full prompts.
- Full resident context snapshots.
- Raw provider errors.
- API keys.
## Failure Handling
Generation failures are classified:
- `missing_configuration`
- `provider_error`
- `timeout`
- `schema_validation_failed`
- `retrieval_failed`
- `unknown`
On failure:
1. Save failed analysis history with sanitized category/reason.
2. Write audit log.
3. Return structured API failure.
4. UI shows a concise error state and keeps prior completed history visible.
## Rollout And Compatibility
- Requires database migration for pgvector extension, AI enums, knowledge tables, chunk table, analysis table, and permissions.
- Local `compose.yaml` should use a pgvector-capable PostgreSQL image or document manual extension installation.
- Production deployment must verify `CREATE EXTENSION vector` support before migration.
- If AI env vars are missing, knowledge CRUD can still work except embedding-dependent save should fail gracefully; analysis generation should return a structured configuration failure.
## Open Trade-Offs
- Embedding dimensions depend on the selected embedding model. The implementation should define a default and document migration impact if changed.
- Synchronous generation is acceptable for MVP but can be replaced later by async jobs using the same history table status field.
- Platform shared knowledge increases utility but requires careful scope filters in every retrieval query.

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,305 @@
# AI Agent Knowledge Analysis Implementation Plan
## Preconditions
- User reviews and approves `prd.md`, `design.md`, and `implement.md`.
- Task status is moved from `planning` to `in_progress` with `task.py start`.
- Before editing code in Phase 2, load `trellis-before-dev` and relevant specs:
- `.trellis/spec/shared/code-quality.md`
- `.trellis/spec/shared/typescript.md`
- `.trellis/spec/shared/dependencies.md`
- `.trellis/spec/backend/directory-structure.md`
- `.trellis/spec/backend/database.md`
- `.trellis/spec/backend/authentication.md`
- `.trellis/spec/backend/type-safety.md`
- `.trellis/spec/backend/quality.md`
- `.trellis/spec/frontend/components.md`
- `.trellis/spec/frontend/type-safety.md`
- `.trellis/spec/frontend/quality.md`
## Implementation Checklist
### 1. Dependencies And Environment
- Add LangChain runtime packages:
- `langchain`
- `@langchain/core`
- `@langchain/openai`
- Decide and document default embedding dimension for `AI_EMBEDDING_MODEL`.
- Add env handling in `modules/ai/server/config.ts`:
- `AI_BASE_URL`
- `AI_API_KEY`
- `AI_CHAT_MODEL`
- `AI_EMBEDDING_MODEL`
- Update local PostgreSQL image or setup notes so pgvector is available.
Validation:
- `pnpm install`
- `pnpm type-check`
Rollback:
- Remove added dependencies and AI env usage if the AI module is backed out.
### 2. Database Schema And Migration
- Update `modules/core/server/schema.ts`:
- AI enums for knowledge scope/status and analysis status.
- `aiKnowledgeEntries`.
- `aiKnowledgeChunks`.
- `elderAiAnalyses`.
- Add pgvector extension migration support.
- Generate Drizzle migration with `pnpm db:generate`.
- Manually inspect generated SQL for:
- `CREATE EXTENSION IF NOT EXISTS vector`.
- Correct foreign keys.
- Correct indexes.
- Correct vector column type/dimension.
Validation:
- `pnpm db:generate`
- `pnpm type-check`
- Apply migration locally when DB is available: `pnpm db:migrate`.
Rollback:
- Drop AI tables and extension-dependent indexes in a rollback migration if needed.
- Do not drop `vector` extension automatically if other future objects may use it.
### 3. Permissions And Role Seeding
- Update `modules/core/types.ts`:
- Add `ai:read`, `ai:manage`, `knowledge:read`, `knowledge:manage`.
- Update `modules/core/server/permissions.ts`:
- Add permission definitions.
- Update default role grants:
- `platform_admin`: all four.
- `org_admin`: all four.
- `manager`: all four.
- `caregiver`: `ai:read`, `knowledge:read`.
- `viewer`, `family`, `resident`: none.
- Ensure `ensureSystemDefaults()` upserts permissions and role grants without deleting custom grants.
Validation:
- `pnpm type-check`
- Targeted tests or assertions for seeded permission definitions and role grants.
Rollback:
- Remove permissions from default grants only through an intentional migration/seed change; avoid deleting custom user role state.
### 4. AI Domain Types
- Create `modules/ai/types.ts`.
- Define:
- Knowledge entry input and status/scope values.
- Analysis status values.
- Data scope values.
- Risk/severity/priority values.
- Structured analysis output type and runtime validator.
- Citation type.
- Sanitized error category type.
- Avoid `any`; use explicit `unknown` validation helpers when parsing API bodies or AI output.
Validation:
- Unit tests for validators if they contain non-trivial parsing.
- `pnpm type-check`
Rollback:
- Types are isolated; remove module if feature is backed out.
### 5. Knowledge Service
- Create `modules/ai/server/knowledge.ts`.
- Implement:
- List visible entries by permission and active organization.
- Create/update/delete entries with scope validation.
- Server-side chunking.
- Embedding generation through LangChain OpenAI-compatible embeddings.
- Chunk rebuild on create/update.
- Scope-filtered retrieval for platform shared + active organization entries.
- Write audit logs for create/update/delete/enable/disable.
- Ensure no organization-private chunks from other organizations can be retrieved.
Validation:
- Tests for scope filtering:
- Platform shared visible to organization user.
- Own organization visible.
- Other organization not visible.
- Disabled entries not retrieved.
- Tests for mutation permission failures.
Rollback:
- Disable `/settings/knowledge` navigation and API routes if embedding is unavailable.
- Existing tables can remain unused.
### 6. Elder Context Service
- Create `modules/ai/server/elder-context.ts`.
- Implement permission-aware resident graph assembly:
- Always include elder basics after `elder:read`.
- Include health only with `health:read`.
- Include care only with `care:read`.
- Include family only with `family:read`.
- Include admission/facility only with matching read permissions.
- Include alert/incident only with matching read permissions.
- Include knowledge only with `knowledge:read`.
- Return `dataScopes` exactly matching included families.
- Use batch queries and `Promise.all` for independent loads.
Validation:
- Tests for data family inclusion/exclusion by permissions.
- Tests that target elder must belong to active organization.
Rollback:
- This service is isolated under `modules/ai`; remove API use if feature is disabled.
### 7. Analysis Service
- Create `modules/ai/server/analysis.ts`.
- Implement:
- Generate structured analysis synchronously with LangChain.
- Validate AI output against fixed schema.
- Save `completed` history with result/citations/model summary/data scopes.
- On failure, save `failed` history with sanitized error category/reason.
- Write success/failure audit logs.
- Redact history details when current permissions do not satisfy stored `dataScopes`.
- Do not store full prompts, full context snapshots, API keys, or raw provider errors.
Validation:
- Tests for:
- Missing config -> structured failure + failed history.
- Schema validation failure -> failed history.
- History detail redaction when permissions are insufficient.
- Completed history shape.
Rollback:
- Keep history rows as audit evidence; hide UI/API if provider issues block rollout.
### 8. API Routes
- Add elder analysis routes:
- `app/api/ai/elders/[id]/analyses/route.ts`
- `GET`: latest/history with permission-aware redaction.
- `POST`: synchronous generation.
- Add knowledge routes:
- `app/api/ai/knowledge/route.ts`
- `GET`, `POST`.
- `app/api/ai/knowledge/[id]/route.ts`
- `PATCH`, `DELETE`.
- Use existing `requirePermission`, `jsonSuccess`, `jsonFailure`, `readJsonBody`, and `recordAuditLog` patterns.
- Preserve `Cache-Control: no-store` through existing helpers.
Validation:
- Route tests for auth/permission failures and structured responses.
- `pnpm type-check`
Rollback:
- Remove routes or return feature-disabled failures while keeping data.
### 9. Knowledge Settings UI
- Create:
- `app/(app)/app/settings/knowledge/page.tsx`
- `app/(app)/app/[organizationSlug]/settings/knowledge/page.tsx`
- `modules/ai/components/KnowledgeManagementClient.tsx`
- Update:
- `modules/shared/lib/navigation.ts`
- `modules/shared/components/AppSidebarNav.tsx` icon map if a new icon key is needed.
- UI supports:
- List/filter/search.
- Create/edit dialog.
- Enable/disable/delete.
- Read-only view if missing `knowledge:manage`.
- Follow existing settings/client component patterns.
Validation:
- Manual browser check.
- Type-check/lint.
Rollback:
- Remove nav entry and route.
### 10. Elder Analysis UI
- Update `modules/elders/components/EldersClient.tsx`.
- Add `AI 分析` row action for authorized users.
- Create `modules/ai/components/ElderAiAnalysisDialog.tsx`.
- Dialog displays:
- Elder summary.
- Generate button/loading state.
- Latest completed analysis.
- Failed state if latest attempt failed.
- Findings, recommendations, data gaps.
- Citations.
- History list with restricted placeholders.
- No chat input.
- No one-click business mutation.
Validation:
- Manual browser check for desktop/mobile widths.
- Confirm button text fits and table layout remains stable.
- Verify unauthorized users do not see the action.
Rollback:
- Remove row action; API/history remains dormant.
### 11. Quality Gate
Run:
- `pnpm lint`
- `pnpm type-check`
- `pnpm test` if route/domain tests were added.
- `pnpm build` before final completion if time and environment allow.
Manual checks:
- Knowledge CRUD as authorized admin/manager.
- Knowledge read-only as caregiver.
- Elder analysis generation with configured AI provider.
- Missing AI config failure.
- History redaction across roles.
- Other-organization knowledge cannot appear in retrieval.
## Risky Files
- `modules/core/server/schema.ts`
- `modules/core/types.ts`
- `modules/core/server/permissions.ts`
- `compose.yaml`
- `drizzle/*`
- `modules/elders/components/EldersClient.tsx`
- `modules/shared/lib/navigation.ts`
## Rollback Points
- After dependency install: revert package changes if LangChain package compatibility fails.
- After schema migration generation: inspect SQL before applying.
- After permissions update: verify default role grants before touching UI.
- After API routes: keep UI disabled until permission and scope tests pass.
- After UI: remove nav/row entry if backend generation is not production-ready.
## Follow-Up Checks Before Start
- Confirm pgvector deployment support on the target PostgreSQL server.
- Pick and document embedding dimensions for the configured default embedding model.
- Confirm production AI provider and env var names.
- Decide whether to add seed knowledge entries for smoke testing.

View File

@@ -0,0 +1,158 @@
# AI agent knowledge analysis
## Goal
Add an AI capability layer for TeaTea Pension that can use agent-style orchestration, retrieve organization-scoped knowledge, and provide AI analysis in relevant care-operation workflows.
The MVP workflow is elder profile AI analysis: given one resident, the system should gather authorized, organization-scoped resident context, retrieve relevant knowledge, and produce a cited, structured operational analysis for staff review.
The MVP interaction is a fixed "generate AI analysis" panel, not a conversational assistant. The panel should produce structured output such as risk summary, key evidence, recommended actions, source citations, and confidence.
The MVP entry point should be an "AI 分析" action in each elder table row. Clicking it opens a `wide` dialog panel that shows elder context, a generate action, the latest analysis, analysis history, and citations.
The MVP resident context should use the full available resident graph: elder basics, current bed/admission context, admission history, health profile, recent vitals, chronic conditions, health anomaly reviews, care tasks, alerts/incidents, family feedback, and relevant knowledge-base snippets.
MVP knowledge-base content should be manually maintained as structured knowledge entries with title, body, category/tags, scope, organization scope when applicable, and enabled status. On save, the server should chunk and embed the content into pgvector-backed storage. File upload, OCR, web crawling, and external-system sync are out of scope for MVP ingestion.
MVP retrieval should support platform shared knowledge plus organization-private knowledge. Ordinary organization users can retrieve enabled platform shared entries and enabled entries for their active organization. Platform knowledge is maintained by platform-level authorized users; organization knowledge is maintained by organization-level authorized users.
MVP must include a `/settings/knowledge` management page under the existing settings area. The sidebar entry should live in the "管理系统" group, be visible to `knowledge:read`, and allow create/edit/enable/disable/delete only with `knowledge:manage`.
MVP elder analyses should be persisted as history records with organization, elder, actor, structured result, source citations, model configuration summary, status, and creation time. Persisting history supports auditability, comparison over time, and token-cost control.
MVP AI analysis output must be a fixed structured schema rendered by the frontend rather than free-form text. The schema should include `overallRiskLevel`, `summary`, `keyFindings[]`, `recommendations[]`, `dataGaps[]`, `citations[]`, `confidence`, and `modelSummary`. Findings should include category, severity, evidence, and citation references. Recommendations should remain advisory and must not create business drafts.
MVP analysis generation should run synchronously in the request. The UI should show a loading state while generation is in progress. Analysis history should support at least `completed` and `failed` statuses so failures are visible and auditable without requiring an async job system.
On generation failure, the system should save a `failed` analysis history record and write an audit log. Failed history records must store only sanitized error category and brief reason, such as `provider_error`, `timeout`, or `schema_validation_failed`; they must not store full prompts, resident context, sensitive data, or raw provider errors.
Each analysis history record must store `dataScopes` describing the data families used to generate it, such as `elder`, `health`, `care`, `family`, `admission`, `alert`, `incident`, and `knowledge`. Viewing a history detail requires `ai:read`, `elder:read`, active organization access, and all read permissions implied by that record's `dataScopes`; otherwise the UI should show a restricted placeholder rather than the analysis content.
The broader planning objective still covers:
- LangChain + agent module.
- Knowledge-base retrieval.
- AI analysis from multiple places in the product.
The feature should improve operational decision support without weakening tenant isolation, role permissions, auditability, or existing deterministic workflows.
## Confirmed Facts
- The application is a single-repo Next.js 15 / React 19 / TypeScript app with API routes, Drizzle ORM, PostgreSQL, and TailwindCSS. See `package.json`.
- Runtime dependencies currently do not include `ai`, `@ai-sdk/*`, `langchain`, vector database clients, or embedding providers. See `package.json`.
- Local infrastructure currently runs a single `postgres:17-alpine` service, and Drizzle migrations are generated from `modules/core/server/schema.ts`. There is no existing pgvector extension or external vector database configuration. See `compose.yaml` and `drizzle.config.ts`.
- Project specs already contain Vercel AI SDK guidance for backend and frontend AI features in `.trellis/spec/backend/ai-sdk-integration.md` and `.trellis/spec/frontend/ai-sdk-integration.md`.
- The actual backend code uses `modules/core/server/*` for database, auth, audit, permissions, and schema; it is not using the package layout shown in some generic specs.
- Authentication uses a custom `teatea_session` cookie and Drizzle-backed `sessions`, `accounts`, `organizations`, `memberships`, `roles`, and permissions. See `modules/core/server/auth.ts` and `.trellis/spec/backend/authentication.md`.
- Most operational tables are tenant-scoped by `organizationId`, and resident-specific data often includes `elderId`. See `modules/core/server/schema.ts`.
- Current permission families include care, health, device, notice, alert, family, facility, admission, elder, incident, audit, role, account, organization, and platform permissions. See `modules/core/types.ts` and `modules/core/server/permissions.ts`.
- The dashboard already aggregates elders, beds, admissions, care tasks, health reviews, and emergency incidents with permission checks. See `app/(app)/app/dashboard/page.tsx`.
- Health currently has deterministic anomaly detection for vital records and creates health anomaly reviews from thresholds. See `modules/health/server/operations.ts`.
- Alerting already has rule and trigger tables that can represent warnings and suggested handling. See `modules/alerts/server/operations.ts` and `modules/core/server/schema.ts`.
- Existing elder/bed operational context can already attach permission-filtered care task, health review/vital, and incident lines to elders. See `modules/operations/server/context.ts`.
- Health profile, vital, chronic condition, anomaly review, family contact, visit, feedback, and admission data exist but would need an AI-specific resident context assembler to gather one resident's full analysis input. See `modules/health/server/operations.ts`, `modules/family/server/operations.ts`, and `modules/core/server/operations.ts`.
- API responses use `{ success, reason, ...payload }` and `Cache-Control: no-store`. See `modules/core/server/api.ts`.
## Requirements
- The AI module must preserve organization isolation: every retrieval, prompt context, generated analysis, and generated action draft must be scoped to the authenticated active organization unless the caller has a platform-level reason to cross scopes.
- The AI module must preserve existing role permissions: analysis endpoints must require the same read permissions as the underlying data they inspect, and any write/action-producing workflow must require the matching manage permission.
- MVP authorization must add dedicated AI and knowledge permissions while still applying underlying domain data permissions.
- Dedicated MVP permission points should include `ai:read`, `ai:manage`, `knowledge:read`, and `knowledge:manage`.
- Generating elder AI analysis must require `ai:read`, `elder:read`, an active organization, and only include optional resident-context data when the caller has the matching read permission such as `health:read`, `care:read`, `alert:read`, `family:read`, `admission:read`, `facility:read`, or `incident:read`.
- Analysis history details must be filtered by stored `dataScopes` so users cannot view generated content derived from data families they cannot currently read.
- Maintaining knowledge entries must require `knowledge:manage`; using retrieved knowledge in analysis must require `knowledge:read`.
- Default role seeding should grant:
- `platform_admin`: `ai:read`, `ai:manage`, `knowledge:read`, `knowledge:manage`.
- `org_admin`: `ai:read`, `ai:manage`, `knowledge:read`, `knowledge:manage`.
- `manager`: `ai:read`, `ai:manage`, `knowledge:read`, `knowledge:manage`.
- `caregiver`: `ai:read`, `knowledge:read`.
- `viewer`, `family`, and `resident`: no AI or knowledge permissions by default.
- The first implementation should add a shared server-side AI boundary rather than scattering provider calls across feature modules.
- The AI boundary must allow future provider/model changes without touching each product surface.
- MVP AI orchestration must use LangChain as the primary server-side runtime for model calls, retrieval chains, agent/tool orchestration, and structured analysis output.
- Vercel AI SDK should not be part of the MVP runtime path; keep it as a future option for conversational or streaming UI work.
- MVP model access must use an OpenAI-compatible configurable provider interface with environment variables for base URL, API key, chat model, and embedding model.
- The MVP UI must be a structured analysis panel attached to the elder profile/list workflow rather than a free-form chat assistant.
- The MVP elder UI entry point must be an "AI 分析" action in each elder table row that opens a `wide` dialog panel.
- MVP knowledge management UI must be available at `/settings/knowledge` with read access gated by `knowledge:read` and mutations gated by `knowledge:manage`.
- The MVP analysis input must use the full available resident graph when the caller has matching read permissions.
- MVP LangChain agent/tools must be read-only. They may load resident context, retrieve knowledge, and read analysis history, but must not provide mutation tools.
- Knowledge retrieval must return source metadata that the UI can display or store with the analysis, so AI outputs are traceable to underlying records or documents.
- MVP knowledge retrieval must store chunks and embeddings in PostgreSQL with pgvector, reusing the existing Drizzle/Postgres persistence boundary.
- MVP knowledge ingestion must support manually maintained knowledge entries and server-side chunking/embedding on save.
- MVP knowledge retrieval must include enabled platform shared knowledge plus enabled active-organization private knowledge, filtered by caller permissions.
- AI analysis must be advisory by default. MVP output is limited to summaries, risk notes, recommendations, data gaps, and citations; future action-draft workflows require separate requirements and explicit user confirmation.
- MVP elder AI analysis must only display structured recommendations. It must not create confirmable business drafts or one-click mutations for care tasks, alert triggers, health review notes, or other operational records.
- MVP elder AI analyses must be saved as history records after generation.
- MVP generation should run synchronously and save completed or failed history status.
- Failed generation attempts must persist sanitized failed history and write audit logs.
- Generated outputs must use structured schemas where the product needs machine-readable data, such as severity, confidence, citations, suggested next steps, or draft entity payloads.
- MVP elder analysis output must use a fixed JSON-compatible schema with risk level, summary, findings, recommendations, data gaps, citations, confidence, and model summary.
- AI calls must have observable failure handling: user-facing failures should be structured, and server logs/audit records should identify operation type, actor, organization, target, and result.
## Candidate Product Surfaces
- MVP: Elder profile analysis: summarize one resident's health notes, vitals, care tasks, alerts, admissions, and family feedback.
- Dashboard executive summary: summarize current risk across care, health, beds, admissions, and incidents.
- Health anomaly explanation: explain why a review matters and suggest follow-up questions or actions.
- Alert and incident triage: summarize context, recommend severity/status, and draft handling notes.
- Care operations assistant: answer operational questions from scoped data and draft care-task notes.
- Knowledge-base Q&A: retrieve internal policies, care protocols, notices, facility SOPs, and relevant system records.
## Constraints
- Do not bypass deterministic rules already present in health anomaly detection and alert handling; AI should augment those flows first.
- Do not introduce unscoped vector search that can leak data across organizations.
- Do not introduce a separate vector database/service for the MVP.
- Do not let AI-specific permissions bypass the underlying domain permissions for resident, health, care, family, alert, admission, facility, device, or incident data.
- Do not show analysis history content to users who lack read permission for any stored `dataScopes` used by the analysis.
- Do not expose organization-private knowledge to other organizations through platform shared retrieval.
- Do not include file upload, document parsing, OCR, crawling, or external knowledge sync in the MVP.
- Do not make knowledge maintenance API-only; MVP needs a user-facing settings page.
- Do not store full prompts or sensitive resident data in third-party logs unless explicitly configured and reviewed.
- Do not store full prompts, resident context, sensitive data, or raw provider errors in failed history records.
- Do not hard-code a single model vendor into feature modules.
- Do not treat generated AI analysis history as an authoritative clinical record; it is advisory operational support.
- Do not add "one-click create" or confirmable draft business mutations from MVP AI analysis output.
- Do not define mutation-capable LangChain tools in the MVP, even if they are not exposed in the UI.
- Do not require frontend components to know provider-specific APIs.
- Do not start implementation until `design.md` and `implement.md` exist and the user has approved the final planning artifacts.
## Acceptance Criteria
- [ ] PRD identifies elder profile AI analysis as the MVP user workflow and fixed structured analysis panel as the MVP interaction.
- [ ] Elder list rows expose an AI analysis action for authorized users and open a wide analysis dialog.
- [ ] PRD defines the full resident graph as MVP data sources and identifies out-of-scope AI surfaces.
- [ ] Technical design defines the AI server module boundary, provider/orchestration choice, RAG storage strategy, authorization model, source-citation contract, audit/logging contract, and fallback behavior.
- [ ] MVP implementation uses LangChain as the server-side AI orchestration layer and does not introduce Vercel AI SDK runtime code.
- [ ] MVP model configuration supports OpenAI-compatible `AI_BASE_URL`, `AI_API_KEY`, `AI_CHAT_MODEL`, and `AI_EMBEDDING_MODEL` style settings.
- [ ] Implementation plan defines ordered steps, validation commands, risky files, migration needs, and rollback points.
- [ ] Any implemented AI analysis endpoint checks authentication, active organization, and required permissions before loading data or retrieving knowledge.
- [ ] MVP adds and seeds `ai:read`, `ai:manage`, `knowledge:read`, and `knowledge:manage` permission definitions and assigns them to appropriate default operational/admin roles.
- [ ] Default role permission seeding matches the agreed AI/knowledge grants for platform admins, org admins, managers, caregivers, viewers, family users, and residents.
- [ ] Elder analysis context only includes each optional data family when the caller has the matching domain read permission.
- [ ] Analysis history records store `dataScopes`, and history detail visibility is denied or redacted when the current user lacks any required scope permission.
- [ ] Any implemented retrieval mechanism stores and filters knowledge by organization scope and returns source metadata with each retrieval result.
- [ ] MVP retrieval uses PostgreSQL + pgvector with Drizzle migrations and organization-scoped query filters.
- [ ] MVP knowledge entries can be manually maintained and embedded into searchable chunks with source metadata.
- [ ] `/settings/knowledge` exists for authorized users and supports list, filter, create, edit, enable/disable, and delete workflows.
- [ ] Knowledge retrieval includes only enabled platform shared knowledge and enabled knowledge for the caller's active organization.
- [ ] MVP AI analysis output contains recommendations only and does not create or persist business drafts/actions beyond the analysis history record itself.
- [ ] MVP LangChain tools are read-only and cannot mutate care, health, alert, family, admission, facility, incident, or knowledge data.
- [ ] MVP AI analysis responses and persisted history use the fixed structured output schema.
- [ ] Generated elder analyses are persisted with actor, elder, organization, structured result, citation metadata, model summary, status, and timestamps.
- [ ] Synchronous generation shows a loading state and returns structured API success/failure responses.
- [ ] Failed generation attempts persist sanitized failed history records and audit logs without full prompts or raw sensitive context.
- [ ] Quality verification includes `pnpm lint`, `pnpm type-check`, and targeted tests for permission scoping and structured failure behavior.
## Likely Out of Scope For MVP
- Fully autonomous agents that mutate production records without a user confirmation step.
- Cross-organization analytics for ordinary organization users.
- Replacing existing deterministic anomaly rules with AI-only decisions.
- Conversational AI/chat UX for MVP elder analysis.
- Real-time streaming chat across the whole product.
- Async generation jobs, background workers, polling, and retry queues.
- Building a full document-management system before the minimum knowledge-ingestion path is defined.
- PDF/Word upload, OCR, web crawling, and external-system knowledge synchronization.

View File

@@ -0,0 +1,26 @@
{
"id": "ai-agent-knowledge-analysis",
"name": "ai-agent-knowledge-analysis",
"title": "AI agent knowledge analysis",
"description": "",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "TalexDreamSoul",
"assignee": "TalexDreamSoul",
"createdAt": "2026-07-04",
"completedAt": "2026-07-05",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,74 @@
# Harden AI Analysis Path Design
## Overview
This change is an in-place hardening pass over the existing AI MVP. It keeps the current module boundaries and synchronous workflow, then adds tests and narrows citation behavior.
## Boundaries
### Backend AI module
- `modules/ai/types.ts`
- Keep runtime validators as the source of truth for output shape.
- Add/adjust helpers only if needed for citation ID validation.
- `modules/ai/server/analysis.ts`
- Validate model output against the allowed citation set before persisting a completed history row.
- Derive final persisted/display citations from citation IDs actually referenced by findings/recommendations.
- Keep all provider and validation failures sanitized.
- `modules/ai/server/knowledge.ts`
- Preserve current save-time embedding and scope-filtered retrieval behavior.
- Add tests around existing behavior rather than introducing background embedding state.
### API routes
- `app/api/ai/elders/[id]/analyses/route.ts`
- `app/api/ai/knowledge/route.ts`
- `app/api/ai/knowledge/[id]/route.ts`
Route behavior stays unchanged except tests protect permission ordering, structured failures, and audit behavior.
### Frontend
- `modules/ai/components/KnowledgeManagementClient.tsx`
- Make AI configuration / vector generation failures more explicit.
- `modules/ai/components/ElderAiAnalysisDialog.tsx`
- Make failed latest history state clearer without exposing raw errors.
### Permissions
- `modules/core/server/permissions.ts`
- Clarify `ai:manage` as reserved/currently governance-oriented wording only.
## Citation Contract
Allowed citations are built by `buildElderAiContext` plus knowledge retrieval. The model may reference only these IDs.
Final persisted `result.citations` must be derived from actual references:
1. Collect citation IDs from all `keyFindings[].citationIds` and `recommendations[].citationIds`.
2. If any referenced ID is absent from the allowed citation map, reject the output as `schema_validation_failed`.
3. Persist/display only the allowed citations whose IDs were referenced.
4. Preserve stable ordering from the original allowed citation list.
This avoids showing unused evidence and prevents hallucinated source IDs.
## Test Strategy
Tests should mock database/provider boundaries and assert observable contracts, not implementation details.
- Validator tests use pure functions.
- Knowledge tests mock `getDatabase` and `OpenAIEmbeddings` or use light fake query builders where practical.
- Analysis tests mock config, elder context, knowledge retrieval, model invocation, database insert, and audit logging.
- Route tests mock `requirePermission`, AI services, validation as needed, and audit logging.
## Compatibility
No new dependency is required.
Deployment hardening additionally changes the still-unapplied `0007` AI migration from pgvector to JSONB embeddings, so production PostgreSQL 18 Alpine can run the migration without a `vector` extension.
Existing persisted `resultJson.citations` remain readable; new analyses will have stricter citation lists.
## Rollback
- Revert citation strictness if a provider cannot satisfy citation references, but keep tests documenting expected loosened behavior.
- Revert UI message changes independently if copy causes product concern.
- Tests are additive and can stay even if implementation is adjusted.

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,44 @@
# Harden AI Analysis Path Implementation Plan
## Steps
1. Add pure validator tests for `modules/ai/types.ts`.
2. Add/adjust analysis citation helper behavior in `modules/ai/server/analysis.ts`.
3. Add analysis service tests for missing config, invalid output, provider errors, retrieval degradation, and redaction.
4. Add knowledge service tests for scope, disabled entries, organization isolation, and embedding failure.
5. Add AI route tests for permission failures, invalid input, service failure propagation, success responses, and audit calls.
6. Improve knowledge UI and analysis dialog failure copy.
7. Clarify `ai:manage` permission description.
8. Align AI knowledge embedding storage to JSONB for production PostgreSQL without pgvector.
9. Run targeted AI tests, then project verification.
## Validation Commands
- `pnpm test -- modules/ai/types.test.ts`
- `pnpm test -- modules/ai/server/analysis.test.ts modules/ai/server/knowledge.test.ts app/api/ai/ai-routes.test.ts`
- `pnpm lint`
- `pnpm type-check`
- `pnpm test`
- `pnpm build`
## Risky Files
- `modules/ai/server/analysis.ts`
- `modules/ai/server/knowledge.ts`
- `modules/ai/types.ts`
- `app/api/ai/*/route.ts`
- `modules/core/server/permissions.ts`
## Review Gates
- Citation helper must reject unknown citation IDs and avoid appending unused citations.
- Tests must not call real AI providers.
- Tests must not require a live PostgreSQL instance unless explicitly scoped to migration smoke testing.
- No new `any`, non-null assertions, `@ts-ignore`, or `@ts-expect-error`.
- Failed history records must remain sanitized.
## Rollback Points
- After citation strictness: revert helper and related tests if provider output compatibility proves too brittle.
- After route tests: routes should remain behavior-compatible except status/copy assertions.
- After UI copy: copy changes can be reverted without affecting backend contracts.

View File

@@ -0,0 +1,49 @@
# Harden AI Analysis Path PRD
## Goal
Harden the existing elder AI analysis and knowledge retrieval MVP so the critical safety boundaries are covered by tests and the displayed evidence chain matches the citations actually used by the model output.
## Background
The current MVP now implements LangChain-backed elder AI analysis, PostgreSQL JSONB-backed knowledge retrieval, permission-scoped resident context, persisted analysis history, and knowledge management UI.
Current evidence from repository inspection:
- AI service files live under `modules/ai/`.
- API routes live under `app/api/ai/`.
- AI tables and JSONB embedding storage are in `modules/core/server/schema.ts` and `drizzle/0007_purple_puff_adder.sql`; the production target does not require PostgreSQL `vector` extension support.
- Existing project verification passes: `pnpm lint`, `pnpm type-check`, and `pnpm test`.
- No AI-specific test file currently covers schema validation, permission scoping, history redaction, knowledge organization isolation, disabled knowledge exclusion, or provider failure behavior.
- `normalizeCitations` currently appends allowed citations that the model did not necessarily cite, weakening the evidence chain.
## Requirements
1. Add focused automated tests for AI validators, knowledge service behavior, analysis service behavior, and AI route authorization/response behavior.
2. Tighten citation handling so the persisted/rendered `citations` list is derived from citation IDs actually referenced by findings and recommendations.
3. Reject AI outputs whose finding or recommendation `citationIds` include IDs not present in the allowed context/knowledge citation set.
4. Keep failed analysis history sanitized: no full prompt, resident context, API key, or raw provider error.
5. Preserve the existing synchronous generation MVP behavior; do not introduce queues, streaming, background jobs, new dependencies, or a new vector store.
6. Improve user-facing failure text where configuration/vector generation failure would otherwise look like a generic save/generation failure.
7. Clarify `ai:manage` as a reserved/future governance permission instead of implying a currently implemented management surface.
## Acceptance Criteria
- `validateKnowledgeEntryInput`, `validateElderAiAnalysisOutput`, and `parseDataScopes` have direct unit coverage for valid and invalid edge cases.
- Knowledge tests cover writable scope failures, disabled entry exclusion, platform/current-organization visibility, other-organization exclusion, and embedding failure behavior without hitting a real provider.
- Analysis tests cover missing config, provider error, schema validation failure, retrieval degradation, and history redaction without hitting a real provider.
- AI route tests cover missing `ai:read`, missing `elder:read`, missing `knowledge:read`, missing `knowledge:manage`, invalid request body, service failure status propagation, and success audit behavior.
- Citation normalization no longer appends unused allowed citations.
- Any unknown citation ID in findings/recommendations causes a structured schema validation failure and failed history record.
- Knowledge UI and AI analysis dialog keep sensitive details out of failure messages while making configuration/vector-generation failures understandable.
- `ai:manage` permission description no longer overpromises current UI/API capability.
- `pnpm lint`, `pnpm type-check`, `pnpm test`, and `pnpm build` pass.
## Out of Scope
- Async generation jobs.
- Streaming AI responses.
- AI usage billing/rate limiting.
- AI configuration UI.
- File upload/OCR/web crawling knowledge ingestion.
- AI-generated business record drafts or one-click mutations.

View File

@@ -0,0 +1,26 @@
{
"id": "07-06-harden-ai-analysis-path",
"name": "07-06-harden-ai-analysis-path",
"title": "Harden AI Analysis Path",
"description": "Add tests and tighten citation evidence chain for the AI knowledge analysis MVP.",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P1",
"creator": "TalexDreamSoul",
"assignee": "TalexDreamSoul",
"createdAt": "2026-07-05",
"completedAt": "2026-07-06",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1,4 @@
{"file":".trellis/spec/backend/type-safety.md","reason":"Check backend AI aggregation and seed helper typing."}
{"file":".trellis/spec/frontend/components.md","reason":"Check dashboard rendering remains Server Component and uses persisted data only."}
{"file":".trellis/spec/shared/code-quality.md","reason":"Check focused tests, no dead code, no console logs, and no forbidden TypeScript patterns."}
{"file":".trellis/spec/guides/pre-implementation-checklist.md","reason":"Check cross-layer dashboard data flow and permission gates."}

View File

@@ -0,0 +1,61 @@
# Design
## Boundaries
This task is a completion pass over existing persisted modules. It does not add schema or a new AI provider path.
- Collaboration data remains owned by `modules/devices`, `modules/notices`, `modules/alerts`, and `modules/family`.
- Dashboard data loading stays in the Server Component at `app/(app)/app/dashboard/page.tsx`.
- Presentation stays in `modules/dashboard/components/DashboardHome.tsx`.
- AI board aggregation belongs in `modules/ai/server/analysis.ts` because redaction depends on existing AI analysis scope rules.
## Data Flow
1. Dashboard page loads auth context and checks whether any visible dashboard section is permitted.
2. It conditionally calls existing operations for core care/health/emergency and collaboration modules.
3. It calls a new AI analysis listing function only when `ai:read` is present.
4. The AI service lists organization-scoped `elder_ai_analyses`, joins elders for display names, and maps each row through `canViewAnalysisScopes`.
5. The dashboard component receives only serializable data and renders queue cards/summary cards from that data.
## Seeding Strategy
Existing `seedDefaultWorkspaceData` exits early when core room/bed/elder/admission rows already exist. That protected existing workspaces, but it skipped collaboration seed records added later. Add a separate idempotent collaboration seeding helper that:
- reads existing rows by stable natural keys per table;
- inserts only missing default rows;
- resolves dependencies from existing or newly inserted rows;
- never updates or deletes existing operator data;
- runs both for fully new workspaces and for already-initialized workspaces.
Stable keys:
- devices: `code`
- tickets: `title`
- notices: `title`
- alert rules: `name`
- alert triggers: `title`
- family contacts: `elderId + name`
- family visits: `elderId + contactId + scheduled offset/status/notes` is not stable across reruns, so use `elderId + contactId + notes` for default records
- family feedback: `elderId + contactId + content`
## Permission Model
- Dashboard visibility expands to collaboration and AI read permissions.
- Collaboration sections call server operations only when their read permission is present.
- AI board calls the aggregation function only with `ai:read`.
- Redaction uses the existing `canViewAnalysisScopes` and `getPermissionForDataScope` rules, so a user may see that an analysis exists without seeing restricted result details.
## UI Shape
- Keep `DashboardHome` as a single Server-rendered component with small local helper render functions.
- Add collaboration queue cards for device tickets, notices, alert triggers, and family items.
- Add an AI analysis board card with risk/status badges, data scopes, created time, and a link back to the elder workspace.
- Empty states must be honest: no generated counts or placeholder rows.
## Compatibility
No schema migration is required. The seeding helper is additive and only inserts missing default data.
## Rollback
Rollback is limited to the AI aggregation helper, dashboard props/rendering, dashboard page conditional loads, and the additive seed helper. Existing collaboration CRUD modules remain untouched unless tests expose a direct integration bug.

View File

@@ -0,0 +1,5 @@
{"file":".trellis/spec/backend/type-safety.md","reason":"Backend service results, scope narrowing, and no non-null assertions."}
{"file":".trellis/spec/frontend/components.md","reason":"Dashboard Server Component data loading, Kumo UI adapter use, and no fake operational data."}
{"file":".trellis/spec/shared/typescript.md","reason":"Shared TypeScript constraints for exported types and discriminated unions."}
{"file":".trellis/spec/shared/code-quality.md","reason":"No any, no non-null assertions, import ordering, and test expectations."}
{"file":".trellis/spec/guides/pre-implementation-checklist.md","reason":"Cross-layer readiness checks before dashboard and seed changes."}

View File

@@ -0,0 +1,34 @@
# Implementation Plan
## 1. Planning and Context
- [x] Read Trellis workflow context and relevant backend/frontend/shared specs.
- [x] Inspect existing collaboration modules, dashboard page/component, AI analysis service, permissions, and seed data.
- [x] Persist PRD, design, implementation plan, and curated spec manifests.
## 2. Seeding
- [x] Extract idempotent collaboration seed helper from the full-workspace seed path.
- [x] Call the helper for both existing core workspaces and newly seeded workspaces.
- [x] Preserve existing operator rows and insert only missing default records.
## 3. AI Analysis Board
- [x] Add an organization-scoped AI board listing function that joins elder names and redacts restricted analysis rows.
- [x] Add serializable dashboard board item types.
- [x] Keep existing elder-specific analysis APIs unchanged.
## 4. Dashboard Integration
- [x] Expand dashboard permission gate to collaboration and AI read permissions.
- [x] Conditionally load devices, notices, alerts, family, and AI board data by permission.
- [x] Render collaboration cards and AI analysis board from persisted data only.
- [x] Preserve slug-aware links via `getWorkspaceHref`.
## 5. Verification
- [x] Add focused tests for collaboration seed idempotency and AI board redaction.
- [x] Run focused tests covering changed code.
- [x] Run `pnpm type-check`.
- [x] Smoke-test the dashboard route when feasible via `pnpm build` route compilation.
- [x] Add latency controls for AI provider timeout, token cap, retry count, and sanitized timeout failure history.

View File

@@ -0,0 +1,30 @@
# Complete operations modules and AI analysis board
## Goal
Finish the currently visible operations surface by making collaboration modules fully reachable from the workspace/dashboard and adding a real persisted AI analysis board that summarizes elder AI analysis history without fabricating data.
## Requirements
- Keep the existing persisted collaboration modules for devices, notices, alerts, and family visible through permission-aware workspace navigation and dashboard entry points.
- Seed collaboration-module records idempotently for already-initialized local/demo organizations without overwriting operator-created records.
- Show an AI analysis board from persisted `elder_ai_analyses` rows, joined to elder names, with permission-aware redaction for unavailable data scopes.
- Restrict AI board access to users with `ai:read`; never expose analysis content when underlying data-scope permissions are missing.
- Preserve organization scoping for all data loads and route links, including slug-aware `/app/{organizationSlug}/...` links.
- Reuse existing module server operations and UI adapters; do not add new dependencies or fake/static operational data.
## Acceptance Criteria
- [x] Existing collaboration modules remain data-backed and reachable from unscoped and organization-scoped workspace routes.
- [x] Existing workspaces with core resident data but no collaboration data receive missing default devices, notices, alerts, and family records on seed rerun.
- [x] The dashboard can be viewed by users with collaboration or AI read permissions, not only core care/facility permissions.
- [x] The dashboard renders collaboration queues/metrics only from persisted server queries and hides unavailable sections by permission.
- [x] The dashboard renders an AI analysis board sourced from persisted analysis history when `ai:read` is present.
- [x] AI analysis board items redact result content when `canViewAnalysisScopes` denies one or more stored data scopes.
- [x] Focused tests cover idempotent collaboration seeding and AI board redaction behavior.
- [x] `pnpm type-check` and focused tests pass.
- [x] Elder AI generation uses bounded provider timeout, token, and retry controls so slow providers fail with sanitized timeout history instead of hanging indefinitely.
## Notes
- Out of scope: background alert-rule execution, family portal/authentication, AI recommendation/business-action generation changes, and new database schema.

View File

@@ -0,0 +1,26 @@
{
"id": "complete-ops-ai-board",
"name": "complete-ops-ai-board",
"title": "Complete operations modules and AI analysis board",
"description": "",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "TalexDreamSoul",
"assignee": "TalexDreamSoul",
"createdAt": "2026-07-08",
"completedAt": "2026-07-09",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,80 @@
# AI Presentation And Fee Workspace Design
## Scope
This task keeps the current AI product surface and adds one new frontend-only fee workspace. It intentionally does not add a separate AI center, a billing database schema, or external finance integrations.
## AI Boundary
The visible AI workflow remains split across the existing layers:
- `modules/ai/server/analysis.ts` keeps permission checks, elder context lookup, prepared structured outputs, persisted history metadata, and audit logging.
- `modules/ai/components/ElderAiAnalysisDialog.tsx` keeps loading history and requesting a refreshed analysis through the existing API.
- `modules/dashboard/components/DashboardHome.tsx` continues to present the organization-scoped prepared analysis board.
- `modules/ai/components/KnowledgeManagementClient.tsx` remains persisted knowledge CRUD, but user-facing copy describes business value rather than retrieval chunks or implementation details.
No external model provider is called by the visible analysis path. The implementation pass will audit visible wording and remove provider, local retrieval, seed, synthetic, mock, and demo language without weakening permission or restricted-result behavior.
## Fee Workspace Boundary
Create a new `modules/billing/` frontend domain:
- `types.ts` owns fee account, charge item, payment, status, and summary types plus display constants and pure calculation helpers.
- `lib/presentation-data.ts` owns the initial organization fee ledger used by the workspace.
- `components/BillingWorkspaceClient.tsx` owns all browser-session interactions.
Create routes:
- `app/(app)/app/billing/page.tsx`
- `app/(app)/app/[organizationSlug]/billing/page.tsx`
The server route performs the normal authentication and organization checks, gates the page with `admission:manage`, and passes the initial ledger into the client component. No API route or database table is added.
## Navigation
Add `费用管理` to the operations group after `床位房间`. Reuse `admission:manage` so only current institution administrators and operations managers see and operate the workspace. Add a Lucide wallet/receipt icon through the existing sidebar icon map.
## Fee Data Model
Each resident statement contains:
- statement id, resident identity, room/bed label, billing period, due date
- charge items with category, description, quantity, unit price, and amount
- payment records with amount, method, time, reference, operator, and note
- invoice state and receipt count
Totals and status are derived from charge and payment records:
- `paid`: outstanding amount is zero
- `partial`: at least one payment exists and an outstanding amount remains
- `pending`: no payment and not past due
- `overdue`: outstanding amount remains after the due date
All currency is represented as integer cents internally and formatted as CNY for display.
## Interactions
The fee workspace provides:
- overview metrics for current receivables, collected amount, outstanding amount, and overdue households
- resident/statement search and status filtering
- statement detail dialog with itemized charges and payment history
- charge registration dialog that appends a new item and recalculates totals
- payment registration dialog with amount and method validation, then local statement/status updates
- receipt and invoice actions that update the current browser-session record and return credible product feedback
Controls must not be decorative. Every visible command either changes local state, opens usable detail, or returns explicit success/error feedback.
## Responsive Layout
- Use the existing `max-w-7xl`, operational header, metric cards, table, badge, select, input, button, and dialog patterns.
- Keep tables horizontally scrollable with explicit minimum widths.
- Stack toolbar controls and dialog form fields at narrow widths.
- Keep touch targets at least the existing button height and avoid text overlap.
## Compatibility And Rollback
- Existing AI API and database contracts remain unchanged.
- No migration or production data backfill is required.
- Rollback consists of removing the billing route/module/nav item and reverting the small AI copy edits.
- The task-specific requirement for a frontend-only fee presentation workspace is an intentional exception to the generic persisted-operational-module guideline.

View File

@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}

View File

@@ -0,0 +1,44 @@
# AI Presentation And Fee Workspace Implementation Plan
## 1. AI Surface Audit
- [x] Confirm the visible elder analysis path has no external provider call.
- [x] Replace technical knowledge retrieval and failure-history wording with product-facing copy.
- [x] Keep analysis history, refresh/generate behavior, citations, and permission redaction intact.
- [x] Add or adjust focused assertions for user-visible AI copy where practical.
## 2. Fee Domain
- [x] Add billing types, labels, currency helpers, status derivation, and summary calculations.
- [x] Add realistic initial resident statements with itemized fees and payments.
- [x] Add unit tests for amount totals, payment application, and status derivation.
## 3. Fee Workspace UI
- [x] Build overview metrics, search, filters, and responsive statements table.
- [x] Build statement detail with charge items and payment history.
- [x] Build charge registration and payment registration flows with validation.
- [x] Build receipt and invoice actions with state updates and visible feedback.
## 4. Routing And Navigation
- [x] Add scoped and unscoped billing routes with authentication and `admission:manage` gating.
- [x] Add `费用管理` and a Lucide icon to the operations navigation.
- [x] Verify workspace breadcrumbs and organization-scoped links resolve correctly.
## 5. Validation
- [x] Run focused billing and AI tests.
- [x] Run `pnpm lint`.
- [x] Run `pnpm type-check`.
- [x] Run `pnpm test`.
- [x] Run `pnpm build`.
- [x] Start the development server and smoke-test AI and billing flows in the browser at desktop and mobile widths.
- [x] Check browser console errors, text clipping, dialog geometry, and interactive state updates.
## Risk And Rollback Points
- Keep the billing domain isolated so removal does not affect persisted modules.
- Do not change `Permission`, role seed, schema, or migrations for the frontend-only workspace.
- Reuse `admission:manage`; changing authorization is out of scope.
- Do not alter AI API response shapes or stored analysis records.

View File

@@ -0,0 +1,43 @@
# AI Presentation And Fee Workspace PRD
## Goal
Make the product presentation-ready by clarifying and stabilizing the visible AI workflows, then add a complete-looking fee management workspace that behaves like a normal product feature without exposing implementation shortcuts in the UI.
## Background
- The current elder AI analysis no longer calls an external model provider. `modules/ai/server/analysis.ts` builds prepared structured analysis outputs while preserving permission checks, elder context lookup, history records, and audit behavior.
- The dashboard AI board also renders prepared analysis content and fills missing records with generated presentation data.
- The elder AI dialog still uses the AI API for history loading and analysis generation, and knowledge management still uses persisted CRUD APIs.
- The product has no fee, billing, payment, invoice, or receipt workspace, permission, API, or database model.
- Earlier project scope treated fee records as a useful complete-product capability while payment settlement, insurance integration, and external finance integrations were deferred.
## Requirements
1. Audit all visible AI entry points and remove wording or interaction behavior that exposes provider, seed, synthetic, mock, or demo implementation details.
2. Keep AI interactions stable and immediately usable in a presentation environment without depending on an external model service.
3. Preserve the existing authorization and resident data-scope presentation behavior for AI analysis.
4. Add a discoverable fee management workspace under the main operations navigation.
5. Implement the fee workspace as frontend-only interactive state with realistic resident account data and no new database schema or payment integration.
6. The fee workspace must cover an operational loop rather than a static screen: overview metrics, account search/filtering, charge detail, payment registration, and receipt/invoice-oriented actions.
7. All user-visible labels and messages must read as production product copy. Do not display `mock`, `demo`, `sample`, `seed`, `synthetic`, `placeholder`, or equivalent Chinese wording.
8. Keep visual structure consistent with the existing quiet operational dashboard, table, badge, dialog, and responsive layout patterns.
## Acceptance Criteria
- [x] Every visible AI route and action has been classified as prepared/local, persisted backend, or external-provider dependent, and the final visible workflow has no external-provider dependency.
- [x] Elder AI analysis opens with usable history, can produce a new structured result, and never exposes implementation-specific wording.
- [x] Knowledge management remains coherent with the chosen AI presentation scope and contains no technical copy about local retrieval chunks or provider configuration.
- [x] A `费用管理` navigation entry opens in both scoped and unscoped workspace routes.
- [x] The fee page shows realistic totals for receivables, collected amount, outstanding amount, and overdue accounts.
- [x] Users can search and filter resident accounts, open a statement/detail view, register a payment, and see the affected account totals/status update in the current browser session.
- [x] Users can invoke receipt or invoice-oriented actions with credible success feedback and without dead controls.
- [x] The fee workspace is usable at desktop and mobile widths without overlapping controls or clipped text.
- [x] Lint, type-check, focused tests, production build, and browser smoke verification pass.
## Out Of Scope
- Persisting fee data to PostgreSQL.
- Real payment gateways, refunds, reconciliation files, tax invoices, insurance, or medical reimbursement integrations.
- Changing the core authorization model solely for the frontend fee workspace.
- Adding a new external AI provider path or restoring model calls.

View File

@@ -0,0 +1,26 @@
{
"id": "ai-fee-frontend",
"name": "ai-fee-frontend",
"title": "梳理 AI 功能并补齐费用前端",
"description": "",
"status": "completed",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "TalexDreamSoul",
"assignee": "TalexDreamSoul",
"createdAt": "2026-07-09",
"completedAt": "2026-07-09",
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -0,0 +1,2 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
{"file": ".trellis/workflow.md", "reason": "核验任务验收与 Git 操作边界"}

View File

@@ -0,0 +1,2 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
{"file": ".trellis/workflow.md", "reason": "遵循任务执行、Git 安全边界与收尾流程"}

Some files were not shown because too many files have changed in this diff Show More