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

store triggered_by for each report #666

Merged
merged 5 commits into from
Feb 7, 2025
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
23 changes: 19 additions & 4 deletions ai-agents/crowd-fund-analysis/cf_analysis_agent/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from flask import Flask, render_template, request, redirect, url_for, jsonify
from flask_cors import CORS
from cf_analysis_agent.utils.env_variables import BUCKET_NAME, OPEN_AI_DEFAULT_MODEL, REGION, ADMIN_CODES
from cf_analysis_agent.utils.agent_utils import generate_hashed_key
from cf_analysis_agent.utils.agent_utils import generate_hashed_key, get_admin_name_from_request

# # Add the parent directory of app.py to the Python path this maybe temporary we can change it later for that we will have to change docker file as well
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
Expand Down Expand Up @@ -75,6 +75,11 @@ def api_submit():
"""
Handles JSON-based form submission, starts processing, and returns a JSON response.
"""
# Get admin name from request
admin_name, error_response = get_admin_name_from_request()
if error_response:
return error_response # If there's an error, return it

if not request.is_json:
return jsonify({"error": "Invalid request. JSON expected."}), 400

Expand Down Expand Up @@ -103,7 +108,7 @@ def api_submit():
}

# Initialize project (store in S3 or DB)
initialize_project_in_s3(project_id=project_id, project_details=project_details)
initialize_project_in_s3(project_id=project_id, project_details=project_details, triggered_by=admin_name)

# Prepare command to run Python script asynchronously
command = [
Expand Down Expand Up @@ -159,10 +164,15 @@ def regenerate_reports(projectId):
Regenerates reports for a given project using values from agent-status.json in S3.
"""
try:
# Get admin name from request
admin_name, error_response = get_admin_name_from_request()
if error_response:
return error_response # If there's an error, return it

data = request.get_json(silent=True) or {} # Handle case if no body was sent
model = data.get("model", OPEN_AI_DEFAULT_MODEL)

update_status_to_not_started_for_all_reports(project_id=projectId)
update_status_to_not_started_for_all_reports(project_id=projectId, triggered_by=admin_name)
command = prepare_processing_command(projectId, model)

# Start the subprocess
Expand Down Expand Up @@ -190,11 +200,16 @@ def regenerate_specific_report(projectId, report_type):
Regenerates a specific report for a given project.
"""
try:
# Get admin name from request
admin_name, error_response = get_admin_name_from_request()
if error_response:
return error_response # If there's an error, return it

data = request.get_json(silent=True) or {} # Handle case if no body was sent
model = data.get("model", OPEN_AI_DEFAULT_MODEL)

# Prepare the command to start processing
update_report_status_in_progress(project_id=projectId, report_type=report_type)
update_report_status_in_progress(project_id=projectId, report_type=report_type, triggered_by=admin_name)
command = prepare_processing_command(projectId, model)

# Add the report_type to the command
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import hashlib
from cf_analysis_agent.utils.env_variables import SECRET_KEY
from flask import request, jsonify
from cf_analysis_agent.utils.env_variables import ADMIN_CODES

def combine_partial_state(state: dict, partial: dict) -> dict:
"""
Expand All @@ -13,4 +15,26 @@ def combine_partial_state(state: dict, partial: dict) -> dict:
def generate_hashed_key(code):
"""Generate a hashed key using SHA256"""
hash_obj = hashlib.sha256(f"{code}{SECRET_KEY}".encode())
return hash_obj.hexdigest()
return hash_obj.hexdigest()

def extract_admin_name(code):
"""Extract admin name from admin code (everything before '-')"""
return code.split("-")[0] # Get the part before '-'

def get_admin_name_from_request():
"""Extract admin name from request headers"""
hashed_key = request.headers.get("x-admin-key")

if not hashed_key:
return None, (jsonify({"status": "error", "message": "Unauthorized"}), 401)

# Find the matching admin code by checking if its hash matches
admin_code = next((code for code in ADMIN_CODES if generate_hashed_key(code) == hashed_key), None)

if not admin_code:
return None, (jsonify({"status": "error", "message": "Invalid code"}), 401)

# Extract the admin name (everything before '-')
admin_name = extract_admin_name(admin_code)

return admin_name, None # Return admin_name if found, otherwise None
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class ReportSchema(TypedDict, total=False):
under certain conditions.
"""
status: ProcessingStatus
lastTriggeredBy: NotRequired[Optional[str]]
markdownLink: Optional[str]
startTime: str
estimatedTimeInSec: int
Expand Down Expand Up @@ -178,21 +179,26 @@ def get_project_info_from_s3(project_id: str) -> ProjectInfo:
}


