Skip to content

Tài liệu yêu cầu sản phẩm (PRD) — Insight Ghi Âm cho BOD

Phiên bản: 1.0 Ngày: 15/05/2026 Tác giả: PO/BA (NGUYỄN SƠN THỌ, IT Leader) Trạng thái: Sẵn sàng review Mục đích: Hợp đồng nghiệp vụ — scope, DEC, FR/AC, formulas, rủi ro. KHÔNG chứa RACI/timeline (xem handoff.md).


Đọc trước: decision-brief.md (cửa vào) → SOURCE_OF_TRUTH.md (truth canonical) → file này. Mọi quyết định nghiệp vụ phải tham chiếu Decision Log (Z section) trước khi diễn giải FR.

Z) Nhật ký quyết định (Decision Log)

15 quyết định canonical đã khóa qua 4 vòng iteration design. Mọi FR/UI/Dev/QA phải nhất quán với Z section. Conflict → check SOURCE_OF_TRUTH.md thắng.

Z1) Quyết định nghiệp vụ (Business decisions)

IDQuyết địnhLý doTác động
DEC-001Tracking-only stance — Dashboard chỉ xem + nghe + drill-down + export. KHÔNG có action: gọi điện, gửi SMS, gửi nhắc nhở, tạo task, ghi chú audit, đánh dấu trạng thái, xóa fileBOD chỉ giám sát; xử lý NV/KH thuộc Manager/HR qua channel khácLoại bỏ ~10 action features, giảm scope 65% so design v1
DEC-0045 KPI cards với KPI #2 = "Thời lượng TB / cuộc" (KHÔNG phải "Tổng thời lượng")Thời lượng TB là quality indicator hơn raw sum5 KPI: Cuộc TV, Thời lượng TB, NV hoạt động, Tuân thủ ghi âm, KH chờ TV
DEC-005Anomaly chỉ 2 mức — 🔴 Missing records + 🟡 KH chờ TV. Loại bỏ Long calls (>60p) + Short calls (<1p)Long/Short calls là quality control thuộc Manager scope; BOD không micromanage chất lượng từng cuộcBỏ 2 anomaly cards + 2 modal liên quan (AnomalyLongCallModal, AnomalyShortCallModal)
DEC-015Time range default 7 ngày qua, max hard limit 365 ngày. Auto-poll 60s khi tab active, pause khi background, disable khi range > 30 ngàyCân bằng UX freshness vs server loadURQL pollInterval logic

Z2) Quyết định UX (User Experience decisions)

IDQuyết địnhLý doTác động
DEC-002Route mới /e/record/insights với role-based guard. KHÔNG modify route /e/record hiện hữu (chỉ extend filter)Tách rõ insight (BOD) vs list (NV); không break user existingSidebar entry "Insight Ghi Âm" + button [📊 Insight →] trên /e/record cho role phù hợp
DEC-006Drill-down navigate /e/record cho mọi click chart (Top Staff bar, Trend point, Heatmap cell, Branch slice). KHÔNG mở modal phức tạpTận dụng list view + player có sẵn ở /e/record; tránh build 5 modal analytics richChỉ giữ 2 modal: MissingRecords + AnomalyMissingTV (case không reuse /e/record được)
DEC-008Branch chart = horizontal bar top 10 + "Khác" gộp (KHÔNG pie 70 slices)70 slices trên pie không readableReuse Chart.js BarChart wrapper
DEC-009Filter dropdown — Branch: search realtime + group by region (nếu có) + bulk select. Staff: search-only + lazy load (không render 700 NV upfront)Scale 70 CN × 700 NVRegion grouping cần BE confirm (PD-001)
DEC-013Pagination chuẩn cho modal có list dài — server-side, 10/20/50 rows/page, total count, search/filter reset page 1. P0 KHÔNG có Export Excel (defer P2 theo DEC-014).Scale có thể từ vài chục đến vài trăm rowsApply 2 modal P0 + Full Staff Ranking P2 + /e/record

Z3) Quyết định kỹ thuật (Technical decisions)

