Spaces:
Running
Running
Delete services/agentic_handlers.py
Browse files- services/agentic_handlers.py +0 -322
services/agentic_handlers.py
DELETED
|
@@ -1,322 +0,0 @@
|
|
| 1 |
-
# handlers/agentic_handlers.py
|
| 2 |
-
import gradio as gr
|
| 3 |
-
import logging
|
| 4 |
-
from collections import defaultdict
|
| 5 |
-
import json # Added for JSON serialization/deserialization
|
| 6 |
-
|
| 7 |
-
# Attempt to import agentic pipeline functions and UI formatters
|
| 8 |
-
try:
|
| 9 |
-
from run_agentic_pipeline import run_full_analytics_orchestration
|
| 10 |
-
from ui.insights_ui_generator import (
|
| 11 |
-
format_report_to_markdown,
|
| 12 |
-
extract_key_results_for_selection,
|
| 13 |
-
format_single_okr_for_display
|
| 14 |
-
)
|
| 15 |
-
AGENTIC_MODULES_LOADED = True
|
| 16 |
-
except ImportError as e:
|
| 17 |
-
logging.error(f"Could not import agentic pipeline modules for AgenticHandlers: {e}.")
|
| 18 |
-
AGENTIC_MODULES_LOADED = False
|
| 19 |
-
# Define placeholder functions if modules are not loaded to avoid NameErrors during class definition
|
| 20 |
-
async def run_full_analytics_orchestration(*args, **kwargs): return None
|
| 21 |
-
def format_report_to_markdown(report_string): return "Agentic modules not loaded. Report unavailable."
|
| 22 |
-
def extract_key_results_for_selection(okrs_dict): return []
|
| 23 |
-
def format_single_okr_for_display(okr_data, **kwargs): return "Agentic modules not loaded. OKR display unavailable."
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
class AgenticHandlers:
|
| 27 |
-
def __init__(self, agentic_report_components, agentic_okrs_components,
|
| 28 |
-
token_state_ref, orchestration_raw_results_st_ref,
|
| 29 |
-
key_results_for_selection_st_ref, selected_key_result_ids_st_ref):
|
| 30 |
-
|
| 31 |
-
self.report_components = agentic_report_components
|
| 32 |
-
self.okrs_components = agentic_okrs_components
|
| 33 |
-
|
| 34 |
-
# References to global states
|
| 35 |
-
self.token_state = token_state_ref
|
| 36 |
-
self.orchestration_raw_results_st = orchestration_raw_results_st_ref
|
| 37 |
-
self.key_results_for_selection_st = key_results_for_selection_st_ref
|
| 38 |
-
self.selected_key_result_ids_st = selected_key_result_ids_st_ref
|
| 39 |
-
|
| 40 |
-
self.agentic_modules_really_loaded = AGENTIC_MODULES_LOADED
|
| 41 |
-
logging.info(f"AgenticHandlers initialized. Modules loaded: {self.agentic_modules_really_loaded}")
|
| 42 |
-
|
| 43 |
-
async def run_agentic_pipeline_autonomously_on_update(self, current_token_state_val):
|
| 44 |
-
"""
|
| 45 |
-
This function is intended to be triggered by changes in token_state.
|
| 46 |
-
It yields updates for the agentic report and OKR tabs.
|
| 47 |
-
State values (5th, 6th, 7th) are serialized to JSON strings.
|
| 48 |
-
Updates for key_results_cbg are now simplified without choices/interactive.
|
| 49 |
-
"""
|
| 50 |
-
logging.info(f"Agentic pipeline auto-trigger. Token: {'Set' if current_token_state_val.get('token') else 'Not Set'}")
|
| 51 |
-
|
| 52 |
-
initial_report_status = "Pipeline AI: In attesa dei dati necessari..."
|
| 53 |
-
initial_okr_details = "Pipeline AI: In attesa dei dati necessari..."
|
| 54 |
-
|
| 55 |
-
initial_orchestration_results = self.orchestration_raw_results_st.value
|
| 56 |
-
initial_selected_krs = self.selected_key_result_ids_st.value
|
| 57 |
-
initial_krs_for_selection = self.key_results_for_selection_st.value
|
| 58 |
-
|
| 59 |
-
report_status_md_update = gr.update(value=initial_report_status) if self.report_components.get("agentic_pipeline_status_md") else gr.update()
|
| 60 |
-
report_display_md_update = gr.update()
|
| 61 |
-
|
| 62 |
-
# Simple update for checkbox component - just reset value
|
| 63 |
-
okrs_cbg_update = gr.update(value=[]) if self.okrs_components.get("key_results_cbg") else gr.update()
|
| 64 |
-
|
| 65 |
-
okrs_detail_md_update = gr.update(value=initial_okr_details) if self.okrs_components.get("okr_detail_display_md") else gr.update()
|
| 66 |
-
|
| 67 |
-
if not current_token_state_val or not current_token_state_val.get("token"):
|
| 68 |
-
logging.info("Agentic pipeline: Token not available in token_state. Skipping actual run.")
|
| 69 |
-
yield (
|
| 70 |
-
report_status_md_update,
|
| 71 |
-
report_display_md_update,
|
| 72 |
-
okrs_cbg_update,
|
| 73 |
-
okrs_detail_md_update,
|
| 74 |
-
json.dumps(initial_orchestration_results), # Serialize to JSON
|
| 75 |
-
json.dumps(initial_selected_krs), # Serialize to JSON
|
| 76 |
-
json.dumps(initial_krs_for_selection) # Serialize to JSON
|
| 77 |
-
)
|
| 78 |
-
return
|
| 79 |
-
|
| 80 |
-
in_progress_status = "Analisi AI (Sempre) in corso..."
|
| 81 |
-
if self.report_components.get("agentic_pipeline_status_md"):
|
| 82 |
-
report_status_md_update = gr.update(value=in_progress_status)
|
| 83 |
-
if self.okrs_components.get("okr_detail_display_md"):
|
| 84 |
-
okrs_detail_md_update = gr.update(value="Dettagli OKR (Sempre) in corso di generazione...")
|
| 85 |
-
|
| 86 |
-
yield (
|
| 87 |
-
report_status_md_update, report_display_md_update, okrs_cbg_update, okrs_detail_md_update,
|
| 88 |
-
json.dumps(initial_orchestration_results), # Serialize to JSON
|
| 89 |
-
json.dumps(initial_selected_krs), # Serialize to JSON
|
| 90 |
-
json.dumps(initial_krs_for_selection) # Serialize to JSON
|
| 91 |
-
)
|
| 92 |
-
|
| 93 |
-
if not self.agentic_modules_really_loaded:
|
| 94 |
-
logging.warning("Agentic modules not loaded. Skipping autonomous pipeline actual run.")
|
| 95 |
-
error_status = "Moduli AI non caricati. Operazione non disponibile."
|
| 96 |
-
if self.report_components.get("agentic_pipeline_status_md"):
|
| 97 |
-
report_status_md_update = gr.update(value=error_status)
|
| 98 |
-
if self.report_components.get("agentic_report_display_md"):
|
| 99 |
-
report_display_md_update = gr.update(value=error_status)
|
| 100 |
-
|
| 101 |
-
# Simple update for checkbox in error case
|
| 102 |
-
if self.okrs_components.get("key_results_cbg"):
|
| 103 |
-
okrs_cbg_update = gr.update(value=[])
|
| 104 |
-
|
| 105 |
-
if self.okrs_components.get("okr_detail_display_md"):
|
| 106 |
-
okrs_detail_md_update = gr.update(value=error_status)
|
| 107 |
-
|
| 108 |
-
yield (
|
| 109 |
-
report_status_md_update, report_display_md_update, okrs_cbg_update, okrs_detail_md_update,
|
| 110 |
-
json.dumps(None), json.dumps([]), json.dumps([]) # Serialize to JSON
|
| 111 |
-
)
|
| 112 |
-
return
|
| 113 |
-
|
| 114 |
-
try:
|
| 115 |
-
date_filter_val_agentic = "Sempre"
|
| 116 |
-
custom_start_val_agentic = None
|
| 117 |
-
custom_end_val_agentic = None
|
| 118 |
-
|
| 119 |
-
logging.info("Agentic pipeline: Calling run_full_analytics_orchestration...")
|
| 120 |
-
orchestration_output = await run_full_analytics_orchestration(
|
| 121 |
-
current_token_state_val,
|
| 122 |
-
date_filter_val_agentic,
|
| 123 |
-
custom_start_val_agentic,
|
| 124 |
-
custom_end_val_agentic
|
| 125 |
-
)
|
| 126 |
-
|
| 127 |
-
final_status_text = "Pipeline AI (Sempre) completata."
|
| 128 |
-
logging.info(f"Autonomous agentic pipeline finished. Output keys: {orchestration_output.keys() if orchestration_output else 'None'}")
|
| 129 |
-
|
| 130 |
-
orchestration_results_update_val = None
|
| 131 |
-
selected_krs_update_val = [] # This will be the value for the CheckboxGroup, initially empty
|
| 132 |
-
krs_for_selection_update_val = []
|
| 133 |
-
|
| 134 |
-
if orchestration_output:
|
| 135 |
-
orchestration_results_update_val = orchestration_output
|
| 136 |
-
|
| 137 |
-
report_str = orchestration_output.get('comprehensive_analysis_report', "Nessun report testuale fornito.")
|
| 138 |
-
if self.report_components.get("agentic_report_display_md"):
|
| 139 |
-
formatted_report = format_report_to_markdown(report_str)
|
| 140 |
-
# Ensure we have a string, not a list
|
| 141 |
-
if isinstance(formatted_report, list):
|
| 142 |
-
formatted_report = "\n".join(formatted_report)
|
| 143 |
-
elif not isinstance(formatted_report, str):
|
| 144 |
-
formatted_report = str(formatted_report)
|
| 145 |
-
report_display_md_update = gr.update(value=formatted_report)
|
| 146 |
-
|
| 147 |
-
actionable_okrs = orchestration_output.get('actionable_okrs_and_tasks')
|
| 148 |
-
krs_for_ui_selection_list = extract_key_results_for_selection(actionable_okrs)
|
| 149 |
-
krs_for_selection_update_val = krs_for_ui_selection_list # This is the list of dicts
|
| 150 |
-
|
| 151 |
-
# For checkbox, just reset the value - don't update choices programmatically
|
| 152 |
-
if self.okrs_components.get("key_results_cbg"):
|
| 153 |
-
okrs_cbg_update = gr.update(value=[])
|
| 154 |
-
|
| 155 |
-
all_okrs_md_parts = []
|
| 156 |
-
if actionable_okrs and isinstance(actionable_okrs.get("okrs"), list):
|
| 157 |
-
for okr_idx, okr_item in enumerate(actionable_okrs["okrs"]):
|
| 158 |
-
all_okrs_md_parts.append(format_single_okr_for_display(okr_item, accepted_kr_indices=None, okr_main_index=okr_idx))
|
| 159 |
-
|
| 160 |
-
if not all_okrs_md_parts:
|
| 161 |
-
if self.okrs_components.get("okr_detail_display_md"):
|
| 162 |
-
okrs_detail_md_update = gr.update(value="Nessun OKR generato o trovato (Sempre).")
|
| 163 |
-
else:
|
| 164 |
-
if self.okrs_components.get("okr_detail_display_md"):
|
| 165 |
-
okrs_detail_md_update = gr.update(value="\n\n---\n\n".join(all_okrs_md_parts))
|
| 166 |
-
|
| 167 |
-
selected_krs_update_val = [] # Reset CheckboxGroup selection
|
| 168 |
-
else:
|
| 169 |
-
final_status_text = "Pipeline AI (Sempre): Nessun risultato prodotto."
|
| 170 |
-
if self.report_components.get("agentic_report_display_md"):
|
| 171 |
-
report_display_md_update = gr.update(value="Nessun report generato dalla pipeline AI (Sempre).")
|
| 172 |
-
|
| 173 |
-
# Simple update for checkbox if no output
|
| 174 |
-
if self.okrs_components.get("key_results_cbg"):
|
| 175 |
-
okrs_cbg_update = gr.update(value=[])
|
| 176 |
-
|
| 177 |
-
if self.okrs_components.get("okr_detail_display_md"):
|
| 178 |
-
okrs_detail_md_update = gr.update(value="Nessun OKR generato o errore nella pipeline AI (Sempre).")
|
| 179 |
-
|
| 180 |
-
if self.report_components.get("agentic_pipeline_status_md"):
|
| 181 |
-
report_status_md_update = gr.update(value=final_status_text)
|
| 182 |
-
|
| 183 |
-
yield (
|
| 184 |
-
report_status_md_update, report_display_md_update, okrs_cbg_update, okrs_detail_md_update,
|
| 185 |
-
json.dumps(orchestration_results_update_val), # Serialize to JSON
|
| 186 |
-
json.dumps(selected_krs_update_val), # Serialize to JSON (value for selected_key_result_ids_st)
|
| 187 |
-
json.dumps(krs_for_selection_update_val) # Serialize to JSON (value for key_results_for_selection_st)
|
| 188 |
-
)
|
| 189 |
-
|
| 190 |
-
except Exception as e:
|
| 191 |
-
logging.error(f"Error during autonomous agentic pipeline execution: {e}", exc_info=True)
|
| 192 |
-
error_status_text = f"Errore pipeline AI (Sempre): {str(e)}"
|
| 193 |
-
if self.report_components.get("agentic_pipeline_status_md"):
|
| 194 |
-
report_status_md_update = gr.update(value=error_status_text)
|
| 195 |
-
if self.report_components.get("agentic_report_display_md"):
|
| 196 |
-
report_display_md_update = gr.update(value=f"Errore generazione report AI (Sempre): {str(e)}")
|
| 197 |
-
|
| 198 |
-
# Simple update for checkbox in case of exception
|
| 199 |
-
if self.okrs_components.get("key_results_cbg"):
|
| 200 |
-
okrs_cbg_update = gr.update(value=[])
|
| 201 |
-
|
| 202 |
-
if self.okrs_components.get("okr_detail_display_md"):
|
| 203 |
-
okrs_detail_md_update = gr.update(value=f"Errore generazione OKR AI (Sempre): {str(e)}")
|
| 204 |
-
|
| 205 |
-
yield (
|
| 206 |
-
report_status_md_update, report_display_md_update, okrs_cbg_update, okrs_detail_md_update,
|
| 207 |
-
json.dumps(None), json.dumps([]), json.dumps([]) # Serialize to JSON
|
| 208 |
-
)
|
| 209 |
-
|
| 210 |
-
def update_okr_display_on_kr_selection(self, selected_kr_unique_ids: list,
|
| 211 |
-
raw_orchestration_results_json: str,
|
| 212 |
-
all_krs_for_selection_list_json: str):
|
| 213 |
-
"""
|
| 214 |
-
Updates the OKR detail display when Key Results are selected in the CheckboxGroup.
|
| 215 |
-
raw_orchestration_results_json and all_krs_for_selection_list_json are expected
|
| 216 |
-
to be JSON strings from state.
|
| 217 |
-
"""
|
| 218 |
-
if not self.agentic_modules_really_loaded:
|
| 219 |
-
return gr.update(value="Moduli AI non caricati. Impossibile visualizzare i dettagli OKR.")
|
| 220 |
-
|
| 221 |
-
# Handle case where selected_kr_unique_ids might be None or not a list
|
| 222 |
-
if not isinstance(selected_kr_unique_ids, list):
|
| 223 |
-
selected_kr_unique_ids = []
|
| 224 |
-
|
| 225 |
-
parsed_orchestration_results = None
|
| 226 |
-
try:
|
| 227 |
-
if raw_orchestration_results_json: # Check if the string is not empty
|
| 228 |
-
parsed_orchestration_results = json.loads(raw_orchestration_results_json)
|
| 229 |
-
except (json.JSONDecodeError, TypeError) as e:
|
| 230 |
-
logging.error(f"Failed to parse raw_orchestration_results_json: {raw_orchestration_results_json}. Error: {e}")
|
| 231 |
-
return gr.update(value="Errore: Dati interni corrotti (orchestration results).")
|
| 232 |
-
|
| 233 |
-
if not parsed_orchestration_results: # This covers None or empty after parsing
|
| 234 |
-
return gr.update(value="Nessun dato dalla pipeline AI (orchestration results).")
|
| 235 |
-
|
| 236 |
-
parsed_krs_for_selection_list = []
|
| 237 |
-
try:
|
| 238 |
-
if all_krs_for_selection_list_json: # Check if the string is not empty
|
| 239 |
-
parsed_krs_for_selection_list = json.loads(all_krs_for_selection_list_json)
|
| 240 |
-
except (json.JSONDecodeError, TypeError) as e:
|
| 241 |
-
logging.error(f"Failed to parse all_krs_for_selection_list_json: {all_krs_for_selection_list_json}. Error: {e}")
|
| 242 |
-
return gr.update(value="Errore: Dati interni corrotti (krs for selection).")
|
| 243 |
-
|
| 244 |
-
# Ensure parsed_krs_for_selection_list is a list, even if JSON was 'null' or other non-list type
|
| 245 |
-
if not isinstance(parsed_krs_for_selection_list, list):
|
| 246 |
-
logging.warning(f"Parsed all_krs_for_selection_list is not a list: {type(parsed_krs_for_selection_list)}. Defaulting to empty list.")
|
| 247 |
-
parsed_krs_for_selection_list = []
|
| 248 |
-
|
| 249 |
-
actionable_okrs_dict = parsed_orchestration_results.get("actionable_okrs_and_tasks") if isinstance(parsed_orchestration_results, dict) else None
|
| 250 |
-
|
| 251 |
-
if not actionable_okrs_dict or not isinstance(actionable_okrs_dict.get("okrs"), list):
|
| 252 |
-
return gr.update(value="Nessun OKR trovato nei risultati della pipeline (o dati in formato imprevisto).")
|
| 253 |
-
|
| 254 |
-
okrs_list = actionable_okrs_dict["okrs"]
|
| 255 |
-
if not okrs_list:
|
| 256 |
-
return gr.update(value="Nessun OKR generato.")
|
| 257 |
-
|
| 258 |
-
kr_id_to_indices = {}
|
| 259 |
-
if isinstance(parsed_krs_for_selection_list, list): # Ensure it's a list before iterating
|
| 260 |
-
for kr_info in parsed_krs_for_selection_list:
|
| 261 |
-
if isinstance(kr_info, dict) and 'unique_kr_id' in kr_info and 'okr_index' in kr_info and 'kr_index' in kr_info:
|
| 262 |
-
kr_id_to_indices[kr_info['unique_kr_id']] = (kr_info['okr_index'], kr_info['kr_index'])
|
| 263 |
-
else:
|
| 264 |
-
logging.warning(f"Skipping invalid kr_info item: {kr_info}")
|
| 265 |
-
|
| 266 |
-
selected_krs_by_okr_idx = defaultdict(list)
|
| 267 |
-
# selected_kr_unique_ids comes directly from CheckboxGroup, should be a list of strings/values
|
| 268 |
-
if isinstance(selected_kr_unique_ids, list):
|
| 269 |
-
for kr_unique_id in selected_kr_unique_ids:
|
| 270 |
-
if kr_unique_id in kr_id_to_indices:
|
| 271 |
-
okr_idx, kr_idx_in_okr = kr_id_to_indices[kr_unique_id]
|
| 272 |
-
selected_krs_by_okr_idx[okr_idx].append(kr_idx_in_okr)
|
| 273 |
-
|
| 274 |
-
output_md_parts = []
|
| 275 |
-
for okr_idx, okr_data in enumerate(okrs_list):
|
| 276 |
-
accepted_indices_for_this_okr = selected_krs_by_okr_idx.get(okr_idx)
|
| 277 |
-
|
| 278 |
-
if selected_kr_unique_ids:
|
| 279 |
-
if accepted_indices_for_this_okr is not None:
|
| 280 |
-
formatted_okr_md = format_single_okr_for_display(
|
| 281 |
-
okr_data,
|
| 282 |
-
accepted_kr_indices=accepted_indices_for_this_okr,
|
| 283 |
-
okr_main_index=okr_idx
|
| 284 |
-
)
|
| 285 |
-
output_md_parts.append(formatted_okr_md)
|
| 286 |
-
else:
|
| 287 |
-
formatted_okr_md = format_single_okr_for_display(
|
| 288 |
-
okr_data,
|
| 289 |
-
accepted_kr_indices=None,
|
| 290 |
-
okr_main_index=okr_idx
|
| 291 |
-
)
|
| 292 |
-
output_md_parts.append(formatted_okr_md)
|
| 293 |
-
|
| 294 |
-
if not output_md_parts and selected_kr_unique_ids:
|
| 295 |
-
final_md = "Nessun OKR corrisponde alla selezione corrente o i KR selezionati non hanno task dettagliati."
|
| 296 |
-
elif not output_md_parts and not selected_kr_unique_ids:
|
| 297 |
-
final_md = "Nessun OKR generato."
|
| 298 |
-
else:
|
| 299 |
-
final_md = "\n\n---\n\n".join(output_md_parts)
|
| 300 |
-
|
| 301 |
-
return gr.update(value=final_md)
|
| 302 |
-
|
| 303 |
-
def setup_event_handlers(self):
|
| 304 |
-
"""Sets up event handlers for the agentic OKRs tab."""
|
| 305 |
-
if not self.agentic_modules_really_loaded:
|
| 306 |
-
logging.warning("Agentic modules not loaded. Skipping agentic event handler setup.")
|
| 307 |
-
return
|
| 308 |
-
|
| 309 |
-
if self.okrs_components.get("key_results_cbg"):
|
| 310 |
-
self.okrs_components['key_results_cbg'].change(
|
| 311 |
-
fn=self.update_okr_display_on_kr_selection,
|
| 312 |
-
inputs=[
|
| 313 |
-
self.okrs_components['key_results_cbg'],
|
| 314 |
-
self.orchestration_raw_results_st,
|
| 315 |
-
self.key_results_for_selection_st
|
| 316 |
-
],
|
| 317 |
-
outputs=[self.okrs_components['okr_detail_display_md']],
|
| 318 |
-
api_name="update_okr_display_on_kr_selection" # Keep api_name for Gradio
|
| 319 |
-
)
|
| 320 |
-
logging.info("Agentic OKR selection handler setup complete.")
|
| 321 |
-
else:
|
| 322 |
-
logging.warning("key_results_cbg component not found for agentic OKR handler setup.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|