Merge branch 'master' into 'RESTwireframe'
# Conflicts: # cara/apps/calculator/static/form.html
This commit is contained in:
commit
7bf3c07296
11 changed files with 445 additions and 15 deletions
|
|
@ -3,15 +3,11 @@ from pathlib import Path
|
|||
|
||||
from tornado.web import Application, RequestHandler, StaticFileHandler
|
||||
|
||||
import cara.models
|
||||
from . import model_generator
|
||||
from .report_generator import build_report
|
||||
|
||||
|
||||
def build_model(request: dict) -> cara.models.Model:
|
||||
return None
|
||||
|
||||
|
||||
def build_response(model: cara.models.Model):
|
||||
return {'items': 'foobar'}
|
||||
DEBUG = True
|
||||
|
||||
|
||||
class ConcentrationModel(RequestHandler):
|
||||
|
|
@ -19,19 +15,41 @@ class ConcentrationModel(RequestHandler):
|
|||
requested_model_config = {
|
||||
name: self.get_argument(name) for name in self.request.arguments
|
||||
}
|
||||
if DEBUG:
|
||||
from pprint import pprint
|
||||
pprint(requested_model_config)
|
||||
|
||||
try:
|
||||
model = build_model(requested_model_config)
|
||||
form = model_generator.FormData.from_dict(requested_model_config)
|
||||
model = form.build_model(
|
||||
# TODO: This argument to be removed.
|
||||
tmp_raw_form_data=requested_model_config,
|
||||
)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except Exception as err:
|
||||
if DEBUG:
|
||||
import traceback
|
||||
traceback.print_last()
|
||||
response_json = {'code': 400, 'error': f'Your request was invalid {err}'}
|
||||
self.set_status(400)
|
||||
self.finish(json.dumps(response_json))
|
||||
return
|
||||
|
||||
response_json = build_response(model)
|
||||
response_json['room_name'] = requested_model_config.get('room_name', 'unknown')
|
||||
self.write(response_json)
|
||||
report = build_report(model, form)
|
||||
self.finish(report)
|
||||
|
||||
|
||||
class StaticModel(RequestHandler):
|
||||
def get(self):
|
||||
requested_model_config = model_generator.baseline_raw_form_data()
|
||||
form = model_generator.FormData.from_dict(model_generator.baseline_raw_form_data())
|
||||
model = form.build_model(
|
||||
# TODO: This argument to be removed.
|
||||
tmp_raw_form_data=requested_model_config,
|
||||
)
|
||||
report = build_report(model, form)
|
||||
self.finish(report)
|
||||
|
||||
|
||||
def make_app(debug=False, prefix='/calculator'):
|
||||
|
|
@ -41,7 +59,10 @@ def make_app(debug=False, prefix='/calculator'):
|
|||
prefix + r'()', StaticFileHandler, {'path': static_dir / 'form.html'}
|
||||
),
|
||||
(
|
||||
prefix + r'/api/calculator', ConcentrationModel
|
||||
prefix + r'/report', ConcentrationModel
|
||||
),
|
||||
(
|
||||
prefix + r'/baseline-model/result', StaticModel
|
||||
),
|
||||
(
|
||||
prefix + r'/static/(.*)',
|
||||
|
|
|
|||
190
cara/apps/calculator/model_generator.py
Normal file
190
cara/apps/calculator/model_generator.py
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
from cara.models import Model
|
||||
from dataclasses import dataclass
|
||||
import typing
|
||||
|
||||
from cara import models
|
||||
|
||||
|
||||
@dataclass
|
||||
class FormData:
|
||||
ceiling_height: float
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, form_data: typing.Dict) -> "FormData":
|
||||
# TODO: This fixup is a problem with the form.html.
|
||||
form_data['ceiling_height'] = 1
|
||||
|
||||
return cls(
|
||||
ceiling_height=float(form_data['ceiling_height']),
|
||||
)
|
||||
|
||||
# TODO: Remove the tmp_raw_form_data usage.
|
||||
def build_model(self, tmp_raw_form_data) -> Model:
|
||||
return model_from_form(self, tmp_raw_form_data)
|
||||
|
||||
def ventilation(self) -> models.Ventilation:
|
||||
# TODO
|
||||
pass
|
||||
|
||||
def present_interval(self) -> models.Interval:
|
||||
# TODO
|
||||
pass
|
||||
|
||||
|
||||
def model_from_form(form: FormData, tmp_raw_form_data) -> models.Model:
|
||||
d = tmp_raw_form_data
|
||||
|
||||
# TODO: This fixup is a problem with the form.html.
|
||||
d['coffee_breaks'] = 1
|
||||
d['activity_type'] = 'Training'
|
||||
d['lunch_start'] = '12:00'
|
||||
d['lunch_finish'] = '13:00'
|
||||
|
||||
# Initializes room with volume either given directly or as product of area and height
|
||||
if d['volume_type'] == 'room_volume':
|
||||
volume = int(d['room_volume'])
|
||||
else:
|
||||
volume = int(float(d['floor_area']) * form.ceiling_height)
|
||||
room = models.Room(volume=volume)
|
||||
|
||||
# Initializes a ventilation instance as a window if 'natural' is selected, or as a HEPA-filter otherwise
|
||||
if d['ventilation_type'] == 'natural':
|
||||
if d['windows_open'] == 'always':
|
||||
period, duration = 120, 120
|
||||
else:
|
||||
period, duration = 15, 120
|
||||
# I multiply the opening width by the number of windows to simulate the correct window area
|
||||
ventilation = models.WindowOpening(active=models.PeriodicInterval(period=period, duration=duration),
|
||||
inside_temp=293, outside_temp=283, cd_b=0.6,
|
||||
window_height=float(d['window_height']),
|
||||
opening_length=float(d['opening_distance']) * int(d['windows_number']))
|
||||
else:
|
||||
q_air_mech = float(d['air_changes']) if d['air_type'] == 'air_changes' else float(d['air_supply'])
|
||||
ventilation = models.HEPAFilter(active=models.PeriodicInterval(period=120, duration=120),
|
||||
q_air_mech=q_air_mech)
|
||||
|
||||
# Initializes the virus as SARS_Cov_2
|
||||
virus = models.Virus.types['SARS_CoV_2']
|
||||
|
||||
# Defines all of the parameters required to construct a list of intervals where the infected person is present in
|
||||
# the room
|
||||
activity_start = int(d['activity_start'][:2]) * 60 + int(d['activity_start'][3:])
|
||||
activity_finish = int(d['activity_finish'][:2]) * 60 + int(d['activity_finish'][3:])
|
||||
lunch_start = int(d['lunch_start'][:2]) * 60 + int(d['lunch_start'][3:])
|
||||
lunch_finish = int(d['lunch_finish'][:2]) * 60 + int(d['lunch_finish'][3:])
|
||||
coffee_duration = int(d['coffee_duration'])
|
||||
coffee_breaks = int(d['coffee_breaks'])
|
||||
coffee_period = (activity_finish - activity_start) // coffee_breaks + 1
|
||||
leave_times = [lunch_start]
|
||||
enter_times = [lunch_finish]
|
||||
for minute in range(activity_start, activity_finish, coffee_period):
|
||||
leave_times.append(minute)
|
||||
enter_times.append(minute + coffee_duration)
|
||||
|
||||
# These lists represent the times where the infected person leaves or enters the room, respectively, sorted in
|
||||
# reverse order. Note that these lists allows the person to "leave" when they should not even be present in the room
|
||||
# The following loop handles this.
|
||||
leave_times.sort(reverse=True)
|
||||
enter_times.sort(reverse=True)
|
||||
|
||||
# This loop iterates through the lists above, populating present_intervals with (enter, leave) intervals
|
||||
# representing the infected person entering and leaving the room. Note that if one of the evenly spaced coffee-
|
||||
# breaks happens to coincide with the lunch-break, it is simply ignored.
|
||||
is_present = True
|
||||
present_intervals = []
|
||||
time = activity_start
|
||||
while time < activity_finish:
|
||||
if is_present:
|
||||
if not leave_times:
|
||||
present_intervals.append((time / 60, activity_finish / 60))
|
||||
break
|
||||
|
||||
if leave_times[-1] < time:
|
||||
leave_times.pop()
|
||||
else:
|
||||
new_time = leave_times.pop()
|
||||
present_intervals.append((time / 60, min(new_time, activity_finish) / 60))
|
||||
is_present = False
|
||||
time = new_time
|
||||
|
||||
else:
|
||||
if not enter_times:
|
||||
break
|
||||
|
||||
if enter_times[-1] < time:
|
||||
enter_times.pop()
|
||||
else:
|
||||
is_present = True
|
||||
time = enter_times.pop()
|
||||
|
||||
# Initializes a mask of type 1 if mask wearing is "continuous", otherwise instantiates the mask attribute as
|
||||
# the "No mask"-mask
|
||||
mask = models.Mask.types['Type I' if d['mask_wearing'] == "Continuous" else 'No mask']
|
||||
|
||||
# A dictionary containing the mapping of activities listed in the UI to the activity level and expiration level
|
||||
# of the infected and exposed occupants respectively.
|
||||
# I.e. (infected_activity, infected_expiration), (exposed_activity, exposed_expiration)
|
||||
|
||||
activity_dict = {'Office/Meeting': (('Seated', 'Talking'), ('Seated', 'Talking')),
|
||||
'Training': (('Light exercise', 'Talking'), ('Seated', 'Whispering')),
|
||||
'Workshop': (('Light exercise', 'Talking'), ('Light exercise', 'Talking'))}
|
||||
|
||||
(infected_activity, infected_expiration), (exposed_activity, exposed_expiration) = activity_dict[d['activity_type']]
|
||||
# Converts these strings to Activity and Expiration instances
|
||||
infected_activity, exposed_activity = models.Activity.types[infected_activity], models.Activity.types[exposed_activity]
|
||||
infected_expiration, exposed_expiration = models.Expiration.types[infected_expiration], models.Expiration.types[exposed_expiration]
|
||||
|
||||
infected_occupants = int(d['infected_people'])
|
||||
# Defines the number of exposed occupants as the total number of occupants minus the number of infected occupants
|
||||
exposed_occupants = int(d['total_people']) - infected_occupants
|
||||
|
||||
# Initializes and returns a model with the attributes defined above
|
||||
return models.Model(
|
||||
room=room,
|
||||
ventilation=ventilation,
|
||||
infected=models.InfectedPerson(
|
||||
virus=virus,
|
||||
presence=models.SpecificInterval(tuple(present_intervals)),
|
||||
mask=mask,
|
||||
activity=infected_activity,
|
||||
expiration=infected_expiration
|
||||
),
|
||||
infected_occupants=infected_occupants,
|
||||
exposed_occupants=exposed_occupants,
|
||||
exposed_activity=exposed_activity
|
||||
)
|
||||
|
||||
|
||||
def baseline_raw_form_data():
|
||||
# Note: This isn't a special "baseline". It can be updated as required.
|
||||
return {
|
||||
'activity_finish': '17:00',
|
||||
'activity_start': '09:00',
|
||||
'activity_type': 'training',
|
||||
'air_changes': '',
|
||||
'air_supply': '',
|
||||
'ceiling_height': '',
|
||||
'coffee_breaks': '',
|
||||
'coffee_duration': '1',
|
||||
'coffee_option': '0',
|
||||
'event_type': 'single_event',
|
||||
'floor_area': '',
|
||||
'infected_people': '1',
|
||||
'lunch_finish': '13:30',
|
||||
'lunch_option': '1',
|
||||
'lunch_start': '12:30',
|
||||
'mask_wearing': 'removed',
|
||||
'opening_distance': '15',
|
||||
'recurrent_event_month': 'January',
|
||||
'room_number': 'baseline room',
|
||||
'room_volume': '75',
|
||||
'simulation_name': 'Baseline simulation',
|
||||
'single_event_date': '11/02/2020',
|
||||
'total_people': '10',
|
||||
'ventilation_type': 'natural',
|
||||
'volume_type': 'room_volume',
|
||||
'window_height': '2',
|
||||
'window_width': '2',
|
||||
'windows_number': '1',
|
||||
'windows_open': 'interval'
|
||||
}
|
||||
30
cara/apps/calculator/report_generator.py
Normal file
30
cara/apps/calculator/report_generator.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import jinja2
|
||||
|
||||
from cara import models
|
||||
from .model_generator import FormData
|
||||
|
||||
|
||||
def build_report(model: models.Model, form: FormData):
|
||||
now = datetime.now()
|
||||
time = now.strftime("%d/%m/%Y %H:%M:%S")
|
||||
request = {'the': 'form', 'request': 'data'}
|
||||
context = {'model': model, 'request': request, 'creation_date': time, 'model_version': 'Beta v1.0.0',
|
||||
'simulation_name': 'SAMPLE', 'room_number': '40/1-02A', 'room_volume': 30, 'mechanical_ventilation': 'Yes',
|
||||
'air_supply': 1, 'air_changes': 2, 'windows_number': 5, 'window_height': 2, 'window_width': 1,
|
||||
'opening_distance': 0.05, 'windows_open': '20 minutes every 2 hours', 'hepa_filtration': 'No', 'total_people': 8,
|
||||
'infected_people': 7, 'activity_type': 'Office work – typical scenario with all persons seated, talking',
|
||||
'activity_start': '00:00', 'activity_finish': '01:15', 'exposure_start': '00:00', 'exposure_finish': '01:15',
|
||||
'single_event_date': '5th November', 'lunch_option': 'Yes', 'lunch_start': '00:00', 'lunch_finish': '01:15',
|
||||
'coffee_option': 'Yes', 'coffee_number': 4,'coffee_duration': 15, 'coffee_start1': '00:00', 'coffee_finish1': '00:00',
|
||||
'coffee_start2': '00:00','coffee_finish2': '00:00', 'coffee_start3': '00:00', 'coffee_finish3': '00:00',
|
||||
'coffee_start4': '00:00', 'coffee_finish4': '00:00', 'mask_wearing': 'Yes',
|
||||
'infection_probability': round(model.infection_probability(), 2), 'reproduction_rate': 2}
|
||||
|
||||
p = Path(__file__).parent / 'templates'
|
||||
env = jinja2.Environment(
|
||||
loader=jinja2.FileSystemLoader(Path(p)))
|
||||
template = env.get_template('report.html.j2')
|
||||
return template.render(**context)
|
||||
43
cara/apps/calculator/static/css/report.css
Normal file
43
cara/apps/calculator/static/css/report.css
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#body {
|
||||
top: 10px;
|
||||
left: 20px;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.image {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 13pt;
|
||||
}
|
||||
|
||||
h1{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
text-align: center;
|
||||
font-size: 13pt;
|
||||
padding-bottom: 15pt;
|
||||
}
|
||||
|
||||
p.data_title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
p.data_text {
|
||||
padding-left: 30px;
|
||||
padding-left:1em;
|
||||
}
|
||||
|
||||
p.data_subtext {
|
||||
padding-left: 60px;
|
||||
margin-left:-3em;
|
||||
}
|
||||
|
||||
p.result_title {
|
||||
font-weight: bold;
|
||||
font-size: 15pt;
|
||||
}
|
||||
|
|
@ -12,11 +12,13 @@
|
|||
|
||||
</head>
|
||||
|
||||
<body onload="clear_form()">
|
||||
<body>
|
||||
|
||||
|
||||
Beta v1.0.0 <span style="float:right; font-weight:bold">Please send feedback to <a href="mailto:CARA-dev@cern.ch">CARA-dev@cern.ch</a></span>
|
||||
<h1> <p><b>CARA</b> Covid Airborne Risk Assessment tool</p></h1>
|
||||
<form id="covid_calculator" name="covid_calculator" onsubmit='return on_submit(this)'>
|
||||
<form id="covid_calculator" name="covid_calculator" action="/calculator/report" method="POST" onsubmit='return on_submit(this)'>
|
||||
|
||||
<div style="width: 33%; float:left;">
|
||||
|
||||
<!-- General Options -->
|
||||
|
|
@ -182,7 +184,7 @@ var request;
|
|||
function on_submit(form){
|
||||
|
||||
// Prevent default posting of form - put here to work in case of errors
|
||||
event.preventDefault();
|
||||
// event.preventDefault();
|
||||
|
||||
// Abort any pending request
|
||||
if (request)
|
||||
|
|
@ -191,6 +193,7 @@ function on_submit(form){
|
|||
// Let's select and cache all the fields
|
||||
var $inputs = $(form).find("input, select, button, textarea");
|
||||
|
||||
<<<<<<< cara/apps/calculator/static/form.html
|
||||
// Serialize the data in the form
|
||||
var serializedData = objectifyForm($(form).serializeArray());
|
||||
|
||||
|
|
@ -217,6 +220,33 @@ function on_submit(form){
|
|||
textStatus, errorThrown
|
||||
);
|
||||
});
|
||||
|
||||
// // Serialize the data in the form
|
||||
// var serializedData = objectifyForm($(form).serializeArray());
|
||||
|
||||
// console.log(['Sending over', JSON.stringify(serializedData)])
|
||||
|
||||
// // Fire off the request to the calculator.
|
||||
// request = $.ajax({
|
||||
// url: "/calculator/report",
|
||||
// type: "post",
|
||||
// data: serializedData,
|
||||
// dataType: "json",
|
||||
// });
|
||||
|
||||
// // Callback handler that will be called on success
|
||||
// request.done(function (response, textStatus, jqXHR){
|
||||
// build_report(response);
|
||||
// });
|
||||
|
||||
// // Callback handler that will be called on failure
|
||||
// request.fail(function (jqXHR, textStatus, errorThrown){
|
||||
// // Log the error to the console
|
||||
// console.error(
|
||||
// "The following error occurred: "+
|
||||
// textStatus, errorThrown
|
||||
// );
|
||||
// });
|
||||
}
|
||||
|
||||
// Convert all type int in form
|
||||
|
|
|
|||
BIN
cara/apps/calculator/static/images/disclaimer.jpg
Normal file
BIN
cara/apps/calculator/static/images/disclaimer.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
91
cara/apps/calculator/templates/report.html.j2
Normal file
91
cara/apps/calculator/templates/report.html.j2
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/calculator/static/css/report.css">
|
||||
</head>
|
||||
|
||||
<body id="body">
|
||||
|
||||
<p class=image> <img src="/calculator/static/images/disclaimer.jpg" width="40" height="40">
|
||||
VERY IMPORTANT DISCLAIMER</p>
|
||||
|
||||
<h1>Output from CARA - COVID Airborne Risk Assessment tool</h1>
|
||||
|
||||
<p class=subtitle> Created {{ creation_date }} using model version {{ model_version }}</p><br>
|
||||
|
||||
<p>Simulation Name: {{ simulation_name }}</p>
|
||||
<p>Room Number: {{ room_number }}</p>
|
||||
|
||||
<p class="data_title">Input data:</p>
|
||||
<ul>
|
||||
<li><p class="data_text">Room Volume: {{ room_volume }} m³</p></li>
|
||||
</ul>
|
||||
|
||||
<p class="data_title">Ventilation data:</p>
|
||||
<ul>
|
||||
<li><p class="data_text">Mechanical ventilation: {{ mechanical_ventilation }}</p></li>
|
||||
<!--If mechanical ventialtion is yes {-->
|
||||
<ul>
|
||||
<li><p class="data_subtext">Air supply flow rate: {{ air_supply }}</p></li>
|
||||
<li><p class="data_subtext">Air changes per hour: {{ air_changes }}</p></li>
|
||||
</ul>
|
||||
<!--}-->
|
||||
<li><p class="data_text">Natural ventilation: No</li>
|
||||
<!--If natural ventialtion is yes {
|
||||
<ul>
|
||||
<li><p class="data_subtext">Number of windows: {{ windows_number }}</p></li>
|
||||
<li><p class="data_subtext">Height of window: {{ window_height }}</p></li>
|
||||
<li><p class="data_subtext">Width of window: {{ window_width }}</p></li>
|
||||
<li><p class="data_subtext">Opening distance: {{ opening_distance }}</p></li>
|
||||
<li><p class="data_subtext">Windows open: {{ windows_open }}</p></li>
|
||||
</ul>
|
||||
}-->
|
||||
<li><p class="data_text">HEPA Filtration: {{ hepa_filtration }}</li>
|
||||
<!--If HEPA filtration is yes {
|
||||
X type of filter with y flow rate (default model assumption) ?
|
||||
}-->
|
||||
</ul>
|
||||
|
||||
<p class="data_title">Event data:</p>
|
||||
<ul>
|
||||
<li><p class="data_text">Number of attendees and infected people: {{ total_people }} in attendance, of whom {{ infected_people }} are infected.</p></li>
|
||||
<li><p class="data_text">Activity type: {{ activity_type }}</p></li>
|
||||
<ul>
|
||||
<li><p class="data_subtext">Start time: {{ activity_start }} End time: {{ activity_finish }}</p></li>
|
||||
</ul>
|
||||
<li><p class="data_text">Exposure time (presence of infected person):</p></li>
|
||||
<ul>
|
||||
<li><p class="data_subtext">Start time: {{ exposure_start }} End time: {{ exposure_finish }}</p></li>
|
||||
</ul>
|
||||
<li><p class="data_text">Single event on {{ single_event_date }}</p></li>
|
||||
</ul>
|
||||
|
||||
<p class="data_title">Break data:</p>
|
||||
<ul>
|
||||
<li><p class="data_text">Lunchbreak: {{ lunch_option }} Start: {{ lunch_start }} End: {{ lunch_end }}</p></li>
|
||||
<li><p class="data_text">Coffee breaks: {{ coffee_option }} Number: {{ coffee_number }} Duration: {{ coffee_duration }}</p></li>
|
||||
<ul>
|
||||
<li><p class="data_subtext">Coffee break 1: Start: {{ coffee_start1 }} End: {{ coffee_finish1 }}</p></li>
|
||||
<li><p class="data_subtext">Coffee break 2: Start: {{ coffee_start2 }} End: {{ coffee_finish2 }}</p></li>
|
||||
<li><p class="data_subtext">Coffee break 3: Start: {{ coffee_start3 }} End: {{ coffee_finish3 }}</p></li>
|
||||
<li><p class="data_subtext">Coffee break 4: Start: {{ coffee_start4 }} End: {{ coffee_finish4 }}</p></li>
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
<!--or Recurrent for months of X, Y and Z…-->
|
||||
|
||||
<p class="data_title">Mask wearing:</p>
|
||||
<ul>
|
||||
<li><p class="data_text">Masks worn at workstations? {{ mask_wearing }}</p></li>
|
||||
<li><p class="data_text">Mask type: Type 1 (default, for now)</p></li>
|
||||
</ul>
|
||||
|
||||
<p class="result_title">Results:</p>
|
||||
<p class="data_text"> In this scenario, the estimated probability of one exposed occupant getting infected (Pi) is {{ infection_probability }} % and the estimated basic reproduction rate (R0) rate is {{ reproduction_rate }}. If you have selected a recurrent event, this is the probability per day, and is cumulative over the number of days.<p>
|
||||
<p class="data_title">Exposure graph:
|
||||
|
||||
</body>
|
||||
</html>
|
||||
0
cara/tests/apps/__init__.py
Normal file
0
cara/tests/apps/__init__.py
Normal file
0
cara/tests/apps/calculator/__init__.py
Normal file
0
cara/tests/apps/calculator/__init__.py
Normal file
25
cara/tests/apps/calculator/test_model_generator.py
Normal file
25
cara/tests/apps/calculator/test_model_generator.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import pytest
|
||||
|
||||
from cara.apps.calculator import model_generator
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def baseline_form_data():
|
||||
return model_generator.baseline_raw_form_data()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def baseline_form(baseline_form_data):
|
||||
return model_generator.FormData.from_dict(baseline_form_data)
|
||||
|
||||
|
||||
def test_model_from_dict(baseline_form_data):
|
||||
model = model_generator.FormData.from_dict(baseline_form_data)
|
||||
# TODO:
|
||||
# assert model.ventilation == cara.models.Ventilation()
|
||||
|
||||
|
||||
def test_ventilation(baseline_form):
|
||||
ventilation = baseline_form.ventilation()
|
||||
# TODO:
|
||||
# assert ventilation == cara.models.Ventilation()
|
||||
Loading…
Reference in a new issue