IDQuyết địnhLý doTác động
DEC-003Reuse RecordForm.tsx HTML5 audio player. KHÔNG build AudioPlayerCompact/Inline/FullModal mớiComponent đã production, props rõ ràng (modelValue: { url, name, mime_type }); tránh duplicate codeTiết kiệm 3 components mới + waveform lib + audio store
DEC-010Permission Dynamic v2 — action view_insight mới cho module voice_recording_management. Seed cho BOD + ITLeader qua schema v2: INSERT vào module_permission_action + array_append(role_module.actions, 'view_insight'). Cập nhật FE ALL_PERMISSION_ACTIONS trong permissions.ts. Dùng hasPermission(module, action) APITách quyền dashboard insight khỏi quyền list records; tuân thủ Dynamic Permission v2 chuẩn DivaVerified migration 1761808767073_perm_v2: module_permission_action table + role_module.actions TEXT[] array. KHÔNG dùng schema cũ
DEC-011Schema chuẩnpublic.record, public.appointment, public.reference_file (KHÔNG ecommerce.*). Join rf.reference_id = r.id (KHÔNG có entity_type). Duration extract qua EXTRACT(EPOCH FROM rf.duration)Verified trên migrations thật; tránh schema gotchaTất cả SQL view, GraphQL query phải tuân thủ
DEC-012Performance P0 mandatory — 4 indexes Tier 1 (3 B-tree compound + 1 BRIN) + Hasura @cached(ttl:60) + URQL stale-while-revalidate + 3 materialized views P0 (record_daily_summary, _staff_summary, _hourly_summary — moved từ P1 sau code review B5; inline aggregation fetch 105k rows sẽ fail TTFMP target). Refresh schedule mỗi 15 phútScale 105k records 30 ngày × 70 CN với TTFMP target 2-3sMigration indexes + materialized views + benchmark gate trước go-live
DEC-007Comparison mode defer P1 — P0 chỉ delta WoW trên KPI cardImplementation phức tạp (2x query + overlay)P0 ship lean; P1 build full comparison overlay nếu BOD cần
DEC-014Export defer P2 — Excel/PDF + schedule email defer P2. P0 chỉ "Copy link dashboard"Cheap P0; export tech stack cần BE assess (PD-009)Defer 3 modals + 1 scheduler + 1 PDF action

Z4) Quyết định QA (Quality assurance decisions)

IDQuyết địnhLý doTác động
Z4-001Performance gate trước go-live P0 — TTFMP < 2s với 7 ngày × 70 CN, < 3s với 30 ngàyDEC-012 mandatoryStaging benchmark + EXPLAIN ANALYZE không có Seq Scan
Z4-002Permission test — BOD/ITLeader có quyền; Staff truy cập URL trực tiếp phải 403DEC-010 enforceCypress E2E test
Z4-003Audio playback — drill-down /e/record → click play → audio stream < 1sDEC-003 reuse RecordFormManual UI test
Z4-004Pagination correctness — page navigation, total count, search/filter reset page 1. P0 KHÔNG có Export Excel (defer P2 theo DEC-014; nếu P2 build sẽ Export = TOÀN BỘ)DEC-013 + DEC-014Integration test

A) PRD (Product Requirements Document)

A1) Blueprint chức năng

┌────────────────────────────────────────────────────────────────────┐
│  /e/record/insights — BOD Insight Dashboard                         │
│                                                                     │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │ Filter Bar: Branch (70 CN) | Date | Staff (700) | Refresh    │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                                                                     │
│  ┌─ 5 KPI Cards ──────────────────────────────────────────────────┐│
│  │ Cuộc TV | Thời lượng TB | NV active | Tuân thủ % | KH chờ TV  ││
│  │ (+ delta WoW indicator)                                        ││
│  └────────────────────────────────────────────────────────────────┘│
│                                                                     │
│  ┌─ Trend (số cuộc/ngày) ─┐ ┌─ Top 10 NV bar ───────────────────┐ │
│  │ single line, click → drill │ │ click → drill /e/record?staffId│ │
│  └────────────────────────────┘ └─────────────────────────────────┘│
│                                                                     │
│  ┌─ Heatmap (giờ × thứ) ──┐ ┌─ Top 10 CN bar + "Khác" ─────────┐ │
│  │ aggregate 70 CN          │ │ click → drill /e/record?branchId│ │
│  │ click → drill /e/record  │ │                                 │ │
│  └──────────────────────────┘ └─────────────────────────────────┘│
│                                                                     │
│  ┌─ Anomaly Alerts (2 cards) ────────────────────────────────────┐ │
│  │ 🔴 X lịch hẹn thiếu record  → MissingRecordsModal             │ │
│  │ 🟡 Y KH chờ TV              → AnomalyMissingTVModal           │ │
│  └────────────────────────────────────────────────────────────────┘│
│                                                                     │
│  Footer: Cập nhật lần cuối ... | Tự động làm mới 60s              │
└────────────────────────────────────────────────────────────────────┘

