Sahil Garg commited on
Commit
24ccd4e
·
1 Parent(s): a9ec4f6

corrected agent working

Browse files
Files changed (5) hide show
  1. .gitignore +5 -1
  2. agents/simple_agent.py +0 -22
  3. agents/simple_tools.py +54 -74
  4. app.py +70 -22
  5. docker-compose.yml +3 -13
.gitignore CHANGED
@@ -17,4 +17,8 @@ data/
17
  docker-compose.override.yml
18
  .vscode/
19
  app/__pycache__/
20
- pnlbs/__pycache__/
 
 
 
 
 
17
  docker-compose.override.yml
18
  .vscode/
19
  app/__pycache__/
20
+ pnlbs/__pycache__/
21
+ AGENT_GUIDE.md
22
+ docker-compose.dev.yml
23
+ agents/financial_agents.py
24
+ agents/financial_tools.py
agents/simple_agent.py CHANGED
@@ -26,28 +26,6 @@ class FinancialStatementAgent:
26
  llm=self.llm
27
  )
28
 
29
- async def generate_all_statements(self, file_path: str, note_numbers: str) -> Dict[str, Any]:
30
- """Generate all financial statements from trial balance file"""
31
- try:
32
- prompt = f"""
33
- Generate all financial statements from the trial balance file: {file_path}
34
- Note numbers to include: {note_numbers}
35
-
36
- Please generate in this order:
37
- 1. Financial notes for note numbers: {note_numbers}
38
- 2. Balance sheet
39
- 3. P&L statement
40
- 4. Cash flow statement
41
-
42
- Use the available tools to generate each statement and provide a summary of what was created.
43
- """
44
-
45
- result = await self.agent_executor.ainvoke({"input": prompt})
46
- return {"status": "success", "result": result["output"]}
47
-
48
- except Exception as e:
49
- logger.error(f"Error generating statements: {e}")
50
- return {"status": "error", "error": str(e)}
51
 
52
  async def generate_specific_statement(self, file_path: str, statement_type: str, note_numbers: str) -> Dict[str, Any]:
53
  """Generate a specific financial statement"""
 
26
  llm=self.llm
27
  )
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
  async def generate_specific_statement(self, file_path: str, statement_type: str, note_numbers: str) -> Dict[str, Any]:
31
  """Generate a specific financial statement"""
agents/simple_tools.py CHANGED
@@ -14,86 +14,66 @@ import logging
14
 
15
  logger = logging.getLogger(__name__)
16
 
17
-
18
  @tool
19
- def generate_notes_from_trial_balance(file_path: str, note_numbers: str = "") -> Dict[str, Any]:
20
  """
21
- Generate financial notes from trial balance file
22
  Args:
23
- file_path: Path to the trial balance Excel file
24
- note_numbers: Optional comma-separated note numbers (e.g., "2,3,4,5"). If empty, generates all notes.
 
 
25
  """
26
- execution_id = str(uuid.uuid4())[:8]
27
- start_time = time.time()
28
- tool_name = "generate_notes_from_trial_balance"
29
-
 
30
  try:
31
-
32
- # Copy file to input directory
33
- input_dir = "data/input"
34
- os.makedirs(input_dir, exist_ok=True)
35
- input_file = os.path.join(input_dir, "trial_balance.xlsx")
36
- shutil.copy2(file_path, input_file)
37
-
38
- file_size = os.path.getsize(input_file)
39
-
40
-
41
- # Run notes generation using existing endpoint logic
42
- env = os.environ.copy()
43
-
44
-
45
- # Use existing notes generation modules
46
- from notes.data_extraction import extract_trial_balance_data
47
- from notes.llm_notes_generator import FlexibleFinancialNoteGenerator
48
- from notes.json_to_excel import json_to_xlsx
49
-
50
-
51
- # Extract trial balance data
52
- trial_balance_data = extract_trial_balance_data(file_path)
53
-
54
-
55
-
56
- # Generate notes
57
- note_generator = FlexibleFinancialNoteGenerator()
58
-
59
- if note_numbers.strip():
60
- # Generate specific notes
61
- note_list = [int(n.strip()) for n in note_numbers.split(',')]
62
- generated_notes = {}
63
- for i, note_num in enumerate(note_list, 1):
64
- note_content = note_generator.generate_note(note_num, trial_balance_data)
65
- generated_notes[f"note_{note_num}"] = note_content
66
- message = f"Notes generated for: {note_numbers}"
67
  else:
68
- # Generate all notes
69
- generated_notes = note_generator.generate_all_notes(trial_balance_data)
70
- message = "All notes generated successfully"
71
- # Save notes
72
- output_path = "data/generated_notes/notes.json"
73
- os.makedirs(os.path.dirname(output_path), exist_ok=True)
74
- with open(output_path, 'w') as f:
75
- json.dump(generated_notes, f, indent=2)
76
-
77
- execution_time = round(time.time() - start_time, 2)
78
- output_file_size = os.path.getsize(output_path)
79
-
80
- return {
81
- "status": "success",
82
- "message": message,
83
- "output_path": output_path,
84
- "execution_id": execution_id,
85
- "execution_time": execution_time
86
- }
87
-
88
  except Exception as e:
89
- execution_time = round(time.time() - start_time, 2)
90
-
91
- return {
92
- "status": "error",
93
- "error": str(e),
94
- "execution_id": execution_id,
95
- "execution_time": execution_time
96
- }
97
 
98
  @tool
99
  def generate_balance_sheet(file_path: str) -> Dict[str, Any]:
@@ -388,7 +368,7 @@ def generate_cash_flow_statement(file_path: str) -> Dict[str, Any]:
388
 
389
  # Simplified tool list - only financial statement generation
390
  FINANCIAL_TOOLS = [
391
- generate_notes_from_trial_balance,
392
  generate_balance_sheet,
393
  generate_pnl_statement,
394
  generate_cash_flow_statement
 
14
 
15
  logger = logging.getLogger(__name__)
16
 
 
17
  @tool
18
+ def generate_notes_full_pipeline_from_path(file_path: str, note_numbers: str = "") -> dict:
19
  """
20
+ Implements the full notes generation pipeline as in /hardcoded route, but as a tool.
21
  Args:
22
+ file_path: Path to the uploaded Excel file
23
+ note_numbers: Optional comma-separated note numbers
24
+ Returns:
25
+ dict with status, output_xlsx_path, and error if any
26
  """
27
+ import logging
28
+ from notes.data_extraction import extract_trial_balance_data, analyze_and_save_results
29
+ from notes.notes_generator import process_json
30
+ from notes.json_to_excel import json_to_xlsx
31
+ logger = logging.getLogger(__name__)
32
  try:
33
+ os.makedirs("data/input", exist_ok=True)
34
+ # Copy file to input dir with original name
35
+ file_location = f"data/input/{os.path.basename(file_path)}"
36
+ if os.path.abspath(file_path) != os.path.abspath(file_location):
37
+ shutil.copyfile(file_path, file_location)
38
+ os.makedirs("data/output1", exist_ok=True)
39
+ structured_data = extract_trial_balance_data(file_location)
40
+ output1_json = "data/output1/parsed_trial_balance.json"
41
+ analyze_and_save_results(structured_data, output1_json)
42
+ os.makedirs("data/output2", exist_ok=True)
43
+ process_json(output1_json)
44
+ notes_json = "data/output2/notes_output.json"
45
+ with open(notes_json, "r", encoding="utf-8") as f:
46
+ notes_data = json.load(f)
47
+ if isinstance(notes_data, dict):
48
+ for key in ["notes", "trial_balance"]:
49
+ if key in notes_data:
50
+ notes_data = notes_data[key]
51
+ break
52
+ def wrap_notes(notes):
53
+ return {"notes": notes}
54
+ if note_numbers:
55
+ numbers = [n.strip() for n in note_numbers.split(",")]
56
+ notes_data = [
57
+ note for note in notes_data
58
+ if str(note.get('note_number', '')).strip() in numbers
59
+ ]
60
+ filtered_json = "data/output2/notes_output_filtered.json"
61
+ with open(filtered_json, "w", encoding="utf-8") as f2:
62
+ json.dump(wrap_notes(notes_data), f2, ensure_ascii=False, indent=2)
63
+ json_input_for_excel = filtered_json
 
 
 
 
 
64
  else:
65
+ temp_json = "data/output2/notes_output_wrapped.json"
66
+ with open(temp_json, "w", encoding="utf-8") as f2:
67
+ json.dump(wrap_notes(notes_data), f2, ensure_ascii=False, indent=2)
68
+ json_input_for_excel = temp_json
69
+ os.makedirs("data/output3", exist_ok=True)
70
+ output3_xlsx = "data/output3/final_output.xlsx"
71
+ json_to_xlsx(json_input_for_excel, output3_xlsx)
72
+ return {"status": "success", "output_xlsx_path": output3_xlsx}
 
 
 
 
 
 
 
 
 
 
 
 
73
  except Exception as e:
74
+ logger.error(f"notes_full_pipeline failed: {e}")
75
+ return {"status": "error", "error": str(e)}
76
+
 
 
 
 
 
77
 
78
  @tool
79
  def generate_balance_sheet(file_path: str) -> Dict[str, Any]:
 
368
 
369
  # Simplified tool list - only financial statement generation
370
  FINANCIAL_TOOLS = [
371
+ generate_notes_full_pipeline_from_path,
372
  generate_balance_sheet,
373
  generate_pnl_statement,
374
  generate_cash_flow_statement
app.py CHANGED
@@ -317,37 +317,85 @@ async def cf_from_notes(file: UploadFile = File(...)):
317
  async def agent_generate_statements(
318
  file: UploadFile = File(...),
319
  note_numbers: str = Form(""),
320
- statement_type: str = Form("all") # all, notes, balance_sheet, pnl, cash_flow
321
  ):
322
- """
323
- Use AI agent to generate financial statements (Notes/BS/P&L/CF)
 
 
324
  """
325
  try:
326
- # Save uploaded file
 
 
 
 
 
327
  upload_dir = "data/input"
328
  os.makedirs(upload_dir, exist_ok=True)
329
  file_path = os.path.join(upload_dir, file.filename)
330
-
331
  with open(file_path, "wb") as buffer:
332
  shutil.copyfileobj(file.file, buffer)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
 
334
- # Initialize financial statement agent
335
- agent = FinancialStatementAgent()
336
-
337
- # Generate requested statements
338
- if statement_type == "all":
339
- result = await agent.generate_all_statements(file_path, note_numbers)
340
- else:
341
- result = await agent.generate_specific_statement(file_path, statement_type, note_numbers)
342
-
343
- return JSONResponse(content={
344
- "message": f"Financial statement generation completed: {statement_type}",
345
- "file_processed": file.filename,
346
- "note_numbers": note_numbers,
347
- "statement_type": statement_type,
348
- "result": result
349
- })
350
-
351
  except Exception as e:
352
  logger.error(f"Error in agent statement generation: {e}")
353
  raise HTTPException(status_code=500, detail=f"Agent generation failed: {str(e)}")
 
317
  async def agent_generate_statements(
318
  file: UploadFile = File(...),
319
  note_numbers: str = Form(""),
320
+ statement_type: str = Form("notes") # notes, balance_sheet, pnl, cash_flow only
321
  ):
322
+ """Unified intelligent generation endpoint.
323
+
324
+ Behaviors:
325
+ - balance_sheet / pnl / cash_flow: run respective tool directly and return the Excel file
326
  """
327
  try:
328
+ # Validate statement_type
329
+ valid_types = {"balance_sheet", "pnl", "cash_flow", "notes"}
330
+ if statement_type not in valid_types:
331
+ raise HTTPException(status_code=400, detail=f"Invalid statement_type: {statement_type}. Allowed: balance_sheet, pnl, cash_flow, notes")
332
+
333
+ # Persist uploaded file
334
  upload_dir = "data/input"
335
  os.makedirs(upload_dir, exist_ok=True)
336
  file_path = os.path.join(upload_dir, file.filename)
 
337
  with open(file_path, "wb") as buffer:
338
  shutil.copyfileobj(file.file, buffer)
339
+
340
+ # Direct tool imports (lazy to avoid import cost if not needed)
341
+ from agents.simple_tools import (
342
+ generate_notes_full_pipeline_from_path,
343
+ generate_balance_sheet,
344
+ generate_pnl_statement,
345
+ generate_cash_flow_statement,
346
+ )
347
+
348
+ # Single statement short‑circuit (return actual file)
349
+ if statement_type in {"balance_sheet", "pnl", "cash_flow", "notes"}:
350
+ if statement_type == "notes":
351
+ output = generate_notes_full_pipeline_from_path(file_path, note_numbers)
352
+ if output["status"] != "success":
353
+ logger.error(f"Notes generation pipeline failed: {output.get('error')}")
354
+ raise HTTPException(status_code=500, detail=f"Notes generation pipeline failed: {output.get('error')}")
355
+ output3_xlsx = output["output_xlsx_path"]
356
+ return FileResponse(
357
+ output3_xlsx,
358
+ filename=os.path.basename(output3_xlsx),
359
+ media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
360
+ )
361
+
362
+ if statement_type == "balance_sheet":
363
+ bs_result = generate_balance_sheet.run({"file_path": file_path}) if hasattr(generate_balance_sheet, "run") else generate_balance_sheet(file_path)
364
+ if bs_result.get("status") != "success":
365
+ raise HTTPException(status_code=500, detail=f"Balance sheet generation failed: {bs_result.get('error')}")
366
+ # Expect directory with XLSX files
367
+ output_dir = bs_result.get("output_path", "data/output/")
368
+ # Choose first xlsx file
369
+ if os.path.isdir(output_dir):
370
+ xlsx_files = [f for f in os.listdir(output_dir) if f.endswith('.xlsx')]
371
+ if not xlsx_files:
372
+ raise HTTPException(status_code=500, detail="No balance sheet Excel file produced")
373
+ output_file = os.path.join(output_dir, xlsx_files[0])
374
+ else:
375
+ output_file = output_dir
376
+ return FileResponse(output_file, filename=os.path.basename(output_file), media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
377
+
378
+ if statement_type == "pnl":
379
+ pnl_result = generate_pnl_statement.run({"file_path": file_path}) if hasattr(generate_pnl_statement, "run") else generate_pnl_statement(file_path)
380
+ if pnl_result.get("status") != "success":
381
+ raise HTTPException(status_code=500, detail=f"P&L generation failed: {pnl_result.get('error')}")
382
+ output_path = pnl_result.get("output_path", "data/pnl_statement.xlsx")
383
+ if not os.path.exists(output_path):
384
+ raise HTTPException(status_code=500, detail="P&L Excel file not found")
385
+ return FileResponse(output_path, filename=os.path.basename(output_path), media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
386
+
387
+ if statement_type == "cash_flow":
388
+ cf_result = generate_cash_flow_statement.run({"file_path": file_path}) if hasattr(generate_cash_flow_statement, "run") else generate_cash_flow_statement(file_path)
389
+ if cf_result.get("status") != "success":
390
+ raise HTTPException(status_code=500, detail=f"Cash flow generation failed: {cf_result.get('error')}")
391
+ output_path = cf_result.get("output_path", "data/cash_flow_statements.xlsx")
392
+ if not os.path.exists(output_path):
393
+ raise HTTPException(status_code=500, detail="Cash flow Excel file not found")
394
+ return FileResponse(output_path, filename=os.path.basename(output_path), media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
395
+
396
 
397
+ except HTTPException:
398
+ raise
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
399
  except Exception as e:
400
  logger.error(f"Error in agent statement generation: {e}")
401
  raise HTTPException(status_code=500, detail=f"Agent generation failed: {str(e)}")
docker-compose.yml CHANGED
@@ -1,16 +1,13 @@
1
- version: '3.8'
2
 
3
  services:
4
  finryver:
5
  build: .
6
- container_name: finryver-app
7
  ports:
8
  - "8000:8000"
9
  volumes:
10
- # Mount data directory for persistent storage
11
- - ./data:/app/data
12
- # Mount config directory for business rules
13
- - ./config:/app/config
14
  environment:
15
  - PYTHONUNBUFFERED=1
16
  - PORT=8000
@@ -28,10 +25,3 @@ services:
28
  retries: 3
29
  start_period: 40s
30
 
31
- # Optional: Add Redis for caching (future enhancement)
32
- # redis:
33
- # image: redis:7-alpine
34
- # container_name: finryver-redis
35
- # ports:
36
- # - "6379:6379"
37
- # restart: unless-stopped
 
 
1
 
2
  services:
3
  finryver:
4
  build: .
5
+ container_name: finryver
6
  ports:
7
  - "8000:8000"
8
  volumes:
9
+ # Bind mount entire project for live code edits (includes data & config)
10
+ - .:/app
 
 
11
  environment:
12
  - PYTHONUNBUFFERED=1
13
  - PORT=8000
 
25
  retries: 3
26
  start_period: 40s
27