rairo commited on
Commit
10290dd
·
verified ·
1 Parent(s): a549688

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +90 -19
main.py CHANGED
@@ -356,43 +356,114 @@ def delete_sozo_project(project_id):
356
 
357
  @app.route('/api/sozo/projects/<string:project_id>/generate-report', methods=['POST'])
358
  def generate_sozo_report(project_id):
359
- logger.info(f"Endpoint /generate-report POST for project {project_id}")
 
360
  try:
361
  token = request.headers.get('Authorization', '').split(' ')[1]
362
  uid = verify_token(token)
363
- if not uid: return jsonify({'error': 'Unauthorized'}), 401
364
- logger.info(f"Token verified for user {uid} for report generation.")
 
 
 
365
 
366
  project_ref = db.reference(f'sozo_projects/{project_id}')
367
  project_data = project_ref.get()
 
368
  if not project_data or project_data.get('uid') != uid:
369
- logger.warning(f"User {uid} failed to generate report: Project {project_id} not found or not owned.")
370
  return jsonify({'error': 'Project not found or unauthorized'}), 404
371
 
372
- logger.info(f"User {uid}: Ownership verified for project {project_id}. Starting report generation.")
373
- project_ref.update({'status': 'generating_report', 'updatedAt': datetime.utcnow().isoformat()})
374
 
375
- blob_path = f"sozo_projects/{uid}/{project_id}/data{Path(project_data['originalFilename']).suffix}"
376
- logger.info(f"User {uid}: Downloading data from {blob_path}")
377
  blob = bucket.blob(blob_path)
378
  file_bytes = blob.download_as_bytes()
379
- df = load_dataframe_safely(io.BytesIO(file_bytes), project_data['originalFilename'])
380
 
