Q5 Plan — roots_hemms_ha_report (Final 1.0)¶
Plan version: Final 1.0 — Approved, ready to execute Approved: 2026-05-22 Module ID: roots_hemms_ha_report Layer: 3 (Hospital lens) — depends on Q1 (PM), Q3 (contracts, indirect via signature), Q4 (roots_hemms_signature) Implementation: Q5.1–Q5.12 ship sequentially, no external blockers (Q3 + Q4 already shipped)
0. Executive Summary¶
What we build: A new Layer 3 module roots_hemms_ha_report that produces the Annual Preventive Maintenance Report PDF required by HA Thailand and the MoPH มาตรฐานระบบบริการสุขภาพ ด้านที่ 6 standard. The report aggregates the PM data Q1 already captures, classifies every equipment item by the MoPH 3-tier risk model (สูง / กลาง / ต่ำ), shows planned vs actual PM per equipment for one Thai fiscal year (ปีงบประมาณ), locks via a JSON snapshot on submit, and routes through Q4's sign_oca bridge for the BME-head + Engineering-head + Director sign-off.
Why: Today BME engineers reconstruct this report manually in Excel each year — 2–4 weeks of work that's error-prone and audit-fragile. Surveyors ask for this document on day 1 of an HA Thailand visit; failing to produce it delays accreditation. Q5 turns Q1's PM data into a one-click compliance artifact, pairing with the existing ปจป. annual asset inspection (roots_hemms_asset_inspection) so HEMMS now covers both year-end compliance reports a Thai public hospital must produce.
Effort: 12 commits / ~2 dev sessions, 20 tests target, 5-page docs, 1 fiscal year demo (30 equipment × 3 depts, ~78% compliance with 3 documented exceptions).
Keystone wins (impact-ranked):
- ⭐ One-click annual report (Q5.7 + Q5.8) — generate wizard builds lines from PM data, QWeb renders the full 7-section government-format PDF
- ⭐ MoPH 83-device risk-tier seed (Q5.2) — every hospital gets the official classification table out of the box; editable for local additions
- ⭐ WHO Fennigkoh-Smith EM score on the equipment form (Q5.3) — 4 sliders + live score lets BME engineers classify off-list equipment in seconds, with the score worksheet preserved in appendix B as auditor evidence
- ⭐ Sign-off via Q4 bridge (Q5.9) — BME head + Engineering head
- Director sign in-app or via portal email; signed PDF auto-attached to chatter; no separate state, badge shows 🟡 รอลงนาม / 🟢 ลงนามครบ
- ⭐ Lock-on-submit snapshot (Q5.6) — reprints are byte-identical even after the underlying equipment data changes; audit-defensible
License: LGPL-3 (wrapper) over AGPL-3 transitive dep (sign_oca via Q4). Same boundary pattern as Q2.1 / Q3 / Q4.
Status of open questions: All 6 resolved 2026-05-22 — OQ-A per-model bridge (no mixin); OQ-B noupdate="1" baseline; OQ-C 4 sliders on equipment form; OQ-D no re-sign cap; OQ-E Q4.6 fixtures reused; OQ-F Q5 = HA Report, contract follow-ons → Q6. See §8.
1. Purpose¶
Thai public hospitals are required by ประกาศกระทรวงสาธารณสุข เรื่อง การกำหนดลักษณะของสถานพยาบาลและมาตรฐาน — มาตรฐานระบบบริการสุขภาพ ด้านที่ 6 ข้อ 6.1.4 to maintain a current equipment register AND perform risk-based preventive maintenance with documented evidence. HA Thailand surveyors ask for the annual evidence on day 1.
The binding standard:
"แนวทางประเมินระดับความเสี่ยงของเครื่องมือแพทย์ที่ต้องการการบำรุงรักษา และกำหนดความถี่ในการตรวจสอบและบำรุงรักษาเชิงป้องกันตามรอบเวลา (พ.ศ. 2564)" — กองวิศวกรรมการแพทย์, กรมสนับสนุนบริการสุขภาพ, กระทรวงสาธารณสุข, co-developed with บริษัท อาร์เอฟเอส (รามาธิบดี/ม.มหิดล) and ECRI APAC.
No single official PDF form exists — hospitals build their own layout that proves these data points. Q5 implements that layout in QWeb, Thai-government style consistent with the existing ปจป. report.
Scope — in¶
- Q5.1: Module skeleton, manifest,
ir.sequence(HA/%(year)s/00001), paperformat (A4, TH Sarabun, Thai government margins) - Q5.2:
hemms.ha.device.classvocabulary model + MoPH 83-device seed data (low/mid/high tier, freq months, duration hours),noupdate="1" - Q5.3:
maintenance.equipmentextension: roots_risk_tier(Selection high/medium/low)roots_pm_freq_months(Integer)device_class_id(M2Ohemms.ha.device.class)roots_em_function/roots_em_application/roots_em_maintenance/roots_em_history(Integer sliders)roots_em_score(Integer, computed/stored)- Waterfall compute: explicit
roots_risk_tier→device_class_idlookup → EM score - Collapsible "Risk Assessment (HA)" section on equipment form
- Q5.4:
hemms.ha.reportheader model: name,fiscal_year,department_id,date_from/to,stateauthor_id,bme_head_id,engineering_head_id,director_id- Computed:
total_equipment,pm_plan_count,pm_actual_count,compliance_pct,tier_high_count,tier_mid_count,tier_low_count mail.thread+mail.activity.mixin, list/form views, menu- Q5.5:
hemms.ha.report.linemodel: equipment_id+ related bilingual names +equipment_ref+serial_no+model+department_idrisk_tier,pm_freq_months(from equipment waterfall)pm_planned_count(derived 12 / freq),pm_actual_count,pm_compliance_pctresult(pass/conditional/fail/not_done),exception_reason,responsible_id- Q5.6:
hemms.ha.report.snapshotJSON model +action_submit()lock — snapshot stores line data + device-class versions + paperformat metadata; reprint always renders from snapshot - Q5.7:
hemms.ha.report.generatewizard: - Fiscal year (Thai พ.ศ.) + department selector
- Reuses
roots_hemms_asset_inspection._get_fiscal_year_dates() - Filters active equipment by department + counts completed PM
maintenance.request(kind = PM) in window - Q5.8: 7-section QWeb report:
- Cover (ปก) — header fields
- บทสรุปผู้บริหาร — totals + tier counts + compliance %
- ตารางครุภัณฑ์ + ผลการบำรุงรักษา — per-equipment detail
- รายการครุภัณฑ์ที่ไม่ได้ทำตามแผน — exceptions with reason
- ภาคผนวก ก — MoPH 83-device table
- ภาคผนวก ข — EM score worksheet (lines using EM path only)
- หน้าลงนาม — 3 signature blocks (BME head / Eng head / Director)
- Q5.9:
sign_ocabridge (per-model pattern, no Q4 edit): sign.oca.request._inheritaddshemms_ha_report_id(computed M2O fromrecord_ref)_check_signedsuper-extension writesroots_signed_pdfwhenrecord_ref._name == 'hemms.ha.report'action_send_for_signature()on report with 3-signerCommand.create()(BME head / Eng head / Director)_check_ha_signers_resolvable()pre-flight guard- 2 new role seeds (
role_bme_head,role_engineering_head); reuse Q4'sroots_hemms_signature.role_hospital_signerfor Director - Demo template XML (
demo/sign_oca_template_ha_report.xml) - QWeb signature page: paper-style empty boxes when
roots_signed_pdfblank; embed signed PDF image when set - Stat button + 🟡 รอลงนาม / 🟢 ลงนามครบ badge on header form
- Q5.10: Tests — 8 unit + 12 integration = 20 tests target, green on
hemms_demoANDhemms_private - Q5.11: 5-page docs under
docs/modules/ha-report/+ this plan - Q5.12: Demo data — 1 FY (2569 พ.ศ.) hospital with 30 equipment × 3 depts, mixed tiers, ~78% compliance, 3 exceptions ready to print
Scope — out (deferred)¶
- ❌ Multi-fiscal-year comparison reports — Q6+
- ❌ Excel export (XLSX) alongside PDF — Q6+
- ❌ Real-time compliance dashboard widget — Q6+
- ❌ Auto-generate on fiscal year end (cron) — Q6+ (manual wizard for V1)
- ❌ Per-department sub-reports inside one master report — Q6+
- ❌ Equipment with multiple sub-locations / mobile equipment tracking — Q6+
- ❌ Vendor performance scorecard inside the report — Q6 (was Q4 "Q5+")
- ❌ Multi-stage renewal alerts / SLA breach / contract template wizard / workflow gating — Q6 (contract follow-ons; was Q4 "Q5+")
- ❌ Auto-recalc compliance % when PM events backdated — by design; snapshot is immutable post-submit
- ❌ Multi-language report (EN-primary version) — Q6+ (TH primary for V1)
- ❌ OTP / 2FA at sign moment — Q6+ (sign_oca roadmap)
- ❌ PKI / ETDA Level 2 certified signatures — Q6+ (sign_oca roadmap)
2. Design decisions (locked)¶
| # | Decision | Choice |
|---|---|---|
| D1 | Map roots_hemms_criticality (Value × Risk → R/Y/G) to MoPH (สูง/กลาง/ต่ำ)? | No mapping — separate roots_risk_tier field. Criticality drives ops triage; risk_tier is the regulator's classification. Different semantics, different sources of truth. Conflating creates a leaky abstraction. |
| D2 | Seed the 83-device MoPH table as data? | Yes — hemms.ha.device.class with noupdate="1". Hospitals can add/edit local entries post-install without losing baseline on upgrade. Mirrors Q3 contract-type vocabulary pattern. |
| D3 | Fiscal year handling | Reuse roots_hemms_asset_inspection._get_fiscal_year_dates() (Thai ปีงบประมาณ Oct–Sep). Lift to a public helper if currently private. |
| D4 | Snapshot mechanism | JSON blob on hemms.ha.report.snapshot (mirrors Q2/asset-inspection). Not a cloned read-only model. |
| D5 | Signature flow | OCA sign_oca via Q4 (roots_hemms_signature dep). |
| D6 | Where does sign integration live? | Inside roots_hemms_ha_report — Q5 owns its own sign.oca.request extension (adds hemms_ha_report_id + further _check_signed). Does NOT modify roots_hemms_signature. Q5 reuses Q4 helpers (_roots_attach_signed_pdf_to_parent) via inheritance chain. |
| D7 | Risk-tier source on equipment | Three-step waterfall: (1) explicit roots_risk_tier → use it; (2) else device_class_id lookup → MoPH table; (3) else compute from EM score. Wizard surfaces which path was used per line. Auditable. |
| D8 | Bilingual rendering | TH primary, EN in parens for equipment names; section headings TH-only (consistent with ปจป. report). |
| D9 | State advance on sign | No state advance — roots_signed_pdf presence IS the signed status. Header stays in submitted. Matches Q4 OQ3. Simpler state machine, fewer transition tests, consistent house pattern. |
| D10 | Lock-on-submit | action_submit() builds JSON snapshot, freezes lines via state check on field readonly. Equipment data changes after submit do NOT alter the report. Reprint always from snapshot, byte-identical. |
| D11 | Rejection path | Reject via sign portal → clear roots_signed_pdf, archive old sign.oca.request, allow new spawn. Report stays in submitted throughout. Audit visible in chatter. No cycle cap (matches Q3 + Q4 patterns). |
| D12 | Security scoping | department_id on header + record rules: BME staff own-dept; BME head all; Engineering head / Director / Quality dept read all. |
| D13 | EM score formula | EM = Function + Application + Maintenance + History per WHO Fennigkoh-Smith. History range −2..+2 (can reduce score; not abs'd). Buckets: ≤12 drop / 12–14 → 1×/yr / 15–18 → 2×/yr / ≥19 → 3×/yr. Boundary interpretation documented in code comment (12 = drop; 14 = 1×/yr; 18 = 2×/yr; 19 = 3×/yr — inclusive upper bounds). |
| D14 | EM editor UX | 4 sliders on the equipment form inside a collapsible "Risk Assessment (HA)" section. Live roots_em_score next to inputs. Wizard-free. |
| D15 | License / status | LGPL-3, Alpha, maintainer jakapolr. |
3. Data model¶
3.1 New models¶
hemms.ha.device.class (Q5.2 — MoPH 83-device vocab)
├─ name_en Char e.g. "Anesthesia Units"
├─ name_th Char e.g. "เครื่องดมยาสลบ"
├─ risk_tier Selection high/medium/low
├─ pm_freq_months Integer e.g. 6
├─ pm_duration_hours Float e.g. 2.4
├─ sequence Integer display order
└─ active Boolean
hemms.ha.report (Q5.4 — header)
├─ name Char readonly, ir.sequence
├─ fiscal_year Integer Thai พ.ศ.
├─ department_id M2O hr.department (null = whole hospital)
├─ date_from / date_to Date auto from fiscal_year
├─ state Selection draft / in_progress / submitted / archived
├─ author_id M2O res.users
├─ bme_head_id M2O res.users
├─ engineering_head_id M2O res.users
├─ director_id M2O res.users
├─ line_ids O2M hemms.ha.report.line
├─ snapshot_id M2O hemms.ha.report.snapshot
├─ sign_request_ids O2M sign.oca.request (via hemms_ha_report_id inverse)
├─ sign_request_count Integer computed, stored, sudo
├─ roots_signed_pdf Binary written by Q4 helper via _check_signed
├─ roots_signed_pdf_filename Char
├─ total_equipment Integer computed, stored
├─ pm_plan_count Integer computed, stored
├─ pm_actual_count Integer computed, stored
├─ compliance_pct Float computed, stored
├─ tier_high_count Integer computed, stored
├─ tier_mid_count Integer computed, stored
├─ tier_low_count Integer computed, stored
├─ signed_badge Char computed (🟡 รอลงนาม / 🟢 ลงนามครบ)
└─ active Boolean
+ mail.thread + mail.activity.mixin
hemms.ha.report.line (Q5.5 — per-equipment row)
├─ report_id M2O parent, required, ondelete cascade
├─ equipment_id M2O maintenance.equipment, required
├─ equipment_name_th Char related, store=True
├─ equipment_name_en Char related, store=True
├─ equipment_ref Char related (roots_ref_no), gov asset no.
├─ serial_no Char related
├─ model Char related
├─ department_id M2O related
├─ risk_tier Selection high/medium/low (from equipment waterfall)
├─ risk_tier_source Selection explicit / device_class / em_score (D7 audit)
├─ pm_freq_months Integer from equipment
├─ pm_planned_count Integer derived: 12 / pm_freq_months
├─ pm_actual_count Integer count of done PM events in window
├─ pm_compliance_pct Float computed
├─ result Selection pass / conditional / fail / not_done
├─ exception_reason Text required if not_done or fail
└─ responsible_id M2O res.users
hemms.ha.report.snapshot (Q5.6 — immutable JSON freeze)
├─ report_id M2O hemms.ha.report (1:1)
├─ data Json full line data + device_class versions
├─ frozen_at Datetime
├─ frozen_by_id M2O res.users
└─ paperformat_hash Char guards against paperformat edits silently changing reprints
3.2 Inherited models¶
maintenance.equipment (INHERIT, Q5.3)
├─ device_class_id M2O hemms.ha.device.class
├─ roots_risk_tier Selection high/medium/low (explicit override)
├─ roots_pm_freq_months Integer (waterfall computed/stored)
├─ roots_em_function Integer (range 2..10, slider)
├─ roots_em_application Integer (range 1..5, slider)
├─ roots_em_maintenance Integer (range 1..5, slider)
├─ roots_em_history Integer (range -2..+2, slider)
└─ roots_em_score Integer computed, stored
sign.oca.request (INHERIT, Q5.9)
└─ hemms_ha_report_id M2O hemms.ha.report (compute from record_ref, store=True, index=True)
+ _check_signed() super-extension:
super() # ← Q4's chain handles inspection + contract
for request in self:
if request.state != "2_signed":
continue
record = request.record_ref
if not record or record._name != "hemms.ha.report":
continue
try:
self._roots_attach_signed_pdf_to_parent(request, record) # Q4 helper, reused
except Exception:
_logger.exception(...)
4. Module layout¶
addons/roots/roots_hemms_ha_report/
__init__.py
__manifest__.py
README.rst
data/
ir_sequence_data.xml # HA-YYYY-#####
ha_device_class_data.xml # MoPH 83-device seed (noupdate=1)
sign_oca_role_data.xml # role_bme_head + role_engineering_head (noupdate=1)
paperformat_data.xml # A4 TH Sarabun (may reuse asset_inspection's)
demo/
sign_oca_template_ha_report.xml # demo PDF template + field placements
hemms_ha_report_demo.xml # Q5.12 — 1 FY hospital scenario
models/
__init__.py
hemms_ha_device_class.py # Q5.2 — MoPH vocab
maintenance_equipment.py # Q5.3 — risk-tier + EM-score fields
hemms_ha_report.py # Q5.4 — header
hemms_ha_report_line.py # Q5.5 — per-equipment
hemms_ha_report_snapshot.py # Q5.6 — JSON freeze
sign_oca_request.py # Q5.9 — _check_signed super-extension
wizard/
__init__.py
hemms_ha_report_generate.py # Q5.7
hemms_ha_report_generate_views.xml
report/
hemms_ha_report_templates.xml # Q5.8 — 7-section QWeb
hemms_ha_report_paperformat.xml # may inherit from asset_inspection
views/
hemms_ha_device_class_views.xml
maintenance_equipment_views.xml # collapsible "Risk Assessment (HA)" section
hemms_ha_report_views.xml # list/form + stat button + badge
menus.xml
security/
ir.model.access.csv
ha_report_security.xml # record rules (own-dept vs all)
tests/
__init__.py
test_device_class.py # Q5.10 — U1
test_em_score.py # Q5.10 — U2, U3
test_risk_tier_waterfall.py # Q5.10 — U4, U5, U6
test_line_compute.py # Q5.10 — U7, U8
test_generate_wizard.py # Q5.10 — I1, I2, I3
test_lock_on_submit.py # Q5.10 — I4, I5, I6
test_sign_bridge.py # Q5.10 — I7, I8, I9, I10
test_security.py # Q5.10 — I11
test_report_render.py # Q5.10 — I12
i18n/
th.po # wrapper UI strings
5. UX touchpoints¶
5.1 maintenance.equipment form (Q5.3)¶
[ Existing tabs ]
General / Inventory / Maintenance / …
[ NEW collapsible section: 🇹🇭 Risk Assessment (HA) ]
Device Class (MoPH) [ Anesthesia Units ▼ ] ← lookup hemms.ha.device.class
Risk Tier [ High / Medium / Low ] ⓘ Auto-filled from class; override here
PM Frequency (months) [ 6 ] ⓘ Auto-filled from class
Source ▶ device_class ← read-only badge (D7 waterfall audit)
▼ EM Score Worksheet (if no Device Class)
Function [——●———————] 9
Application [—————●—————] 4
Maintenance [——●————————] 3
History [————●——————] 0
Score = 16 → Medium tier, 2× PM per year
5.2 hemms.ha.report list view¶
| Reference | FY (พ.ศ.) | Dept | State | 🚦 | Compliance |
| HA-2569-00001 | 2569 | BME | Submitted | 🟢 | 78% |
| HA-2569-00002 | 2569 | Radiology | In Progress | — | — |
| HA-2568-00001 | 2568 | BME | Archived | 🟢 | 82% |
5.3 hemms.ha.report form¶
[ Header ]
HA-2569-00001 [ 🟡 รอลงนาม / 🟢 ลงนามครบ ]
Fiscal Year (พ.ศ.) 2569
Department BME
State submitted
[ Header buttons ]
[ Send for Signature ] ← visible if state=='submitted' AND sign_request_count==0
[ Print Report ]
[ Archive ] ← visible if state=='submitted'
[ Statbox ]
[ 30 Equipment ] [ 78% Compliance ] [ 📝 1 Sign Request ]
↑ visible if sign_request_count > 0
[ Notebook tabs ]
- Equipment Lines (o2m)
- Exceptions (filtered domain on lines)
- Signers (BME head / Eng head / Director m2o pickers)
- Snapshot Info (read-only, post-submit)
[ Chatter ]
Standard mail.thread — sign events post here
5.4 Generate wizard (Q5.7)¶
┌─ Generate HA Annual PM Report ──────────────────────────┐
│ Fiscal Year (พ.ศ.) [ 2569 ▼ ] │
│ Department [ BME ▼ ] (blank = whole hospital) │
│ │
│ Period: 1 ต.ค. 2568 – 30 ก.ย. 2569 │
│ Equipment found: 30 (estimated) │
│ │
│ [ Cancel ] [ Generate Report ] │
└──────────────────────────────────────────────────────────┘
5.5 QWeb PDF (Q5.8) — 7 sections¶
┌─────────────────────────────────────────────────────────┐
│ ปก │
│ รายงานการบำรุงรักษาเชิงป้องกันประจำปี │
│ หน่วยงาน BME, รพ. ตัวอย่าง │
│ ปีงบประมาณ พ.ศ. 2569 │
│ เลขที่: HA-2569-00001 │
│ จัดทำโดย: [ผู้จัดทำ] │
├─────────────────────────────────────────────────────────┤
│ บทสรุปผู้บริหาร │
│ ครุภัณฑ์ทั้งหมด: 30 │
│ สูง: 5 | กลาง: 12 | ต่ำ: 13 │
│ PM ตามแผน: 64 ครั้ง | ทำจริง: 50 | เป็นไปตามแผน: 78% │
├─────────────────────────────────────────────────────────┤
│ ตารางครุภัณฑ์ + ผลการบำรุงรักษา │
│ | ลำดับ | ชื่อเครื่อง | รุ่น | S/N | เลขครุภัณฑ์ | ระดับ │
│ | | (TH/EN) | | | | ความ │
│ | | | | | | เสี่ยง| ความถี่ | แผน | จริง | % | ผล |
├─────────────────────────────────────────────────────────┤
│ รายการครุภัณฑ์ที่ไม่ได้ทำตามแผน │
│ 3 รายการ พร้อมเหตุผล │
├─────────────────────────────────────────────────────────┤
│ ภาคผนวก ก — ตาราง MoPH 83 ประเภท (อ้างอิง) │
├─────────────────────────────────────────────────────────┤
│ ภาคผนวก ข — EM Score Worksheet (เฉพาะรายการที่ใช้) │
├─────────────────────────────────────────────────────────┤
│ หน้าลงนาม │
│ [ ☐ หัวหน้างานวิศวกรรมการแพทย์ ] │
│ [ ☐ หัวหน้าฝ่ายเทคนิค ] │
│ [ ☐ ผู้อำนวยการ รพ. ] │
│ ↑ paper-style boxes when roots_signed_pdf blank │
│ ↑ embed signed PDF image when set │
└─────────────────────────────────────────────────────────┘
6. UX features captured¶
| Feature | From analysis | In which Q5 step |
|---|---|---|
ir.sequence HA-YYYY-##### | Q3 D3 pattern | Q5.1 |
| Thai gov paperformat (A4, TH Sarabun) | asset_inspection reuse | Q5.1 |
| MoPH 83-device vocab + seed | Trusted source page 2–7 | Q5.2 |
| Risk-tier waterfall (D7) | Trusted source + design | Q5.3 |
| EM score worksheet (Function + Application + Maintenance + History) | WHO Fennigkoh-Smith + trusted source page 8–9 | Q5.3 |
| 4 sliders + live score on equipment form | OQ-C answer | Q5.3 |
3-state machine (no signed) | D9 + Q4 OQ3 alignment | Q5.4 |
| Computed totals + tier counts | Trusted source executive summary | Q5.4 |
| Signed badge 🟡/🟢 | D9 visualisation | Q5.4 |
| Per-equipment PM planned/actual/compliance | Trusted source detail table | Q5.5 |
| Risk-tier source audit field | D7 traceability | Q5.5 |
| JSON snapshot + lock-on-submit | D4 + D10 (mirrors asset_inspection) | Q5.6 |
| Fiscal year wizard with Thai พ.ศ. | D3 reuse | Q5.7 |
| PM event aggregation from Q1 | Q1 maintenance.request data | Q5.7 |
| 7-section QWeb PDF | Trusted source layout | Q5.8 |
| Paper-style empty signature boxes fallback | Q4 D11 pattern | Q5.8 |
| Stat button + bridge fields | Q4 per-model bridge pattern | Q5.9 |
_check_signed super-extension reusing Q4 helper | Q4.5 helper inheritance | Q5.9 |
| 3 signers (BME head / Eng head / Director) | Trusted source signature block | Q5.9 |
| 2 new role seeds + reuse Q4 hospital signer | Q4 D12 extension | Q5.9 |
Pre-flight _check_ha_signers_resolvable | Q4 pattern | Q5.9 |
| Demo template for HA report | Q4 D8 pattern | Q5.9 |
| 20 tests on dual DB | Q3 pattern | Q5.10 |
| Bilingual (TH) .po | Standard | Q5.11 |
| 5-page docs | Q3 / Q4 standard | Q5.11 |
| 1 FY demo hospital | Standard | Q5.12 |
7. Test Cases (20)¶
Unit tests (8)¶
| # | File | Test | Verifies |
|---|---|---|---|
| U1 | test_device_class.py | test_83_devices_seeded | 83 records loaded; all 3 tiers represented; counts match trusted source (13 low / 17 mid / 53 high) |
| U2 | test_em_score.py | test_em_score_formula_boundaries | F+A+M+H at each boundary (≤12, 12–14, 15–18, ≥19) → correct tier + freq per D13 |
| U3 | test_em_score.py | test_em_history_negative_allowed | History=−2 reduces score; not coerced to abs (D13) |
| U4 | test_risk_tier_waterfall.py | test_explicit_wins | Explicit roots_risk_tier overrides device_class AND EM score (D7 step 1) |
| U5 | test_risk_tier_waterfall.py | test_device_class_fallback | No explicit + has device_class → device_class wins over EM (D7 step 2) |
| U6 | test_risk_tier_waterfall.py | test_em_score_last_resort | No explicit + no device_class → EM tier used (D7 step 3); risk_tier_source == 'em_score' |
| U7 | test_line_compute.py | test_pm_planned_count_derivation | 12 / pm_freq_months math for 3-month / 6-month / 12-month freq (4 / 2 / 1) |
| U8 | test_line_compute.py | test_compliance_pct_handles_zero_plan | planned=0 → compliance_pct=0.0 (no div-by-zero); planned=4 / actual=3 → 75% |
Integration tests (12)¶
| # | File | Test | Verifies |
|---|---|---|---|
| I1 | test_generate_wizard.py | test_generate_builds_lines_for_dept | Wizard dept=BME builds N lines for active BME equipment only; total_equipment matches |
| I2 | test_generate_wizard.py | test_fiscal_year_window_filters_pm_events | Only PM maintenance.request closed in FY window contribute to pm_actual_count |
| I3 | test_generate_wizard.py | test_stale_stage_pm_excluded | Non-Done PM requests excluded; only stage=Done counts |
| I4 | test_lock_on_submit.py | test_submit_freezes_lines | After action_submit(), writes to line_ids raise UserError; state advances to submitted |
| I5 | test_lock_on_submit.py | test_snapshot_preserves_device_class_version | Editing MoPH device_class after submit does NOT change submitted report's appendix A render |
| I6 | test_lock_on_submit.py | test_reprint_byte_identical | PDF bytes from reprint hash-equal to first print |
| I7 | test_sign_bridge.py | test_action_send_for_signature_creates_request | Creates sign.oca.request with record_ref pointing back, 3 signers with correct role assignments (BME head / Eng head / Director) |
| I8 | test_sign_bridge.py | test_pre_flight_guard_missing_signer | Missing BME head / Eng head / Director → UserError, no sign.oca.request created |
| I9 | test_sign_bridge.py | test_check_signed_writes_pdf | Stub request.state='2_signed' via Q4.6 helper → roots_signed_pdf populated, chatter posted, state UNCHANGED (stays submitted — D9) |
| I10 | test_sign_bridge.py | test_rejection_clears_and_respawns | Reject → roots_signed_pdf cleared, old request archived, second action_send_for_signature() succeeds |
| I11 | test_security.py | test_bme_staff_own_dept_only | BME staff in dept A cannot read/write reports for dept B (D12 record rule) |
| I12 | test_report_render.py | test_qweb_pdf_renders_signed_and_unsigned | PDF renders for both empty-box (unsigned) and embedded-image (signed) cases; no QWeb errors; PDF byte length > 0 |
Test infrastructure¶
- All
TransactionCase, noHttpCase - Sign completion stubbed via
roots_hemms_signature.tests.common._helper_sign_request_complete()from Q4.6 - Fixtures: 1 hospital, 3 depts, 24 equipment (8 per tier), 1 FY window, demo BME head + Eng head + Director users (each with linked
res.usersandres.partner) - Green gate: run on
hemms_demoAND onhemms_private(with Q2 + Q3 + Q4 installed + demo data) — same dual-DB gate as Q3/Q4
Coverage targets¶
| Subsystem | Coverage |
|---|---|
| MoPH seed integrity (Q5.2) | U1 |
| EM-score math (Q5.3) | U2 + U3 |
| Risk-tier waterfall (Q5.3) | U4 + U5 + U6 |
| Line math (Q5.5) | U7 + U8 |
| Generate wizard (Q5.7) | I1 + I2 + I3 |
| Lock-on-submit (Q5.6) | I4 + I5 + I6 |
| Sign bridge (Q5.9) | I7 + I8 + I9 + I10 |
| Security ACL (Q5.4) | I11 |
| QWeb render (Q5.8) | I12 |
8. Open Questions — resolved¶
| # | Question | Resolution |
|---|---|---|
| OQ-A | Q4 mixin shape — does Q5 use a mixin or per-model bridge? | Per-model bridge. Q4 did not ship a mixin (each bridged model has its own dedicated file with copy-paste shape). Q5 mirrors the pattern in its own module — does not edit roots_hemms_signature. |
| OQ-B | MoPH 83-device seed — noupdate=0 or =1? | noupdate="1" baseline. Hospitals can add/edit post-install without losing changes on module upgrade. Same as Q3 contract-type vocabulary. |
| OQ-C | EM-score editor UX — sliders vs wizard? | 4 sliders + live computed score on the equipment form, inside a collapsible "Risk Assessment (HA)" section. Discoverable, no extra click, score recomputes live. |
| OQ-D | Re-sign cycle cap (D11)? | No cap. Matches Q3 + Q4 patterns. Each rejection respawns a fresh sign.oca.request; old archived. |
| OQ-E | Test fixtures from Q4 reusable? | Yes. Q4.6 ships _helper_sign_request_complete() in roots_hemms_signature/tests/common.py. Q5 imports and reuses for I9/I10 stub completion. |
| OQ-F | Scope priority — Q5 = HA Report or contract follow-ons? | Q5 = HA Report. Contract follow-ons (multi-stage renewal alerts, SLA breach, vendor scorecard, spare-only RFQ, contract template wizard, workflow gating) → Q6. HA Report has higher demo value (new compliance artifact) than polish on already-shipped contract module. |
9. Plan execution gotchas¶
- Q5.2 MoPH seed multi-line names — the trusted source has equipment names like
"Patient Warming Units, Air"and"Defibrillators and Noninvasive Pacemaker"split across lines in the PDF. Concatenate carefully when seeding; keep both EN and TH columns; preserve original spelling (don't auto-correct). - Q5.3 EM-score storage —
roots_em_scoreiscomputed=True, store=True. Depends on the 4 input fields. Ensure store invalidates when any input changes (verify via test). - Q5.3 boundary case ambiguity — the trusted source PDF shows bucket boundaries (≤12, 12–14, 15–18, ≥19) without explicit inclusivity. Q5 fixes them as: 12 = drop, 13/14 = 1×/yr, 15/16/17/18 = 2×/yr, 19+ = 3×/yr (per D13). Document in code comment +
configuration.md. - Q5.5
pm_actual_countmust filter by stage=Done — PMmaintenance.requestin In Progress or stale stages must NOT count toward actual. Otherwise compliance % is inflated. - Q5.5 PM kind filter — counts only
maintenance.requestwhereroots_kind_id.code == 'PM'(Q1 maintenance.kind code). Repair/Calibration kinds are out of scope for this report. - Q5.6 snapshot must include
device_class_id.write_dateper line — so later edits to MoPH table don't silently change reprints. Snapshot stores the resolved values, not just FK ids. - Q5.6 paperformat fingerprint — snapshot also stores a hash of the paperformat
format,margin_*,header_*so paperformat edits flag a warning on reprint (do NOT silently change). - Q5.7 fiscal year helper — if
roots_hemms_asset_inspection._get_fiscal_year_dates()is currently a private method (_method), lift to_get_thai_fiscal_year_dates()as a public utility before Q5.7 (in Q5.1 or as part of Q5.7). - Q5.8 QWeb appendix B — only render lines where
risk_tier_source == 'em_score'. Hide section entirely if no such lines. - Q5.9
_check_signedsuper-chain order —super()._check_signed()MUST be called FIRST. Lets Q4 handle inspection/contract. Q5's HA-only branch must NOT raise ifrecord_ref._nameis not'hemms.ha.report'. - Q5.9 demo template PDF — ship as base64 in XML data file,
noupdate=1(same as Q4 D8). Use a simple A4 PDF with 3 placeholder signature boxes. - Q5.9
role_hospital_signerreuse — Q4 already seeded this withpartner_selection_policy='empty'. Q5 uses it as-is for the Director slot; partner_id passed explicitly viaCommand.create()(matches Q4 pattern). - Q5.10 sign_oca completion stub — verify Q4.6's
_helper_sign_request_complete()exists inroots_hemms_signature/tests/common.py(not just in test files). If private/missing, lift in Q5.10 commit before relying. - Q5.10 dual-DB gate — must run on both
hemms_demoANDhemms_private. Q3 had an I1 supplier-domain test that was skipped due topurchasedep; check no similar skip needed here. - Q5.11 docs sequencing —
docs/modules/ha-report/index.mdmust be added tomkdocs.ymlnav; otherwise it won't render. - Q5.12 demo data deps — depends on Q1 PM demo + maintenance.equipment demo. Generate after both module installs cleanly; verify
compliance_pctlands on the intended ~78% to make the demo realistic.
10. Commit sequence (Q5.1 → Q5.12)¶
| # | Commit message | Subject |
|---|---|---|
| Q5.1 | chore(ha): skeleton + manifest + sequence + paperformat [Q5.1] | Module installs cleanly. Sequence + paperformat ready. |
| Q5.2 | feat(ha): hemms.ha.device.class + MoPH 83-device seed [Q5.2] | Vocab model + 83-device seed; smoke test for count + tier distribution. |
| Q5.3 | feat(ha): equipment risk-tier + EM-score sliders [Q5.3] | maintenance.equipment extension; collapsible Risk Assessment section. |
| Q5.4 | feat(ha): hemms.ha.report header + state machine [Q5.4] | Header model, 3 states, totals, mail.thread, list/form, menu. |
| Q5.5 | feat(ha): hemms.ha.report.line + per-equipment compute [Q5.5] | Line model + derived counts + result enum. |
| Q5.6 | feat(ha): snapshot + lock-on-submit [Q5.6] | JSON snapshot model + action_submit freeze. |
| Q5.7 | feat(ha): generate wizard [Q5.7] | Fiscal year + dept wizard; builds lines from PM data. |
| Q5.8 | feat(ha): QWeb report with appendices [Q5.8] | 7-section government-format PDF + paper signature fallback. |
| Q5.9 | feat(ha): sign_oca bridge + roles + demo template [Q5.9] | sign.oca.request extension + 3-signer action + 2 role seeds + demo template + signed badge. |
| Q5.10 | test(ha): 8 unit + 12 integration tests [Q5.10] | 20 tests; dual-DB green. |
| Q5.11 | docs(ha): 5-page module docs + plan [Q5.11] | docs/modules/ha-report/ + q5-ha-report-plan.md + mkdocs.yml nav. |
| Q5.12 | chore(ha): demo data — 1 FY hospital scenario [Q5.12] | 30 equipment × 3 depts, 78% compliance, 3 exceptions. |
11. Resume cue¶
When user says "เริ่ม Q5.1" / "start Q5" / "ship Q5" / continues this plan:
- Re-read this plan +
project_q5_status.mdfor full context - Confirm dependencies installed cleanly:
- Q1 (
roots_hemms_pm) — PM event source - Q3 (
roots_hemms_service_contracts) — indirect via Q4 - Q4 (
roots_hemms_signature) — sign bridge primitives - Execute Q5.1 — module skeleton + manifest + sequence + paperformat
- After each commit:
pre-commit run --all-files; run module tests (after Q5.10); smoke-install on bothhemms_demoandhemms_private - Final check at Q5.12: full end-to-end demo — generate wizard → review lines → submit → send for signature → all 3 sign → final PDF in chatter