Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation for disabling workflows from context menu #643

Merged
merged 5 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 22 additions & 33 deletions geest/core/generate_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,8 @@ def load_spreadsheet(self):
self.dataframe = self.dataframe[
[
"Dimension",
"Dimension Required",
"Default Dimension Analysis Weighting",
"Factor",
"Factor Required",
"Default Factor Dimension Weighting",
"Indicator",
"Default Indicator Factor Weighting",
Expand Down Expand Up @@ -62,8 +60,7 @@ def load_spreadsheet(self):
"Use Nighttime Lights",
"Use Environmental Hazards",
"Use Street Lights",
"Analysis Mode", # New column
"Indicator Required", # New column
"Analysis Mode",
]
]

Expand All @@ -81,25 +78,20 @@ def parse_to_json(self):
"""
Parse the dataframe into the hierarchical JSON structure.
"""
dimension_map = {}
analysis_model = {}

for _, row in self.dataframe.iterrows():
dimension = row["Dimension"]
factor = row["Factor"]

# Prepare dimension data
dimension_id = self.create_id(dimension)
dimension_required = (
row["Dimension Required"]
if not pd.isna(row["Dimension Required"])
else ""
)
default_dimension_analysis_weighting = (
row["Default Dimension Analysis Weighting"]
if not pd.isna(row["Default Dimension Analysis Weighting"])
else ""
)
if dimension_id not in dimension_map:
if dimension_id not in analysis_model:
# Hardcoded descriptions for specific dimensions
description = ""
if dimension_id == "contextual":
Expand All @@ -110,58 +102,60 @@ def parse_to_json(self):
description = "The Place-Characterization Dimension refers to the social, environmental, and infrastructural attributes of geographical locations, such as walkability, safety, and vulnerability to natural hazards. Unlike the Accessibility Dimension, these factors do not involve mobility but focus on the inherent characteristics of a place that influence women’s ability to participate in the workforce."

# If the Dimension doesn't exist yet, create it
if dimension not in dimension_map:
if dimension not in analysis_model:
new_dimension = {
"id": dimension_id,
"name": dimension,
"required": dimension_required,
"default_analysis_weighting": default_dimension_analysis_weighting,
# Initialise the weighting to the default value
"analysis_weighting": default_dimension_analysis_weighting,
"description": description,
"factors": [],
}
self.result["dimensions"].append(new_dimension)
dimension_map[dimension] = new_dimension
analysis_model[dimension] = new_dimension

# Prepare factor data
factor_id = self.create_id(factor)
factor_required = (
row["Factor Required"] if not pd.isna(row["Factor Required"]) else ""
)
default_factor_dimension_weighting = (
row["Default Factor Dimension Weighting"]
if not pd.isna(row["Default Factor Dimension Weighting"])
else ""
)

# If the Factor doesn't exist in the current dimension, add it
factor_map = {f["name"]: f for f in dimension_map[dimension]["factors"]}
factor_map = {f["name"]: f for f in analysis_model[dimension]["factors"]}
if factor not in factor_map:
new_factor = {
"id": factor_id,
"name": factor,
"required": factor_required,
"default_dimension_weighting": default_factor_dimension_weighting,
# Initialise the weighting to the default value
"dimension_weighting": default_factor_dimension_weighting,
"indicators": [],
"description": (
row["Factor Description"]
if not pd.isna(row["Factor Description"])
else ""
),
}
dimension_map[dimension]["factors"].append(new_factor)
analysis_model[dimension]["factors"].append(new_factor)
factor_map[factor] = new_factor

