/kb/cases + /kb/case-portal render contract only.
Runtime = 07:00 queue · review SLA · alerting · sign-off.
Phase 12E ประกอบ Case Issues Portal ครบ: schema 27 fields · 6 creation modes (manual + 5 AI) · 7 view modes · 11 lifecycle statuses · 10 mapping rules · 12-stage AI analytics · alerting 4 levels · SLA per-level · PDPA discipline. A ต้อง build runtime: cron scheduler · source-monitoring pipeline · AI inference service · review queue UI · notification escalation · SLA enforcement. Daily possible-case cap = 20/day (12E.1 locked) · 07:00 Asia/Bangkok cadence · 4h review SLA.
case_record schema · indexes on status/case_type/severity/datetime/locationdaily_possible_case_generation.max_items_per_dayai_issue_analytics.pipeline_stages · Claude API via CLAUDE.md Layer F · faithfulness floor 0.70sla_model.breach_consequences · sensitive_override requires dual-approvalcase_portal.export_formats · PDPA-safeSource: case_schema.case_record (27 fields).
| B Field | B Type | A Runtime Target | A Owner | Approval Gate | Binding | Notes |
|---|---|---|---|---|---|---|
| id | string | cases.id PK (UUID or CASE-YYMMDD-XXXX) |
Backend | none | mapped-not-bound | Stable; referenced by signals/dashboards |
| title_th / title_en / title_zh | string × 3 | cases.title_{lang} columns |
Backend | none | mapped-not-bound | Trilingual parity · auto-translate fallback with review flag |
| case_type | enum(12) | cases.case_type · FK → case_types lookup |
Backend | none | mapped-not-bound | Drives alert_level default via case_type_to_alert_level_default |
| severity | enum | cases.severity |
Backend | none | mapped-not-bound | Surfaced in alerting routing + SLA band |
| alert_level | enum(4) | cases.alert_level · triggers alerting_model.escalation_paths |
Backend | gate-alert-routing | mapped-not-bound | L0/L1 = PagerDuty · L2/L3 = Slack · L0 also LINE |
| status | enum(11) | cases.status · state machine enforced at API layer |
Backend | none | mapped-not-bound | 11 statuses from alerting_model.lifecycle_statuses |
| datetime | ISO-8601 | cases.occurred_at TIMESTAMPTZ · TimescaleDB hypertable dim |
Backend | none | mapped-not-bound | Separate created_at for record audit |
| reporter | object | cases.reporter JSONB · PII-hashed email/phone |
Backend | gate-pdpa | mapped-not-bound | SHA-256 hash per CLAUDE.md PDPA rules |
| location | object | cases.location · PostGIS point + H3 cell |
Backend | gate-pdpa | mapped-not-bound | GPS fuzz ±50m before storage · k-anon on map view |
| issue_definition_{th/en/zh} | text × 3 | Text columns · full-text index | Backend | none | mapped-not-bound | pgvector embedding for semantic search |
| attributes | object | cases.attributes JSONB from pattern_extraction |
Backend | none | mapped-not-bound | AI-extracted; requires_human_review flag if AI-generated |
| pattern | object | cases.pattern JSONB · ref to pattern_extraction output |
Backend | none | mapped-not-bound | From AI pipeline stage 4-6 |
| related_actors / related_services / related_signals / related_dashboards / related_source_provenance | array<id> | Edge tables case_links · FK pairs |
Backend | none | mapped-not-bound | Drives case-detail "Connections" panel · related-card graph |
| owner | object | cases.owner_user_id FK + role |
Backend | gate-auth | mapped-not-bound | Admin surface for reassignment |
| follow_up | array | case_follow_ups child table · each has action/owner/due |
Backend | none | mapped-not-bound | — |
| ai_analysis_ref | string | cases.ai_analysis_ref → ai_analysis table |
Backend | gate-faithfulness | mapped-not-bound | Faithfulness <0.70 → regenerate · full log retained |
| sensitive_surface_flag | boolean | cases.sensitive_flag · enforces dual-approval path |
Backend | gate-sensitive-dual | mapped-not-bound | Triggers approval_matrix.row-sensitive-override |
| attachments | array | S3 URLs + metadata · PII scan before persist | Backend | gate-pii-scan | mapped-not-bound | OCR + PDF parse for AI analysis |
| language_detected | string | cases.language_detected |
Backend | none | mapped-not-bound | Whisper/detector output · drives default render lang |
| export_log | array | case_exports audit table · WORM |
Backend | none | mapped-not-bound | Every export (CSV/JSON/PDF/PNG) logged |
Source: case_portal.daily_possible_case_generation.
| B Field | A Runtime Target | A Owner | Binding | Notes |
|---|---|---|---|---|
| queue_name | case_queues.name |
Backend | mapped-not-bound | Registry of queues (future: multiple queues) |
| default_status | Status assigned on queue-insert | Backend | mapped-not-bound | Must be one of 11 lifecycle_statuses |
| max_items_per_day | Cron rate-limit = 20 (locked 12E.1) |
Backend | mapped-not-bound | Hard cap · overflow → next day queue |
| sla | Timer service · 4h review SLA | Backend | mapped-not-bound | Breach → alerting escalation per sla_model.breach_consequences |
| requires_human_review | UI banner + blocking flag | Frontend | mapped-not-bound | AI-generated cases show review banner until signed-off |
Source: ai_issue_analytics.pipeline_stages (12 stages · each has id + inputs + outputs + owner).
Each stage maps to an Airflow task (dag_case_ai_pipeline). Every stage output carries confidence + requires_human_review.
| Stage (B) | A Task | A Owner | Binding | Notes |
|---|---|---|---|---|
| intake | Kafka consumer → case_inbox | Backend | mapped-not-bound | Multi-source per multi_source_intake |
| language_detect | Whisper/langdetect | Backend | mapped-not-bound | Populates language_detected |
| normalise | OCR + PDF parse + HTML strip | Backend | mapped-not-bound | — |
| embed | Voyage AI voyage-3 → pgvector | Backend | mapped-not-bound | CLAUDE.md Layer E |
| cluster | HDBSCAN or HNSW neighbors | Backend | mapped-not-bound | Deduplicate near-duplicates |
| pattern_extract | Claude prompt per pattern_extraction | Backend | mapped-not-bound | Outputs → cases.pattern |
| attribute_extract | Claude prompt per attribute_extraction | Backend | mapped-not-bound | Outputs → cases.attributes |
| classify | case_type + severity inference | Backend | mapped-not-bound | Haiku for speed · Sonnet if low-confidence |
| alert_level_decide | Apply case_type_to_alert_level_default | Backend | mapped-not-bound | Override if severity mismatch |
| map_relations | Apply case_mapping_model 10 rules | Backend | mapped-not-bound | Populates related_* edges |
| faithfulness_check | RAGAS score ≥0.70 | Backend | mapped-not-bound | Retry once → if still <0.70 decline |
| queue_or_alert | Daily queue insert OR immediate alert | Backend | mapped-not-bound | L0/L1 bypass queue |
cases.ai_pipeline_v1)approval_matrix.row-sensitive-overridesensitive_flag=true → requires two distinct reviewer identities before status transitionscases.runtime_v1 staged rollout + rollback drilled