GuglielmoTor commited on
Commit
1c7993b
·
verified ·
1 Parent(s): 8ed7829

Create insights_ui_generator.py

Browse files
Files changed (1) hide show
  1. insights_ui_generator.py +193 -0
insights_ui_generator.py ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # insights_ui_generator.py
2
+ import logging
3
+ from typing import Dict, Any, List, Optional
4
+
5
+ # Configure logger for this module. Assumes logging is configured in app.py or main entry point.
6
+ logger = logging.getLogger(__name__)
7
+
8
+ def format_report_to_markdown(report_string: Optional[str]) -> str:
9
+ """
10
+ Formats the comprehensive analysis report string into a displayable Markdown format.
11
+ This can be enhanced to add more structure if the report has implicit sections.
12
+
13
+ Args:
14
+ report_string: The raw text report from the orchestrator.
15
+
16
+ Returns:
17
+ A Markdown formatted string.
18
+ """
19
+ if not report_string or not report_string.strip():
20
+ return "## Comprehensive Analysis Report\n\n*No analysis report was generated, or an error occurred during its generation.*"
21
+
22
+ # Simple formatting for now. Could be enhanced (e.g., looking for patterns like "Section X:" to make them H3)
23
+ # Ensure paragraphs are separated. Replace multiple newlines with double newlines for Markdown paragraphs.
24
+ # report_string_cleaned = re.sub(r'\n\s*\n', '\n\n', report_string.strip())
25
+
26
+ formatted_report = f"## Comprehensive Analysis Report\n\n{report_string.strip()}"
27
+ # You might add more sophisticated parsing here if your LLM output for the report
28
+ # has a consistent structure that can be converted to richer Markdown.
29
+ return formatted_report
30
+
31
+ def extract_key_results_for_selection(
32
+ actionable_okrs_and_tasks_dict: Optional[Dict[str, Any]]
33
+ ) -> List[Dict[str, Any]]:
34
+ """
35
+ Extracts Key Results from the OKR structure for UI selection in Gradio.
36
+ Each Key Result is given a unique ID for state management in the Gradio app.
37
+
38
+ Args:
39
+ actionable_okrs_and_tasks_dict: The dictionary representation of TaskExtractionOutput,
40
+ typically `orchestration_results["actionable_okrs_and_tasks"]`.
41
+ Expected structure: {'okrs': List[OKR_dict], ...}
42
+
43
+ Returns:
44
+ A list of dictionaries, where each dictionary represents a Key Result:
45
+ {'okr_index': int, 'kr_index': int, 'okr_objective': str,
46
+ 'kr_description': str, 'unique_kr_id': str}
47
+ """
48
+ key_results_for_ui: List[Dict[str, Any]] = []
49
+
50
+ if not actionable_okrs_and_tasks_dict or not isinstance(actionable_okrs_and_tasks_dict.get('okrs'), list):
51
+ logger.warning("No 'okrs' list found or it's not a list in the provided task extraction output.")
52
+ return key_results_for_ui
53
+
54
+ okrs_list = actionable_okrs_and_tasks_dict['okrs']
55
+
56
+ for okr_idx, okr_data in enumerate(okrs_list):
57
+ if not isinstance(okr_data, dict):
58
+ logger.warning(f"OKR item at index {okr_idx} is not a dictionary, skipping.")
59
+ continue
60
+
61
+ okr_objective = okr_data.get('objective_description', f"Objective {okr_idx + 1} (Unnamed)")
62
+ key_results_list = okr_data.get('key_results', [])
63
+
64
+ if not isinstance(key_results_list, list):
65
+ logger.warning(f"Expected 'key_results' in OKR '{okr_objective}' (index {okr_idx}) to be a list, got {type(key_results_list)}.")
66
+ continue
67
+
68
+ for kr_idx, kr_data in enumerate(key_results_list):
69
+ if not isinstance(kr_data, dict):
70
+ logger.warning(f"Key Result item for OKR '{okr_objective}' at KR index {kr_idx} is not a dictionary, skipping.")
71
+ continue
72
+
73
+ kr_description = kr_data.get('key_result_description', f"Key Result {kr_idx + 1} (No description provided)")
74
+ key_results_for_ui.append({
75
+ 'okr_index': okr_idx, # Index of the parent OKR in the original list
76
+ 'kr_index': kr_idx, # Index of this KR within its parent OKR
77
+ 'okr_objective': okr_objective,
78
+ 'kr_description': kr_description,
79
+ 'unique_kr_id': f"okr{okr_idx}_kr{kr_idx}" # Unique ID for Gradio component linking
80
+ })
81
+
82
+ if not key_results_for_ui:
83
+ logger.info("No Key Results were extracted for selection from the OKR data.")
84
+
85
+ return key_results_for_ui
86
+
87
+ def format_single_okr_for_display(
88
+ okr_data: Dict[str, Any],
89
+ accepted_kr_indices: Optional[List[int]] = None,
90
+ okr_main_index: Optional[int] = None # For titling if needed
91
+ ) -> str:
92
+ """
93
+ Formats a single complete OKR object (with its Key Results and Tasks) into a
94
+ detailed Markdown string for display. Optionally filters to show only accepted Key Results.
95
+
96
+ Args:
97
+ okr_data: A dictionary representing a single OKR from the TaskExtractionOutput.
98
+ accepted_kr_indices: Optional list of indices of Key Results within this OKR
99
+ that were accepted by the user. If None, all KRs are displayed.
100
+ okr_main_index: Optional index of this OKR in the main list, for titling.
101
+
102
+
103
+ Returns:
104
+ A Markdown formatted string representing the OKR.
105
+ """
106
+ if not okr_data or not isinstance(okr_data, dict):
107
+ return "*Invalid OKR data provided for display.*\n"
108
+
109
+ md_parts = []
110
+
111
+ objective_title_num = f" {okr_main_index + 1}" if okr_main_index is not None else ""
112
+ objective = okr_data.get('objective_description', f"Unnamed Objective{objective_title_num}")
113
+ objective_timeline = okr_data.get('objective_timeline', '')
114
+ objective_owner = okr_data.get('objective_owner', 'N/A')
115
+
116
+ md_parts.append(f"### Objective{objective_title_num}: {objective}")
117
+ if objective_timeline:
118
+ md_parts.append(f"**Overall Timeline:** {objective_timeline}")
119
+ if objective_owner and objective_owner != 'N/A':
120
+ md_parts.append(f"**Overall Owner:** {objective_owner}")
121
+ md_parts.append("\n---")
122
+
123
+ key_results_list = okr_data.get('key_results', [])
124
+ displayed_kr_count = 0
125
+
126
+ if not isinstance(key_results_list, list) or not key_results_list:
127
+ md_parts.append("\n*No Key Results defined for this objective.*")
128
+ else:
129
+ for kr_idx, kr_data in enumerate(key_results_list):
130
+ if accepted_kr_indices is not None and kr_idx not in accepted_kr_indices:
131
+ continue # Skip this KR if a filter is applied and it's not in the accepted list
132
+
133
+ displayed_kr_count +=1
134
+
135
+ if not isinstance(kr_data, dict):
136
+ md_parts.append(f"\n**Key Result {kr_idx+1}:** *Invalid data format for this Key Result.*")
137
+ continue
138
+
139
+ kr_desc = kr_data.get('key_result_description', f"Key Result {kr_idx+1} (No description)")
140
+ target_metric = kr_data.get('target_metric')
141
+ target_value = kr_data.get('target_value')
142
+
143
+ md_parts.append(f"\n#### Key Result {displayed_kr_count} (Original Index: {kr_idx+1}): {kr_desc}")
144
+ if target_metric and target_value:
145
+ md_parts.append(f" - **Target:** Measure `{target_metric}` to achieve/reach `{target_value}`")
146
+
147
+ tasks_list = kr_data.get('tasks', [])
148
+ if tasks_list and isinstance(tasks_list, list):
149
+ md_parts.append(" **Associated Tasks:**")
150
+ for task_idx, task_data in enumerate(tasks_list):
151
+ if not isinstance(task_data, dict):
152
+ md_parts.append(f" - Task {task_idx+1}: *Invalid data format for this task.*")
153
+ continue
154
+
155
+ task_desc = task_data.get('task_description', f"Task {task_idx+1} (No description)")
156
+ task_cat = task_data.get('task_category', 'N/A')
157
+ task_effort = task_data.get('effort', 'N/A')
158
+ task_timeline = task_data.get('timeline', 'N/A')
159
+ task_priority = task_data.get('priority', 'N/A')
160
+ task_responsible = task_data.get('responsible_party', 'N/A')
161
+ task_type = task_data.get('task_type', 'N/A')
162
+ data_subject_val = task_data.get('data_subject')
163
+ data_subject_str = f", Data Subject: `{data_subject_val}`" if data_subject_val and task_type == 'tracking' else ""
164
+
165
+ md_parts.append(f" - **{task_idx+1}. {task_desc}**")
166
+ md_parts.append(f" - *Category:* {task_cat} | *Type:* {task_type}{data_subject_str}")
167
+ md_parts.append(f" - *Priority:* **{task_priority}** | *Effort:* {task_effort} | *Timeline:* {task_timeline}")
168
+ md_parts.append(f" - *Responsible:* {task_responsible}")
169
+
170
+ obj_deliv = task_data.get('objective_deliverable')
171
+ if obj_deliv: md_parts.append(f" - *Objective/Deliverable:* {obj_deliv}")
172
+
173
+ success_crit = task_data.get('success_criteria_metrics')
174
+ if success_crit: md_parts.append(f" - *Success Metrics:* {success_crit}")
175
+
176
+ why_prop = task_data.get('why_proposed')
177
+ if why_prop: md_parts.append(f" - *Rationale:* {why_prop}")
178
+
179
+ priority_just = task_data.get('priority_justification')
180
+ if priority_just: md_parts.append(f" - *Priority Justification:* {priority_just}")
181
+
182
+ dependencies = task_data.get('dependencies_prerequisites')
183
+ if dependencies: md_parts.append(f" - *Dependencies:* {dependencies}")
184
+ md_parts.append("") # Extra newline for spacing between tasks details
185
+ else:
186
+ md_parts.append(" *No tasks defined for this Key Result.*")
187
+ md_parts.append("\n---\n") # Separator between Key Results
188
+
189
+ if displayed_kr_count == 0 and accepted_kr_indices is not None:
190
+ md_parts.append("\n*No Key Results matching the 'accepted' filter for this objective.*")
191
+
192
+ return "\n".join(md_parts)
193
+