diff --git a/backend/src/ai/LocalAIApi.js b/backend/src/ai/LocalAIApi.js index fd571ae..a4df0f4 100644 --- a/backend/src/ai/LocalAIApi.js +++ b/backend/src/ai/LocalAIApi.js @@ -154,6 +154,7 @@ async function awaitResponse(aiRequestId, options = {}) { const interval = Math.max(Number(options.interval ?? 5), 1); const deadline = Date.now() + Math.max(timeout, interval) * 1000; + /* eslint-disable no-constant-condition */ while (true) { const statusResp = await fetchStatus(aiRequestId, { headers: options.headers, @@ -306,7 +307,7 @@ function buildUrl(pathValue, baseUrl) { } function resolveStatusPath(aiRequestId, cfg) { - const basePath = (cfg.responsesPath || "").replace(/\/+$/, ""); + const basePath = (cfg.responsesPath || "").replace(new RegExp("/+"), ""); if (!basePath) { return `/ai-request/${encodeURIComponent(String(aiRequestId))}/status`; } diff --git a/backend/src/auth/auth.js b/backend/src/auth/auth.js index 251c149..c166d68 100644 --- a/backend/src/auth/auth.js +++ b/backend/src/auth/auth.js @@ -56,7 +56,7 @@ passport.use(new MicrosoftStrategy({ )); function socialStrategy(email, profile, provider, done) { - db.users.findOrCreate({where: {email, provider}}).then(([user, created]) => { + db.users.findOrCreate({where: {email, provider}}).then(([user]) => { const body = { id: user.id, email: user.email, @@ -65,4 +65,4 @@ function socialStrategy(email, profile, provider, done) { const token = helpers.jwtSign({user: body}); return done(null, {token}); }); -} +} \ No newline at end of file diff --git a/backend/src/db/api/alerts.js b/backend/src/db/api/alerts.js index 82605ac..24b57d4 100644 --- a/backend/src/db/api/alerts.js +++ b/backend/src/db/api/alerts.js @@ -1,7 +1,4 @@ - const db = require('../models'); -const FileDBApi = require('./file'); -const crypto = require('crypto'); const Utils = require('../utils'); @@ -156,7 +153,6 @@ module.exports = class AlertsDBApi { static async update(id, data, options) { const currentUser = (options && options.currentUser) || {id: null}; const transaction = (options && options.transaction) || undefined; - const globalAccess = currentUser.app_role?.globalAccess; const alerts = await db.alerts.findByPk(id, {}, {transaction}); @@ -362,9 +358,6 @@ module.exports = class AlertsDBApi { offset = currentPage * limit; - const orderBy = null; - - const transaction = (options && options.transaction) || undefined; let include = [ @@ -659,7 +652,7 @@ module.exports = class AlertsDBApi { where, limit: limit ? Number(limit) : undefined, offset: offset ? Number(offset) : undefined, - orderBy: [['title_text', 'ASC']], + order: [['title_text', 'ASC']], }); return records.map((record) => ({ @@ -669,5 +662,4 @@ module.exports = class AlertsDBApi { } -}; - +}; \ No newline at end of file diff --git a/backend/src/db/migrations/1772302510315.js b/backend/src/db/migrations/1772302510315.js new file mode 100644 index 0000000..2960adf --- /dev/null +++ b/backend/src/db/migrations/1772302510315.js @@ -0,0 +1,79 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @returns {Promise} + */ + async up(queryInterface) { + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.createTable('funding_opportunities', { + id: { + type: queryInterface.sequelize.Sequelize.DataTypes.UUID, + defaultValue: queryInterface.sequelize.Sequelize.DataTypes.UUIDV4, + primaryKey: true, + }, + name: { + type: queryInterface.sequelize.Sequelize.DataTypes.TEXT, + }, + focus: { + type: queryInterface.sequelize.Sequelize.DataTypes.TEXT, + }, + stage: { + type: queryInterface.sequelize.Sequelize.DataTypes.TEXT, + }, + contact_type: { + type: queryInterface.sequelize.Sequelize.DataTypes.TEXT, + }, + description: { + type: queryInterface.sequelize.Sequelize.DataTypes.TEXT, + }, + website: { + type: queryInterface.sequelize.Sequelize.DataTypes.TEXT, + }, + amount_min: { + type: queryInterface.sequelize.Sequelize.DataTypes.DECIMAL, + }, + amount_max: { + type: queryInterface.sequelize.Sequelize.DataTypes.DECIMAL, + }, + createdById: { + type: queryInterface.sequelize.Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + updatedById: { + type: queryInterface.sequelize.Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + createdAt: { type: queryInterface.sequelize.Sequelize.DataTypes.DATE }, + updatedAt: { type: queryInterface.sequelize.Sequelize.DataTypes.DATE }, + deletedAt: { type: queryInterface.sequelize.Sequelize.DataTypes.DATE }, + importHash: { + type: queryInterface.sequelize.Sequelize.DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, { transaction }); + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + + async down(queryInterface) { + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.dropTable('funding_opportunities', { transaction }); + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + } +}; \ No newline at end of file diff --git a/backend/src/db/models/funding_opportunities.js b/backend/src/db/models/funding_opportunities.js new file mode 100644 index 0000000..d5e47b9 --- /dev/null +++ b/backend/src/db/models/funding_opportunities.js @@ -0,0 +1,58 @@ +module.exports = function(sequelize, DataTypes) { + const funding_opportunities = sequelize.define( + 'funding_opportunities', + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + name: { + type: DataTypes.TEXT, + }, + focus: { + type: DataTypes.TEXT, + }, + stage: { + type: DataTypes.TEXT, + }, + contact_type: { + type: DataTypes.TEXT, + }, + description: { + type: DataTypes.TEXT, + }, + website: { + type: DataTypes.TEXT, + }, + amount_min: { + type: DataTypes.DECIMAL, + }, + amount_max: { + type: DataTypes.DECIMAL, + }, + importHash: { + type: DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { + timestamps: true, + paranoid: true, + freezeTableName: true, + }, + ); + + funding_opportunities.associate = (db) => { + db.funding_opportunities.belongsTo(db.users, { + as: 'createdBy', + }); + + db.funding_opportunities.belongsTo(db.users, { + as: 'updatedBy', + }); + }; + + return funding_opportunities; +}; diff --git a/backend/src/db/seeders/20260228000000-funding-opportunities.js b/backend/src/db/seeders/20260228000000-funding-opportunities.js new file mode 100644 index 0000000..3031c4f --- /dev/null +++ b/backend/src/db/seeders/20260228000000-funding-opportunities.js @@ -0,0 +1,144 @@ +'use strict'; + +module.exports = { + up: async (queryInterface) => { + const opportunities = [ + { + id: '10000000-0000-0000-0000-000000000001', + name: 'Sequoia Capital', + focus: 'Enterprise, AI, Consumer', + stage: 'Seed to IPO', + contact_type: 'VC', + description: 'Global venture capital firm helping daring founders build legendary companies.', + website: 'https://www.sequoiacap.com', + amount_min: 500000, + amount_max: 100000000, + createdAt: new Date(), + updatedAt: new Date(), + }, + { + id: '10000000-0000-0000-0000-000000000002', + name: 'Andreessen Horowitz (a16z)', + focus: 'Fintech, Crypto, AI, Games', + stage: 'All stages', + contact_type: 'VC', + description: 'Venture capital firm that backs bold entrepreneurs building the future through technology.', + website: 'https://a16z.com', + amount_min: 1000000, + amount_max: 500000000, + createdAt: new Date(), + updatedAt: new Date(), + }, + { + id: '10000000-0000-0000-0000-000000000003', + name: 'Index Ventures', + focus: 'Global SaaS, Commerce', + stage: 'Early to Growth', + contact_type: 'VC', + description: 'Partnering with exceptional entrepreneurs from seed to IPO.', + website: 'https://www.indexventures.com', + amount_min: 2000000, + amount_max: 50000000, + createdAt: new Date(), + updatedAt: new Date(), + }, + { + id: '10000000-0000-0000-0000-000000000004', + name: 'Naval Ravikant', + focus: 'Deep Tech, AI, Crypto', + stage: 'Seed', + contact_type: 'Angel', + description: 'Founding CEO of AngelList, investor in 200+ companies including Uber, Twitter, and Wish.', + website: 'https://twitter.com/naval', + amount_min: 50000, + amount_max: 500000, + createdAt: new Date(), + updatedAt: new Date(), + }, + { + id: '10000000-0000-0000-0000-000000000005', + name: 'Tiger Global', + focus: 'Hyper-growth, Consumer, SaaS', + stage: 'Growth', + contact_type: 'Institutional', + description: 'Investment firm focused on public and private companies in the global Internet, software, consumer, and financial technology industries.', + website: 'https://www.tigerglobal.com', + amount_min: 10000000, + amount_max: 1000000000, + createdAt: new Date(), + updatedAt: new Date(), + }, + { + id: '10000000-0000-0000-0000-000000000006', + name: 'Goldman Sachs Principal', + focus: 'Late Stage, Fintech, Infrastructure', + stage: 'Pre-IPO', + contact_type: 'Bank/Institutional', + description: 'Principal investment arm of Goldman Sachs.', + website: 'https://www.goldmansachs.com', + amount_min: 50000000, + amount_max: 500000000, + createdAt: new Date(), + updatedAt: new Date(), + }, + { + id: '10000000-0000-0000-0000-000000000007', + name: 'First Round Capital', + focus: 'Tech, SaaS, Mobile', + stage: 'Seed', + contact_type: 'VC', + description: 'Seed-stage venture capital firm focused on building community.', + website: 'https://firstround.com', + amount_min: 250000, + amount_max: 2000000, + createdAt: new Date(), + updatedAt: new Date(), + }, + { + id: '10000000-0000-0000-0000-000000000008', + name: 'SV Angel', + focus: 'Consumer, Enterprise', + stage: 'Seed', + contact_type: 'Angel Group', + description: 'Early-stage venture capital firm based in San Francisco.', + website: 'https://svangel.com', + amount_min: 50000, + amount_max: 1000000, + createdAt: new Date(), + updatedAt: new Date(), + }, + { + id: '10000000-0000-0000-0000-000000000009', + name: 'Benchmark', + focus: 'Consumer, Mobile, SaaS', + stage: 'Early', + contact_type: 'VC', + description: 'Venture capital firm that focuses on early-stage startups.', + website: 'https://www.benchmark.com', + amount_min: 1000000, + amount_max: 20000000, + createdAt: new Date(), + updatedAt: new Date(), + }, + { + id: '10000000-0000-0000-0000-000000000010', + name: 'General Catalyst', + focus: 'Fintech, Healthcare, AI', + stage: 'Seed to Growth', + contact_type: 'VC', + description: 'Venture capital firm that invests in powerful, positive change.', + website: 'https://www.generalcatalyst.com', + amount_min: 1000000, + amount_max: 100000000, + createdAt: new Date(), + updatedAt: new Date(), + } + ]; + + await queryInterface.bulkInsert('funding_opportunities', opportunities, {}); + }, + + down: async (queryInterface) => { + await queryInterface.bulkDelete('funding_opportunities', null, {}); + } +}; \ No newline at end of file diff --git a/backend/src/index.js b/backend/src/index.js index ce6cf48..9219a3e 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -1,4 +1,3 @@ - const express = require('express'); const cors = require('cors'); const app = express(); @@ -16,6 +15,7 @@ const fileRoutes = require('./routes/file'); const searchRoutes = require('./routes/search'); const sqlRoutes = require('./routes/sql'); const pexelsRoutes = require('./routes/pexels'); +const intelligenceRoutes = require('./routes/intelligence'); const organizationForAuthRoutes = require('./routes/organizationLogin'); @@ -197,6 +197,12 @@ app.use( passport.authenticate('jwt', { session: false }), sqlRoutes); +app.use( + '/api/intelligence', + passport.authenticate('jwt', { session: false }), + intelligenceRoutes, +); + app.use( '/api/org-for-auth', organizationForAuthRoutes, @@ -226,4 +232,4 @@ db.sequelize.sync().then(function () { }); }); -module.exports = app; +module.exports = app; \ No newline at end of file diff --git a/backend/src/routes/intelligence.js b/backend/src/routes/intelligence.js new file mode 100644 index 0000000..ebc285d --- /dev/null +++ b/backend/src/routes/intelligence.js @@ -0,0 +1,54 @@ +const express = require('express'); +const router = express.Router(); +const IntelligenceService = require('../services/intelligence'); +const Helpers = require('../helpers'); + +router.get('/', Helpers.wrapAsync(async (req, res) => { + const organizationsId = req.currentUser.organizationsId; + const result = await IntelligenceService.getIntegratedAnalysis(organizationsId); + res.status(200).send(result); +})); + +router.get('/modeling', Helpers.wrapAsync(async (req, res) => { + const organizationsId = req.currentUser.organizationsId; + const result = await IntelligenceService.getFinancialModeling(organizationsId); + res.status(200).send(result); +})); + +router.get('/opportunities', Helpers.wrapAsync(async (req, res) => { + const organizationsId = req.currentUser.organizationsId; + const result = await IntelligenceService.getMarketOpportunities(organizationsId); + res.status(200).send(result); +})); + +router.get('/stewardship', Helpers.wrapAsync(async (req, res) => { + const organizationsId = req.currentUser.organizationsId; + const result = await IntelligenceService.getFinancialStewardship(organizationsId); + res.status(200).send(result); +})); + +router.get('/risk', Helpers.wrapAsync(async (req, res) => { + const organizationsId = req.currentUser.organizationsId; + const result = await IntelligenceService.getCashFlowRisk(organizationsId); + res.status(200).send(result); +})); + +router.get('/capital', Helpers.wrapAsync(async (req, res) => { + const organizationsId = req.currentUser.organizationsId; + const result = await IntelligenceService.getCapitalStrategy(organizationsId); + res.status(200).send(result); +})); + +router.get('/leadership', Helpers.wrapAsync(async (req, res) => { + const organizationsId = req.currentUser.organizationsId; + const result = await IntelligenceService.getStrategicLeadership(organizationsId); + res.status(200).send(result); +})); + +router.get('/esg', Helpers.wrapAsync(async (req, res) => { + const organizationsId = req.currentUser.organizationsId; + const result = await IntelligenceService.getESGStrategy(organizationsId); + res.status(200).send(result); +})); + +module.exports = router; \ No newline at end of file diff --git a/backend/src/services/auth.js b/backend/src/services/auth.js index bcc3411..53fd1ea 100644 --- a/backend/src/services/auth.js +++ b/backend/src/services/auth.js @@ -1,3 +1,4 @@ +const db = require("../db/models"); const UsersDBApi = require('../db/api/users'); const ValidationError = require('./notifications/errors/validation'); const ForbiddenError = require('./notifications/errors/forbidden'); diff --git a/backend/src/services/intelligence.js b/backend/src/services/intelligence.js new file mode 100644 index 0000000..fc82a5b --- /dev/null +++ b/backend/src/services/intelligence.js @@ -0,0 +1,354 @@ +const db = require('../db/models'); +const { LocalAIApi } = require('../ai/LocalAIApi'); + +module.exports = class IntelligenceService { + static async getExternalIntelligence() { + // Fetch real funding opportunities from the database + const funding_opportunities = await db.funding_opportunities.findAll(); + + // In a real app, this would fetch from a news API or a market data provider. + return { + trends: [ + { + title: "Agentic AI in Finance", + content: "AI has moved from assistance to transactional authority, driving autonomous decision-making in risk modeling and fraud detection.", + impact: "High", + date: "2026-02-28" + }, + { + title: "Consolidation in Fintech", + content: "Market maturation is leading to traditional banks acquiring challengers. Profitability is prioritized over 'growth at all costs'.", + impact: "Medium", + date: "2026-02-25" + }, + { + title: "Real-Time Payments Global Standard", + content: "24/7 settlement and cross-border efficiency are now industry baselines.", + impact: "Medium", + date: "2026-02-20" + } + ], + benchmarks: { + saas: { + gross_margin_target: 75, + net_dollar_retention_median: 101, + net_dollar_retention_top: 111, + rule_of_40_target: 40, + valuation_multiple_median: 6.5, + valuation_multiple_top: 12.0 + }, + fintech: { + cac_payback_months_target: 12, + burn_multiplier_target: 1.5, + ltv_cac_target: 3.5 + } + }, + market_caps: [ + { segment: "AI-Fintech", multiple_trend: "+15%", opportunity_score: 9.2 }, + { segment: "Traditional SaaS", multiple_trend: "-5%", opportunity_score: 6.5 }, + { segment: "Infrastructure/API", multiple_trend: "+8%", opportunity_score: 8.8 } + ], + funding_partners: funding_opportunities, + regulatory_updates: [ + { title: "SEC AI Disclosure Rules", content: "New requirements for disclosing AI-driven financial models.", date: "2026-01-15" }, + { title: "Global ESG Reporting Standard", content: "Mandatory ESG reporting for companies over $5M ARR.", date: "2026-02-01" } + ] + }; + } + + static async getProprietaryData(organizationsId) { + if (!organizationsId) return {}; + + const metrics = await db.metrics_snapshots.findAll({ + where: { organizationsId }, + order: [['as_of_at', 'DESC'], ['createdAt', 'DESC']], + limit: 12 + }); + + const latest = metrics[0] || {}; + + return { + mrr: parseFloat(latest.mrr || 0), + arr: parseFloat(latest.arr || 0), + margin: parseFloat(latest.gross_margin_percent || 0), + burn: parseFloat(latest.net_burn_monthly || 0), + runway: parseFloat(latest.runway_months || 0), + history: metrics.map(m => ({ + date: m.as_of_at || m.createdAt, + mrr: parseFloat(m.mrr || 0), + burn: parseFloat(m.net_burn_monthly || 0), + margin: parseFloat(m.gross_margin_percent || 0) + })) + }; + } + + static async getFinancialStewardship(organizationsId) { + const proprietary = await this.getProprietaryData(organizationsId); + const external = await this.getExternalIntelligence(); + + const prompt = ` + Act as a Virtual CFO specializing in Financial Stewardship and Compliance. + + Company State: + - ARR: $${proprietary.arr} + - Margin: ${proprietary.margin}% + + Regulatory Environment (2026): + ${JSON.stringify(external.regulatory_updates)} + + Tasks: + 1. **Audit Readiness**: Based on the $${proprietary.arr} ARR, suggest an audit schedule and key control areas (e.g., Revenue Recognition under ASC 606). + 2. **Closing the Books**: Provide a checklist for a 'Continuous Close' process to ensure real-time financial reporting accuracy. + 3. **Regulatory Compliance**: How do the new ${external.regulatory_updates[0].title} affect this company's AI-driven insights? + 4. **Tax Strategy**: Suggest R&D tax credit opportunities and international transfer pricing considerations if scaling globally. + + Format as a professional CFO report for the Board of Directors. + IMPORTANT: This analysis is strictly for organizationsId ${organizationsId}. Data isolation is paramount. + `; + + const aiResponse = await LocalAIApi.createResponse({ + input: [{ role: 'user', content: prompt }] + }); + + return { + stewardship: LocalAIApi.extractText(aiResponse) || "Stewardship analysis unavailable." + }; + } + + static async getCashFlowRisk(organizationsId) { + const proprietary = await this.getProprietaryData(organizationsId); + + const prompt = ` + Act as a Risk Management & Cash Flow Expert (CFO role). + + Company Financials: + - Monthly Burn: $${proprietary.burn} + - Runway: ${proprietary.runway} months + - Margin: ${proprietary.margin}% + + Tasks: + 1. **Liquidity Analysis**: Assess the safety of the current ${proprietary.runway}-month runway. When is the "Drop Dead Date"? + 2. **Risk Identification**: Identify top 3 financial risks (e.g., Customer Concentration, Currency Risk, or Cost Overruns). + 3. **Mitigation Strategy**: Suggest hedging or cost-cutting measures to extend runway by 25% without impacting core growth. + 4. **Profitability Path**: Build a high-level roadmap to "Default Alive" status. + + Be precise, data-driven, and conservative. + IMPORTANT: This analysis is strictly for organizationsId ${organizationsId}. Data isolation is paramount. + `; + + const aiResponse = await LocalAIApi.createResponse({ + input: [{ role: 'user', content: prompt }] + }); + + return { + risk: LocalAIApi.extractText(aiResponse) || "Risk analysis unavailable." + }; + } + + static async getCapitalStrategy(organizationsId) { + const proprietary = await this.getProprietaryData(organizationsId); + const external = await this.getExternalIntelligence(); + + const prompt = ` + Act as a CFO & Head of Capital Markets. Your goal is to structure capital and connect with funding. + + Company State: + - ARR: $${proprietary.arr} + - Runway: ${proprietary.runway} months + + Available Funding Partners (2026): + ${JSON.stringify(external.funding_partners)} + + Tasks: + 1. **Funding Matchmaker**: Based on the company's profile, identify the top 3 best-fit partners from the list above. Explain WHY they are a match. + 2. **Fundraising Strategy**: Structure a "Series A" or "Series B" (as appropriate) pitch focus. Should we lead with "Growth" or "Efficiency"? + 3. **Capital Structure**: Advise on 'Venture Debt' vs 'Equity' mix to minimize dilution. + 4. **Investor Relations**: Suggest a communication cadence and key KPIs to share with current stakeholders to build trust. + + Provide actionable, high-conviction advice. + IMPORTANT: This analysis is strictly for organizationsId ${organizationsId}. Data isolation is paramount. + `; + + const aiResponse = await LocalAIApi.createResponse({ + input: [{ role: 'user', content: prompt }] + }); + + return { + capital: LocalAIApi.extractText(aiResponse) || "Capital strategy unavailable." + }; + } + + static async getStrategicLeadership(organizationsId) { + const proprietary = await this.getProprietaryData(organizationsId); + const external = await this.getExternalIntelligence(); + + const prompt = ` + Act as a Strategic CFO & Advisor to the Board. + + Company Context: + - ARR: $${proprietary.arr} + - Margin: ${proprietary.margin}% + + Market Trends: + ${JSON.stringify(external.trends)} + + Tasks: + 1. **Digital Transformation**: How should the company leverage "Agentic AI" (Trend 1) to reduce OpEx and drive efficiency? + 2. **Business Growth Strategy**: Identify "Blue Ocean" opportunities in the fintech/SaaS space for 2026. + 3. **C-Suite Advisory**: Draft a 1-page memo for the CEO on "Navigating the 2026 Macro Environment." + + Be visionary, bold, and strategic. + IMPORTANT: This analysis is strictly for organizationsId ${organizationsId}. Data isolation is paramount. + `; + + const aiResponse = await LocalAIApi.createResponse({ + input: [{ role: 'user', content: prompt }] + }); + + return { + leadership: LocalAIApi.extractText(aiResponse) || "Strategic leadership analysis unavailable." + }; + } + + static async getESGStrategy(organizationsId) { + const proprietary = await this.getProprietaryData(organizationsId); + const external = await this.getExternalIntelligence(); + + const prompt = ` + Act as a CFO specializing in ESG (Environmental, Social, Governance) Strategy. + + Regulatory Context: + ${external.regulatory_updates[1].title}: ${external.regulatory_updates[1].content} + + Company Metrics: + - ARR: $${proprietary.arr} + + Tasks: + 1. **ESG Framework**: Propose a realistic ESG framework that adds value to valuation rather than just being a cost. + 2. **Impact Reporting**: How can we quantify our "Social" impact for potential impact investors? + 3. **Governance Audit**: Suggest improvements to board-level oversight for AI ethics and data privacy. + 4. **Carbon Footprint Optimization**: For a SaaS company, suggest 3 high-impact areas to reduce carbon footprint. + + Format as a professional CFO strategy paper. + IMPORTANT: This analysis is strictly for organizationsId ${organizationsId}. Data isolation is paramount. + `; + + const aiResponse = await LocalAIApi.createResponse({ + input: [{ role: 'user', content: prompt }] + }); + + return { + esg: LocalAIApi.extractText(aiResponse) || "ESG strategy unavailable." + }; + } + + static async getFinancialModeling(organizationsId) { + const proprietary = await this.getProprietaryData(organizationsId); + const external = await this.getExternalIntelligence(); + + const prompt = ` + Act as an elite Tier-1 Investment Banker. Build a sophisticated financial model for a company with the following metrics: + + Proprietary Financials: + - ARR: $${proprietary.arr} + - Gross Margin: ${proprietary.margin}% + - Net Burn: $${proprietary.burn}/mo + - Historical Data Points: ${proprietary.history.length} months + + Market Context (2026): + - Median SaaS Multiples: ${external.benchmarks.saas.valuation_multiple_median}x + - Top Decile Multiples: ${external.benchmarks.saas.valuation_multiple_top}x + + Deliver a "Modeling Pack" including: + 1. **Valuation Analysis**: Estimate Current Enterprise Value (EV) using Revenue Multiples (Base and Optimistic cases). + 2. **3-Year Projection**: Forecast ARR and Burn based on current trends. + 3. **DCF Insights**: Discuss required WACC and Terminal Value assumptions for an IPO exit in 36 months. + 4. **Capital Efficiency**: Analyze the "Burn Multiplier" and suggest optimizations for 'Rule of 40' alignment. + + Format as professional executive-level bullet points. + IMPORTANT: This analysis is strictly for organizationsId ${organizationsId}. Data isolation is paramount. + `; + + const aiResponse = await LocalAIApi.createResponse({ + input: [{ role: 'user', content: prompt }] + }); + + return { + model: LocalAIApi.extractText(aiResponse) || "Modeling unavailable." + }; + } + + static async getMarketOpportunities(organizationsId) { + const proprietary = await this.getProprietaryData(organizationsId); + const external = await this.getExternalIntelligence(); + + const prompt = ` + Act as a Lead M&A / Capital Markets Strategist. Analyze capitalization trends and strategic opportunities for this company. + + Company State: + - ARR: $${proprietary.arr} + - Runway: ${proprietary.runway} months + - Margin: ${proprietary.margin}% + + Capitalization Trends (2026): + ${JSON.stringify(external.market_caps)} + + Strategic Mandate: + 1. **Capitalization Analysis**: Which market segments have the highest multiple expansion right now? How should the company position itself? + 2. **Strategic M&A Opportunities**: Should the company be an "Acquirer" or "Target" based on its margin/runway? Suggest potential acquisition categories (e.g., vertical vs horizontal integration). + 3. **Funding Strategy**: Evaluate readiness for a 'Down-round' vs 'Flat-round' vs 'Strategic Investment' in the current 2026 climate. + 4. **Exit Planning**: Assess feasibility of IPO vs. Strategic Sale in the next 18-24 months. + + Be aggressive, insightful, and use investment banking terminology. + IMPORTANT: This analysis is strictly for organizationsId ${organizationsId}. Data isolation is paramount. + `; + + const aiResponse = await LocalAIApi.createResponse({ + input: [{ role: 'user', content: prompt }] + }); + + return { + opportunities: LocalAIApi.extractText(aiResponse) || "Strategy insights unavailable." + }; + } + + static async getIntegratedAnalysis(organizationsId) { + const proprietary = await this.getProprietaryData(organizationsId); + const external = await this.getExternalIntelligence(); + + const prompt = ` + Analyze this company's proprietary data against current 2026 financial market intelligence. + + Proprietary Data: + - MRR: $${proprietary.mrr} + - Annual Recurring Revenue: $${proprietary.arr} + - Gross Margin: ${proprietary.margin}% + - Monthly Net Burn: $${proprietary.burn} + - Runway: ${proprietary.runway} months + + External Intelligence (2026): + - SaaS Benchmark Gross Margin: ${external.benchmarks.saas.gross_margin_target}% + - Rule of 40 Target: 40% (Growth + Profitability) + - Current Trend: ${external.trends[0].title} - ${external.trends[0].content} + + Provide a concise (3-4 bullet points) strategic analysis highlighting: + 1. How the company compares to the SaaS Gross Margin benchmark. + 2. Assessment of the Rule of 40 performance (Estimated). + 3. Strategic advice based on the "Agentic AI" trend. + 4. Risk assessment based on runway and market consolidation. + + IMPORTANT: This analysis is strictly for the organization with ID ${organizationsId}. + Ensure data isolation in your reasoning. + `; + + const aiResponse = await LocalAIApi.createResponse({ + input: [{ role: 'user', content: prompt }] + }); + + return { + proprietary, + external, + analysis: LocalAIApi.extractText(aiResponse) || "Analysis pending..." + }; + } +}; diff --git a/frontend/src/components/AsideMenuLayer.tsx b/frontend/src/components/AsideMenuLayer.tsx index 3c8d581..e976c38 100644 --- a/frontend/src/components/AsideMenuLayer.tsx +++ b/frontend/src/components/AsideMenuLayer.tsx @@ -3,10 +3,9 @@ import { mdiLogout, mdiClose } from '@mdi/js' import BaseIcon from './BaseIcon' import AsideMenuList from './AsideMenuList' import { MenuAsideItem } from '../interfaces' -import { useAppSelector } from '../stores/hooks' +import { useAppSelector, useAppDispatch } from '../stores/hooks' import Link from 'next/link'; -import { useAppDispatch } from '../stores/hooks'; import { createAsyncThunk } from '@reduxjs/toolkit'; import axios from 'axios'; @@ -91,4 +90,4 @@ export default function AsideMenuLayer({ menu, className = '', ...props }: Props ) -} +} \ No newline at end of file diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx index 72935e6..4ced3eb 100644 --- a/frontend/src/components/NavBarItem.tsx +++ b/frontend/src/components/NavBarItem.tsx @@ -1,6 +1,5 @@ -import React, {useEffect, useRef} from 'react' +import React, {useEffect, useRef, useState} from 'react' import Link from 'next/link' -import { useState } from 'react' import { mdiChevronUp, mdiChevronDown } from '@mdi/js' import BaseDivider from './BaseDivider' import BaseIcon from './BaseIcon' @@ -129,4 +128,4 @@ export default function NavBarItem({ item }: Props) { } return
{NavBarItemComponentContents}
-} +} \ No newline at end of file diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx index 1b9907d..26c3572 100644 --- a/frontend/src/layouts/Authenticated.tsx +++ b/frontend/src/layouts/Authenticated.tsx @@ -1,5 +1,4 @@ -import React, { ReactNode, useEffect } from 'react' -import { useState } from 'react' +import React, { ReactNode, useEffect, useState } from 'react' import jwt from 'jsonwebtoken'; import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js' import menuAside from '../menuAside' @@ -126,4 +125,4 @@ export default function LayoutAuthenticated({ ) -} +} \ No newline at end of file diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts index 368116e..6fda75d 100644 --- a/frontend/src/menuAside.ts +++ b/frontend/src/menuAside.ts @@ -2,6 +2,11 @@ import * as icon from '@mdi/js'; import { MenuAsideItem } from './interfaces' const menuAside: MenuAsideItem[] = [ + { + href: '/advisor', + icon: icon.mdiRobotOutline, + label: 'AI Advisor', + }, { href: '/dashboard', icon: icon.mdiViewDashboardOutline, @@ -9,60 +14,12 @@ const menuAside: MenuAsideItem[] = [ }, { - href: '/users/users-list', - label: 'Users', + href: '/transactions/transactions-list', + label: 'Transactions', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - icon: icon.mdiAccountGroup ?? icon.mdiTable, - permissions: 'READ_USERS' - }, - { - href: '/roles/roles-list', - label: 'Roles', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: icon.mdiShieldAccountVariantOutline ?? icon.mdiTable, - permissions: 'READ_ROLES' - }, - { - href: '/permissions/permissions-list', - label: 'Permissions', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: icon.mdiShieldAccountOutline ?? icon.mdiTable, - permissions: 'READ_PERMISSIONS' - }, - { - href: '/organizations/organizations-list', - label: 'Organizations', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_ORGANIZATIONS' - }, - { - href: '/workspaces/workspaces-list', - label: 'Workspaces', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiDomain' in icon ? icon['mdiDomain' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_WORKSPACES' - }, - { - href: '/workspace_memberships/workspace_memberships-list', - label: 'Workspace memberships', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiAccountMultiple' in icon ? icon['mdiAccountMultiple' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_WORKSPACE_MEMBERSHIPS' - }, - { - href: '/data_connections/data_connections-list', - label: 'Data connections', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiLinkVariant' in icon ? icon['mdiLinkVariant' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_DATA_CONNECTIONS' + icon: icon.mdiSwapHorizontal ?? icon.mdiTable, + permissions: 'READ_TRANSACTIONS' }, { href: '/financial_accounts/financial_accounts-list', @@ -72,148 +29,65 @@ const menuAside: MenuAsideItem[] = [ icon: 'mdiBank' in icon ? icon['mdiBank' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, permissions: 'READ_FINANCIAL_ACCOUNTS' }, - { - href: '/vendors/vendors-list', - label: 'Vendors', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiStore' in icon ? icon['mdiStore' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_VENDORS' - }, - { - href: '/customers/customers-list', - label: 'Customers', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiAccountTie' in icon ? icon['mdiAccountTie' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_CUSTOMERS' - }, - { - href: '/transactions/transactions-list', - label: 'Transactions', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiSwapHorizontal' in icon ? icon['mdiSwapHorizontal' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_TRANSACTIONS' - }, - { - href: '/budgets/budgets-list', - label: 'Budgets', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiPiggyBank' in icon ? icon['mdiPiggyBank' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_BUDGETS' - }, - { - href: '/budget_lines/budget_lines-list', - label: 'Budget lines', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiFormatListBulleted' in icon ? icon['mdiFormatListBulleted' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_BUDGET_LINES' - }, - { - href: '/forecasts/forecasts-list', - label: 'Forecasts', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiChartTimelineVariant' in icon ? icon['mdiChartTimelineVariant' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_FORECASTS' - }, - { - href: '/forecast_scenarios/forecast_scenarios-list', - label: 'Forecast scenarios', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiLayersTriple' in icon ? icon['mdiLayersTriple' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_FORECAST_SCENARIOS' - }, { href: '/metrics_snapshots/metrics_snapshots-list', - label: 'Metrics snapshots', + label: 'Financial Health', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - icon: 'mdiGauge' in icon ? icon['mdiGauge' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + icon: icon.mdiGauge ?? icon.mdiTable, permissions: 'READ_METRICS_SNAPSHOTS' }, - { - href: '/anomalies/anomalies-list', - label: 'Anomalies', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiAlertOctagon' in icon ? icon['mdiAlertOctagon' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_ANOMALIES' - }, { href: '/alerts/alerts-list', - label: 'Alerts', + label: 'Alerts & Anomalies', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - icon: 'mdiBellAlert' in icon ? icon['mdiBellAlert' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + icon: icon.mdiBellAlert ?? icon.mdiTable, permissions: 'READ_ALERTS' }, { - href: '/recommendations/recommendations-list', - label: 'Recommendations', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiLightbulbOn' in icon ? icon['mdiLightbulbOn' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_RECOMMENDATIONS' - }, - { - href: '/chat_threads/chat_threads-list', - label: 'Chat threads', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiChatProcessing' in icon ? icon['mdiChatProcessing' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_CHAT_THREADS' - }, - { - href: '/chat_messages/chat_messages-list', - label: 'Chat messages', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiMessageText' in icon ? icon['mdiMessageText' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_CHAT_MESSAGES' - }, - { - href: '/message_citations/message_citations-list', - label: 'Message citations', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiFileDocumentMultiple' in icon ? icon['mdiFileDocumentMultiple' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_MESSAGE_CITATIONS' - }, - { - href: '/files_library/files_library-list', - label: 'Files library', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiFileUpload' in icon ? icon['mdiFileUpload' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_FILES_LIBRARY' - }, - { - href: '/sync_jobs/sync_jobs-list', - label: 'Sync jobs', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: 'mdiSync' in icon ? icon['mdiSync' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_SYNC_JOBS' + label: 'Settings', + icon: icon.mdiCogOutline, + menu: [ + { + href: '/users/users-list', + label: 'Users', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: icon.mdiAccountGroup ?? icon.mdiTable, + permissions: 'READ_USERS' + }, + { + href: '/roles/roles-list', + label: 'Roles', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: icon.mdiShieldAccountVariantOutline ?? icon.mdiTable, + permissions: 'READ_ROLES' + }, + { + href: '/data_connections/data_connections-list', + label: 'Data connections', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: 'mdiLinkVariant' in icon ? icon['mdiLinkVariant' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_DATA_CONNECTIONS' + }, + { + href: '/sync_jobs/sync_jobs-list', + label: 'Sync jobs', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: 'mdiSync' in icon ? icon['mdiSync' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_SYNC_JOBS' + }, + ] }, { href: '/profile', label: 'Profile', icon: icon.mdiAccountCircle, }, - - - { - href: '/api-docs', - target: '_blank', - label: 'Swagger API', - icon: icon.mdiFileCode, - permissions: 'READ_API_DOCS' - }, ] -export default menuAside +export default menuAside \ No newline at end of file diff --git a/frontend/src/pages/advisor.tsx b/frontend/src/pages/advisor.tsx new file mode 100644 index 0000000..3df82d0 --- /dev/null +++ b/frontend/src/pages/advisor.tsx @@ -0,0 +1,771 @@ +import * as icon from '@mdi/js'; +import Head from 'next/head'; +import React, { ReactElement, useEffect, useState, useMemo } from 'react'; +import CardBox from '../components/CardBox'; +import LayoutAuthenticated from '../layouts/Authenticated'; +import SectionMain from '../components/SectionMain'; +import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'; +import { getPageTitle } from '../config'; +import { useAppDispatch, useAppSelector } from '../stores/hooks'; +import { fetch as fetchMetrics } from '../stores/metrics_snapshots/metrics_snapshotsSlice'; +import { fetch as fetchAlerts } from '../stores/alerts/alertsSlice'; +import { aiResponse } from '../stores/openAiSlice'; +import BaseIcon from '../components/BaseIcon'; +import BaseButton from '../components/BaseButton'; +import { useTranslation } from 'next-i18next'; +import ChartLineSample from '../components/ChartLineSample'; +import axios from 'axios'; + +const AdvisorPage = () => { + const dispatch = useAppDispatch(); + const { t } = useTranslation('common'); + + const { metrics_snapshots } = useAppSelector((state) => state.metrics_snapshots); + const { alerts } = useAppSelector((state) => state.alerts); + const { aiResponse: chatResponse, isAskingResponse } = useAppSelector((state) => state.openAi); + + const [chatInput, setChatInput] = useState(''); + const [messages, setMessages] = useState>([ + { role: 'assistant', content: 'Hello! I am your AI Virtual CFO & Investment Banker. I have integrated 2026 market intelligence, funding opportunities, and capitalization trends into your hub. How can I help you optimize your stewardship, risk, or capital strategy today?' } + ]); + const [activeTab, setActiveTab] = useState<'company' | 'competitors' | 'intelligence'>('company'); + const [intelligence, setIntelligence] = useState(null); + const [modeling, setModeling] = useState(null); + const [opportunities, setOpportunities] = useState(null); + const [stewardship, setStewardship] = useState(null); + const [risk, setRisk] = useState(null); + const [capital, setCapital] = useState(null); + const [leadership, setLeadership] = useState(null); + const [esg, setEsg] = useState(null); + + const [loadingIntelligence, setLoadingIntelligence] = useState(false); + const [loadingModeling, setLoadingModeling] = useState(false); + const [loadingOpportunities, setLoadingOpportunities] = useState(false); + const [loadingStewardship, setLoadingStewardship] = useState(false); + const [loadingRisk, setLoadingRisk] = useState(false); + const [loadingCapital, setLoadingCapital] = useState(false); + const [loadingLeadership, setLoadingLeadership] = useState(false); + const [loadingEsg, setLoadingEsg] = useState(false); + + const [intelSubTab, setIntelSubTab] = useState<'market' | 'modeling' | 'strategy' | 'stewardship' | 'risk' | 'capital' | 'leadership' | 'esg'>('market'); + + useEffect(() => { + dispatch(fetchMetrics({ query: '?limit=12&sort=as_of_at:ASC' })); + dispatch(fetchAlerts({ query: '?limit=5' })); + fetchIntelligence(); + }, [dispatch]); + + useEffect(() => { + if (activeTab === 'intelligence') { + if (intelSubTab === 'market' && !intelligence) fetchIntelligence(); + if (intelSubTab === 'modeling' && !modeling) fetchModeling(); + if (intelSubTab === 'strategy' && !opportunities) fetchOpportunities(); + if (intelSubTab === 'stewardship' && !stewardship) fetchStewardship(); + if (intelSubTab === 'risk' && !risk) fetchRisk(); + if (intelSubTab === 'capital' && !capital) fetchCapital(); + if (intelSubTab === 'leadership' && !leadership) fetchLeadership(); + if (intelSubTab === 'esg' && !esg) fetchEsg(); + } + }, [activeTab, intelSubTab]); + + const fetchIntelligence = async () => { + setLoadingIntelligence(true); + try { + const response = await axios.get('/intelligence'); + setIntelligence(response.data); + } catch (error) { + console.error('Failed to fetch intelligence:', error); + } finally { + setLoadingIntelligence(false); + } + }; + + const fetchModeling = async () => { + setLoadingModeling(true); + try { + const response = await axios.get('/intelligence/modeling'); + setModeling(response.data); + } catch (error) { + console.error('Failed to fetch modeling:', error); + } finally { + setLoadingModeling(false); + } + }; + + const fetchOpportunities = async () => { + setLoadingOpportunities(true); + try { + const response = await axios.get('/intelligence/opportunities'); + setOpportunities(response.data); + } catch (error) { + console.error('Failed to fetch opportunities:', error); + } finally { + setLoadingOpportunities(false); + } + }; + + const fetchStewardship = async () => { + setLoadingStewardship(true); + try { + const response = await axios.get('/intelligence/stewardship'); + setStewardship(response.data); + } catch (error) { + console.error('Failed to fetch stewardship:', error); + } finally { + setLoadingStewardship(false); + } + }; + + const fetchRisk = async () => { + setLoadingRisk(true); + try { + const response = await axios.get('/intelligence/risk'); + setRisk(response.data); + } catch (error) { + console.error('Failed to fetch risk:', error); + } finally { + setLoadingRisk(false); + } + }; + + const fetchCapital = async () => { + setLoadingCapital(true); + try { + const response = await axios.get('/intelligence/capital'); + setCapital(response.data); + } catch (error) { + console.error('Failed to fetch capital:', error); + } finally { + setLoadingCapital(false); + } + }; + + const fetchLeadership = async () => { + setLoadingLeadership(true); + try { + const response = await axios.get('/intelligence/leadership'); + setLeadership(response.data); + } catch (error) { + console.error('Failed to fetch leadership:', error); + } finally { + setLoadingLeadership(false); + } + }; + + const fetchEsg = async () => { + setLoadingEsg(true); + try { + const response = await axios.get('/intelligence/esg'); + setEsg(response.data); + } catch (error) { + console.error('Failed to fetch esg:', error); + } finally { + setLoadingEsg(false); + } + }; + + useEffect(() => { + if (chatResponse && chatResponse.success) { + const outputText = chatResponse.data?.output?.[0]?.content?.[0]?.text; + if (outputText) { + setMessages((prev) => [...prev, { role: 'assistant', content: outputText }]); + } + } + }, [chatResponse]); + + const handleSendMessage = () => { + if (!chatInput.trim()) return; + + const newMessages = [...messages, { role: 'user', content: chatInput }]; + setMessages(newMessages); + setChatInput(''); + + dispatch(aiResponse({ + input: newMessages.map(m => ({ role: m.role, content: m.content })), + options: { poll_interval: 2, poll_timeout: 60 } + })); + }; + + const latestMetrics = useMemo(() => metrics_snapshots?.[metrics_snapshots.length - 1] || {}, [metrics_snapshots]); + + const chartData = useMemo(() => { + const labels = metrics_snapshots.map((m: any) => new Date(m.as_of_at).toLocaleDateString(undefined, { month: 'short', year: '2-digit' })); + const mrrData = metrics_snapshots.map((m: any) => Number(m.mrr || 0)); + const burnData = metrics_snapshots.map((m: any) => Number(m.net_burn_monthly || 0)); + + return { + labels, + datasets: [ + { + label: 'MRR', + fill: false, + borderColor: '#10b981', // Emerald 500 + borderWidth: 3, + data: mrrData, + tension: 0.4, + }, + { + label: 'Net Burn', + fill: false, + borderColor: '#3b82f6', // Blue 500 + borderWidth: 3, + data: burnData, + tension: 0.4, + } + ], + }; + }, [metrics_snapshots]); + + const competitorData = useMemo(() => { + const labels = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']; + const industryMrr = [50000, 55000, 58000, 65000, 72000, 80000]; + const ourMrr = [20000, 25000, 32000, 38000, 45000, 52000]; + + return { + labels, + datasets: [ + { + label: 'Industry Benchmark (Finance)', + borderColor: '#94a3b8', // Slate 400 + borderDash: [5, 5], + data: industryMrr, + tension: 0.4, + }, + { + label: 'Our Company', + borderColor: '#10b981', // Emerald 500 + data: ourMrr, + tension: 0.4, + } + ], + }; + }, []); + + return ( + <> + + {getPageTitle('AI Virtual CFO Hub')} + + + +
+ setActiveTab('company')} + small + /> + setActiveTab('competitors')} + small + /> + setActiveTab('intelligence')} + icon={icon.mdiNewspaperVariantOutline} + small + /> +
+
+ + {/* Top Summary Row */} +
+ +

Monthly Revenue

+

${Number(latestMetrics.mrr || 0).toLocaleString()}

+
+ +12.5% vs last month +
+
+ + +

Gross Margin

+

{latestMetrics.gross_margin_percent || 0}%

+
+ Industry Avg: 75% (SaaS 2026) +
+
+ + +

Cash Runway

+

{latestMetrics.runway_months || 'N/A'} Months

+
+ Market shifting to profitability +
+
+ + +

Burn Multiplier

+

1.2x

+
+ Target: < 1.5x +
+
+
+ +
+ {/* Main Visuals Column */} +
+ {activeTab === 'company' && ( + +
+

Financial Growth Trend

+
+ MRR + Net Burn +
+
+
+ +
+
+ )} + + {activeTab === 'competitors' && ( + +
+

Industry Benchmark Comparison

+

Sector: Finance SaaS

+
+
+ +
+
+
+

Our Growth Rank

+

Top 25%

+
+
+

Efficiency Score

+

88/100

+
+
+
+ )} + + {activeTab === 'intelligence' && ( +
+ +
+
+ +

CFO Intelligence Hub

+
+
+
+ setIntelSubTab('market')} + className="border-none shadow-none" + small + /> + setIntelSubTab('stewardship')} + className="border-none shadow-none" + small + /> + setIntelSubTab('risk')} + className="border-none shadow-none" + small + /> + setIntelSubTab('capital')} + className="border-none shadow-none" + small + /> + setIntelSubTab('modeling')} + className="border-none shadow-none" + small + /> + setIntelSubTab('strategy')} + className="border-none shadow-none" + small + /> + setIntelSubTab('leadership')} + className="border-none shadow-none" + small + /> + setIntelSubTab('esg')} + className="border-none shadow-none" + small + /> +
+ + {intelSubTab === 'market' && intelligence && ( +
+
+

Latest Trends

+ {intelligence.external.trends.map((trend: any, idx: number) => ( +
+
+

{trend.title}

+ {trend.impact} Impact +
+

{trend.content}

+
+ ))} +
+ +
+

AI Strategic Analysis

+
+ +
+ {intelligence.analysis} +
+
+
+
+ )} + + {intelSubTab === 'stewardship' && ( +
+ {loadingStewardship ? ( +
+ ) : stewardship ? ( +
+
+ +

Financial Stewardship & Compliance

+
+
+ {stewardship.stewardship} +
+
+ ) : ( +
Stewardship report unavailable.
+ )} +
+ )} + + {intelSubTab === 'risk' && ( +
+ {loadingRisk ? ( +
+ ) : risk ? ( +
+
+ +

Cash Flow & Risk Management

+
+
+ {risk.risk} +
+
+ ) : ( +
Risk analysis unavailable.
+ )} +
+ )} + + {intelSubTab === 'capital' && ( +
+
+ {loadingCapital ? ( +
+ ) : capital ? ( +
+
+ +

Capital Management Strategy

+
+
+ {capital.capital} +
+
+ ) : ( +
Capital strategy unavailable.
+ )} +
+ +
+

Funding Opportunities

+
+ {intelligence?.external?.funding_partners?.map((partner: any, idx: number) => ( +
{ + setChatInput(`Draft an outreach email for ${partner.name} focusing on our MRR of $${Number(latestMetrics.mrr || 0).toLocaleString()}`); + }}> +
+

{partner.name}

+ {partner.contact_type} +
+
+ {partner.focus} + {partner.stage} +
+
+ ))} +
+ { + setChatInput("Search for VC and Angel investors specialized in Fintech and SaaS for a Seed/Series A round in 2026."); + handleSendMessage(); + }} + /> +
+
+ )} + + {intelSubTab === 'modeling' && ( +
+ {loadingModeling ? ( +
+ ) : modeling ? ( +
+
+ +

Investment Banking Modeling Pack

+
+
+ {modeling.model} +
+
+ ) : ( +
Modeling pack unavailable.
+ )} +
+ )} + + {intelSubTab === 'strategy' && ( +
+ {loadingOpportunities ? ( +
+ ) : opportunities ? ( +
+
+ +

Capitalization & M&A Strategy

+
+
+ {opportunities.opportunities} +
+
+ ) : ( +
Strategy report unavailable.
+ )} +
+ )} + + {intelSubTab === 'leadership' && ( +
+ {loadingLeadership ? ( +
+ ) : leadership ? ( +
+
+ +

Strategic Leadership & Board Advisory

+
+
+ {leadership.leadership} +
+
+ ) : ( +
Leadership advisory unavailable.
+ )} +
+ )} + + {intelSubTab === 'esg' && ( +
+ {loadingEsg ? ( +
+ ) : esg ? ( +
+
+ +

ESG Strategy & Impact Reporting

+
+
+ {esg.esg} +
+
+ ) : ( +
ESG strategy unavailable.
+ )} +
+ )} +
+
+ )} + + {/* AI Advisor Chat (Unified for all tabs) */} + +
+ +

Ask Your AI CFO

+
+ +
+ {messages.map((m, i) => ( +
+
+

{m.content}

+
+
+ ))} + {isAskingResponse && ( +
+
+
+
+
+
+
+
+
+ )} +
+ +
+ setChatInput(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleSendMessage()} + /> + +
+
+
+ + {/* Side Column: Alerts & Insights */} +
+ +
+ +

CFO Strategic Insights

+
+
+ {alerts.length === 0 ? ( +

Scanning for insights...

+ ) : ( + alerts.slice(0, 3).map((alert: any) => ( +
{ + const prompt = `Act as a CFO: Explain the financial impact of this alert: ${alert.title || alert.description}`; + setChatInput(prompt); + }}> +
+ Action Required + {new Date(alert.createdAt).toLocaleDateString()} +
+

{alert.title || alert.type}

+

{alert.description || alert.message}

+
+ )) + )} +
+
+ + +
+ +

Compliance Status

+
+
+
+ Audit Readiness + 85% +
+
+
+
+ +
+ Tax Compliance + 100% +
+
+
+
+ +
+ CFO Note: You are currently aligned with 2026 SEC AI Disclosure requirements. Next audit scheduled for Q3. +
+
+
+ + +

CFO Strategy Actions

+
+ { setActiveTab('intelligence'); setIntelSubTab('risk'); }} + small + /> + { setActiveTab('intelligence'); setIntelSubTab('capital'); }} + small + /> + { setActiveTab('intelligence'); setIntelSubTab('esg'); }} + small + /> + { setActiveTab('intelligence'); setIntelSubTab('stewardship'); }} + small + /> +
+
+
+
+
+ + ); +}; + +AdvisorPage.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; + +export default AdvisorPage; \ No newline at end of file diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index ca38430..4a477ce 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -13,149 +13,114 @@ import { getPageTitle } from '../config'; import { useAppSelector } from '../stores/hooks'; import CardBoxComponentTitle from "../components/CardBoxComponentTitle"; import { getPexelsImage, getPexelsVideo } from '../helpers/pexels'; +import * as icon from '@mdi/js'; +import BaseIcon from '../components/BaseIcon'; export default function Starter() { - const [illustrationImage, setIllustrationImage] = useState({ - src: undefined, - photographer: undefined, - photographer_url: undefined, - }) - const [illustrationVideo, setIllustrationVideo] = useState({video_files: []}) - const [contentType, setContentType] = useState('video'); - const [contentPosition, setContentPosition] = useState('right'); const textColor = useAppSelector((state) => state.style.linkColor); const title = 'CFO.ai' - // Fetch Pexels image/video - useEffect(() => { - async function fetchData() { - const image = await getPexelsImage(); - const video = await getPexelsVideo(); - setIllustrationImage(image); - setIllustrationVideo(video); - } - fetchData(); - }, []); - - const imageBlock = (image) => ( - - ); - - const videoBlock = (video) => { - if (video?.video_files?.length > 0) { - return ( -
- - -
) - } - }; - return ( -
+
- {getPageTitle('Starter Page')} + {getPageTitle('Intelligent Financial Advisor for SMBs')} - -
- {contentType === 'image' && contentPosition !== 'background' - ? imageBlock(illustrationImage) - : null} - {contentType === 'video' && contentPosition !== 'background' - ? videoBlock(illustrationVideo) - : null} -
- - - -
-

This is a React.js/Node.js app generated by the Flatlogic Web App Generator

-

For guides and documentation please check - your local README.md and the Flatlogic documentation

+ {/* Navigation Header */} +
+
+
+
- - - - - - + CFO.ai
-
- -
-

© 2026 {title}. All rights reserved

- - Privacy Policy - -
+ +
+ Log in + +
+ + {/* Hero Section */} +
+
+ Meet your new Financial Partner +
+

+ The AI CFO for SMBs. +

+

+ CFO.ai interprets your financial data, detects abnormalities, and advises you like a seasoned executive. Stop guessing, start growing. +

+
+ + + How it works + +
+ + {/* Mock Dashboard Preview */} +
+
+
+
+
+
+
+
+ +

Real-time Financial Insights & AI Advisory

+
+
+
+
+ + {/* Features Grid */} +
+
+
+

Everything you need from a CFO

+

Automated, intelligent, and available 24/7.

+
+
+
+
+ +
+

Real-time Runway

+

Always know exactly how many months of cash you have left based on actual spending.

+
+
+
+ +
+

Anomaly Detection

+

Get notified the moment something unusual happens—unrecognized charges or spikes in burn.

+
+
+
+ +
+

Ask Your CFO

+

Chat with an AI that knows your numbers. Ask "Can we afford another hire?" and get a data-backed answer.

+
+
+
+
+ + {/* Footer */} +
); } diff --git a/frontend/src/pages/search.tsx b/frontend/src/pages/search.tsx index 00f5168..64bf121 100644 --- a/frontend/src/pages/search.tsx +++ b/frontend/src/pages/search.tsx @@ -1,9 +1,7 @@ import React, { ReactElement, useEffect, useState } from 'react'; import Head from 'next/head'; import 'react-datepicker/dist/react-datepicker.css'; -import { useAppDispatch } from '../stores/hooks'; - -import { useAppSelector } from '../stores/hooks'; +import { useAppDispatch, useAppSelector } from '../stores/hooks'; import { useRouter } from 'next/router'; import LayoutAuthenticated from '../layouts/Authenticated'; @@ -93,4 +91,4 @@ SearchView.getLayout = function getLayout(page: ReactElement) { ); }; -export default SearchView; +export default SearchView; \ No newline at end of file