A2) Bối cảnh (Context)

Trang /e/record hiện tại đáp ứng nhu cầu thao tác file ghi âm cho NV/Manager. Diva có 70 chi nhánh active với ~700 NV tư vấn, ~3.500 records/ngày, ~105.000 records/30 ngày. BOD (Board of Directors) hiện không có view tổng quan executive về compliance recording + coverage tư vấn. Phải gọi Manager hỏi từng CN → không scale.

A3) Mục tiêu (Goals)

Mục tiêuĐo lường
BOD nắm tình hình ghi âm toàn 70 CN trong < 5 giây sau khi mở dashboardTime-to-comprehension test với 3 BOD users
Phát hiện compliance gap (lịch hẹn thiếu record) ngay khi xảy raAnomaly section hiển thị critical alert đỏ
Phát hiện coverage gap (KH chờ TV) để BOD delegate xử lýAnomaly section hiển thị info alert
Hỗ trợ drill-down chi tiết qua /e/record để verifyClick chart → navigate URL params correct
Performance load TTFMP < 2s/7d, < 3s/30d với 70 CNStaging benchmark trước go-live

A4) Personas

PersonaMô tảQuyền action P0Branch scope
BOD (NGUYỄN SƠN THỌ + executive team)Board of Directors / CEO / CFO — quyết định chiến lượcaccess, view_all, view_insightAll 70
IT LeaderQuản lý infrastructureaccess, view_all, view_insight, deleteAll 70
Admin (ROLE_MODERATOR)System admin bypass tất cả checksAll actions tự độngAll
Branch ManagerQuản lý 1-N chi nhánhaccess, view_all (?), KHÔNG view_insight P0Own branches
Staff (NV)NV tư vấn — chỉ thao tác list recordsaccess onlySelf

A5) Yêu cầu chức năng + tiêu chí chấp nhận (FR/AC)

FR-001: Dashboard Page với 5 KPI cards

Mô tả: Khi user có quyền voice_recording_management:view_insight truy cập route /e/record/insights, hệ thống hiển thị dashboard với header "Insight Ghi Âm" + filter bar + 5 KPI cards.

5 KPI cards:

#TênCông thứcĐơn vịDelta indicator
1CUỘC TƯ VẤNCOUNT(record) WHERE disabled=false AND in filterSốWoW % (so tuần trước)
2THỜI LƯỢNG TBAVG(EXTRACT(EPOCH FROM rf.duration)) / 60Phút:giây hoặc giờ:phútWoW %
3NV HOẠT ĐỘNGCOUNT(DISTINCT record.created_by)Số NVWoW absolute (+/-)
4TUÂN THỦ GHI ÂM(appointment có record) / (tổng appointment loại tư vấn) (PD-003)%WoW điểm phần trăm
5KH CHỜ TVCOUNT(appointment_không_có_record WHERE type=tư vấn)Số KH⚠ Cần xem (warning indicator)

AC:

  • AC-001-1: Khi page load lần đầu trong < 2s, 5 KPI cards hiển thị skeleton loading rồi render số liệu
  • AC-001-2: Khi filter change, KPI cards re-fetch và update với loading state intermediate
  • AC-001-3: Delta indicator hiển thị ▲green/▼red/─gray với % và label "so tuần trước"
  • AC-001-4: Hover ⓘ icon → tooltip giải thích công thức + sample con số (xem ui-spec B9)
  • AC-001-5: KPI #4 "Tuân thủ ghi âm" hiển thị nếu mẫu số = 0 (không có appointment loại tư vấn)
  • AC-001-6: KPI #2 "Thời lượng TB" hiển thị warning "X cuộc không có metadata duration" nếu có records bị NULL

FR-002: Trend Chart (số cuộc theo ngày)

Mô tả: Single-line chart hiển thị số cuộc tư vấn theo ngày trong period đã filter.

AC:

  • AC-002-1: Y-axis = số cuộc; X-axis = ngày trong khoảng filter
  • AC-002-2: Hover 1 điểm → tooltip "Thứ X, DD/MM/YYYY: N cuộc"
  • AC-002-3: Click 1 điểm → navigate /e/record?dateExact=YYYY-MM-DD&branchId=<scope>
  • AC-002-4: Empty state khi < 2 ngày có data: "Chưa đủ dữ liệu để vẽ xu hướng"
  • AC-002-5: Time range default 7 ngày; max 365 ngày

