# Kế hoạch xử lý triệt để vấn đề semantics review-derived data bị phân tán

## 1. Phạm vi và mục tiêu

Tài liệu này lập kế hoạch cho vấn đề #4 trong DSCons: semantics nghiệp vụ phát sinh từ dossier review đang bị phân tán giữa nhiều service, chủ yếu ở:

- `app/services/company_operational_state_service.py`
- `app/services/operations_case_management_service.py`
- `app/services/remediation_planning_service.py`
- `app/services/workflow_persistence_service.py`
- các schema liên quan trong `app/models/company_operational_state_schemas.py` và `app/models/schemas.py`
- các tests hiện có cho operational state, remediation, workflow persistence, dossier review routes

Mục tiêu không phải thay API ngay, mà thiết kế một **shared projection layer/domain model** dùng chung cho review-derived data, để các service hiện tại không còn tự diễn giải lại cùng một finding/follow-up/blocker theo nhiều cách khác nhau.

Projection layer này phải bao phủ tối thiểu các semantics chung:

- follow-up
- blocker
- backlog
- action
- risk
- remediation signal

Đồng thời phải cho phép refactor incremental, không phá API hiện tại, có cơ chế đo semantic drift trước/sau refactor.

---

## 2. Root-cause sâu

## 2.1. Vấn đề thực chất không phải “thiếu helper”, mà là thiếu canonical business projection

Qua `CompanyOperationalStateService`, có thể thấy service này đang:

- đọc raw `dossier_review_sessions` từ PostgreSQL
- tự flatten `findings`
- tự suy diễn risk từ finding
- tự suy diễn blocked item từ:
  - finding severity/status
  - `is_blocking`
  - `blocking_reasons`
  - metadata keys biến thể như `blocked_reasons` / `blocking_reasons`
  - follow-up items tìm được trong review metadata, finding metadata, assignment metadata, submission metadata, action payload
- tự suy diễn backlog từ:
  - finding chưa assigned
  - assignment mở
  - follow-up mở
  - operations actions mở
- tự derive priority
- tự normalize status open/closed
- tự dedupe follow-up items

Nói cách khác, `CompanyOperationalStateService` hiện đang đóng đồng thời 3 vai:

1. data access consumer  
2. domain interpreter  
3. presentation assembler

Điều này là gốc của drift semantics.

## 2.2. Semantics bị embed trong presentation service

Các method như:

- `_build_risks`
- `_build_blocked_items`
- `_build_backlog`
- `_build_blocked_items_from_review`
- `_build_backlog_from_review`
- `_collect_review_follow_ups`
- `_normalize_follow_up_item`
- `_derive_priority`
- `_is_blocking_review_follow_up`
- `_is_assignment_open`
- `_is_open_status`

đều là logic nghiệp vụ lõi về lifecycle của review-derived data, nhưng lại đang nằm trong service chuyên trả response cho digital twin endpoint.

Hệ quả:

- service khác muốn dùng semantics này phải copy hoặc tự định nghĩa lại
- semantics thay đổi ở một nơi không propagate sang nơi khác
- test hiện tại chủ yếu verify output cuối, chưa khóa được domain meaning trung tâm

## 2.3. Cùng một raw finding đang bị nhìn dưới nhiều “bản dịch” khác nhau

Một finding mở có thể được diễn giải đồng thời thành:

- risk trong `CompanyOperationalStateService`
- blocker nếu severity cao hoặc có blocking_reasons
- backlog nếu chưa assign hoặc assignment mở
- remediation plan nếu có `document_type`
- operations case/action ở service khác

Nhưng hiện chưa có một lớp canonical nói rõ:

- khi nào một finding sinh ra `risk`
- khi nào sinh `blocker`
- khi nào sinh `follow-up`
- khi nào follow-up là blocker
- khi nào assignment là backlog item
- khi nào finding đủ điều kiện sinh remediation signal thay vì full plan
- quan hệ identity giữa finding/action/follow-up/case/remediation là gì

Thiếu canonical identity nên cùng một thực thể có thể bị đếm trùng, bỏ sót, hoặc gán source_type khác nhau giữa service.

## 2.4. Metadata contract quá mềm và bị parse rải rác

`CompanyOperationalStateService._collect_review_follow_ups()` đang đọc nhiều key alias:

- `post_close_actions`
- `post_close_backlog`
- `close_follow_ups`
- `post_verification_actions`
- `verification_follow_ups`
- `post_verify_actions`
- `follow_up_action`
- `next_action`
- `post_action`

