Files
HOA_Financial_Platform/frontend/src/App.tsx
JoeBot 4797669591 feat: add shadow AI benchmarking for admin model comparison
Add a new admin-only feature that allows the platform owner to benchmark
the production AI model against up to 2 alternate models (any OpenAI-compatible
API) using real tenant data, without impacting users.

Backend:
- Shared AI caller utility (ai-caller.ts) for OpenAI-compatible endpoints
- Shadow AI module with service, controller, and 3 entities
- 6 admin API endpoints for model config CRUD, run trigger, and history
- Auto-creates shadow_ai_models, shadow_runs, shadow_run_results tables
- Exposes health-scores and investment-planning prompt builders for reuse

Frontend:
- New admin page at /admin/shadow-ai with 3 tabs:
  - Model Configuration (production + 2 alternate slots)
  - Run Comparison (tenant select, feature select, side-by-side results)
  - History (filterable run log with detail drill-down)
- Full side-by-side output display with diff highlighting
- Sidebar navigation link for AI Benchmarking

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 07:50:59 -04:00

189 lines
8.6 KiB
TypeScript

import { Routes, Route, Navigate } from 'react-router-dom';
import { useAuthStore } from './stores/authStore';
import { AppLayout } from './components/layout/AppLayout';
import { LoginPage } from './pages/auth/LoginPage';
import { RegisterPage } from './pages/auth/RegisterPage';
import { SelectOrgPage } from './pages/auth/SelectOrgPage';
import { ActivatePage } from './pages/auth/ActivatePage';
import { DashboardPage } from './pages/dashboard/DashboardPage';
import { AccountsPage } from './pages/accounts/AccountsPage';
import { TransactionsPage } from './pages/transactions/TransactionsPage';
import { BudgetsPage } from './pages/budgets/BudgetsPage';
import { UnitsPage } from './pages/units/UnitsPage';
import { InvoicesPage } from './pages/invoices/InvoicesPage';
import { PaymentsPage } from './pages/payments/PaymentsPage';
import { VendorsPage } from './pages/vendors/VendorsPage';
import { ProjectsPage } from './pages/projects/ProjectsPage';
import { InvestmentsPage } from './pages/investments/InvestmentsPage';
import { CapitalProjectsPage } from './pages/capital-projects/CapitalProjectsPage';
import { BalanceSheetPage } from './pages/reports/BalanceSheetPage';
import { IncomeStatementPage } from './pages/reports/IncomeStatementPage';
import { BudgetVsActualPage } from './pages/reports/BudgetVsActualPage';
import { SankeyPage } from './pages/reports/SankeyPage';
import { CashFlowPage } from './pages/reports/CashFlowPage';
import { AgingReportPage } from './pages/reports/AgingReportPage';
import { YearEndPage } from './pages/reports/YearEndPage';
import { QuarterlyReportPage } from './pages/reports/QuarterlyReportPage';
import { CapitalPlanningPage } from './pages/reports/CapitalPlanningPage';
import { SettingsPage } from './pages/settings/SettingsPage';
import { UserPreferencesPage } from './pages/preferences/UserPreferencesPage';
import { OrgMembersPage } from './pages/org-members/OrgMembersPage';
import { AdminPage } from './pages/admin/AdminPage';
import { AdminIdeasPage } from './pages/admin/AdminIdeasPage';
import { AdminShadowAiPage } from './pages/admin/AdminShadowAiPage';
import { AssessmentGroupsPage } from './pages/assessment-groups/AssessmentGroupsPage';
import { CashFlowForecastPage } from './pages/cash-flow/CashFlowForecastPage';
import { MonthlyActualsPage } from './pages/monthly-actuals/MonthlyActualsPage';
import { InvestmentPlanningPage } from './pages/investment-planning/InvestmentPlanningPage';
import { InvestmentScenariosPage } from './pages/board-planning/InvestmentScenariosPage';
import { InvestmentScenarioDetailPage } from './pages/board-planning/InvestmentScenarioDetailPage';
import { AssessmentScenariosPage } from './pages/board-planning/AssessmentScenariosPage';
import { AssessmentScenarioDetailPage } from './pages/board-planning/AssessmentScenarioDetailPage';
import { ScenarioComparisonPage } from './pages/board-planning/ScenarioComparisonPage';
import { BudgetPlanningPage } from './pages/board-planning/BudgetPlanningPage';
import { PricingPage } from './pages/pricing/PricingPage';
import { OnboardingPage } from './pages/onboarding/OnboardingPage';
import { OnboardingPendingPage } from './pages/onboarding/OnboardingPendingPage';
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const token = useAuthStore((s) => s.token);
if (!token) return <Navigate to="/login" replace />;
return <>{children}</>;
}
function OrgRequiredRoute({ children }: { children: React.ReactNode }) {
const token = useAuthStore((s) => s.token);
const currentOrg = useAuthStore((s) => s.currentOrg);
if (!token) return <Navigate to="/login" replace />;
if (!currentOrg) return <Navigate to="/select-org" replace />;
return <>{children}</>;
}
function SuperAdminRoute({ children }: { children: React.ReactNode }) {
const token = useAuthStore((s) => s.token);
const user = useAuthStore((s) => s.user);
if (!token) return <Navigate to="/login" replace />;
if (!user?.isSuperadmin) return <Navigate to="/dashboard" replace />;
return <>{children}</>;
}
function AuthRoute({ children }: { children: React.ReactNode }) {
const token = useAuthStore((s) => s.token);
const user = useAuthStore((s) => s.user);
const currentOrg = useAuthStore((s) => s.currentOrg);
const organizations = useAuthStore((s) => s.organizations);
if (token && currentOrg) return <Navigate to="/" replace />;
// Platform owner / superadmin with no org memberships → admin panel
if (token && user?.isSuperadmin && (!organizations || organizations.length === 0)) {
return <Navigate to="/admin" replace />;
}
if (token && !currentOrg) return <Navigate to="/select-org" replace />;
return <>{children}</>;
}
export function App() {
return (
<Routes>
{/* Public routes (no auth required) */}
<Route path="/pricing" element={<PricingPage />} />
<Route path="/activate" element={<ActivatePage />} />
<Route path="/onboarding/pending" element={<OnboardingPendingPage />} />
{/* Auth routes (redirect if already logged in) */}
<Route
path="/login"
element={
<AuthRoute>
<LoginPage />
</AuthRoute>
}
/>
<Route
path="/register"
element={
<AuthRoute>
<RegisterPage />
</AuthRoute>
}
/>
<Route
path="/select-org"
element={
<ProtectedRoute>
<SelectOrgPage />
</ProtectedRoute>
}
/>
{/* Onboarding (requires auth but not org selection) */}
<Route
path="/onboarding"
element={
<ProtectedRoute>
<OnboardingPage />
</ProtectedRoute>
}
/>
{/* Admin routes */}
<Route
path="/admin"
element={
<SuperAdminRoute>
<AppLayout />
</SuperAdminRoute>
}
>
<Route index element={<AdminPage />} />
<Route path="ideas" element={<AdminIdeasPage />} />
<Route path="shadow-ai" element={<AdminShadowAiPage />} />
</Route>
{/* Main app routes (require auth + org) */}
<Route
path="/*"
element={
<OrgRequiredRoute>
<AppLayout />
</OrgRequiredRoute>
}
>
<Route index element={<Navigate to="/dashboard" replace />} />
<Route path="dashboard" element={<DashboardPage />} />
<Route path="accounts" element={<AccountsPage />} />
<Route path="transactions" element={<TransactionsPage />} />
<Route path="budgets/:year" element={<BudgetsPage />} />
<Route path="units" element={<UnitsPage />} />
<Route path="invoices" element={<InvoicesPage />} />
<Route path="payments" element={<PaymentsPage />} />
<Route path="vendors" element={<VendorsPage />} />
<Route path="projects" element={<ProjectsPage />} />
<Route path="investments" element={<InvestmentsPage />} />
<Route path="capital-projects" element={<CapitalProjectsPage />} />
<Route path="investment-planning" element={<InvestmentPlanningPage />} />
<Route path="assessment-groups" element={<AssessmentGroupsPage />} />
<Route path="cash-flow" element={<CashFlowForecastPage />} />
<Route path="monthly-actuals" element={<MonthlyActualsPage />} />
<Route path="reports/balance-sheet" element={<BalanceSheetPage />} />
<Route path="reports/income-statement" element={<IncomeStatementPage />} />
<Route path="reports/budget-vs-actual" element={<BudgetVsActualPage />} />
<Route path="reports/cash-flow" element={<CashFlowPage />} />
<Route path="reports/aging" element={<AgingReportPage />} />
<Route path="reports/sankey" element={<SankeyPage />} />
<Route path="reports/year-end" element={<YearEndPage />} />
<Route path="reports/quarterly" element={<QuarterlyReportPage />} />
<Route path="reports/capital-planning" element={<CapitalPlanningPage />} />
<Route path="board-planning/budgets" element={<BudgetPlanningPage />} />
<Route path="board-planning/investments" element={<InvestmentScenariosPage />} />
<Route path="board-planning/investments/:id" element={<InvestmentScenarioDetailPage />} />
<Route path="board-planning/assessments" element={<AssessmentScenariosPage />} />
<Route path="board-planning/assessments/:id" element={<AssessmentScenarioDetailPage />} />
<Route path="board-planning/compare" element={<ScenarioComparisonPage />} />
<Route path="settings" element={<SettingsPage />} />
<Route path="preferences" element={<UserPreferencesPage />} />
<Route path="org-members" element={<OrgMembersPage />} />
</Route>
</Routes>
);
}