FR-003: Top 10 Nhân viên bar chart

Mô tả: Horizontal bar chart top 10 NV theo số cuộc, sorted desc.

AC:

  • AC-003-1: Bar dài tỷ lệ với số cuộc; hiển thị số cuộc bên cạnh
  • AC-003-2: Click bar → navigate /e/record?staffId=<acc_uuid>&from=<>&to=<>&branchId=<scope>
  • AC-003-3: Empty state khi không có data: "Không có nhân viên tư vấn trong khoảng đã chọn"
  • AC-003-4: Footer "Click vào tên → drill /e/record" hint

FR-004: Heatmap giờ × thứ trong tuần

Mô tả: Grid 24h × 7 weekday hiển thị mật độ cuộc tư vấn. Aggregate qua các CN đã filter.

AC:

  • AC-004-1: Cell color theo amber scale: 0 (gray), 1-3 (amber-100), 4-7 (amber-500), 8+ (amber-900)
  • AC-004-2: Hover cell → tooltip "Thứ X lúc Hh: N cuộc, TB Y phút"
  • AC-004-3: Click cell → navigate /e/record?dateExact=<recent_matching_date>&branchId=<scope> (chính xác đến NGÀY, không đến GIỜ — DEC-006 tradeoff)
  • AC-004-4: Auto-hide giờ không có hoạt động (vd ban đêm 22h-7h)
  • AC-004-5: Legend "Ít / Nhiều" với color scale

FR-005: Top 10 Chi nhánh bar + "Khác" gộp

Mô tả: Horizontal bar chart top 10 CN theo số cuộc; CN xếp sau gộp thành "Khác (N CN)".

AC:

  • AC-005-1: Bar hiển thị số cuộc + % tổng (vd "Long Khánh 156 (28%)")
  • AC-005-2: Row "Khác (60 CN)" gộp tất cả CN ngoài top 10
  • AC-005-3: Click bar → navigate /e/record?branchId=<branch_uuid>&from=<>&to=<>
  • AC-005-4: CTA "[Xem tất cả 70 chi nhánh →]" defer P2 (link tới full ranking view)

FR-006: Anomaly — Missing Records (lịch hẹn thiếu ghi âm) → Modal

Mô tả: Card 🔴 critical hiển thị số lịch hẹn tư vấn KHÔNG có record được tạo. Click CTA → mở modal pagination.

AC:

  • AC-006-1: Card hiển thị số count + summary "Cao nhất: NV X (N cases) — Chi nhánh Y"
  • AC-006-2: Click [Xem N lịch hẹn →] → mở MissingRecordsModal với pagination
  • AC-006-3: Modal có: title dynamic số count, search box, sort dropdown, filter CN dropdown, table 10 rows/page, total count, page selector. Footer chỉ có [Đóng] — KHÔNG có Export Excel ở P0 (defer P2, chốt theo DEC-014)
  • AC-006-4: Empty state: "✅ Tất cả lịch hẹn đều được ghi âm đầy đủ"
  • AC-006-5: Baseline đếm phụ thuộc PD-003 — phải align với BE team trước implement

FR-007: Anomaly — KH chờ TV → Modal

Mô tả: Card 🟡 info hiển thị số KH có lịch hẹn loại tư vấn nhưng chưa có record.

AC:

  • AC-007-1: Card hiển thị số count + section "Phân loại tự động" (no-show / compliance gap / pending follow-up)
  • AC-007-2: Click [Xem danh sách N KH →] → mở AnomalyMissingTVModal với pagination tương tự
  • AC-007-3: Modal có thêm filter "Lọc lý do" (3 options phân loại)
  • AC-007-4: Row action: [Mở appt] (tab mới appointment detail) + [KH] (tab mới customer detail)

FR-008: Filter Bar (70 CN + 700 NV scale)

Mô tả: Filter bar với 3 dropdown — Chi nhánh, Khoảng thời gian, Nhân viên — và button làm mới.