Điều này cho thấy source data thực tế chưa có semantic envelope ổn định. Service đang phải “cứ thấy giống follow-up thì gom vào”.

Tác hại:

- schema mềm nhưng meaning cứng, nên drift rất khó phát hiện
- mỗi service sẽ có xu hướng đọc một tập key hơi khác nhau
- khi workflow persistence hoặc policy service thêm metadata shape mới, downstream service có thể hiểu lệch mà không fail rõ ràng

## 2.5. Remediation semantics đang ở mức plan-centric, chưa có signal-centric layer

`RemediationPlanningService` hiện build trực tiếp `RemediationPlanItem` từ finding mở có `document_type`.

Điều này khiến remediation trở thành một endpoint-specific interpretation, trong khi về domain nó nên bắt đầu từ một mức abstraction thấp hơn:

- finding có cần remediation không
- remediation thuộc loại nào
- readiness / blocking factors là gì
- owner và required inputs signal là gì
- coverage gap signal là gì

Tức là remediation hiện đang thiếu một lớp trung gian “remediation signal/projection”. Vì thế operational state chỉ có thể consume plan summary hoặc tự suy diễn lại từ finding.

## 2.6. Workflow persistence đang là source dữ liệu, nhưng chưa là source of semantic truth

`workflow_persistence_service.py` nhiều khả năng đang chịu trách nhiệm persist review sessions/findings/actions. Tuy nhiên semantic lifecycle sau khi persist chưa được chuẩn hóa thành một projection contract độc lập.

Vì vậy:

- persistence lưu raw shape
- downstream service tự suy luận meaning
- meaning nằm ở consumers thay vì nằm ở domain projection

Đây là anti-pattern lớn nhất của vấn đề #4.

---

## 3. Target domain model: Shared Review Projection Layer

## 3.1. Nguyên tắc thiết kế

Thiết kế đích nên dựa trên 4 nguyên tắc:

1. **Canonical projection trước, presentation mapping sau**  
   Mọi service downstream consume cùng một projection/domain model cho review-derived data.

2. **Projection là read model/domain interpretation, không phải raw persistence model**  
   Raw workflow payload vẫn được lưu như hiện tại; projection chỉ chuẩn hóa meaning.

3. **Một nguồn sự thật cho lifecycle semantics**  
   Open/closed, blocking, priority, assignee, identity, remediation need phải được xác định ở một nơi.

4. **Không phá API hiện tại**  
   Company operational state, remediation endpoints, operations case endpoints vẫn giữ response shape cũ trong giai đoạn đầu; chỉ đổi phần nội bộ sinh dữ liệu.

## 3.2. Các thực thể canonical đề xuất

Projection layer nên có một cụm domain objects/read models nội bộ, ví dụ ở mức thiết kế:

### A. ReviewProjectionEnvelope
Đại diện toàn bộ projection cho một review session.

Trường lõi:

- `review_session_id`
- `review_code`
- `project_code`
- `project_name`
- `workflow_status`
- `generated_at` hoặc `projected_at`
- `source_version` / `projection_version`
- `findings`
- `follow_ups`
- `risks`
- `blockers`
- `backlog_items`
- `actions`
- `remediation_signals`
- `stats`

Vai trò:
- làm output chuẩn từ raw persisted review session
- cho downstream service dùng một lần project, nhiều lần map

### B. ReviewFindingProjection
Canonical finding sau khi normalize.

Trường lõi:

- `finding_id`
- `finding_code`
- `review_session_id`
- `project_code`
- `title`
- `finding_group`
- `finding_type`
- `document_type`
- `dossier_stage`
- `severity`
- `impact_level`
- `status`
- `supplement_status`
- `responsible_department_code`
- `primary_owner_employee_code`
- `is_open`
- `is_blocking`
- `derived_priority`
- `assignment_state`
- `raw_reference_ids`
- `metadata`

Vai trò:
- là node gốc để derive các projection con
- đảm bảo mọi service dùng cùng logic status/priority/blocking

### C. ReviewFollowUpProjection
Canonical follow-up item, gom từ metadata/action payload/assignment/submission.

Trường lõi:

- `follow_up_id`
- `review_session_id`
- `finding_id`
- `finding_code`
- `project_code`
- `title`
- `description`
- `reason`
- `status`
- `priority`
- `action_type`
- `trigger_phase`
- `owner_employee_code`
- `department_code`
- `due_date`
- `is_open`
- `is_blocking`
- `source_origin`
- `reference_id`
- `dedupe_key`
- `metadata`

Vai trò:
- chấm dứt việc mỗi service tự parse follow-up aliases
- cung cấp identity ổn định để tránh đếm trùng

### D. ReviewRiskProjection
Risk view canonical của finding/follow-up.

Trường lõi:

- `risk_id`
- `review_session_id`
- `finding_id`
- `project_code`
- `title`
- `severity`
- `status`
- `owner_code`
- `source_origin`
- `reference_id`
- `mitigation`
- `is_open`
- `is_high_severity`
- `metadata`

Quy tắc:
- risk không nhất thiết đồng nghĩa blocker
- risk có thể phát sinh từ finding mở và/hoặc flagged review action, nhưng phải qua cùng một rule engine

### E. ReviewBlockerProjection
Canonical blocker.

Trường lõi:

- `blocker_id`
- `review_session_id`
- `finding_id`
- `finding_code`
- `project_code`
- `title`
- `status`
- `reason`
- `owner_employee_code`
- `department_code`
- `due_date`
- `source_origin`
- `reference_id`
- `blocker_kind`
- `is_open`
- `metadata`

`blocker_kind` nên chuẩn hóa ít nhất:
- `finding_blocking`
- `finding_reason_blocking`
- `follow_up_blocking`
- `operations_blocker_linked`

### F. ReviewBacklogProjection
Canonical work item cần follow-up.

Trường lõi:

- `backlog_item_id`
- `review_session_id`
- `finding_id`
- `finding_code`
- `project_code`
- `title`
- `status`
- `priority`
- `assignee_employee_code`
- `assignee_department_code`
- `due_date`
- `source_origin`
- `reference_id`
- `backlog_kind`
- `is_open`
- `metadata`

`backlog_kind` tối thiểu:
- `finding_triage`
- `finding_assignment`
- `follow_up`
- `operations_action_linked`

### G. ReviewActionProjection
Canonical action record liên quan review.

Trường lõi:

- `action_id`
- `review_session_id`
- `finding_id`
- `finding_code`
- `project_code`
- `action_type`
- `title`
- `status`
- `priority`
- `owner_employee_code`
- `owner_department_code`
- `requires_human_confirmation`
- `source_origin`
- `created_at`
- `due_date`
- `is_open`
- `metadata`

Vai trò:
- bridge giữa review workflow actions và operations case management

### H. RemediationSignalProjection
Lớp mới quan trọng nhất để nối review với remediation.

Trường lõi:

- `signal_id`
- `review_session_id`
- `finding_id`
- `finding_code`
- `project_code`
- `document_type`
- `dossier_stage`
- `priority`
- `owner_department_code`
- `owner_employee_code`
- `requires_remediation`
- `readiness_status`
- `blocking_reasons`
- `missing_input_codes`
- `coverage_gap_count`
- `coverage_gap_blocking_count`
- `suggested_destination_folder`
- `suggested_output_filename`
- `signal_kind`
- `source_origin`
- `metadata`

`signal_kind` tối thiểu:
- `document_rebuild_required`
- `coverage_gap_review`
- `input_collection_required`

Ý nghĩa:
- operational state không cần biết chi tiết RemediationPlanItem để show tình trạng remediation
- remediation planning service có thể nâng từ signal lên full plan
- tránh duplicate rule giữa operational state và remediation planning

## 3.3. Shared semantic rules nên tách riêng

Projection layer cần một rule set dùng chung, không nằm rải trong service presentation:

- normalize status lifecycle:
  - open / in_progress / pending / submitted / returned / closed / verified / cancelled
- normalize priority:
  - low / medium / high / critical
- classify open vs closed
- classify blocking vs non-blocking
- derive assignment-open
- derive follow-up-trigger phase
- alias map cho metadata keys follow-up
- dedupe strategy
- derive remediation eligibility
- derive owner precedence:
  - assignment employee
  - assignment department
  - finding responsible_department_code
  - fallback owner

Các rule này nên là pure functions hoặc rule objects, dễ snapshot test.

## 3.4. Boundary giữa projection và presentation

### Projection layer chịu trách nhiệm:
- đọc raw review session record
- normalize finding/action/assignment/follow-up/remediation intent
- tạo canonical domain projections
- gán identity ổn định
- dedupe
- derive lifecycle semantics
- giữ trace về source_origin và raw references