381
- logger.info(f"User {uid}: Calling core logic to generate report draft for project {project_id}.")
382
- draft_data = generate_report_draft(df, project_data['userContext'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
 
384
- logger.info(f"User {uid}: Saving report content specification to database for project {project_id}.")
385
- project_ref.child('report_content').set(draft_data)
386
 
387
- project_ref.update({'status': 'draft_complete', 'updatedAt': datetime.utcnow().isoformat()})
388
 
389
- full_project_data = project_ref.get()
390
- logger.info(f"User {uid}: Report generation for project {project_id} complete.")
391
- return jsonify({'success': True, 'project': full_project_data}), 200
 
392
 
393
  except Exception as e:
394
- db.reference(f'sozo_projects/{project_id}').update({'status': 'failed', 'error': str(e), 'updatedAt': datetime.utcnow().isoformat()})
395
- logger.error(f"CRITICAL ERROR generating report for {project_id}: {traceback.format_exc()}")
 
 
 
 
396
  return jsonify({'error': str(e)}), 500
397
 
398
  @app.route('/api/sozo/projects/<string:project_id>/generate-video', methods=['POST'])
 
356
 
357
  @app.route('/api/sozo/projects/<string:project_id>/generate-report', methods=['POST'])
358
  def generate_sozo_report(project_id):
359
+ logger.info(f"POST /api/sozo/projects/{project_id}/generate-report - Generating report")
360
+
361
  try:
362
  token = request.headers.get('Authorization', '').split(' ')[1]
363
  uid = verify_token(token)
364
+ if not uid:
365
+ logger.warning(f"Unauthorized access attempt to generate report for project {project_id}")
366
+ return jsonify({'error': 'Unauthorized'}), 401
367
+
368
+ logger.info(f"User {uid} generating report for project {project_id}")
369
 
370
  project_ref = db.reference(f'sozo_projects/{project_id}')
371
  project_data = project_ref.get()
372
+
373
  if not project_data or project_data.get('uid') != uid:
374
+ logger.warning(f"Project {project_id} not found or unauthorized for user {uid}")
375
  return jsonify({'error': 'Project not found or unauthorized'}), 404
376
 
377
+ logger.info(f"Project {project_id} validated for user {uid}")
 
378
 
379
+ blob_path = "/".join(project_data['originalDataUrl'].split('/')[4:])
 
380
  blob = bucket.blob(blob_path)
381
  file_bytes = blob.download_as_bytes()
 
382
 
383
+ # This function is the source of the inconsistent response
384
+ draft_data = generate_report_draft(
385
+ io.BytesIO(file_bytes),
386
+ project_data['originalFilename'],
387
+ project_data['userContext'],
388
+ uid,
389
+ project_id,
390
+ bucket
391
+ )
392
+
393
+ # Initialize variables from the draft data, providing safe defaults
394
+ final_raw_md = draft_data.get('raw_md', '')
395
+ final_chart_urls = draft_data.get('chartUrls', {})
396
+
397
+ # CORE LOGIC: If raw_md is empty BUT we received chart_specs, it means the
398
+ # primary markdown generation failed. We must reconstruct it to ensure the
399
+ # user sees their report.
400
+ if not final_raw_md.strip() and draft_data.get('report_content', {}).get('chart_specs'):
401
+ logger.warning(f"raw_md was empty for project {project_id}. Reconstructing from chart_specs.")
402
+
403
+ # 1. Reconstruct the Markdown String
404
+ final_raw_md = "## Executive Summary\n\nThis report provides a visual analysis of the key metrics from the provided dataset. Each section below highlights a specific insight through a detailed chart.\n\n"
405
+ chart_specs_list = draft_data['report_content']['chart_specs']
406
+
407
+ for spec_data in chart_specs_list:
408
+ title = spec_data.get('title', 'Chart')
409
+ final_raw_md += f"### {title}\n\n"
410
+ final_raw_md += f"<generate_chart: \"{title}\">\n\n"
411
+
412
+ # 2. Reconstruct the Chart URLs (as they will also be missing)
413
+ logger.warning(f"Reconstructing missing chart URLs for project {project_id}.")
414
+ df = load_dataframe_safely(io.BytesIO(file_bytes), project_data['originalFilename'])
415
+ llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", google_api_key=os.getenv("GOOGLE_API_KEY"), temperature=0.1)
416
+
417
+ for spec_data in chart_specs_list:
418
+ try:
419
+ spec_title = spec_data.get('title')
420
+ # Create a ChartSpecification instance from the dictionary data
421
+ chart_spec = ChartSpecification(
422
+ chart_type=spec_data.get('chart_type'),
423
+ title=spec_data.get('title'),
424
+ x_col=spec_data.get('x_col'),
425
+ y_col=spec_data.get('y_col'),
426
+ agg_method=spec_data.get('agg_method'),
427
+ top_n=spec_data.get('top_n')
428
+ )
429
+
430
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as temp_file:
431
+ img_path = Path(temp_file.name)
432
+ if execute_chart_spec(chart_spec, df, img_path):
433
+ blob_name = f"sozo_projects/{uid}/{project_id}/charts/{uuid.uuid4().hex}.png"
434
+ chart_blob = bucket.blob(blob_name)
435
+ chart_blob.upload_from_filename(str(img_path))
436
+ final_chart_urls[spec_title] = chart_blob.public_url
437
+ logger.info(f"Re-generated and uploaded chart '{spec_title}' to {chart_blob.public_url}")
438
+ except Exception as chart_gen_error:
439
+ logger.error(f"Error re-generating chart '{spec_data.get('title')}': {chart_gen_error}")
440
+ finally:
441
+ if 'img_path' in locals() and os.path.exists(img_path):
442
+ os.unlink(img_path)
443
+
444
+ # Prepare the final, corrected data for Firebase
445
+ update_data = {
446
+ 'status': 'draft',
447
+ 'rawMarkdown': final_raw_md,
448
+ 'chartUrls': final_chart_urls
449
+ }
450
 
451
+ project_ref.update(update_data)
 
452
 
453
+ logger.info(f"Project {project_id} successfully updated with draft data. Markdown length: {len(final_raw_md)}")
454
 
455
+ return jsonify({
456
+ 'success': True,
457
+ 'project': {**project_data, **update_data}
458
+ }), 200
459
 
460
  except Exception as e:
461
+ logger.error(f"CRITICAL error generating report for project {project_id}: {str(e)}")
462
+ logger.error(f"Traceback: {traceback.format_exc()}")
463
+ db.reference(f'sozo_projects/{project_id}').update({
464
+ 'status': 'failed',
465
+ 'error': str(e)
466
+ })
467
  return jsonify({'error': str(e)}), 500
468
 
469
  @app.route('/api/sozo/projects/<string:project_id>/generate-video', methods=['POST'])