Shivam4096 commited on
Commit
46de1f3
·
verified ·
1 Parent(s): cba0a4f

Upload folder using huggingface_hub

Browse files
Files changed (2) hide show
  1. builders/build_excel.py +163 -1
  2. pipeline/analyst.py +23 -11
builders/build_excel.py CHANGED
@@ -507,7 +507,169 @@ def _dcf_sheet(wb, data):
507
  _widths(ws, [30, 20, 20, 20, 20, 20])
508
 
509
 
510
- def _narrative_sheet(wb, data):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
511
  ws = wb.create_sheet("Industry & AI Insights")
512
  _title(ws, "AI Narrative Analysis", "Generated by AI Models")
513
  r = 4
 
507
  _widths(ws, [30, 20, 20, 20, 20, 20])
508
 
509
 
510
+ def _dcf_sheet(wb, data):
511
+ ws = wb.create_sheet("DCF Valuation Model")
512
+ _title(ws, "Interactive Discounted Cash Flow (DCF)", "Change the yellow highlighted cells to update assumptions")
513
+
514
+ currency = data.get("currency", "USD")
515
+ units = data.get("company", {}).get("units", "Millions")
516
+
517
+ # 1. Historical data
518
+ rev_hist = data.get("income_statement", {}).get("revenue", [])
519
+ fcf_hist = data.get("cash_flow", {}).get("free_cash_flow", [])
520
+
521
+ last_rev = _last(rev_hist) or 0
522
+ last_fcf = _last(fcf_hist) or 0
523
+
524
+ # Default assumptions
525
+ default_wacc = 0.09
526
+ default_tg = 0.025
527
+
528
+ # Historical FCF Margin & Growth
529
+ rev_cagr = data.get("growth_metrics", {}).get("revenue_cagr_5y")
530
+ if rev_cagr is None:
531
+ rev_cagr = 0.05
532
+ rev_cagr = max(0.02, min(0.15, rev_cagr)) # Cap between 2% and 15%
533
+
534
+ fcf_margins = [_safe_div(f, r) for f, r in zip(fcf_hist, rev_hist) if f is not None and r is not None and r != 0]
535
+ avg_fcf_margin = sum(fcf_margins[-3:]) / len(fcf_margins[-3:]) if len(fcf_margins) > 0 else 0.10
536
+
537
+ shares_out = _last(data.get("income_statement", {}).get("diluted_shares", [])) or 1
538
+ if shares_out <= 0: shares_out = 1 # Prevent division by zero
539
+ net_debt = _last(data.get("net_debt", [])) or 0
540
+
541
+ # --- Assumptions Block ---
542
+ r = 4
543
+ _sec(ws, r, "1. Model Assumptions", 3)
544
+ ws.cell(row=r, column=4, value="Yellow cells are editable").font = Font(italic=True, color="595959")
545
+ r += 1
546
+
547
+ def add_assumption(row_idx, label, default_val, is_pct=True):
548
+ ws.cell(row=row_idx, column=1, value=label).font = LBL_FONT
549
+ c = ws.cell(row=row_idx, column=2, value=default_val)
550
+ c.fill = YLW_FILL
551
+ c.font = Font(name="Calibri", size=11, bold=True, color="1F4E78")
552
+ c.border = THIN
553
+ c.alignment = RIGHT
554
+ c.number_format = FMT_PCT if is_pct else FMT_DEC
555
+ return c.coordinate
556
+
557
+ wacc_cell = add_assumption(r, "Weighted Average Cost of Capital (WACC)", default_wacc); r += 1
558
+ tg_cell = add_assumption(r, "Terminal Growth Rate", default_tg); r += 1
559
+ rev_gr_cell = add_assumption(r, "Projected Revenue Growth (Years 1-5)", rev_cagr); r += 1
560
+ fcf_m_cell = add_assumption(r, "Projected FCF Margin", avg_fcf_margin); r += 1
561
+
562
+ r += 1
563
+
564
+ # --- Projections Block ---
565
+ _sec(ws, r, "2. Free Cash Flow Projections", 7)
566
+ r += 1
567
+
568
+ _hdr(ws, r, ["Metric", "Year 0 (Current)", "Year 1", "Year 2", "Year 3", "Year 4", "Year 5"])
569
+ r += 1
570
+
571
+ # Revenue row
572
+ ws.cell(row=r, column=1, value=f"Revenue ({currency} {units})").font = LBL_FONT
573
+ ws.cell(row=r, column=2, value=last_rev).number_format = FMT_MONEY
574
+ ws.cell(row=r, column=2).font = DAT_FONT
575
+
576
+ rev_cells = []
577
+ for i in range(1, 6):
578
+ c = ws.cell(row=r, column=2+i, value=f"={get_column_letter(2+i-1)}{r}*(1+{rev_gr_cell})")
579
+ c.number_format = FMT_MONEY; c.font = DAT_FONT
580
+ rev_cells.append(c.coordinate)
581
+ r += 1
582
+
583
+ # FCF row
584
+ ws.cell(row=r, column=1, value=f"Free Cash Flow ({currency} {units})").font = LBL_FONT
585
+ ws.cell(row=r, column=2, value=last_fcf).number_format = FMT_MONEY
586
+ ws.cell(row=r, column=2).font = DAT_FONT
587
+
588
+ fcf_cells = []
589
+ for i in range(1, 6):
590
+ c = ws.cell(row=r, column=2+i, value=f"={rev_cells[i-1]}*{fcf_m_cell}")
591
+ c.number_format = FMT_MONEY; c.font = DAT_FONT
592
+ fcf_cells.append(c.coordinate)
593
+ r += 1
594
+
595
+ # Discount Factor
596
+ ws.cell(row=r, column=1, value="Discount Factor").font = LBL_FONT
597
+ ws.cell(row=r, column=2, value=1.0).number_format = FMT_DEC
598
+ ws.cell(row=r, column=2).font = Font(italic=True, color="595959")
599
+
600
+ df_cells = []
601
+ for i in range(1, 6):
602
+ c = ws.cell(row=r, column=2+i, value=f"=1/((1+{wacc_cell})^{i})")
603
+ c.number_format = FMT_DEC; c.font = Font(italic=True, color="595959")
604
+ df_cells.append(c.coordinate)
605
+ r += 1
606
+
607
+ # PV of FCF
608
+ ws.cell(row=r, column=1, value="Present Value of FCF").font = Font(name="Calibri", bold=True)
609
+ ws.cell(row=r, column=2, value="--").alignment = RIGHT
610
+
611
+ pv_fcf_cells = []
612
+ for i in range(1, 6):
613
+ c = ws.cell(row=r, column=2+i, value=f"={fcf_cells[i-1]}*{df_cells[i-1]}")
614
+ c.number_format = FMT_MONEY; c.font = Font(name="Calibri", bold=True)
615
+ pv_fcf_cells.append(c.coordinate)
616
+
617
+ sum_pv_fcf_cell = f"SUM({pv_fcf_cells[0]}:{pv_fcf_cells[-1]})"
618
+ r += 2
619
+
620
+ # --- Valuation Bridge ---
621
+ _sec(ws, r, "3. Valuation Bridge", 3)
622
+ r += 1
623
+
624
+ # TV
625
+ ws.cell(row=r, column=1, value="Terminal Value (TV)").font = LBL_FONT
626
+ tv_formula = f"={fcf_cells[-1]}*(1+{tg_cell})/({wacc_cell}-{tg_cell})"
627
+ c = ws.cell(row=r, column=2, value=tv_formula); c.number_format = FMT_MONEY; c.font = DAT_FONT
628
+ tv_cell = c.coordinate
629
+ r += 1
630
+
631
+ # PV of TV
632
+ ws.cell(row=r, column=1, value="PV of Terminal Value").font = LBL_FONT
633
+ pv_tv_formula = f"={tv_cell}*{df_cells[-1]}"
634
+ c = ws.cell(row=r, column=2, value=pv_tv_formula); c.number_format = FMT_MONEY; c.font = DAT_FONT
635
+ pv_tv_cell = c.coordinate
636
+ r += 1
637
+
638
+ # EV
639
+ ws.cell(row=r, column=1, value="Implied Enterprise Value").font = Font(name="Calibri", bold=True, size=12)
640
+ ev_formula = f"={sum_pv_fcf_cell}+{pv_tv_cell}"
641
+ c = ws.cell(row=r, column=2, value=ev_formula); c.number_format = FMT_MONEY; c.font = Font(name="Calibri", bold=True, size=12)
642
+ ev_cell = c.coordinate
643
+ r += 1
644
+
645
+ # Net Debt
646
+ ws.cell(row=r, column=1, value="Less: Net Debt").font = LBL_FONT
647
+ c = ws.cell(row=r, column=2, value=net_debt); c.number_format = FMT_MONEY; c.font = DAT_FONT
648
+ nd_cell = c.coordinate
649
+ r += 1
650
+
651
+ # Equity Value
652
+ ws.cell(row=r, column=1, value="Implied Equity Value").font = Font(name="Calibri", bold=True, size=12)
653
+ eq_formula = f"={ev_cell}-{nd_cell}"
654
+ c = ws.cell(row=r, column=2, value=eq_formula); c.number_format = FMT_MONEY; c.font = Font(name="Calibri", bold=True, size=12)
655
+ eq_cell = c.coordinate
656
+ r += 1
657
+
658
+ # Shares
659
+ ws.cell(row=r, column=1, value="Diluted Shares Outstanding").font = LBL_FONT
660
+ c = ws.cell(row=r, column=2, value=shares_out); c.number_format = FMT_DEC; c.font = DAT_FONT
661
+ shares_cell = c.coordinate
662
+ r += 1
663
+
664
+ # Implied Share Price
665
+ ws.cell(row=r, column=1, value="Implied Share Price").font = Font(name="Calibri", bold=True, size=14, color="1F4E78")
666
+ share_price_formula = f"={eq_cell}/{shares_cell}"
667
+ c = ws.cell(row=r, column=2, value=share_price_formula); c.number_format = FMT_DEC; c.font = Font(name="Calibri", bold=True, size=14, color="1F4E78")
668
+ c.fill = GRN_FILL
669
+ r += 2
670
+
671
+ # Format and fit
672
+ _widths(ws, [35, 18, 18, 18, 18, 18, 18])
673
  ws = wb.create_sheet("Industry & AI Insights")