### Presentation/service layer chịu trách nhiệm:
- map canonical projections sang API schema hiện có
- combine với source khác như project management / employee logs / coverage manifest / operations DB
- sort/filter/top-N/display labels
- attach source_status, summary cards, response message

### Không để presentation layer:
- parse metadata aliases
- tự suy diễn blocking/open/priority
- tự dedupe follow-ups
- tự invent backlog semantics

Đây là boundary quan trọng nhất của refactor.

---

## 4. Đề xuất kiến trúc thực thi trong codebase DSCons

## 4.1. Lớp mới đề xuất

Không sửa code ở tài liệu này, nhưng target structure nên theo hướng:

- `app/services/review_projection_service.py`
- `app/models/review_projection_schemas.py`

hoặc nếu muốn rõ domain hơn:

- `app/services/review_derived_projection_service.py`
- `app/models/review_derived_projection_schemas.py`

Khuyến nghị tên ngắn gọn hơn để dễ dùng chung: `review_projection`.

## 4.2. Interface tối thiểu

Service mới nên expose tối thiểu:

- `project_review_session(session: dict[str, Any]) -> ReviewProjectionEnvelope`
- `list_review_projections(project_code: str | None = None, review_id: str | None = None) -> list[ReviewProjectionEnvelope]`
- `get_review_projection(review_id: str) -> ReviewProjectionEnvelope | None`

Có thể thêm helper read-model:
- `list_review_blockers(...)`
- `list_review_backlog(...)`
- `list_remediation_signals(...)`

Nhưng phase đầu nên giữ một API chính trả envelope, để service downstream tự map theo schema cũ.

## 4.3. Dependency direction mong muốn

Dependency đúng sau refactor:

`workflow_persistence_service / postgres raw sessions`
→ `review_projection_service`
→ `company_operational_state_service`
→ `operations_case_management_service`
→ `remediation_planning_service`

Chiều phụ thuộc này quan trọng vì:

- persistence không nên phụ thuộc vào presentation
- remediation và operations consume cùng semantics
- operational state chỉ là aggregator, không là semantic owner

## 4.4. Projection versioning

Vì raw metadata hiện mềm, projection cần có:
- `projection_version`, ví dụ `review_projection_v1`
- có thể thêm `rule_set_version`

Mục đích:
- semantic drift có thể được theo dõi theo version
- hỗ trợ rollout song song old/new
- snapshot test có target rõ ràng

---

## 5. Chiến lược refactor incremental không phá API hiện tại

## Phase 0 - Baseline và đo drift hiện trạng

### Mục tiêu
Đóng băng semantics hiện tại trước khi refactor.

### Việc làm
1. Lập inventory các rule đang nằm trong:
   - `CompanyOperationalStateService`
   - `RemediationPlanningService`
   - `OperationsCaseManagementService`
2. Liệt kê bảng mapping:
   - finding → risk
   - finding → blocker
   - finding/assignment/follow-up → backlog
   - finding → remediation
3. Chọn 10–20 fixture review sessions đại diện:
   - finding severity cao
   - finding có/không assignment
   - finding có follow-up nhiều alias key
   - follow-up blocking
   - finding có document_type
   - finding closed/verified
   - review có actions payload generic
4. Snapshot output hiện tại của:
   - operational state blocked_items / backlog / risks
   - remediation list_plans / list_gaps
   - operations case/action views nếu đang derive từ review data

### Deliverable
- semantic baseline matrix
- golden fixtures
- drift metrics definition

### Acceptance criteria
- có thể chỉ ra với mỗi fixture hiện đang sinh ra bao nhiêu risk/blocker/backlog/remediation plan
- có snapshot làm chuẩn trước refactor

## Phase 1 - Trích xuất canonical rule set thành pure functions

### Mục tiêu
Tách meaning khỏi presentation nhưng chưa đổi response.

### Việc làm
1. Tạo schema/domain objects cho projection nội bộ.
2. Trích các rule sau khỏi `CompanyOperationalStateService` thành shared rules:
   - status normalization
   - priority derivation
   - follow-up normalization
   - follow-up dedupe
   - blocker classification
   - assignment open/closed
3. Viết adapter project từ raw finding/action/assignment sang projection objects.
4. Chưa thay public API; chỉ chuẩn bị library/domain layer.

