generate_investment_memo
Create a 2-page investment memo with financial analysis for a company using its ticker symbol, generating a Word document with consolidation data from FCCS.
Instructions
Generate a 2-page investment memo (Word doc) with financial analysis / Gerar memorando de investimento
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| ticker | Yes | Company ticker symbol (e.g., 'TECH') |
Implementation Reference
- fccs_agent/tools/memo.py:170-401 (handler)Main tool handler that generates a 2-page investment memo Word document. Fetches financial data from FCCS or uses mock data, performs ratio analysis, and structures the document with executive summary, key metrics table, financial analysis, recommendation, and risks.async def generate_investment_memo(ticker: str) -> dict[str, Any]: """Generate a 2-page investment memo (Word doc) with financial analysis. Gerar memorando de investimento (Word) com analise financeira. Args: ticker: Company ticker symbol (e.g., 'TECH'). Returns: dict: Path to generated memo file and summary. """ if not DOCX_AVAILABLE: return { "status": "error", "error": "python-docx not available. Install with: pip install python-docx" } try: # Try to get real FCCS data, otherwise generate realistic mock data financial_data = await _get_financial_data(ticker) # Perform financial analysis analysis = _analyze_financials(financial_data, ticker) # Generate Word document doc = Document() # Set margins for 2-page memo sections = doc.sections for section in sections: section.top_margin = Inches(0.75) section.bottom_margin = Inches(0.75) section.left_margin = Inches(1.0) section.right_margin = Inches(1.0) # PAGE 1: Executive Summary & Key Metrics _add_memo_header(doc, ticker, financial_data.get("company_name", f"{ticker} Corp")) # Executive Summary exec_summary = doc.add_heading('Executive Summary', 1) exec_summary.runs[0].font.color.rgb = RGBColor(31, 71, 136) exec_summary.paragraph_format.space_after = Pt(6) summary_text = ( f"{financial_data.get('company_name', ticker)} demonstrates {'strong' if analysis['recommendation'] == 'BUY' else 'moderate' if analysis['recommendation'] == 'HOLD' else 'weak'} " f"financial performance with revenue of ${financial_data['revenue']:,.0f}M and net income of ${financial_data['net_income']:,.0f}M. " f"The company shows a {'positive' if analysis['revenue_growth'] > 0 else 'negative'} revenue growth trend " f"of {abs(analysis['revenue_growth']):.1f}% and maintains a {'healthy' if analysis['profit_margin'] > 0.15 else 'moderate' if analysis['profit_margin'] > 0.05 else 'narrow'} " f"profit margin of {analysis['profit_margin']:.1%}. " f"Based on our analysis, we recommend a {analysis['recommendation']} position." ) doc.add_paragraph(summary_text) doc.add_paragraph() # Spacing # Key Financial Metrics metrics_heading = doc.add_heading('Key Financial Metrics', 1) metrics_heading.runs[0].font.color.rgb = RGBColor(31, 71, 136) metrics_heading.paragraph_format.space_after = Pt(6) metrics = [ ("Revenue (TTM)", f"${financial_data['revenue']:,.0f}M"), ("Net Income (TTM)", f"${financial_data['net_income']:,.0f}M"), ("Profit Margin", f"{analysis['profit_margin']:.1%}"), ("Revenue Growth (YoY)", f"{analysis['revenue_growth']:+.1f}%"), ("ROE", f"{analysis['roe']:.1%}"), ("Current Ratio", f"{analysis['current_ratio']:.2f}"), ("Debt-to-Equity", f"{analysis['debt_to_equity']:.2f}"), ] for metric_name, metric_value in metrics: metric_para = doc.add_paragraph() metric_run = metric_para.add_run(f"{metric_name}: ") metric_run.font.bold = True metric_run.font.size = Pt(10) value_run = metric_para.add_run(metric_value) value_run.font.size = Pt(10) metric_para.paragraph_format.space_after = Pt(3) doc.add_paragraph() # Spacing # Financial Highlights Table highlights_heading = doc.add_heading('Financial Highlights', 1) highlights_heading.runs[0].font.color.rgb = RGBColor(31, 71, 136) highlights_heading.paragraph_format.space_after = Pt(6) table = doc.add_table(rows=1, cols=3) table.style = 'Light Grid Accent 1' # Header row header_cells = table.rows[0].cells header_cells[0].text = 'Metric' header_cells[1].text = 'Current' header_cells[2].text = 'Prior Year' for cell in header_cells: cell.paragraphs[0].runs[0].font.bold = True cell.paragraphs[0].runs[0].font.color.rgb = RGBColor(255, 255, 255) cell.paragraphs[0].runs[0].font.size = Pt(9) # Data rows table_data = [ ("Revenue", f"${financial_data['revenue']:,.0f}M", f"${financial_data.get('prior_revenue', financial_data['revenue'] * 0.95):,.0f}M"), ("Net Income", f"${financial_data['net_income']:,.0f}M", f"${financial_data.get('prior_net_income', financial_data['net_income'] * 0.92):,.0f}M"), ("Total Assets", f"${financial_data.get('total_assets', financial_data['revenue'] * 2.5):,.0f}M", f"${financial_data.get('prior_total_assets', financial_data.get('total_assets', financial_data['revenue'] * 2.5) * 0.96):,.0f}M"), ("Shareholders' Equity", f"${financial_data.get('equity', financial_data['revenue'] * 1.2):,.0f}M", f"${financial_data.get('prior_equity', financial_data.get('equity', financial_data['revenue'] * 1.2) * 0.94):,.0f}M"), ] for row_data in table_data: row_cells = table.add_row().cells for i, cell_text in enumerate(row_data): row_cells[i].text = cell_text row_cells[i].paragraphs[0].runs[0].font.size = Pt(9) # PAGE 2: Analysis & Recommendation doc.add_page_break() # Financial Analysis analysis_heading = doc.add_heading('Financial Analysis', 1) analysis_heading.runs[0].font.color.rgb = RGBColor(31, 71, 136) analysis_heading.paragraph_format.space_after = Pt(6) # Revenue Analysis revenue_para = doc.add_paragraph() revenue_run = revenue_para.add_run("Revenue Performance: ") revenue_run.font.bold = True revenue_run.font.size = Pt(10) revenue_para.add_run( f"The company reported revenue of ${financial_data['revenue']:,.0f}M, " f"{'representing strong growth' if analysis['revenue_growth'] > 10 else 'showing moderate growth' if analysis['revenue_growth'] > 0 else 'reflecting a decline'} " f"of {abs(analysis['revenue_growth']):.1f}% compared to the prior year. " f"This performance {'exceeds' if analysis['revenue_growth'] > 10 else 'meets' if analysis['revenue_growth'] > 0 else 'falls short of'} " f"industry expectations." ) revenue_para.paragraph_format.space_after = Pt(6) # Profitability Analysis profit_para = doc.add_paragraph() profit_run = profit_para.add_run("Profitability: ") profit_run.font.bold = True profit_run.font.size = Pt(10) profit_para.add_run( f"Net income of ${financial_data['net_income']:,.0f}M results in a profit margin of {analysis['profit_margin']:.1%}, " f"which is {'above' if analysis['profit_margin'] > 0.15 else 'in line with' if analysis['profit_margin'] > 0.05 else 'below'} " f"industry standards. The company demonstrates {'strong' if analysis['roe'] > 0.15 else 'adequate' if analysis['roe'] > 0.10 else 'weak'} " f"return on equity of {analysis['roe']:.1%}, indicating {'efficient' if analysis['roe'] > 0.15 else 'moderate'} capital utilization." ) profit_para.paragraph_format.space_after = Pt(6) # Financial Position position_para = doc.add_paragraph() position_run = position_para.add_run("Financial Position: ") position_run.font.bold = True position_run.font.size = Pt(10) position_para.add_run( f"The company maintains a current ratio of {analysis['current_ratio']:.2f}, " f"indicating {'strong' if analysis['current_ratio'] > 2.0 else 'adequate' if analysis['current_ratio'] > 1.0 else 'limited'} " f"liquidity. Debt-to-equity ratio of {analysis['debt_to_equity']:.2f} suggests " f"{'conservative' if analysis['debt_to_equity'] < 0.5 else 'moderate' if analysis['debt_to_equity'] < 1.0 else 'aggressive'} " f"leverage positioning." ) position_para.paragraph_format.space_after = Pt(6) # Investment Recommendation rec_heading = doc.add_heading('Investment Recommendation', 1) rec_heading.runs[0].font.color.rgb = RGBColor(31, 71, 136) rec_heading.paragraph_format.space_after = Pt(6) recommendation_text = ( f"Based on our comprehensive financial analysis, we recommend a {analysis['recommendation']} position " f"on {ticker}. " f"{'The company demonstrates strong fundamentals with' if analysis['recommendation'] == 'BUY' else 'The company shows mixed signals with' if analysis['recommendation'] == 'HOLD' else 'The company faces challenges with'} " f"{'robust' if analysis['recommendation'] == 'BUY' else 'moderate' if analysis['recommendation'] == 'HOLD' else 'weak'} " f"financial metrics and {'favorable' if analysis['recommendation'] == 'BUY' else 'neutral' if analysis['recommendation'] == 'HOLD' else 'concerning'} " f"growth prospects. " f"{'Key strengths include' if analysis['recommendation'] == 'BUY' else 'Areas of concern include' if analysis['recommendation'] == 'SELL' else 'Notable factors include'} " f"{'strong profitability, healthy balance sheet, and positive revenue trends' if analysis['recommendation'] == 'BUY' else 'declining margins, high leverage, and negative growth' if analysis['recommendation'] == 'SELL' else 'mixed performance indicators and uncertain outlook'}." ) doc.add_paragraph(recommendation_text) doc.add_paragraph() # Spacing # Risk Factors risk_heading = doc.add_heading('Key Risk Factors', 1) risk_heading.runs[0].font.color.rgb = RGBColor(31, 71, 136) risk_heading.paragraph_format.space_after = Pt(6) risk_factors = [ "Market volatility and economic uncertainty may impact performance", "Competitive pressures in the industry could affect market share", "Regulatory changes may require operational adjustments", "Currency fluctuations could impact international operations" ] for risk in risk_factors: risk_para = doc.add_paragraph(risk, style='List Bullet') risk_para.runs[0].font.size = Pt(9) risk_para.paragraph_format.space_after = Pt(2) # Footer doc.add_paragraph() # Spacing footer = doc.add_paragraph() footer.alignment = WD_ALIGN_PARAGRAPH.CENTER footer_run = footer.add_run( f'Investment Memo - {ticker} | Generated: {datetime.now().strftime("%B %d, %Y")} | ' 'FCCS MCP Agentic Server' ) footer_run.font.size = Pt(8) footer_run.font.color.rgb = RGBColor(128, 128, 128) footer_run.font.italic = True # Save document filename = f"Investment_Memo_{ticker}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.docx" filepath = Path(filename).absolute() doc.save(filename) return { "status": "success", "data": { "file_path": str(filepath), "filename": filename, "ticker": ticker, "recommendation": analysis['recommendation'], "message": f"2-page investment memo generated successfully for {ticker}", "note": f"Document includes financial analysis with {analysis['recommendation']} recommendation" } } except Exception as e: return { "status": "error", "error": f"Failed to generate investment memo: {str(e)}" }
- fccs_agent/tools/memo.py:577-590 (schema)Input schema definition for the generate_investment_memo tool, specifying the required 'ticker' parameter.{ "name": "generate_investment_memo", "description": "Generate a 2-page investment memo (Word doc) with financial analysis / Gerar memorando de investimento", "inputSchema": { "type": "object", "properties": { "ticker": { "type": "string", "description": "Company ticker symbol (e.g., 'TECH')", }, }, "required": ["ticker"], }, },
- fccs_agent/agent.py:138-185 (registration)Registration of the generate_investment_memo tool handler in the central TOOL_HANDLERS dictionary, mapping the tool name to memo.generate_investment_memo function.TOOL_HANDLERS = { # Application "get_application_info": application.get_application_info, "get_rest_api_version": application.get_rest_api_version, # Jobs "list_jobs": jobs.list_jobs, "get_job_status": jobs.get_job_status, "run_business_rule": jobs.run_business_rule, "run_data_rule": jobs.run_data_rule, # Dimensions "get_dimensions": dimensions.get_dimensions, "get_members": dimensions.get_members, "get_dimension_hierarchy": dimensions.get_dimension_hierarchy, # Journals "get_journals": journals.get_journals, "get_journal_details": journals.get_journal_details, "perform_journal_action": journals.perform_journal_action, "update_journal_period": journals.update_journal_period, "export_journals": journals.export_journals, "import_journals": journals.import_journals, # Data "export_data_slice": data.export_data_slice, "smart_retrieve": data.smart_retrieve, "smart_retrieve_consolidation_breakdown": data.smart_retrieve_consolidation_breakdown, "smart_retrieve_with_movement": data.smart_retrieve_with_movement, "copy_data": data.copy_data, "clear_data": data.clear_data, # Reports "generate_report": reports.generate_report, "get_report_job_status": reports.get_report_job_status, "generate_report_script": reports.generate_report_script, # Consolidation "export_consolidation_rulesets": consolidation.export_consolidation_rulesets, "import_consolidation_rulesets": consolidation.import_consolidation_rulesets, "validate_metadata": consolidation.validate_metadata, "generate_intercompany_matching_report": consolidation.generate_intercompany_matching_report, "import_supplementation_data": consolidation.import_supplementation_data, "deploy_form_template": consolidation.deploy_form_template, "generate_consolidation_process_report": consolidation.generate_consolidation_process_report, # Memo "generate_system_pitch": memo.generate_system_pitch, "generate_investment_memo": memo.generate_investment_memo, # Feedback "submit_feedback": feedback.submit_feedback, "get_recent_executions": feedback.get_recent_executions, # Local Data "query_local_metadata": local_data.query_local_metadata, }
- fccs_agent/tools/memo.py:506-565 (helper)Helper function that performs financial analysis on data, calculating key ratios (profit margin, ROE, growth, liquidity, leverage) and generates BUY/HOLD/SELL recommendation.def _analyze_financials(data: dict[str, Any], ticker: str) -> dict[str, Any]: """Perform financial analysis and generate metrics.""" revenue = data.get("revenue", 0) net_income = data.get("net_income", 0) total_assets = data.get("total_assets", revenue * 2.5) equity = data.get("equity", revenue * 1.2) # Calculate ratios profit_margin = net_income / revenue if revenue > 0 else 0 roe = net_income / equity if equity > 0 else 0 # Generate growth (mock) import hashlib ticker_hash = int(hashlib.md5(ticker.encode()).hexdigest()[:8], 16) revenue_growth = -5 + (ticker_hash % 30) # -5% to +25% # Calculate other metrics current_assets = total_assets * 0.3 # Assume 30% current current_liabilities = total_assets * 0.15 # Assume 15% current liabilities current_ratio = current_assets / current_liabilities if current_liabilities > 0 else 2.0 total_debt = total_assets - equity debt_to_equity = total_debt / equity if equity > 0 else 0.5 # Determine recommendation score = 0 if profit_margin > 0.15: score += 2 elif profit_margin > 0.05: score += 1 if revenue_growth > 10: score += 2 elif revenue_growth > 0: score += 1 if roe > 0.15: score += 2 elif roe > 0.10: score += 1 if current_ratio > 1.5 and debt_to_equity < 1.0: score += 1 if score >= 5: recommendation = "BUY" elif score >= 3: recommendation = "HOLD" else: recommendation = "SELL" return { "profit_margin": profit_margin, "revenue_growth": revenue_growth, "roe": roe, "current_ratio": current_ratio, "debt_to_equity": debt_to_equity, "recommendation": recommendation }
- fccs_agent/tools/memo.py:424-504 (helper)Helper function to fetch real financial data from FCCS (revenue/net income slices) or generate consistent mock data based on ticker hash.async def _get_financial_data(ticker: str) -> dict[str, Any]: """Get financial data from FCCS or generate realistic mock data.""" # Try to use FCCS client if available global _client, _app_name if _client and _app_name: # Try to get real data from FCCS try: # Build grid definition for revenue revenue_grid = { "suppressMissingBlocks": True, "pov": { "members": [ ["FY25"], ["Actual"], ["FCCS_YTD"], ["FCCS_Entity Total"], ["FCCS_Intercompany Top"], ["FCCS_Total Data Source"], ["FCCS_Mvmts_Total"], [f"FCCS_{ticker}"], ["Entity Currency"], ["Total Custom 3"], ["Total Region"], ["Total Venturi Entity"], ["Total Custom 4"] ] }, "columns": [{"members": [["Dec"]]}], "rows": [{"members": [["FCCS_Revenue"]]}] } net_income_grid = { "suppressMissingBlocks": True, "pov": { "members": [ ["FY25"], ["Actual"], ["FCCS_YTD"], ["FCCS_Entity Total"], ["FCCS_Intercompany Top"], ["FCCS_Total Data Source"], ["FCCS_Mvmts_Total"], [f"FCCS_{ticker}"], ["Entity Currency"], ["Total Custom 3"], ["Total Region"], ["Total Venturi Entity"], ["Total Custom 4"] ] }, "columns": [{"members": [["Dec"]]}], "rows": [{"members": [["FCCS_Net Income"]]}] } revenue_result = await _client.export_data_slice(_app_name, "Consol", revenue_grid) net_income_result = await _client.export_data_slice(_app_name, "Consol", net_income_grid) if revenue_result and net_income_result: rev_rows = revenue_result.get("rows", []) ni_rows = net_income_result.get("rows", []) if rev_rows and ni_rows: rev_data = rev_rows[0].get("data", [0])[0] or 0 ni_data = ni_rows[0].get("data", [0])[0] or 0 if rev_data and ni_data: return { "company_name": f"{ticker} Corporation", "revenue": float(rev_data) / 1_000_000, # Convert to millions "net_income": float(ni_data) / 1_000_000, "source": "FCCS" } except Exception: pass # Fall through to mock data # Generate realistic mock financial data based on ticker import hashlib # Use ticker to generate consistent mock data ticker_hash = int(hashlib.md5(ticker.encode()).hexdigest()[:8], 16) # Generate realistic financial metrics base_revenue = 500 + (ticker_hash % 2000) # $500M - $2.5B revenue = base_revenue profit_margin = 0.08 + (ticker_hash % 20) / 100 # 8% - 28% net_income = revenue * profit_margin return { "company_name": f"{ticker} Corporation", "revenue": revenue, "net_income": net_income, "total_assets": revenue * (2.0 + (ticker_hash % 10) / 10), # 2x - 3x revenue "equity": revenue * (1.0 + (ticker_hash % 8) / 10), # 1x - 1.8x revenue "source": "Mock" }