AC:

  • AC-008-1: Branch dropdown: chip compact ("Long Khánh +2" / "70 chi nhánh") + search realtime + group by region (nếu BE có column region — PD-001) + bulk action "Chọn tất cả" / "Bỏ chọn tất cả"
  • AC-008-2: Date range dropdown: preset (Hôm nay/Hôm qua/7 ngày/30 ngày/Tháng này/Tháng trước/Quý này/Tùy chỉnh) + custom date picker
  • AC-008-3: Staff dropdown: search-only mode (KHÔNG render 700 NV upfront), debounce 300ms, lazy load 10 results/lần, quick filter "Top 10 NV active 7 ngày qua"
  • AC-008-4: Button "[🔄 Làm mới]" → force re-fetch tất cả query bypassing cache
  • AC-008-5: Filter change → reset pagination về page 1 ở tất cả widget có pagination
  • AC-008-6: Default scope: BOD/Admin/ITLeader có view_all → "70 chi nhánh"; BranchManager/Staff → branch của họ

FR-009: Audio playback qua drill-down /e/record

Mô tả: Khi BOD muốn nghe sample audio, drill-down sang /e/record để dùng player có sẵn (RecordForm.tsx).

AC:

  • AC-009-1: Mọi click chart segment (Top Staff bar, Trend point, Heatmap cell, Branch slice) → navigate /e/record với query params phù hợp
  • AC-009-2: /e/record parse query params trong onMounted, apply vào selected ref
  • AC-009-3: Player HTML5 hiện hữu hoạt động không thay đổi
  • AC-009-4: Browser back → quay lại dashboard với state filter giữ nguyên (qua URQL cache + URL state)

FR-010: Permission v2 — action view_insight

Mô tả: Tách quyền xem dashboard insight khỏi quyền list records hiện hữu.

AC:

  • AC-010-1: Sidebar entry "Insight Ghi Âm" CHỈ hiển thị khi hasPermission('voice_recording_management', 'view_insight') returns true
  • AC-010-2: Button [📊 Insight →] trên /e/record CHỈ hiển thị với điều kiện như trên
  • AC-010-3: Route /e/record/insightsbeforeEnter guard — fail thì redirect forbidden
  • AC-010-4: Migration seed schema v2: INSERT row mới (module_id='voice_recording_management', action='view_insight') vào module_permission_action catalog + array_append(role_module.actions, 'view_insight') cho role BOD + ITLeader; FE update ALL_PERMISSION_ACTIONS trong permissions.ts
  • AC-010-5: Admin (ROLE_MODERATOR) bypass tất cả (theo useGlobalStore.ts:169-181)

FR-011: Extend /e/record — URL query params + 2 filter mới

Mô tả: RecordTable parse URL query params khi mount + thêm 2 filter mới (durationGt/Lt) cho drill-down anomaly tương lai.

AC:

  • AC-011-1: Khi mount, RecordTable đọc useRoute().query và apply: branchId, from, to, staffId, customerId, durationGt, durationLt, dateExact
  • AC-011-2: Nếu dateExact có → set from = dateExact, to = dateExact (overrides range)
  • AC-011-3: Filter durationGt + durationLt extend GraphQL query với files.duration { _gt/_lt } (thời gian theo PostgreSQL time type)
  • AC-011-4: KHÔNG break existing behavior — nếu không có query params, RecordTable hoạt động như cũ

A6) Giả định (Assumptions)

Giả địnhTác động nếu sai
70 chi nhánh là số chính xác và stable trong 6 tháng tớiNếu scale lên 150+ CN, có thể cần re-design filter dropdown
~3.500 records/ngày là estimate hợp lýNếu thực tế 10.000+/ngày, materialized view refresh 15p có thể chậm → escalate Tier 4 daily snapshot (P2) sớm hơn
reference_file.duration được populate đủ cho records mớiNếu NULL nhiều → metric "Thời lượng TB" skewed (PD-004)
appointment."from" field exists và đáng tin cậyNếu field khác (vd scheduled_at) → SQL view phải update
BE chấp nhận seed action view_insight theo pattern hiện hữuNếu BE muốn rename → update tất cả AC + migration

A7) Rủi ro & giảm thiểu (Risks & Mitigations)