### Tradeoff
- nhanh giảm duplication logic
- nhưng chưa giảm được duplicate fetch path nếu services vẫn tự load dữ liệu riêng

### Acceptance criteria
- pure rule functions có test độc lập
- cùng input raw tạo ra cùng canonical follow-up/blocker/backlog classification trên mọi service consumer

## Phase 2 - Giới thiệu ReviewProjectionService ở chế độ shadow

### Mục tiêu
Chạy projection mới song song với logic cũ để so sánh.

### Việc làm
1. Tạo `ReviewProjectionService` đọc raw sessions từ Postgres client.
2. Sinh `ReviewProjectionEnvelope` cho từng session.
3. Trong `CompanyOperationalStateService`, thêm branch shadow:
   - vẫn trả dữ liệu bằng logic cũ
   - nhưng nội bộ có thể build projection mới và compare count/IDs
4. Log metric drift:
   - old_blocker_count vs projected_blocker_count
   - old_backlog_count vs projected_backlog_count
   - old_risk_count vs projected_risk_count
   - remediation_signal_count vs remediation_plan_count
5. Không expose projection qua API public ở phase này.

### Tradeoff
- thêm chi phí CPU nhẹ vì build song song
- đổi lại đo drift được trước khi cutover

### Acceptance criteria
- có telemetry drift cho fixtures và môi trường staging/dev
- chênh lệch semantics được phân loại thành:
  - expected improvement
  - regression
  - data ambiguity

## Phase 3 - Chuyển CompanyOperationalStateService sang consume projection

### Mục tiêu
Service có duplication semantics nặng nhất phải ngừng tự diễn giải raw review data.

### Việc làm
1. Đổi đường dữ liệu:
   - từ `_fetch_dossier_reviews()` + local derive
   - sang `ReviewProjectionService.list_review_projections()`
2. `CompanyOperationalStateService` chỉ còn:
   - map `ReviewRiskProjection` → `CompanyOperationalRiskItem`
   - map `ReviewBlockerProjection` → `CompanyOperationalBlockedItem`
   - map `ReviewBacklogProjection` → `CompanyOperationalBacklogItem`
   - attach raw dossier review list nếu schema vẫn yêu cầu
3. Giữ nguyên response schema hiện tại.

### Tradeoff
- cần cẩn thận với field names như `source_type`, `reference_id`, `metadata`
- một số count có thể thay đổi nhẹ do dedupe chuẩn hóa

### Acceptance criteria
- test contract cũ của company operational state vẫn pass
- snapshot count/identity với baseline chênh lệch trong ngưỡng đã chấp nhận
- không còn method parse follow-up phức tạp nằm trong service này

## Phase 4 - Chuyển RemediationPlanningService sang signal-first

### Mục tiêu
Remediation không tự suy diễn lại finding semantics từ đầu.

### Việc làm
1. `ReviewProjectionService` sinh `RemediationSignalProjection`.
2. `RemediationPlanningService` consume signal để build:
   - `RemediationPlanItem`
   - `RemediationGapItem`
3. Rule chung:
   - finding open?
   - có document_type?
   - owner/priority/blocking_reasons là gì?
   - coverage gap merge như thế nào?
   được lấy từ projection + enrichment, thay vì derive lại rải rác.

### Tradeoff
- cần giữ chỗ cho coverage manifest enrichment vì đây là external source ngoài review session
- remediation vẫn có logic riêng về document playbook và artifact requirements, không nên nhét hết vào projection

### Acceptance criteria
- remediation service chỉ còn domain-specific planning logic, không còn own lifecycle semantics cho finding/follow-up
- plan outputs giữ backward compatible field contract

## Phase 5 - Đồng bộ Operations Case Management với shared projection

### Mục tiêu
Operations case/action/blocker semantics dùng cùng canonical identity với review projection.

### Việc làm
1. Xác định mapping:
   - projection action ↔ operations action
   - projection blocker ↔ operations blocker
   - projection finding ↔ case seed
2. Nếu service hiện đang tạo case từ raw finding, đổi sang từ projection.
3. Dùng stable IDs:
   - `finding_id`
   - `follow_up_id`
   - `blocker_id`
   - `signal_id`
4. Đảm bảo case dedupe không tạo lại cùng case cho cùng semantic item.

### Acceptance criteria
- cùng một finding/follow-up không còn xuất hiện như nhiều case semantic-equivalent
- blocker/action counts giữa operational state và operations case management nhất quán theo canonical IDs