674
  _title(ws, "AI Narrative Analysis", "Generated by AI Models")
675
  r = 4
pipeline/analyst.py CHANGED
@@ -222,14 +222,23 @@ Guidelines:
222
  - Output ONLY the final synthesized text, with NO markdown formatting, NO introductory text, and NO commentary. Just the plain text paragraphs.
223
  """
224
 
225
- GENERATE_PPT_JSON = """You are a professional investment banker creating a pitch deck from financial data.
226
- Your task is to generate the structure and content for a PowerPoint presentation summarizing the company's financial health.
 
 
227
  Output a strict JSON array of slide objects. Each slide object MUST have:
228
- - "title": A short, punchy title for the slide
229
- - "bullets": An array of 3-5 strings, each containing a compelling insight, metric, or trend based on the data.
 
 
 
 
 
 
 
 
230
 
231
- Do not include any markdown fences, introduction, or conclusion. Just the raw JSON array.
232
- Focus on: Executive Summary, Revenue & Margin Trends, Cash Flow Health, Valuation, and Key Risks.
233
  """
234
 
235
  # -- Section prompts -----------------------------------------------------------
@@ -676,10 +685,10 @@ def generate_narrative(
676
  return _synthesize_drafts(section, drafts)
677
 
678
  def _generate_ppt_json() -> str:
679
- # Pick the smartest model available for structured JSON generation
680
  synthesizer = None
681
- if use_openai: synthesizer = ("ChatGPT", _openai_generate)
682
- elif use_claude: synthesizer = ("Claude", _claude_generate)
683
  elif use_gemini: synthesizer = ("Gemini", _gemini_generate)
684
  elif use_nvidia: synthesizer = ("NVIDIA", _nvidia_generate)
685
  elif use_groq: synthesizer = ("Groq", _groq_generate)
@@ -688,12 +697,15 @@ def generate_narrative(
688
  return ""
689
 
690
  name, fn = synthesizer
691
- log(f"\n[PPT] Generating PowerPoint Structure using {name} ...")
 
 
 
692
 
693
  original_prompt = SECTION_PROMPTS.get("ppt_json", "")
694
  SECTION_PROMPTS["ppt_json"] = GENERATE_PPT_JSON
695
  try:
696
- res = fn(context, "ppt_json")
697
  finally:
698
  if "ppt_json" in SECTION_PROMPTS:
699
  del SECTION_PROMPTS["ppt_json"]
 
222
  - Output ONLY the final synthesized text, with NO markdown formatting, NO introductory text, and NO commentary. Just the plain text paragraphs.
223
  """