Rủi roMứcGiảm thiểu
Performance TTFMP > 3s với 30 ngày × 70 CN🟠Tier 1 indexes mandatory P0 (DEC-012); benchmark staging trước go-live (PD-008)
Appointment baseline cho "Tuân thủ" chưa rõ (PD-003)🟠BLOCK FR-006 query cho đến khi PO/BE align; fallback temporary all appointments
Duration NULL nhiều (PD-004)🟡View aggregation expose records_without_duration; UI warning "X cuộc không tính được"; backfill script nếu > 5%
JWT claim size 70 branches (PD-006)🟡Test JWT size < 8KB; optimize bằng view_all flag nếu cần
Materialized view refresh fail (P0 monitor)🟡Sentry alert nếu refresh fail 3 lần liên tiếp; manual REFRESH MATERIALIZED VIEW qua admin. Stale data tối đa 15 phút — acceptable cho tracking dashboard. KHÔNG fallback inline (sẽ fail TTFMP).
JWT claim x-hasura-allowed-branches chưa có code Diva (PD-011)🔴BE middleware implement P0 mandatory trước FE deploy. Test JWT decode 3 role; verify Hasura filter _in: [] cho Staff trả 0 rows
Region grouping (PD-001) chưa có🟢Fallback flat list 70 CN với search realtime; OK cho P0
Audio retention policy (PD-002)🟢UI handle "File hết hạn lưu trữ" gracefully

A8) Metrics đo lường thành công

MetricTargetCách đo
BOD adoption (active users)≥ 3/5 BOD members weekly trong tháng đầuAnalytics event record_insight_viewed
TTFMP performance< 2s với 7d, < 3s với 30dSentry transaction tracking
Compliance gap detection rateBOD phát hiện gap trong < 24h sau khi xảy raAnomaly card hiển thị + BOD click rate
Drill-down usage≥ 50% session có ít nhất 1 drill-down /e/recordAnalytics event record_drilldown_clicked
Permission setup100% BOD + ITLeader được seed view_insight trước go-liveMigration audit log

A9) Bảng thuật ngữ (Glossary)

Thuật ngữ (VI)Thuật ngữ (EN)Định nghĩaPhân biệt với
Ghi âm tư vấnVoice consultation recording (Record)Entity public.record chứa metadata + FK đến audio file≠ Ghi âm cuộc gọi (telephony)
Insight Ghi ÂmRecord Insight DashboardTên feature canonical≠ "BOD Dashboard" / "Recording Analytics"
Tuân thủ ghi âmRecording complianceTỷ lệ % appointment loại tư vấn có ghi âm vs tổng appointment loại tư vấn trong period≠ Coverage (KH đã được TV)
KH chờ TVCustomers awaiting consultationKH có lịch hẹn loại tư vấn nhưng KHÔNG có record được tạo≠ "KH chưa được phục vụ"
Drill-down /e/recordDrill-down to record list pageNavigate sang /e/record với URL query params filter pre-applied≠ "Mở modal"
Tracking-onlyTracking-only stanceDashboard chỉ xem + nghe + drill-down + export; KHÔNG action management
AuditAudit (verb — kiểm tra)"Cần xem xét cuộc tư vấn" — verb chung≠ "Audit notes" (đã bị bỏ DEC-001)
Drill-down navigateDrill-down navigation patternClick chart → navigate /e/record với query params; KHÔNG mở modal≠ Modal drill-down
Cuộc tư vấnConsultation call / Voice consultation session1 cuộc gặp/gọi giữa NV và KH có ghi âm
PeriodKhoảng thời gian filterDate range được user chọn (default 7 ngày)
Compliance gapCompliance gap (lịch hẹn thiếu record)Appointment loại tư vấn không có record được tạo≠ Coverage gap (KH chờ TV)
Coverage gapCoverage gap (KH chưa được TV)KH có lịch nhưng không có record (lý do: no-show / compliance gap / pending)≠ Compliance gap

A10) Công thức kinh doanh (Business formulas — single source)

Mọi công thức nghiệp vụ canonical ở đây. Dev-spec C3 chỉ ghi SQL implementation delta, KHÔNG duplicate định nghĩa nghiệp vụ.

FORMULA-001: Số cuộc tư vấn

  • Mô tả: Tổng số ghi âm tư vấn đã tạo trong period × scope branch
  • Công thức: count = COUNT(record) WHERE record.disabled = false AND appointment.branch_id IN $branchIds AND record.created_at >= $from AND record.created_at < $to + 1 day
  • Đơn vị: Số (integer)
  • Ví dụ: Period 7 ngày × 70 CN có 124 records active → count = 124
  • Edge cases:
    • $branchIds rỗng → trả về 0
    • Period > 365 ngày → cap hard limit 365