## Phase 6 - Cutover hoàn toàn và dọn logic cũ

### Mục tiêu
Loại bỏ semantic logic trùng lặp khỏi các service presentation.

### Việc làm
1. Xóa hoặc deprecate các method cũ trong `CompanyOperationalStateService`:
   - `_collect_review_follow_ups`
   - `_build_backlog_from_review`
   - `_build_blocked_items_from_review`
   - `_normalize_follow_up_item`
   - các derive helpers trùng với projection rules
2. Tài liệu hóa projection contract trong `docs/`.
3. Thêm regression suite chuyên cho projection.

### Acceptance criteria
- chỉ còn một semantic interpreter chính cho review-derived data
- các service presentation không còn parse raw metadata aliases

---

## 6. Backward compatibility strategy

## 6.1. Giữ nguyên public API schemas ở phase đầu

Các schema công khai như:

- `CompanyOperationalStateResponse`
- `CompanyOperationalRiskItem`
- `CompanyOperationalBlockedItem`
- `CompanyOperationalBacklogItem`
- `RemediationPlanItem`
- `RemediationGapItem`

nên giữ nguyên trong ít nhất 1 vòng rollout.

Projection layer là nội bộ trước.

## 6.2. Dùng adapter mapping thay vì sửa schema đột ngột

Projection objects không cần trùng 1-1 với public response schemas. Nên có adapter methods:

- projection risk → operational risk item
- projection blocker → operational blocked item
- projection backlog → operational backlog item
- remediation signal → remediation plan/gap

Cách này giúp:
- domain model sạch hơn
- API stability cao hơn
- rollback dễ hơn

## 6.3. Song song old/new semantics có cờ chuyển đổi

Khuyến nghị rollout bằng feature flag nội bộ, ví dụ conceptually:
- `USE_REVIEW_PROJECTION_FOR_OPERATIONAL_STATE`
- `USE_REVIEW_PROJECTION_FOR_REMEDIATION`
- `USE_REVIEW_PROJECTION_FOR_OPERATIONS_CASES`

Không nhất thiết public config ngay, nhưng cần khả năng:
- shadow compare
- selective cutover
- rollback nhanh

## 6.4. Compatibility với raw dossier_reviews field

`CompanyOperationalStateResponse` hiện có thể đính kèm `dossier_reviews`. Field này vẫn nên giữ raw session records hoặc record-compatible payload trong phase đầu vì frontend/consumers có thể đang dựa vào đó.

Tuy nhiên cần quy định rõ:
- `dossier_reviews` là raw/persisted view
- `risks`, `blocked_items`, `backlog`, `remediation_*` là projected/presented view

Điều này tránh hiểu nhầm rằng raw payload đã là canonical semantics.

## 6.5. Metadata không bị drop âm thầm

Vì hiện nhiều meaning đang sống trong metadata mềm, projection layer nên giữ:
- `source_origin`
- `raw_reference_ids`
- `metadata`

để backward investigation được, kể cả khi public payload chưa expose hết.

---

## 7. Kế hoạch đo semantic drift trước/sau

## 7.1. Drift phải đo ở mức identity và classification, không chỉ count

Chỉ so count là chưa đủ. Cần đo ít nhất 4 loại drift:

### A. Cardinality drift
- số risk/blocker/backlog/action/remediation signal thay đổi bao nhiêu

### B. Membership drift
- item nào xuất hiện ở old nhưng không có ở new
- item nào mới xuất hiện ở new

### C. Classification drift
- cùng identity nhưng đổi loại:
  - backlog → blocker
  - risk → không còn risk
  - remediation eligible → not eligible

### D. Attribute drift
- priority khác
- owner khác
- due_date khác
- status open/closed khác
- reason/blocking_reasons khác

## 7.2. Chỉ số drift đề xuất

Cho mỗi review session và mỗi project:

- `semantic_drift.blocker.count_delta`
- `semantic_drift.blocker.membership_delta`
- `semantic_drift.backlog.count_delta`
- `semantic_drift.backlog.membership_delta`
- `semantic_drift.risk.count_delta`
- `semantic_drift.follow_up.deduped_count`
- `semantic_drift.remediation_signal.count_delta`
- `semantic_drift.priority.changed_count`
- `semantic_drift.owner.changed_count`
- `semantic_drift.status.changed_count`

Kèm sample payload trong log cho top N drift records.

## 7.3. Golden fixture strategy