224
 
225
+ GENERATE_PPT_JSON = """You are a top-tier Management Consultant (e.g., McKinsey, BCG) tasked with creating a highly visual, insightful, and strategic slide deck for a C-suite presentation.
226
+ You have been provided with a detailed textual narrative analysis of the company's financial health, trends, SWOT, and peer benchmarking.
227
+ Your task is to synthesize this narrative into a structured PowerPoint presentation.
228
+
229
  Output a strict JSON array of slide objects. Each slide object MUST have:
230
+ - "title": A short, punchy, action-oriented title for the slide (e.g., "Margins Contract Amidst Pricing Pressure" rather than "Margin Analysis").
231
+ - "bullets": An array of 3-5 strings. These MUST NOT be generic summaries. Each bullet must be a deep, consultant-level insight, data-backed takeaway, or strategic implication extracted directly from the provided narrative.
232
+
233
+ DO NOT include an Executive Summary slide, as that is handled separately.
234
+ Focus on:
235
+ 1. Strategic Overview & Market Position
236
+ 2. Revenue & Profitability Dynamics
237
+ 3. Balance Sheet & Cash Flow Health
238
+ 4. Competitive Benchmarking Insights
239
+ 5. Key Risks & Strategic Imperatives
240
 
241
+ Do not include any markdown fences (like ```json), introduction, or conclusion. Output ONLY the raw JSON array.
 
242
  """