FORMULA-002: Thời lượng trung bình / cuộc

  • Mô tả: Thời lượng audio trung bình mỗi cuộc tư vấn
  • Công thức: avg_duration_sec = AVG(EXTRACT(EPOCH FROM rf.duration)) WHERE rf.duration IS NOT NULL AND record matches scope
  • Đơn vị: Giây → format "Xp Ys" hoặc "Xh Ym"
  • Biến số:
    • rf.duration: column trực tiếp public.reference_file.duration (PostgreSQL time type, HH:MM:SS)
  • Ví dụ: 124 cuộc với tổng duration 1.935.000 giây (32h 15m) / 124 = 15.605 giây ≈ 15p 36s
  • Edge cases:
    • Tất cả records có duration IS NULL → hiển thị "—" + warning
    • Records có duration NULL được EXCLUDE khỏi AVG, không tính 0

FORMULA-003: NV hoạt động

  • Mô tả: Số nhân viên unique có ít nhất 1 cuộc tư vấn trong period × scope
  • Công thức: active_staff = COUNT(DISTINCT record.created_by) WHERE record matches scope
  • Đơn vị: Số NV
  • Ví dụ: Period 7 ngày × 70 CN có 124 records từ 8 NV unique → active = 8

FORMULA-004: Tỷ lệ tuân thủ ghi âm

  • Mô tả: % appointment loại tư vấn có ghi âm vs tổng appointment loại tư vấn
  • Công thức:
    compliance_rate = (
      COUNT(appointment có record) /
      COUNT(appointment loại "tư vấn" trong period × scope)
    ) × 100
  • Đơn vị: % (2 decimal)
  • Biến số:
    • "appointment có record": JOIN appointment với record qua appointment_id, có ≥ 1 record không disabled
    • "appointment loại tư vấn": baseline cần PD-003 — tạm thời all appointments có "from" < now() (column from trong public.appointment, là PostgreSQL reserved keyword nên phải quote)
  • Ví dụ: Period có 50 appointment loại tư vấn, 47 có record → 47/50 × 100 = 94.00%
  • Edge cases:
    • Mẫu số = 0 (không có appointment loại TV) → hiển thị "—", không 0%/NaN%

FORMULA-005: KH chờ TV

  • Mô tả: Số khách hàng có lịch hẹn loại tư vấn trong period nhưng chưa được tạo ghi âm
  • Công thức (schema correction):
    awaiting_count = COUNT(DISTINCT au.user_id)
      FROM appointment_user au
      JOIN appointment a ON a.id = au.appointment_id
      WHERE a."from" >= $from AND a."from" < $to + 1 day
      AND a.branch_id IN $branchIds
      AND a loại "tư vấn" (PD-003)
      AND NOT EXISTS (record r WHERE r.appointment_id = a.id AND r.disabled = false)

    Schema note: appointment KHÔNG có column customer_id direct (verified migration 1662366406542). Customer extraction qua pivot appointment_user.user_idecommerce_user. Trong context tư vấn, mỗi appointment thường có 1 customer trong pivot.

  • Đơn vị: Số KH unique
  • Ví dụ: Period 7 ngày có 156 KH thỏa điều kiện → awaiting = 156
  • Edge cases:
    • Không tính KH đã rebook (chỉ trong period đang xem)
    • Phân loại "lý do" (no-show / compliance gap / pending) là heuristic — chỉ tham khảo

FORMULA-006: Delta WoW (Week-over-Week)

  • Mô tả: % thay đổi so với khoảng so sánh (mặc định tuần trước)
  • Công thức: delta_pct = ((current - previous) / previous) × 100
  • Biến số:
    • current: KPI value của period hiện tại
    • previous: KPI value của period so sánh (default: shift back theo độ dài period — vd period 7 ngày → previous 7 ngày liền trước)
  • Đơn vị: % với indicator ▲ (positive) / ▼ (negative) / ─ (= 0%)
  • Ví dụ: Hiện tại 124 cuộc / Trước 111 cuộc → delta = (124-111)/111 × 100 = +11.71% → "▲ 12% so tuần trước"
  • Edge cases:
    • previous = 0 → hiển thị "▲ Mới" (không tính delta vô cực)
    • Đối với KPI #3 (NV active) → dùng absolute delta thay vì % (vd "─ Không đổi" hoặc "▲ +2 NV")

A11) Traceability — FR → AC → DEC → File downstream

Đảm bảo 100% coverage. Mọi FR phải có ≥ 1 AC + reference DEC + map đến file downstream.