Tạo bộ fixture canonical từ các review sessions thực tế đã anonymize hoặc stubbed, tập trung vào edge cases:

1. finding high severity + open + no assignment  
2. finding open + assignment pending  
3. finding closed + post-close follow-up open  
4. finding verified + post-verification action  
5. metadata dùng `blocked_reasons`  
6. metadata dùng `blocking_reasons`  
7. actions payload dùng `follow_up_action`  
8. actions payload chỉ có `next_action`  
9. duplicate follow-up xuất hiện ở assignment metadata và action payload  
10. finding có `document_type` + coverage gaps  

Mỗi fixture cần expected projection:
- findings
- blockers
- backlog
- follow-ups
- remediation signals

## 7.4. Ngưỡng chấp nhận drift

Không phải drift nào cũng là regression. Nên phân loại:

### Expected-positive drift
- giảm duplicate backlog/follow-up
- blocker count giảm do dedupe
- remediation signal count rõ hơn plan count cũ

### Suspicious drift
- item open biến mất hoàn toàn
- owner bị rỗng trong khi trước có
- finding blocking không còn blocker
- finding có document_type không còn remediation signal

### Unacceptable drift
- mất toàn bộ blockers/backlog cho review session có findings mở
- nhiều item closed bị đánh dấu open
- source identity không trace lại được raw record

---

## 8. Testing và verification strategy

## 8.1. Unit tests cho rule layer

Phải có unit test riêng cho:

- normalize status
- derive priority
- detect blocking finding
- detect blocking follow-up
- detect assignment open
- follow-up alias parsing
- dedupe follow-up identity
- derive remediation eligibility

Mục tiêu:
- semantics lõi được khóa độc lập khỏi API response

## 8.2. Snapshot tests cho projection envelopes

Dùng fixture raw review sessions → snapshot `ReviewProjectionEnvelope`.

Snapshot nên verify:
- canonical IDs
- counts
- field normalization
- source_origin
- metadata preservation tối thiểu

## 8.3. Contract tests cho existing services

Giữ và mở rộng test cho:

- `tests/test_company_operational_state_service.py`
- `tests/test_remediation_planning_service.py`
- `tests/test_workflow_persistence_service.py`
- `tests/test_dossier_review_routes.py`

Bổ sung cases:
- cùng fixture trước/sau refactor phải giữ API contract
- response schema không đổi
- count/fields quan trọng không regress

## 8.4. Cross-service consistency tests

Đây là lớp test còn thiếu và cực quan trọng cho vấn đề #4.

Cần test với cùng một review fixture:

- blocker trong operational state có canonical reference tới finding/follow_up
- remediation signal cùng finding_id với remediation plan
- operations action/blocker nếu có phải trùng semantic identity
- cùng finding không bị vừa mất ở remediation vừa còn ở backlog nếu rule nói ngược lại

Ví dụ assertion:
- mỗi `RemediationPlanItem.finding_id` phải map được đến đúng 1 `ReviewFindingProjection`
- blocker/reference_id trong operational state phải trace về `blocker_id` hoặc raw reference ổn định
- backlog open của follow-up blocking phải có cùng `finding_id`/`review_session_id`

## 8.5. Shadow-mode verification trong runtime

Trong giai đoạn rollout:

- build old payload
- build new projection-derived payload
- compare
- log drift summary

Không fail request ở phase đầu, nhưng phải surfacing được drift.

## 8.6. Definition of Done cho workstream này

Workstream #4 được coi là xong khi:

1. semantics review-derived không còn được định nghĩa độc lập ở nhiều service  
2. có một shared projection/domain model cho finding/follow-up/blocker/backlog/action/risk/remediation signal  
3. `CompanyOperationalStateService` không còn parse raw follow-up metadata trực tiếp  
4. `RemediationPlanningService` consume remediation signal/projection thay vì tự derive lại lifecycle cơ bản  
5. có drift instrumentation trước/sau cutover  
6. test suite có cross-service consistency coverage  
7. API responses hiện tại vẫn backward compatible hoặc có migration plan rõ ràng

---

## 9. Tradeoff và quyết định thiết kế

## 9.1. Tại sao chọn projection layer thay vì sửa từng service?

Vì vấn đề không nằm ở một service sai, mà ở chỗ nhiều service cùng đúng-một-phần. Nếu chỉ sửa từng service:

- duplication vẫn còn
- drift sẽ quay lại
- test khó khóa consistency xuyên service

