Spaces:
Running
Running
Create insights_ui_generator.py
Browse files- 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 |
+
|