# Add layer data to the current Factor, including new columns
layer_data = {
# Add indicator data to the current Factor, including new columns
default_factor_weighting = (
row["Default Indicator Factor Weighting"]
if not pd.isna(row["Default Indicator Factor Weighting"])
else ""
)
indicator_data = {
# These are all parsed from the spreadsheet
"indicator": row["Indicator"] if not pd.isna(row["Indicator"]) else "",
"id": row["ID"] if not pd.isna(row["ID"]) else "",
"description": "",
"default_indicator_factor_weighting": (
row["Default Indicator Factor Weighting"]
if not pd.isna(row["Default Indicator Factor Weighting"])
else ""
),
"default_factor_weighting": default_factor_weighting,
# Initialise the weighting to the default value
"factor_weighting": default_factor_weighting,
"default_index_score": (
row["Default Index Score"]
if not pd.isna(row["Default Index Score"])
Expand Down Expand Up @@ -291,14 +285,9 @@ def parse_to_json(self):
"analysis_mode": (
row["Analysis Mode"] if not pd.isna(row["Analysis Mode"]) else ""
), # New column
"indicator_required": (
row["Indicator Required"]
if not pd.isna(row["Indicator Required"])
else ""
), # New column
}

factor_map[factor]["indicators"].append(layer_data)
factor_map[factor]["indicators"].append(indicator_data)

def get_json(self):
"""
Expand Down
1 change: 0 additions & 1 deletion geest/core/generate_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ def infer_schema(data):
return {
"type": "object",
"properties": properties,
"required": required_keys,
}
elif isinstance(data, list):
if len(data) > 0:
Expand Down
132 changes: 99 additions & 33 deletions geest/core/json_tree_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,47 @@ def clear(self):
data["result_file"] = ""
data["error"] = ""
data["error_file"] = ""
data["execution_start_time"] = ""
data["execution_end_time"] = ""

def disable(self):
"""
Mark the item as disabled, which is essentially just setting its weight to zero.
"""
data = self.attributes()
data["analysis_mode"] = "Do Not Use"

if self.isDimension():
data["analysis_weighting"] = 0.0
if self.isFactor():
data["dimension_weighting"] = 0.0
if self.isIndicator():
data["factor_weighting"] = 0.0

def enable(self):
"""
Mark the item as enabled, which is essentially just setting its weight to its default.
"""
data = self.attributes()
data["analysis_mode"] = ""
if self.isDimension():
data["analysis_weighting"] = data["default_analysis_weighting"]
if self.isFactor():
data["dimension_weighting"] = data["default_dimension_weighting"]
if self.parent().getStatus() == "Excluded from analysis":
self.parent().attributes()[
"analysis_weighting"
] = self.parent().attribute("default_analysis_weighting")
if self.isIndicator():
data["factor_weighting"] = data["default_factor_weighting"]
if self.parent().getStatus() == "Excluded from analysis":
self.parent().attributes()[
"dimension_weighting"
] = self.parent().attribute("default_dimension_weighting")
if self.parent().parent().getStatus() == "Excluded from analysis":
self.parent().parent().attributes()["analysis_weighting"] = (
self.parent().parent().attribute("default_analysis_weighting")
)

def getIcon(self):
"""Retrieve the appropriate icon for the item based on its role."""
Expand Down Expand Up @@ -163,8 +204,7 @@ def getItemTooltip(self):
def getStatusIcon(self):
"""Retrieve the appropriate icon for the item based on its role."""
status = self.getStatus()
if self.isAnalysis():
return None

if status == "Excluded from analysis":
return QIcon(resources_path("resources", "icons", "excluded.svg"))
if status == "Completed successfully":
Expand Down Expand Up @@ -199,7 +239,6 @@ def getStatus(self):
# First check if the item weighting is 0, or its parent factor is zero
# If so, return "Excluded from analysis"
if self.isIndicator():
# Required flag can be overridden by the factor so we dont check it right now
required_by_parent = float(
self.parentItem.attributes().get("dimension_weighting", 0.0)
)
Expand All @@ -211,16 +250,31 @@ def getStatus(self):
# log_message(f"Excluded from analysis: {data.get('id')}")
return "Excluded from analysis"
if self.isFactor():
if not data.get("required", False):
if not float(data.get("dimension_weighting", 0.0)):
return "Excluded from analysis"
if self.isDimension():
# if the sum of the factor weightings is 0, return "Excluded from analysis"
if not float(data.get("dimension_weighting", 0.0)):
return "Excluded from analysis"
# If the sum of the indicator weightings is zero, return "Excluded from analysis"
weight_sum = 0
for child in self.childItems:
weight_sum += float(child.attribute("factor_weighting", 0.0))
if not weight_sum:
return "Excluded from analysis"
if self.isDimension():
# If the analysis weighting is zero, return "Excluded from analysis"
if not float(data.get("analysis_weighting", 0.0)):
return "Excluded from analysis"
# If the sum of the factor weightings is zero, return "Excluded from analysis"
weight_sum = 0
for child in self.childItems:
weight_sum += float(child.attribute("dimension_weighting", 0.0))
if not weight_sum:
return "Excluded from analysis"
if self.isAnalysis():
# If the sum of the dimension weightings is zero, return "Excluded from analysis"
weight_sum = 0
for child in self.childItems:
weight_sum += float(child.attribute("analysis_weighting", 0.0))
if not weight_sum:
return "Excluded from analysis"

if "Error" in data.get("result", ""):
return "Workflow failed"
Expand All @@ -237,13 +291,17 @@ def getStatus(self):
):
return "Not configured (optional)"
# Item required and not configured
if (data.get("analysis_mode", "") == "") and data.get(
"indicator_required", False
if (
self.isIndicator()
and (data.get("analysis_mode", "") == "")
and data.get("indicator_required", False)
):
return "Required and not configured"
# Item not required but not configured
if (data.get("analysis_mode", "") == "") and not data.get(
"indicator_required", False
if (
self.isIndicator()
and (data.get("analysis_mode", "") == "")
and not data.get("indicator_required", False)
):
return "Not configured (optional)"
if "Not Run" in data.get("result", "") and not data.get("result_file", ""):
Expand Down Expand Up @@ -331,28 +389,13 @@ def getDimensionFactorGuids(self):
return guids
# attributes["analysis_mode"] = "dimension_aggregation"

def getAnalysisAttributes(self):
"""Return the dict of dimensions under this analysis."""
attributes = {}
def getAnalysisDimensionGuids(self):
"""Return the list of factors under this dimension."""
guids = []
if self.isAnalysis():
attributes["analysis_name"] = self.attribute("analysis_name", "Not Set")
attributes["description"] = self.attribute(
"analysis_description", "Not Set"
)
attributes["working_folder"] = self.attribute("working_folder", "Not Set")
attributes["cell_size_m"] = self.attribute("cell_size_m", 100)

attributes["dimensions"] = [
{
"dimension_no": i,
"dimension_id": child.attribute("id", ""),
"dimension_name": child.data(0),
"dimension_weighting": child.data(2),
"result_file": child.attribute(f"result_file", ""),
}
for i, child in enumerate(self.childItems)
]
return attributes
guids = [child.guid for i, child in enumerate(self.childItems)]
return guids
# attributes["analysis_mode"] = "dimension_aggregation"

def getItemByGuid(self, guid):
"""Return the item with the specified guid."""
Expand Down Expand Up @@ -412,3 +455,26 @@ def updateFactorWeighting(self, factor_guid, new_weighting):
except Exception as e:
# Handle any exceptions and log the error
log_message(f"Error updating weighting: {e}", level=Qgis.Warning)

def updateDimensionWeighting(self, dimension_guid, new_weighting):
"""Update the weighting of a specific dimension by its guid."""
try:
# Search for the factor by name
dimension_item = self.getItemByGuid(dimension_guid)
# If found, update the weighting
if dimension_item:
dimension_item.setData(2, f"{new_weighting:.2f}")
# weighting references the level above (i.e. analysis)
dimension_item.attributes()["analysis_weighting"] = new_weighting

else:
# Log if the factor name is not found
log_message(
f"Factor '{dimension_guid}' not found.",
tag="Geest",
level=Qgis.Warning,
)

except Exception as e:
# Handle any exceptions and log the error
log_message(f"Error updating weighting: {e}", level=Qgis.Warning)
1 change: 0 additions & 1 deletion geest/core/workflow_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ def create_workflow(
return PointPerCellWorkflow(item, cell_size_m, feedback, context)
elif analysis_mode == "use_polyline_per_cell":
return PolylinePerCellWorkflow(item, cell_size_m, feedback, context)
# TODO fix inconsistent abbreviation below for Poly
elif analysis_mode == "use_polygon_per_cell":
return PolygonPerCellWorkflow(item, cell_size_m, feedback, context)
elif analysis_mode == "factor_aggregation":
Expand Down
12 changes: 10 additions & 2 deletions geest/core/workflows/aggregation_workflow_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,11 @@ def get_raster_list(self, index) -> list:
item = self.item.getItemByGuid(guid)
status = item.getStatus() == "Completed successfully"
mode = item.attributes().get("analysis_mode", "Do Not Use") == "Do Not Use"
excluded = item.getStatus() == "Excluded from analysis"
id = item.attribute("id").lower()
if not status and not mode:
if not status and not mode and not excluded:
raise ValueError(
f"{id} is not completed successfully and is not set to 'Do Not Use'"
f"{id} is not completed successfully and is not set to 'Do Not Use' or 'Excluded from analysis'"
)

if mode:
Expand All @@ -202,6 +203,13 @@ def get_raster_list(self, index) -> list:
level=Qgis.Info,
)
continue
if excluded:
log_message(
f"Skipping {item.attribute('id')} as it is excluded from analysis",
tag="Geest",
level=Qgis.Info,
)
continue
if not item.attribute("result_file", ""):
log_message(
f"Skipping {id} as it has no result file",
Expand Down
12 changes: 9 additions & 3 deletions geest/core/workflows/analysis_aggregation_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,14 @@ def __init__(
super().__init__(
item, cell_size_m, feedback, context
) # ⭐️ Item is a reference - whatever you change in this item will directly update the tree
self.id = "geest_analysis"
self.aggregation_attributes = self.item.getAnalysisAttributes()
self.layers = self.aggregation_attributes.get(f"dimensions", [])
self.guids = (
self.item.getAnalysisDimensionGuids()
) # get a list of the items to aggregate
self.id = (
self.item.attribute("analysis_name")
.lower()
.replace(" ", "_")
.replace("'", "")
) # should not be needed any more
self.weight_key = "dimension_weighting"
self.workflow_name = "analysis_aggregation"
1 change: 1 addition & 0 deletions geest/gui/dialogs/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .factor_aggregation_dialog import FactorAggregationDialog
from .indicator_detail_dialog import IndicatorDetailDialog
from .dimension_aggregation_dialog import DimensionAggregationDialog
from .analysis_aggregation_dialog import AnalysisAggregationDialog
Loading
Loading