-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[feature/PI-565-questionnaire_rethink] add new spine questionnaires
- Loading branch information
Showing
27 changed files
with
662 additions
and
61 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 92 additions & 0 deletions
92
src/layers/domain/core/questionnaire/tests/test_questionnaire_v3.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import json | ||
|
||
import pytest | ||
from domain.core.enum import Status | ||
from domain.core.questionnaire.v3 import Questionnaire | ||
from domain.core.timestamp import now | ||
from jsonschema import ValidationError | ||
|
||
VALID_SCHEMA = { | ||
"$schema": "http://json-schema.org/draft-07/schema#", | ||
"type": "object", | ||
"properties": { | ||
"size": { | ||
"type": "number", | ||
"minimum": 1, | ||
"maximum": 14, | ||
}, | ||
"colour": { | ||
"type": "string", | ||
"enum": ["black", "white"], | ||
}, | ||
"brand": {"type": "string"}, # not required | ||
}, | ||
"required": ["size", "colour"], | ||
"additionalProperties": False, | ||
} | ||
|
||
INVALID_SCHEMA = { | ||
"$schema": "http://json-schema.org/draft-07/schema#", | ||
"type": "object", | ||
"properties": { | ||
"a-field": { | ||
"type": "not-a-type", | ||
} | ||
}, | ||
"required": ["a-field"], | ||
} | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"data", | ||
[ | ||
{"size": 1, "colour": "black"}, | ||
{"size": 14, "colour": "white"}, | ||
{"size": 7, "colour": "white", "brand": "something"}, | ||
], | ||
) | ||
def test_schema_validation_pass(data): | ||
questionnaire = Questionnaire( | ||
name="foo", version="1", json_schema=json.dumps(VALID_SCHEMA) | ||
) | ||
response = questionnaire.validate(data=data) | ||
assert response.name == "foo" | ||
assert response.version == "1" | ||
assert response.data == data | ||
assert response.status is Status.ACTIVE | ||
assert response.created_on.date() == now().date() | ||
assert response.updated_on is None | ||
assert response.deleted_on is None | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"data", | ||
[ | ||
{"size": 1, "colour": "red"}, | ||
{"size": "not a number", "colour": "white"}, | ||
{ | ||
"size": 7, | ||
"colour": "white", | ||
"brand": "something", | ||
"unknown_field": "foo", | ||
}, | ||
], | ||
) | ||
def test_schema_validation_fail(data): | ||
questionnaire = Questionnaire( | ||
name="foo", version="1", json_schema=json.dumps(VALID_SCHEMA) | ||
) | ||
with pytest.raises(ValidationError): | ||
questionnaire.validate(data=data) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"schema", | ||
[ | ||
{}, | ||
INVALID_SCHEMA, | ||
{"$schema": "invalid-path"}, | ||
], | ||
) | ||
def test_invalid_schema(schema): | ||
Questionnaire(name="name", version="123", schema=json.dumps(schema)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
from datetime import datetime | ||
|
||
import jsonschema | ||
from domain.core.base import BaseModel | ||
from domain.core.enum import Status | ||
from domain.core.timestamp import now | ||
from pydantic import Field, Json, validator | ||
|
||
|
||
class Questionnaire(BaseModel): | ||
name: str | ||
version: str | ||
json_schema: Json | ||
|
||
@validator("json_schema") | ||
def validate_json_schema(cls, json_schema): | ||
try: | ||
jsonschema.Draft7Validator.check_schema(json_schema) | ||
except jsonschema.SchemaError as err: | ||
raise ValueError(err.message) | ||
if not json_schema: | ||
raise ValueError("Cannot be empty") | ||
return json_schema | ||
|
||
def validate(self, data) -> "QuestionnaireResponse": | ||
jsonschema.validate(instance=data, schema=self.json_schema) | ||
return QuestionnaireResponse(name=self.name, version=self.version, data=data) | ||
|
||
|
||
class QuestionnaireResponse(BaseModel): | ||
name: str | ||
version: str | ||
data: dict | ||
status: Status = Status.ACTIVE | ||
created_on: datetime = Field(default_factory=now) | ||
updated_on: str = None | ||
deleted_on: str = None |
54 changes: 1 addition & 53 deletions
54
src/layers/domain/repository/questionnaire_repository/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,53 +1 @@ | ||
from pathlib import Path | ||
|
||
from domain.core.questionnaire.v2 import Questionnaire | ||
from domain.repository.errors import ItemNotFound | ||
from domain.repository.questionnaire_repository.deserialisers import ( | ||
QUESTION_DESERIALISERS, | ||
) | ||
from event.json import json_load | ||
|
||
PATH_TO_QUESTIONNAIRES = Path(__file__).parent / "questionnaires" | ||
|
||
|
||
def deserialise_question(question: dict) -> dict: | ||
for field, deserialiser in QUESTION_DESERIALISERS.items(): | ||
value = question.get(field) | ||
if value: | ||
question[field] = deserialiser(value) | ||
return question | ||
|
||
|
||
def version_from_file_path(file_path: Path) -> int: | ||
return int(file_path.stem.lstrip("v")) | ||
|
||
|
||
def get_latest_questions_by_name(name: str) -> Path | None: | ||
possible_paths = PATH_TO_QUESTIONNAIRES.glob(f"{name}/v*.json") | ||
paths_sorted_by_version = sorted(possible_paths, key=version_from_file_path) | ||
try: | ||
path = paths_sorted_by_version[-1] | ||
except IndexError: | ||
path = None | ||
return path | ||
|
||
|
||
def read_questions(path: Path): | ||
with open(path, "r") as fp: | ||
raw_questions = json_load(fp) | ||
return list(map(deserialise_question, raw_questions)) | ||
|
||
|
||
class QuestionnaireRepository: | ||
|
||
def read(self, name: str) -> Questionnaire: | ||
path = get_latest_questions_by_name(name=name) | ||
if not path: | ||
raise ItemNotFound(name, item_type=Questionnaire) | ||
|
||
version = version_from_file_path(path) | ||
questions = read_questions(path=path) | ||
questionnaire = Questionnaire(name=name, version=version) | ||
for question in questions: | ||
questionnaire.add_question(**question) | ||
return questionnaire | ||
from .v1 import * # noqa |
52 changes: 52 additions & 0 deletions
52
src/layers/domain/repository/questionnaire_repository/v1/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
from pathlib import Path | ||
|
||
from domain.core.questionnaire.v2 import Questionnaire | ||
from domain.repository.errors import ItemNotFound | ||
from event.json import json_load | ||
|
||
from .deserialisers import QUESTION_DESERIALISERS | ||
|
||
PATH_TO_QUESTIONNAIRES = Path(__file__).parent / "questionnaires" | ||
|
||
|
||
def deserialise_question(question: dict) -> dict: | ||
for field, deserialiser in QUESTION_DESERIALISERS.items(): | ||
value = question.get(field) | ||
if value: | ||
question[field] = deserialiser(value) | ||
return question | ||
|
||
|
||
def version_from_file_path(file_path: Path) -> int: | ||
return int(file_path.stem.lstrip("v")) | ||
|
||
|
||
def get_latest_questions_by_name(name: str) -> Path | None: | ||
possible_paths = PATH_TO_QUESTIONNAIRES.glob(f"{name}/v*.json") | ||
paths_sorted_by_version = sorted(possible_paths, key=version_from_file_path) | ||
try: | ||
path = paths_sorted_by_version[-1] | ||
except IndexError: | ||
path = None | ||
return path | ||
|
||
|
||
def read_questions(path: Path): | ||
with open(path, "r") as fp: | ||
raw_questions = json_load(fp) | ||
return list(map(deserialise_question, raw_questions)) | ||
|
||
|
||
class QuestionnaireRepository: | ||
|
||
def read(self, name: str) -> Questionnaire: | ||
path = get_latest_questions_by_name(name=name) | ||
if not path: | ||
raise ItemNotFound(name, item_type=Questionnaire) | ||
|
||
version = version_from_file_path(path) | ||
questions = read_questions(path=path) | ||
questionnaire = Questionnaire(name=name, version=version) | ||
for question in questions: | ||
questionnaire.add_question(**question) | ||
return questionnaire |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
4 changes: 2 additions & 2 deletions
4
.../tests/test_spine_device_questionnaire.py → .../tests/test_spine_device_questionnaire.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
...ests/test_spine_endpoint_questionnaire.py → ...ests/test_spine_endpoint_questionnaire.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
...re_repository/tests/test_deserialisers.py → ...repository/v1/tests/test_deserialisers.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
src/layers/domain/repository/questionnaire_repository/v2/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
from pathlib import Path | ||
|
||
from domain.core.questionnaire.v3 import Questionnaire | ||
from domain.repository.errors import ItemNotFound | ||
|
||
PATH_TO_QUESTIONNAIRES = Path(__file__).parent / "questionnaires" | ||
|
||
|
||
def version_from_file_path(file_path: Path) -> int: | ||
return int(file_path.stem.lstrip("v")) | ||
|
||
|
||
def get_latest_schema_path_by_name(name: str) -> Path | None: | ||
possible_paths = PATH_TO_QUESTIONNAIRES.glob(f"{name}/v*.json") | ||
paths_sorted_by_version = sorted(possible_paths, key=version_from_file_path) | ||
try: | ||
path = paths_sorted_by_version[-1] | ||
except IndexError: | ||
path = None | ||
return path | ||
|
||
|
||
def read_schema(path: Path) -> str: | ||
with open(path, "r") as fp: | ||
return fp.read() | ||
|
||
|
||
class QuestionnaireRepository: | ||
|
||
def read(self, name: str) -> Questionnaire: | ||
path = get_latest_schema_path_by_name(name=name) | ||
if not path: | ||
raise ItemNotFound(name, item_type=Questionnaire) | ||
version = version_from_file_path(path) | ||
schema = read_schema(path=path) | ||
return Questionnaire(name=name, version=version, json_schema=schema) |
8 changes: 8 additions & 0 deletions
8
src/layers/domain/repository/questionnaire_repository/v2/questionnaires/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from enum import StrEnum, auto | ||
|
||
|
||
class QuestionnaireInstance(StrEnum): | ||
SPINE_AS = auto() | ||
SPINE_MHS = auto() | ||
SPINE_AS_INTERACTIONS = auto() | ||
SPINE_MHS_INTERACTIONS = auto() |
18 changes: 18 additions & 0 deletions
18
.../domain/repository/questionnaire_repository/v2/questionnaires/spine_as/field_mapping.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"nhs_mhs_manufacturer_org": "MHS Manufacturer Organisation", | ||
"nhs_mhs_party_key": "Party Key", | ||
"nhs_id_code": "ODS Code", | ||
"nhs_product_name": "Product Name", | ||
"unique_identifier": "ASID", | ||
"nhs_as_client": "Client ODS Codes", | ||
"nhs_approver_urp": "Approver URP", | ||
"nhs_date_approved": "Date Approved", | ||
"nhs_requestor_urp": "Requestor URP", | ||
"nhs_date_requested": "Date Requested", | ||
"nhs_product_key": "Product Key", | ||
"nhs_product_version": "Product Version", | ||
"nhs_as_acf": "AS ACF", | ||
"nhs_temp_uid": "Temp UID", | ||
"description": "Description", | ||
"nhs_as_category_bag": "AS Category Bag" | ||
} |
Oops, something went wrong.