243
 
244
  # -- Section prompts -----------------------------------------------------------
 
685
  return _synthesize_drafts(section, drafts)
686
 
687
  def _generate_ppt_json() -> str:
688
+ # Pick the smartest model available for structured JSON generation (prioritize Anthropic Claude for Consultant output)
689
  synthesizer = None
690
+ if use_claude: synthesizer = ("Claude", _claude_generate)
691
+ elif use_openai: synthesizer = ("ChatGPT", _openai_generate)
692
  elif use_gemini: synthesizer = ("Gemini", _gemini_generate)
693
  elif use_nvidia: synthesizer = ("NVIDIA", _nvidia_generate)
694
  elif use_groq: synthesizer = ("Groq", _groq_generate)
 
697
  return ""
698
 
699
  name, fn = synthesizer
700
+ log(f"\n[PPT] Generating PowerPoint Structure using {name} (Consultant-level Insights) ...")
701
+
702
+ # Build narrative context to feed the PPT generator
703
+ narrative_context = f"=== Company Overview ===\n{company_overview}\n\n=== SWOT ===\n{swot_raw}\n\n=== Trends ===\n{trend_revenue}\n{trend_bs}\n{trend_cf}\n\n=== Peer Benchmarking ===\n{peer_commentary}\n\n=== Risks & Insights ===\n{risks_raw}\n{insights_raw}"
704
 
705
  original_prompt = SECTION_PROMPTS.get("ppt_json", "")
706
  SECTION_PROMPTS["ppt_json"] = GENERATE_PPT_JSON
707
  try:
708
+ res = fn(narrative_context, "ppt_json")
709
  finally:
710
  if "ppt_json" in SECTION_PROMPTS:
711
  del SECTION_PROMPTS["ppt_json"]