-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: generate mandatory XLS forms programmatically
- Loading branch information
Showing
5 changed files
with
465 additions
and
114 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
"""OSM Fieldwork Form Builder Package.""" | ||
|
||
import os | ||
|
||
package_root = os.path.dirname(os.path.abspath(__file__)) |
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,69 @@ | ||
#!/usr/bin/python3 | ||
|
||
# Copyright (c) 2020, 2021, 2022, 2023, 2024 Humanitarian OpenStreetMap Team | ||
# | ||
# This file is part of OSM-Fieldwork. | ||
# | ||
# This is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation, either version 3 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# This is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with OSM-Fieldwork. If not, see <https:#www.gnu.org/licenses/>. | ||
# | ||
|
||
"""This creates DataFrames for choice lists used in the survey form. | ||
These include predefined options for fields such as yes/no responses | ||
and issues related to digitization problems. Each choice contains | ||
multilingual labels to support various languages. | ||
Returns: | ||
tuple: Two pandas DataFrames containing the `choices` data for yes/no | ||
and digitisation problems respectively. | ||
""" | ||
|
||
import pandas as pd | ||
|
||
# Define the choices sheet | ||
choices_data = [ | ||
{"list_name": "yes_no", "name": "yes", "label::english(en)": "Yes"}, | ||
{"list_name": "yes_no", "name": "no", "label::english(en)": "No"}, | ||
] | ||
|
||
choices_df = pd.DataFrame(choices_data) | ||
|
||
digitisation_choices = [ | ||
{ | ||
"list_name": "digitisation_problem", | ||
"name": "lumped", | ||
"label::english(en)": "Lumped - one polygon (more than one building digitized as one)", | ||
"label::swahili(sw)": "Lumped - poligoni moja (zaidi ya jengo moja limewekwa dijiti kuwa moja)", | ||
"label::french(fr)": "Lumped - un polygone (plus d'un bâtiment numérisé en un seul)", | ||
"label::spanish(es)": "Agrupado - un polígono (más de un edificio digitalizado como uno)", | ||
}, | ||
{ | ||
"list_name": "digitisation_problem", | ||
"name": "split", | ||
"label::english(en)": "Split - one building (one building digitized as more than one polygon)", | ||
"label::swahili(sw)": "Mgawanyiko - jengo moja (jengo moja limebadilishwa kuwa zaidi ya poligoni moja)", | ||
"label::french(fr)": "Fractionnement - un bâtiment (un bâtiment numérisé sous la forme de plusieurs polygones)", | ||
"label::spanish(es)": "Split - un edificio (un edificio digitalizado como más de un polígono)", | ||
}, | ||
{ | ||
"list_name": "digitisation_problem", | ||
"name": "other", | ||
"label::english(en)": "OTHER", | ||
"label::swahili(sw)": "MENGINEYO", | ||
"label::french(fr)": "AUTRES", | ||
"label::spanish(es)": "OTROS", | ||
}, | ||
] | ||
|
||
digitisation_choices_df = pd.DataFrame(digitisation_choices) |
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,78 @@ | ||
#!/usr/bin/python3 | ||
|
||
# Copyright (c) 2020, 2021, 2022, 2023, 2024 Humanitarian OpenStreetMap Team | ||
# | ||
# This file is part of OSM-Fieldwork. | ||
# | ||
# This is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation, either version 3 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# This is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with OSM-Fieldwork. If not, see <https:#www.gnu.org/licenses/>. | ||
# | ||
|
||
"""This script creates a DataFrame for digitization-related fields in the survey form. | ||
These fields focus on verifying the accuracy of digitized locations, | ||
capturing any issues with digitization, and adding additional notes or | ||
images if required. The fields include logic for relevance, mandatory | ||
requirements, and conditions based on user responses. | ||
Returns: | ||
pd.DataFrame: A DataFrame containing the digitization-related fields. | ||
""" | ||
|
||
import pandas as pd | ||
|
||
digitisation_fields = [ | ||
{ | ||
"type": "begin group", | ||
"name": "verification", | ||
"label::english(en)": "Verification", | ||
"relevant": "(${new_feature} != '') or (${building_exists} = 'yes')", | ||
}, | ||
{ | ||
"type": "select_one yes_no", | ||
"name": "digitisation_correct", | ||
"label::english(en)": "Is the digitized location for this feature correct?", | ||
"relevant": "(${new_feature} != '') or (${building_exists} = 'yes')", | ||
"calculation": "once(if(${new_feature} != '', 'yes', ''))", | ||
"read_only": "${new_feature} != ''", | ||
"required": "yes", | ||
}, | ||
{ | ||
"type": "select_one digitisation_problem", | ||
"name": "digitisation_problem", | ||
"label::english(en)": "What is wrong with the digitization?", | ||
"relevant": "${digitisation_correct}='no'", | ||
}, | ||
{ | ||
"type": "text", | ||
"name": "digitisation_problem_other", | ||
"label::english(en)": "You said “Other.” Please tell us what went wrong with the digitization!", | ||
"relevant": "${digitisation_problem}='other' ", | ||
}, | ||
{"type": "end group"}, | ||
{ | ||
"type": "image", | ||
"name": "image", | ||
"label::english(en)": "Take a Picture", | ||
"apperance": "minimal", | ||
"parameters": "max-pixels=1000", | ||
}, | ||
{ | ||
"type": "note", | ||
"name": "end_note", | ||
"label::english(en)": "You can't proceed with data acquisition, if the building doesn't exist.", | ||
"relevant": "${building_exists} = 'no'", | ||
}, | ||
] | ||
|
||
digitisation_df = pd.DataFrame(digitisation_fields) |
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,175 @@ | ||
#!/usr/bin/python3 | ||
|
||
# Copyright (c) 2020, 2021, 2022, 2023, 2024 Humanitarian OpenStreetMap Team | ||
# | ||
# This file is part of OSM-Fieldwork. | ||
# | ||
# This is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation, either version 3 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# This is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with OSM-Fieldwork. If not, see <https:#www.gnu.org/licenses/>. | ||
# | ||
|
||
"""This script generates an XLS form with mandatory fields. | ||
For use in data collection and mapping tasks. | ||
The generated form includes metadata, survey questions, and settings | ||
required for compatibility with HOT's FMTM tools. | ||
It programmatically organizes form sections into metadata, | ||
mandatory fields, and entities, and outputs them in a structured format. | ||
Modules and functionalities: | ||
- **Metadata Sheet**: Includes default metadata fields | ||
such as `start`, `end`, `username`, and `deviceid`. | ||
- **Survey Sheet**: Combines metadata with mandatory fields required for FMTM workflows. | ||
- `warmup` for collecting initial location. | ||
- `feature` for selecting map geometry from predefined options. | ||
- `new_feature` for capturing GPS coordinates of new map features. | ||
- Calculated fields such as `form_category`, `xid`, `xlocation`, `status`, and others. | ||
- **Entities Sheet**: Defines entity management rules to handle mapping tasks dynamically. | ||
- Includes rules for entity creation and updates with user-friendly labels. | ||
- **Settings Sheet**: Sets the form ID, version, and configuration options. | ||
""" | ||
|
||
from datetime import datetime | ||
|
||
import pandas as pd | ||
|
||
current_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S") | ||
|
||
meta_data = [ | ||
{"type": "start", "name": "start"}, | ||
{"type": "end", "name": "end"}, | ||
{"type": "today", "name": "today"}, | ||
{"type": "phonenumber", "name": "phonenumber"}, | ||
{"type": "deviceid", "name": "deviceid"}, | ||
{"type": "username", "name": "username"}, | ||
{ | ||
"type": "email", | ||
"name": "email", | ||
}, | ||
] | ||
|
||
meta_df = pd.DataFrame(meta_data) | ||
|
||
mandatory_data = [ | ||
{ | ||
"type": "note", | ||
"name": "instructions", | ||
"label::english(en)": """Welcome ${username}. This survey form was generated | ||
by HOT's FMTM to record ${form_category} map features.""", | ||
}, | ||
{"notes": "Fields essential to FMTM"}, | ||
{"type": "start-geopoint", "name": "warmup", "notes": "collects location on form start"}, | ||
{"type": "select_one_from_file features.csv", "name": "feature", "label::english(en)": "Geometry", "appearance": "map"}, | ||
{ | ||
"type": "geopoint", | ||
"name": "new_feature", | ||
"label::english(en)": "Alternatively, take a gps coordinates of a new feature", | ||
"appearance": "placement-map", | ||
"relevant": "${feature}= ''", | ||
"required": "yes", | ||
}, | ||
{ | ||
"type": "calculate", | ||
"name": "form_category", | ||
"label::english(en)": "FMTM form category", | ||
"appearance": "minimal", | ||
"calculation": "once('Unkown')", | ||
}, | ||
{ | ||
"type": "calculate", | ||
"name": "xid", | ||
"notes": "e.g. OSM ID", | ||
"label::english(en)": "Feature ID", | ||
"appearance": "minimal", | ||
"calculation": "if(${feature} != '', instance('features')/root/item[name=${feature}]/osm_id, '')", | ||
}, | ||
{ | ||
"type": "calculate", | ||
"name": "xlocation", | ||
"notes": "e.g. OSM Geometry", | ||
"label::english(en)": "Feature Geometry", | ||
"appearance": "minimal", | ||
"calculation": "if(${feature} != '', instance('features')/root/item[name=${feature}]/geometry, ${new_feature})", | ||
"save_to": "geometry", | ||
}, | ||
{ | ||
"type": "calculate", | ||
"name": "task_id", | ||
"notes": "e.g. FMTM Task ID", | ||
"label::english(en)": "Task ID", | ||
"appearance": "minimal", | ||
"calculation": "if(${feature} != '', instance('features')/root/item[name=${feature}]/task_id, '')", | ||
"save_to": "task_id", | ||
}, | ||
{ | ||
"type": "calculate", | ||
"name": "status", | ||
"notes": "Update the Entity 'status' field", | ||
"label::english(en)": "Mapping Status", | ||
"appearance": "minimal", | ||
"calculation": """if(${new_feature} != '', 2, | ||
if(${building_exists} = 'no', 5, | ||
if(${digitisation_correct} = 'no', 6, | ||
${status})))""", | ||
"default": "2", | ||
"trigger": "${new_feature}", | ||
"save_to": "status", | ||
}, | ||
{ | ||
"type": "select_one yes_no", | ||
"name": "building_exists", | ||
"label::english(en)": "Does this feature exist?", | ||
"relevant": "${feature} != '' ", | ||
}, | ||
{ | ||
"type": "calculate", | ||
"name": "submission_id", | ||
"notes": "Update the submission id", | ||
"label::english(en)": "Submission id", | ||
"appearance": "minimal", | ||
"calculation": "once(${instanceID})", | ||
"save_to": "submission_id", | ||
}, | ||
] | ||
|
||
mandatory_df = pd.DataFrame(mandatory_data) | ||
|
||
# Define the survey sheet | ||
survey_df = pd.concat([meta_df, mandatory_df]) | ||
|
||
# Define entities sheet | ||
entities_data = [ | ||
{ | ||
"list_name": "features", | ||
"entity_id": "coalesce(${feature}, uuid())", | ||
"create_if": "if(${new_feature}, true(), false())", | ||
"update_if": "if(${new_feature}, false(), true())", | ||
"label": """concat(if(${status} = '1', "🔒 ", | ||
if(${status} = '2', "✅ ", if(${status} = '5', "❌ ", | ||
if(${status} = '6', "❌ ", '')))), "Task ", ${task_id}, | ||
" Feature ", if(${xid} != ' ', ${xid}, ' '))""", | ||
} | ||
] | ||
entities_df = pd.DataFrame(entities_data) | ||
|
||
# Define the settings sheet | ||
settings_data = [ | ||
{ | ||
"form_id": "mandatory_fields", | ||
"version": current_datetime, | ||
"form_title": "Mandatory Fields Form", | ||
"allow_choice_duplicates": "yes", | ||
} | ||
] | ||
|
||
settings_df = pd.DataFrame(settings_data) |
Oops, something went wrong.