def get_init_data_for_report(report_type: ReportType) -> ReportSchema:
def get_init_data_for_report(report_type: ReportType, triggered_by = '') -> ReportSchema:
"""
Returns an initialized ReportSchema dictionary
for the given report_type.
"""
return {
report_data = {
"status": ProcessingStatus.NOT_STARTED,
"markdownLink": None,
"startTime": datetime.now().isoformat(),
"estimatedTimeInSec": 240 if report_type in [ReportType.FOUNDER_AND_TEAM, ReportType.FINANCIAL_HEALTH] else 150,
"performanceChecklist": []
}

if triggered_by:
report_data["lastTriggeredBy"] = triggered_by

return report_data


def initialize_project_in_s3(project_id: str, project_details: ProjectInfo):
def initialize_project_in_s3(project_id: str, project_details: ProjectInfo, triggered_by = ''):
"""
Creates or re-initializes the agent-status.json file for a project,
setting all reports to 'in_progress' along with basic metadata.
Expand All @@ -203,7 +209,7 @@ def initialize_project_in_s3(project_id: str, project_details: ProjectInfo):
# Initialize all reports
reports_data = {}
for r_type in ALL_REPORT_TYPES:
reports_data[r_type] = get_init_data_for_report(r_type)
reports_data[r_type] = get_init_data_for_report(r_type, triggered_by)
# Construct the status data
project_file_contents: ProjectStatusFileSchema = {
"id": project_id,
Expand Down Expand Up @@ -271,6 +277,8 @@ def update_project_file(project_id: str, project_file_contents: ProjectStatusFil
"confidence": report.get("confidence"),
"performanceChecklist": new_performance_checklist
}
if report.get("lastTriggeredBy"):
new_report["lastTriggeredBy"] = report["lastTriggeredBy"]
new_reports[report_type] = new_report

sec_info = project_file_contents["processedProjectInfo"].get("secInfo") or {}
Expand Down Expand Up @@ -335,16 +343,27 @@ def update_project_file(project_id: str, project_file_contents: ProjectStatusFil
f"Updated status file: https://{BUCKET_NAME}.s3.us-east-1.amazonaws.com/crowd-fund-analysis/{agent_status_file_path}")


def update_report_status_in_progress(project_id: str, report_type: ReportType):
def update_report_status_in_progress(project_id: str, report_type: ReportType, triggered_by = ''):
"""
Updates the `agent-status.json` file in S3 to set a report's status to "in_progress".
Handles both individual reports and `finalReport`.
"""
project_file_contents = get_project_file(project_id)
existing_report_data = project_file_contents["reports"].get(report_type, {})

# Get default values (including "lastTriggeredBy" if provided)
updated_fields = get_init_data_for_report(report_type, triggered_by)

# Only update fields that exist in `updated_fields`
for field, value in updated_fields.items():
existing_report_data[field] = value

project_file_contents["reports"][report_type] = get_init_data_for_report(report_type)
project_file_contents["reports"][report_type]["status"] = ProcessingStatus.IN_PROGRESS
# Ensure status is always updated to IN_PROGRESS
existing_report_data["status"] = ProcessingStatus.IN_PROGRESS

# Save updated report data
project_file_contents["reports"][report_type] = existing_report_data

update_project_file(project_id, project_file_contents)
print(f"Updated status of report '{report_type}' to 'in_progress'.")

Expand Down Expand Up @@ -427,14 +446,14 @@ def update_report_status_failed(project_id: str, report_type: ReportType, error_
print(f"Updated status of report '{report_type}' to 'failed' with error message: {error_message}")


def update_status_to_not_started_for_all_reports(project_id):
def update_status_to_not_started_for_all_reports(project_id, triggered_by):
agent_status_file_path = f"{project_id}/agent-status.json"

project_file_contents = get_project_file(project_id)

# Initialize all reports to "in_progress" and set timestamps
for report_type in ALL_REPORT_TYPES:
project_file_contents["reports"][report_type] = get_init_data_for_report(report_type)
project_file_contents["reports"][report_type] = get_init_data_for_report(report_type, triggered_by)
print(f"Set status of report '{report_type}' to 'not_started'. Initialized startTime and estimatedTimeInSec.")

print(f"Set status of report 'finalReport' to 'not_started'. Initialized startTime and estimatedTimeInSec.")
Expand All @@ -444,7 +463,7 @@ def update_status_to_not_started_for_all_reports(project_id):
f"Updated status file: https://{BUCKET_NAME}.s3.us-east-1.amazonaws.com/crowd-fund-analysis/{agent_status_file_path}")


def create_report_file_and_upload_to_s3(project_id: str, report_type: ReportType, report_content: str, summary: str = ""):
def create_report_file_and_upload_to_s3(project_id: str, report_type: ReportType, report_content: str):
report_file_path = f"{project_id}/{report_type}.md"
upload_to_s3(report_content, report_file_path)
# Update status file to "completed"
Expand Down
3 changes: 3 additions & 0 deletions insights-ui/src/util/regenerate.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { getAuthKey } from './auth/authKey';

export async function regenerateReport(projectId: string, model: string, reportType?: string): Promise<{ success: boolean; message: string }> {
const baseURL = process.env.NEXT_PUBLIC_AGENT_APP_URL?.toString() || '';
const url: string = reportType ? `reports/${reportType}/regenerate` : `reports/regenerate`;
Expand All @@ -8,6 +10,7 @@ export async function regenerateReport(projectId: string, model: string, reportT
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-admin-key': getAuthKey(),
},
body: JSON.stringify({ model }),
});
Expand Down
2 changes: 2 additions & 0 deletions insights-ui/src/util/submit-project.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ProjectSubmissionData } from '@/types/project/project';
import { getAuthKey } from './auth/authKey';

export async function submitProject(projectDetails: ProjectSubmissionData): Promise<{ success: boolean; message: string }> {
const baseURL = process.env.NEXT_PUBLIC_AGENT_APP_URL?.toString() || '';
Expand All @@ -8,6 +9,7 @@ export async function submitProject(projectDetails: ProjectSubmissionData): Prom
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-admin-key': getAuthKey(),
},
body: JSON.stringify({
projectId: projectDetails.projectId,
Expand Down