File size: 6,638 Bytes
3615850
c58585a
 
 
 
 
3615850
 
c58585a
 
 
 
3615850
 
 
 
c58585a
3615850
 
4cc5e6b
3615850
 
4cc5e6b
3615850
c58585a
 
3615850
c58585a
3615850
 
4cc5e6b
 
 
c58585a
 
4cc5e6b
 
4192d57
 
c58585a
4cc5e6b
 
 
c58585a
4cc5e6b
 
 
 
c58585a
4cc5e6b
c58585a
 
4cc5e6b
 
 
 
3615850
4192d57
 
 
c58585a
 
4192d57
 
c58585a
4192d57
 
c58585a
 
4192d57
c58585a
 
 
 
4192d57
57c9bbb
c58585a
 
 
4192d57
 
c58585a
4192d57
 
c58585a
 
 
4192d57
 
c58585a
4192d57
c58585a
4192d57
 
 
c58585a
4192d57
c58585a
4192d57
 
 
 
 
 
 
 
 
c58585a
4192d57
c58585a
4192d57
 
 
 
 
 
 
 
 
c58585a
4192d57
c58585a
 
 
4192d57
 
c58585a
 
 
 
 
 
 
 
 
 
 
4192d57
c58585a
 
4192d57
 
 
 
 
c58585a
 
4192d57
 
c58585a
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# services/report_data_handler.py
"""
This module is responsible for fetching pre-computed agentic analysis data
(reports, OKRs, etc.) from Bubble.io and reconstructing it into a nested
dictionary format that the Gradio UI can easily display.
"""
import pandas as pd
import logging
from typing import Dict, Any, Optional, Tuple

# This is the only function needed from the Bubble API module for this handler
from apis.Bubble_API_Calls import fetch_linkedin_posts_data_from_bubble
from config import (
    BUBBLE_REPORT_TABLE_NAME,
    BUBBLE_OKR_TABLE_NAME,
    BUBBLE_KEY_RESULTS_TABLE_NAME,
    BUBBLE_TASKS_TABLE_NAME
)

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def fetch_latest_agentic_analysis(org_urn: str) -> Tuple[Optional[pd.DataFrame], Optional[str]]:
    """
    Fetches all agentic analysis report data for a given org_urn from Bubble.
    This function is called once during the initial data load.
    """
    logger.info(f"Fetching latest agentic analysis data from Bubble for org_urn: {org_urn}")
    if not org_urn:
        logger.warning("fetch_latest_agentic_analysis: org_urn is missing.")
        return None, "org_urn is missing."

    try:
        # We fetch all reports and will sort them later if needed, but typically the
        # external process should manage providing the "latest" or "active" report.
        report_data_df, error = fetch_linkedin_posts_data_from_bubble(
            data_type=BUBBLE_REPORT_TABLE_NAME,
            constraint_value=org_urn,
            constraint_key='organization_urn',
            constraint_type='equals'
        )

        if error:
            logger.error(f"Error fetching agentic reports from Bubble for org_urn {org_urn}: {error}")
            return None, str(error)

        if report_data_df is None or report_data_df.empty:
            logger.info(f"No existing agentic analysis found in Bubble for org_urn {org_urn}.")
            return pd.DataFrame(), None # Return empty DataFrame, no error

        logger.info(f"Successfully fetched {len(report_data_df)} agentic report records for org_urn {org_urn}")
        return report_data_df, None

    except Exception as e:
        logger.exception(f"An unexpected error occurred in fetch_latest_agentic_analysis for org_urn {org_urn}: {e}")
        return None, str(e)


def fetch_and_reconstruct_data_from_bubble(report_df: pd.DataFrame) -> Optional[Dict[str, Any]]:
    """
    Takes a DataFrame of report data, fetches all related child items (OKRs, KRs, Tasks)
    from Bubble, and reconstructs the full nested dictionary expected by the UI.

    Args:
        report_df: The DataFrame containing one or more reports, fetched previously.

    Returns:
        A dictionary containing the reconstructed data ('report_str', 'actionable_okrs'),
        or None if the report is not found or a critical error occurs.
    """
    logger.info("Starting data reconstruction from fetched Bubble data.")
    if report_df is None or report_df.empty:
        logger.warning("Cannot reconstruct data, the provided report DataFrame is empty.")
        return None

    try:
        # Assuming the most recent report is desired if multiple are returned.
        # You might need more sophisticated logic here to select the "active" report.
        latest_report = report_df.sort_values(by='Created Date', ascending=False).iloc[0]
        report_id = latest_report.get('_id')
        if not report_id:
            logger.error("Fetched report is missing a Bubble '_id', cannot reconstruct children.")
            return None

        logger.info(f"Reconstructing data for the latest report, ID: {report_id}")

        # 1. Fetch all related OKRs using the report_id
        okrs_df, error = fetch_linkedin_posts_data_from_bubble(
            data_type=BUBBLE_OKR_TABLE_NAME,
            constraint_value=report_id,
            constraint_key='report',
            constraint_type='equals'
        )
        if error:
            logger.error(f"Error fetching OKRs for report_id {report_id}: {error}")
            return None # Fail reconstruction if children can't be fetched

        # 2. Fetch all related Key Results using the OKR IDs
        okr_ids = okrs_df['_id'].tolist() if not okrs_df.empty else []
        krs_df = pd.DataFrame()
        if okr_ids:
            krs_df, error = fetch_linkedin_posts_data_from_bubble(
                data_type=BUBBLE_KEY_RESULTS_TABLE_NAME,
                constraint_value=okr_ids,
                constraint_key='okr',
                constraint_type='in'
            )
            if error: logger.error(f"Error fetching Key Results: {error}")

        # 3. Fetch all related Tasks using the Key Result IDs
        kr_ids = krs_df['_id'].tolist() if not krs_df.empty else []
        tasks_df = pd.DataFrame()
        if kr_ids:
            tasks_df, error = fetch_linkedin_posts_data_from_bubble(
                data_type=BUBBLE_TASKS_TABLE_NAME,
                constraint_value=kr_ids,
                constraint_key='key_result',
                constraint_type='in'
            )
            if error: logger.error(f"Error fetching Tasks: {error}")

        # 4. Reconstruct the nested dictionary
        tasks_by_kr_id = tasks_df.groupby('key_result').apply(lambda x: x.to_dict('records')).to_dict() if not tasks_df.empty else {}
        krs_by_okr_id = krs_df.groupby('okr').apply(lambda x: x.to_dict('records')).to_dict() if not krs_df.empty else {}

        reconstructed_okrs = []
        if not okrs_df.empty:
            for okr_data in okrs_df.to_dict('records'):
                okr_id = okr_data['_id']
                key_results_list = krs_by_okr_id.get(okr_id, [])
                for kr_data in key_results_list:
                    kr_id = kr_data['_id']
                    kr_data['tasks'] = tasks_by_kr_id.get(kr_id, [])
                okr_data['key_results'] = key_results_list
                reconstructed_okrs.append(okr_data)

        # 5. Assemble the final payload for the UI
        actionable_okrs = {"okrs": reconstructed_okrs}
        final_reconstructed_data = {
            "report_str": latest_report.get("report_text", "Report text not found."),
            "quarter": latest_report.get("quarter"),
            "year": latest_report.get("year"),
            "actionable_okrs": actionable_okrs,
            "report_id": report_id
        }
        logger.info("Successfully reconstructed nested data structure for the UI.")
        return final_reconstructed_data

    except Exception as e:
        logger.exception(f"An unexpected error occurred during data reconstruction: {e}")
        return None