Repository navigation
[17.0][MIG] ni_questionnaire: Migration to 17.0 - #159
Conversation
dcbfec8 to
e8f6d96
Compare
e8f6d96 to
e248e3e
Compare
e248e3e to
0843dfa
Compare
piruin
left a comment
There was a problem hiding this comment.
Review of ni_questionnaire only (the patient/observation/survey commits belong to the stacked PRs).
Blocking — clinicians can't start the questionnaire from the encounter on 17.0 (models/ni_encounter.py, action_start_survey)
self.survey_id._create_answer(...) runs as the logged-in user without sudo(). In 17.0 core, survey_user_input_rule_survey_user_cw only allows a survey officer to create a survey.user_input for a survey they created (survey_id.create_uid = user). Clinicians only get survey.group_survey_user (implied by ni_patient.group_viewer), so anyone who did not create the survey gets an AccessError. The survey_subject wizard works because it calls self.sudo().survey_id._create_answer(...). The new tests run as superuser (record rules skipped) and the manual check used admin, so neither covers this. Since the PR states the start-from-encounter flow now works, this is in scope even though 16.0 has the same bug.
Suggested: .sudo() like the wizard, plus a test that calls encounter.with_user(<user in ni_patient.group_user only>).action_start_survey(). (18.0/19.0 core lets officers create on unrestricted surveys, so #160/#161 are not affected.)
Medium — subject wizard now shows the patient/encounter pickers that 16.0 hid (wizard/survey_subject_views.xml)
On 16.0, subject_ni_patient / subject_ni_encounter had invisible="context.get('default_subject_ni_…')" next to attrs. A truthy context value overrode the attrs domain (16.0 core ir_ui_view.py, context-dependent modifiers). The 17.0 conversion kept only type not in [...], so opening the wizard from an encounter or observation ("Start Survey") now shows editable patient/encounter fields. Suggested:
invisible="context.get('default_subject_ni_patient') or type not in ['ni.patient', 'ni.encounter']"(and the same with default_subject_ni_encounter for subject_ni_encounter). This is the only element in the module that had both.
Low — the latest-response view and compute break ties differently (report/ni_patient_survey_latest.py)
The survey_latest views order by create_date DESC with no tie-breaker, while ni.encounter._compute_response sorts by (create_date, id). If two done responses share a create_date (e.g. created in one transaction), response_latest_id can point to a row the view's DISTINCT ON dropped. Adding , id DESC to the view's ORDER BY aligns them (and matches the index, which already includes id).
Nit — the error branch of _answer_group_observation raises AttributeError (models/survey_user_input.py)
code is group.observation_code_id, already an ni.observation.type, which has no observation_type_id. So code.observation_type_id.name / .value_type crash instead of raising the ValidationError. Mostly unreachable thanks to the field domain, but these lines were just rewritten; use code.name / code.value_type.
Note (non-blocking) — creating the answer with user=env.user counts attempts against the clinician's partner, not the patient's. On a survey with limited attempts, a clinician runs out across patients. The survey_subject wizard behaves the same way.
Checked and fine: attrs conversions (apart from the wizard), selection_add with ondelete, action_start_survey(answer=) / action_print_survey(answer=), compute dependencies, the post-migration recompute, and the explicit view column list (covers every stored field of the view model).
bd39466 to
d4997b7
Compare
|
Thanks for the review. Pushed (head
Tests: 93 pass on a fresh 17.0 database ( |
…response smart button
… write issue) Took 24 seconds
Clinical instruments such as MMSE are not scored out of a fixed total: a respondent with no schooling is not given three of the items, so the test is scored out of 23 instead of 30 and the normal/abnormal cutoff moves with education level, at 14, 17 or 22 points. Core already shrinks the maximum, since _mark_done() drops the inactive conditional questions from predefined_question_ids. What was missing is everything downstream, so survey.grade gains two things: a points basis, because a fixed points cutoff stops being a fixed percentage once the total moves, and triggering_answer_ids, so one survey can hold several sets of ranges keyed on the answer that decided which items were asked. Those ranges have to overlap numerically, so the unique and overlap rules move from _sql_constraints to Python keyed on _scope_key() - a Many2many cannot take part in a SQL unique index. Observations are built in _mark_done() after super(). The old write() hook ran before core trimmed the denominator and so recorded the pre-trim percentage. survey.grade.interpretation_id lets the observation follow the grade rather than ni.observation.reference.range, which scopes only by age and gender and therefore cannot read a total whose maximum varies. grading_basis, observation_answer_type and observation_score_type are deliberately not required: a NOT NULL column on survey.survey or survey.question breaks the tests of every module upstream of these two, which run before either is in the registry. Also corrects the value-mapped answer branch, which dispatched on truthiness: a numerical answer of 0 was taken for "no answer given" and datetime had no branch at all, both of which blocked survey completion. The tests are modelled on MMSE-Thai 2002 itself so the shrinking maximum and the three cutoffs are checked against a real instrument. Stock Odoo 16 gates a conditional question on a single answer, so the skipped items hang off a binary "Attended school?" question while the cutoff bands hang off a separate three-level "Education level" question. Grade UI follows: triggering answers read "Question: Answer" since many surveys reuse "Yes"/"No" across questions; a scoped grade is flagged in the tree with the fa-code-fork icon the question tree already uses, with the answers themselves on a "Conditional" tab of the grade form; and the patient scoping (gender, age, interpretation) is reachable from the grade form too, gender drawn as a mars/venus icon in the tree. Both modules are committed together because the change is not separable - ni_questionnaire overrides the very constraint and grade lookup that survey_grading reworks, so neither half passes its own tests alone. Took 3 minutes Took 8 minutes
Core gates the entire score block of the completion page on scoring_success_min, but a graded questionnaire has no meaningful passing percentage - a points-based instrument such as ADL leaves it at 0. The badge sat inside that block and so never reached the page. Widening the gate to `scoring_success_min or grade_ids` is preferred over moving the badge out of the block, because survey_subject rewrites the same column through a positional t[1]/div xpath: inserting any element ahead of that target breaks it with "cannot be located in parent view". It also brings back survey_subject's own score line, which shared the gate. Passed/failed stays tied to scoring_success_min. Without one, scoring_success is 0 >= 0 and every respondent would be congratulated, and since a bare t-else always fires, the failure branch has to become a t-elif on the same condition. Both verdict specs address direct children of the scoring block rather than matching t-else anywhere in the template. survey_subject carries a t-else of its own and the apply order between the two modules follows install order, so an unscoped match rewrites the wrong node - silently, leaving a broken score line and an ungated verdict. The guard for that combination lives in ni_questionnaire, the only module that depends on both. Took 49 minutes
grade_for() narrows the bands by the patient's gender and age, which is invisible to whoever reads the result: two patients with the same score can hold different grades with nothing on screen to explain it. Extend grade_conditions() so the grading reference says which traits applied. Only traits the survey actually grades differently by are named - a gender when any band carries one, an age when the bands fall into more than one window. Printing them unconditionally would put an age and a gender on every result page that had no bearing on the grade. Took 48 minutes
…upgrade 16.0 computed the encounter's latest response from a SQL view before the response reached the database, so the stored response_latest_id, its grade and date are empty or stale in 16.0 databases. 17.0 computes them from the responses, but only when a response changes. Recompute them once in the 17.0 post-migration script, so that migrated encounters show their latest grade and the "Print Questionnaire" button. Found by the Phase 1 data migration pilot (nirun-life#141).
d4997b7 to
e0b294d
Compare
Decisions to review
ni_questionnaireneeds both the patient/observation stack ([17.0][MIG] ni_period, ni_identifier, ni_coding, partner_age, partner_gender, partner_religion, uom_alias: Migration to 17.0 #147 → [17.0][MIG] ni_patient: Migration to 17.0 #153 → [17.0][MIG] ni_observation: Migration to 17.0 #156) and the survey stack ([17.0][MIG] survey_subject, survey_grading: Migration to 17.0 #150). This branch is [17.0][MIG] ni_observation: Migration to 17.0 #156 plus the commits of [17.0][MIG] survey_subject, survey_grading: Migration to 17.0 #150 rebased on top (the shared[FIX] 17.0pre-commit commit is dropped as already applied), thenni_questionnaire. After [17.0][MIG] survey_subject, survey_grading: Migration to 17.0 #150 and [17.0][MIG] ni_observation: Migration to 17.0 #156 merge, a rebase leaves only theni_questionnairecommits.survey_tokenis not a context key Odoo reads (16–19), andresponse.tokenis not a field (access_token). The second click on "Start Questionnaire" raisedAttributeError, and printing opened a blank survey. Now it uses coreaction_start_survey(answer=…)/action_print_survey(answer=…).partner=<patient>. Core then rejects the logged-in clinician with "Survey Access Error" (answer_wrong_user). It is now created like thesurvey_subjectwizard:user=env.user, subject = the encounter.patient_id/encounter_id, so the observation from the score had no patient. Both are now set, like the wizard does.ni.encounter.survey_latestSQL view, which cannot see the response before the ORM flushes, and it had no dependency onstate. It now readsresponse_idsthrough the ORM and depends onresponse_ids.state.subject_typeusesselection_add(ondelete= set default) instead of redeclaringsurvey_subject's whole selection.sudo_compute→compute_sudo(typo, unknown parameter warning). P0-4 ([MIG 19.0][P0-4][FIX] Pre-existing bugs found during the 19.0 migration scan (16.0) #135) fixes the same on 16.0; the forward port is trivial.What changed
ni_questionnairehistory replayed, then[IMP]pre-commit and[MIG] ni_questionnaire: Migration to 17.0.attrs→ expressions (including the<attribute name="attrs">override on the response form), version.triggering_answer_ids(17 removedis_conditional,triggering_question_id,triggering_answer_id).write().column_invisible.survey_user_input(SQL()with identifiers), instead ofSELECT … *._table_query(itsinit()created the same view a second time).tests/test_encounter_survey.py: start creates and reuses the encounter's response (with patient, encounter, current user); print uses it; the latest response follows completion.init()returnssuper(), redundant labels.th.poregenerated (new msgids filled).Why
Part of #140 (P1-5, epic #131).
Migration notes
attrsin 9 view files.selection_add;survey_tokenwas never an Odoo API, and the patient partner blocks the clinician.How tested
pre-commitclean onni_questionnaire.Documentation
README.mdupdated (17.0, subject types, encounter flow, report views, tests)Notes for reviewer
Added after review started (P1-6 pilot, [MIG 19.0][P1-6] Data migration pilot: OpenUpgrade 16→17→18→19 on Phase 1 modules #141):
[FIX] ni_questionnaire: recompute the encounters' latest response on upgradeaddsmigrations/17.0.1.0.0/post-migration.py. The 16.0 storedresponse_latest_id/grade/date are empty or stale (the bug fixed here), and nothing recomputes them on upgrade, so migrated encounters showed no latest grade and no "Print Questionnaire" button. Checked on the pilot DB: 6 of 6 encounters with a completed questionnaire get their latest response; values carry through 18 and 19. Branch rebased on the updated [17.0][MIG] ni_observation: Migration to 17.0 #156.Stacked on [17.0][MIG] ni_observation: Migration to 17.0 #156 (P1-4 17.0) and [17.0][MIG] survey_subject, survey_grading: Migration to 17.0 #150 (P1-3 17.0). Merge those first.