Projection layer giải quyết root cause hơn.

## 9.2. Tại sao không biến raw persisted schema thành domain model luôn?

Vì persisted schema phục vụ durability/audit/workflow replay, còn domain projection phục vụ business interpretation.

Trộn hai vai này sẽ:
- làm persistence khó thay đổi
- khiến schema DB phải gánh logic presentation
- cản rollback semantic rule

## 9.3. Tại sao remediation signal là lớp riêng?

Vì full remediation plan chứa:
- playbook chuyên biệt
- artifact requirements
- coverage enrichment
- destination/output suggestions

Đó là logic planning. Nhưng mọi service khác chỉ cần biết:
- finding có cần remediation không
- readiness/blocking ra sao

Signal-first giảm coupling và tránh lặp lại plan logic.

## 9.4. Rủi ro chính

- raw metadata quá không đồng nhất, projection v1 có thể phải chứa nhiều alias rules
- cutover có thể làm count dashboard thay đổi, gây hiểu nhầm là regression
- operations case management có thể đang có semantics riêng chưa được audit đủ sâu

## 9.5. Cách giảm rủi ro

- shadow mode
- drift metrics
- fixture thực tế
- adapter giữ nguyên API
- rollout theo service, không big-bang

---

## 10. Thứ tự triển khai tối ưu và phụ thuộc

## Nên triển khai theo thứ tự nội bộ của workstream #4

1. **Phase 0 baseline + drift matrix**
2. **Phase 1 canonical rules + schemas**
3. **Phase 2 ReviewProjectionService shadow mode**
4. **Phase 3 cutover CompanyOperationalStateService**
5. **Phase 4 cutover RemediationPlanningService**
6. **Phase 5 align OperationsCaseManagementService**
7. **Phase 6 cleanup + docs + stronger tests**

## Phụ thuộc với các workstream khác

### Phụ thuộc với #1 workflow transaction hardening
Projection layer nên consume dữ liệu persisted ổn định hơn sau khi transaction boundary rõ hơn. Nhưng không cần chờ xong toàn bộ #1 mới bắt đầu phase 0–2.  
Điểm giao nhau:
- canonical IDs cho review/finding/action
- đảm bảo persisted raw records không ở partial shape quá khó project

### Phụ thuộc với #2 db schema sync
Nếu schema drift ảnh hưởng fields của review sessions/findings/actions, projection contract phải dựa trên source-of-truth schema đã thống nhất.  
Nên align trước khi cutover production.

### Phụ thuộc với #3 degraded observability
Drift metrics và projection source health nên tích hợp vào mô hình observability/degraded-state thay vì log rời rạc.

### Phụ thuộc với #5 integration verification
Cross-service consistency tests của workstream #4 nên trở thành một phần của kiến trúc verification tổng thể.

---

## 11. Acceptance criteria tổng hợp

## Functional
- có shared projection model cho review-derived data
- finding/follow-up/blocker/backlog/action/risk/remediation signal dùng chung lifecycle rules
- operational state và remediation không còn tự diễn giải raw review payload theo cách riêng

## Compatibility
- không phá `CompanyOperationalStateResponse`
- không phá `RemediationPlanItem` / `RemediationGapItem` contracts
- raw `dossier_reviews` vẫn có thể trả như cũ trong phase đầu

## Quality
- có unit tests cho semantic rules
- có snapshot tests cho projection
- có cross-service consistency tests
- có drift measurement trước/sau cutover

## Operability
- có projection version
- có feature-flag/shadow rollout
- có rollback về old semantics path nếu drift vượt ngưỡng

---

## 12. Kết luận

Vấn đề #4 của DSCons là một vấn đề domain architecture: meaning của review-derived data đang nằm rải rác trong các service presentation và planning. Giải pháp triệt để không phải thêm vài helper, mà là đưa vào một **shared review projection layer** làm canonical semantic interpreter cho:

- finding
- follow-up
- blocker
- backlog
- action
- risk
- remediation signal

Từ đó:

- `CompanyOperationalStateService` trở thành aggregator/maper
- `RemediationPlanningService` trở thành planner từ signal
- `OperationsCaseManagementService` dùng chung identity và lifecycle
- semantic drift có thể đo, kiểm soát và rollout an toàn

Đây là hướng tối ưu để giải quyết tận gốc duplication semantics mà vẫn giữ API hiện tại ổn định trong DSCons.