FRACDEC refUI file refDev file refQA file ref
FR-001AC-001-1 đến 6DEC-004, DEC-011ui-spec §B-KPIdev-spec §C3 (FORMULA-001 đến 005)qa-test-plan §D-KPI
FR-002AC-002-1 đến 5DEC-006ui-spec §B-Trenddev-spec §C5 queryqa-test-plan §D-Chart
FR-003AC-003-1 đến 4DEC-006ui-spec §B-TopStaffdev-spec §C5qa-test-plan §D-Chart
FR-004AC-004-1 đến 5DEC-006ui-spec §B-Heatmapdev-spec §C5 + §C9 custom SVGqa-test-plan §D-Chart
FR-005AC-005-1 đến 4DEC-008ui-spec §B-Branchdev-spec §C5qa-test-plan §D-Chart
FR-006AC-006-1 đến 5DEC-005, DEC-013ui-spec §B-MissingRecordsModaldev-spec §C5 query #7 + §C7 paginationqa-test-plan §D-Modal
FR-007AC-007-1 đến 4DEC-005, DEC-013ui-spec §B-AnomalyMissingTVModaldev-spec §C5 + §C7qa-test-plan §D-Modal
FR-008AC-008-1 đến 6DEC-009ui-spec §B-FilterBardev-spec §C5 staff lazy + branch groupqa-test-plan §D-Filter
FR-009AC-009-1 đến 4DEC-003, DEC-006ui-spec §B-DrillDowndev-spec §C5 navigation logicqa-test-plan §D-DrillDown
FR-010AC-010-1 đến 5DEC-010ui-spec §B-Permissiondev-spec §C8 + §C7 migration seedqa-test-plan §D-Permission
FR-011AC-011-1 đến 4DEC-002ui-spec §B-RecordTableExtenddev-spec §C5 RecordTable mod + GraphQL extendqa-test-plan §D-Extend

A12) Tooltip & microcopy chính (UI copy contract)

UI label ngắn tự nhiên; định nghĩa chặt nghĩa + công thức + scope đưa vào tooltip. Detail full: ui-spec.md §B9 Tooltip Dictionary.

Vị tríLabel (ngắn)Tooltip (chi tiết)
KPI #1CUỘC TƯ VẤNTổng số ghi âm tư vấn đã tạo trong khoảng thời gian + chi nhánh đã chọn
KPI #2THỜI LƯỢNG TBThời lượng trung bình mỗi cuộc tư vấn. Insight: TB quá ngắn (< 5p) có thể NV chưa tư vấn đủ; TB quá dài (> 30p) có thể KH có vấn đề phức tạp
KPI #3NV HOẠT ĐỘNGSố nhân viên unique có ít nhất 1 cuộc tư vấn được ghi âm trong period
KPI #4TUÂN THỦ GHI ÂMTỷ lệ % cuộc tư vấn đã có ghi âm so với tổng số lịch hẹn loại "Tư vấn dịch vụ" trong khoảng thời gian
KPI #5KH CHỜ TƯ VẤNSố khách hàng có lịch hẹn loại tư vấn nhưng chưa được tạo ghi âm
Anomaly 🔴"X lịch hẹn tư vấn KHÔNG có ghi âm"Compliance gap — appointment đã đến nhưng NV chưa upload audio. Cần BOD theo dõi tỷ lệ và delegate Manager xử lý
Anomaly 🟡"Y khách hàng có lịch hẹn nhưng chưa được tư vấn"Coverage gap — KH đặt lịch nhưng không có record. Có thể: no-show / compliance gap / pending follow-up
Footer"Cập nhật lần cuối: ..."Thời điểm dashboard refresh data gần nhất. Tự động làm mới mỗi 60s khi tab active
Filter branch chip"70 chi nhánh"BOD đang xem dữ liệu của toàn bộ 70 chi nhánh trong hệ thống
Filter date chip"7 ngày qua"Hôm nay + 6 ngày trước (rolling window)
Pagination footer"Hiển thị X-Y / TOTAL"Số rows đang hiển thị trên tổng số rows toàn dataset

Tham chiếu chéo:

  • Decision Brief: ./decision-brief.md
  • Source of Truth: ./SOURCE_OF_TRUTH.md
  • Evidence Pack: ./EVIDENCE_PACK.md
  • Design doc gốc: ../../superpowers/specs/2026-05-15-record-bod-insight-design.md
  • UI Spec: ./ui-spec.md (next file)
  • Dev Spec: ./dev-spec.md
  • QA Test Plan: ./qa-test-plan.md
  • Go-Live: ./go-live-checklist.md
  • Handoff: ./handoff.md