/** * Risk Analysis Service * * Encapsulates risk analysis business logic with dependency injection. * Demonstrates service layer pattern for complex multi-step operations. */ import { BaseService } from '../base.service.js'; import { VaultData, CompositionData } from '../../graphql/fragments/index.js'; import { RiskScoreBreakdown } from '../../utils/risk-scoring.js'; import { type OperationalSignal } from '../../utils/operational-signals.js'; /** * Risk analysis input data extracted from GraphQL. * `priceHistory` is pre-projected to {timestamp, pricePerShareUsd} pairs and * already filtered for null/non-positive values; downstream code can iterate * without re-checking. Uses assetByProtocols from Octav API for protocol-based * diversification analysis. */ export interface RiskAnalysisData { vault: VaultData; allVaults: { items: Array<{ state: { totalAssetsUsd: number; }; }>; }; curatorVaults: { items: Array<{ address: string; state: { totalAssetsUsd: number; }; }>; }; priceHistory: Array<{ timestamp: number; pricePerShareUsd: number; }>; composition: CompositionData | null; } /** * Comparative risk context for benchmarking */ export interface ComparativeRiskContext { percentile: number; betterThanPercent: number; medianRisk: number; isApproximate: boolean; averageRisk: number; isOutlier: boolean; riskRanking: string; } /** * Cost-of-trade summary surfaced alongside the weighted risk score. * Transactional fees (entry/exit/haircut) belong here rather than in * `calculateFeeRisk()` because that function models annualized drag, not * one-shot deposit/redeem cost. * * All percentages have already been converted from basis points via * `basisPointsToPercent` in `src/utils/fee-formatting.ts`. */ export interface TradingCosts { entryPct: number; exitPct: number; /** Applied only on syncRedeem, after the exit fee. Burned shares. */ haircutPct: number; upcomingManagementPct: number | null; upcomingPerformancePct: number | null; /** Cooldown between rate update and enforcement, in seconds (BigInt string). */ feeRatesCooldownSeconds: string | null; /** Unix timestamp at which the upcoming rates take effect (BigInt string). */ newRatesActivateAt: string | null; } /** * Extended risk score breakdown with comparative context, operational * signals (v0.6+), and trading-cost summary. * * `operationalSignals` and `effectiveRiskLevel` do NOT modify * `overallRisk` (the numeric weighted score). They can only raise * `effectiveRiskLevel` ABOVE `riskLevel` (the bucket derived from the score), * never lower it. See `src/utils/operational-signals.ts` for the rules. */ export interface ExtendedRiskScoreBreakdown extends RiskScoreBreakdown { comparative?: ComparativeRiskContext; operationalSignals?: OperationalSignal[]; effectiveRiskLevel?: 'Low' | 'Medium' | 'High' | 'Critical'; tradingCosts?: TradingCosts; } /** * Batch risk analysis response from GraphQL */ export interface BatchRiskAnalysisResponse { vaults: { items: VaultData[]; }; allVaults: { items: Array<{ state: { totalAssetsUsd: number; }; }>; }; } /** * Result for a single vault in batch analysis */ export interface BatchVaultRiskResult { address: string; chainId: number; name: string; riskScore: number; riskLevel: string; factors: Array<{ name: string; score: number; level: string; }>; breakdown: ExtendedRiskScoreBreakdown; } /** * Complete batch analysis result */ export interface BatchRiskAnalysisResult { vaults: BatchVaultRiskResult[]; summary: { lowestRisk: { address: string; score: number; } | null; highestRisk: { address: string; score: number; } | null; averageScore: number; vaultCount: number; }; } /** * Structured risk data for UI block rendering (single vault) * Contains pre-formatted fields for direct frontend consumption */ export interface StructuredRiskData { address: string; chainId: number; name: string; overallRisk: { score: number; scoreFormatted: string; level: 'low' | 'medium' | 'high' | 'critical'; }; topRisks: Array<{ name: string; score: number; scoreFormatted: string; level: 'low' | 'medium' | 'high' | 'critical'; }>; allFactors: Record; comparative?: { percentile: number; ranking: string; isOutlier: boolean; }; dataQuality: 'high' | 'medium' | 'low'; operationalSignals?: OperationalSignal[]; effectiveRiskLevel?: 'low' | 'medium' | 'high' | 'critical'; tradingCosts?: TradingCosts; } /** * Structured batch risk data for UI block rendering * Contains summary and array of vault risk data */ export interface StructuredBatchRiskData { summary: { vaultCount: number; averageScore: number; averageScoreFormatted: string; lowestRisk: { address: string; score: number; scoreFormatted: string; } | null; highestRisk: { address: string; score: number; scoreFormatted: string; } | null; }; vaults: StructuredRiskData[]; } /** * Risk analysis service for vault risk assessment */ export declare class RiskService extends BaseService { /** * Fetch risk analysis data from GraphQL * Composition is fetched separately using correct addresses from bundles.octav */ fetchRiskData(vaultAddress: string, chainId: number): Promise; /** * Calculate risk breakdown from fetched data */ calculateRisk(data: RiskAnalysisData): RiskScoreBreakdown; /** * Fetch the typed `Vault.composition` for a single (address, chainId) * pair. Returns null on backend error so callers can degrade gracefully * without composition-derived risk factors. * * The old Octav-bundle-URL parsing and multi-composition merging are * gone — `Vault.composition` is a single direct call per vault. */ private fetchCompositionForVault; /** * Calculate comparative risk context by benchmarking against all vaults * * @param vaultRisk - Risk score for the target vault * @param allVaultsData - Data for all vaults on the chain * @returns Comparative risk context with percentile rankings */ calculateComparativeContext(vaultRisk: number, allVaultsData: RiskAnalysisData['allVaults']): ComparativeRiskContext; /** * Perform complete risk analysis for a vault * * @param vaultAddress - Vault address to analyze * @param chainId - Chain ID of the vault * @param includeComparative - Whether to include comparative benchmarking * @returns Risk score breakdown or null if vault not found */ analyze(vaultAddress: string, chainId: number, includeComparative?: boolean): Promise; /** * Format risk breakdown as markdown table */ formatRiskBreakdown(breakdown: ExtendedRiskScoreBreakdown, responseFormat?: 'score' | 'summary' | 'detailed'): string; /** * Analyze multiple vaults in a single batch operation * * Supports both same-chain (single chainId) and cross-chain (chainIds array) analysis. * For cross-chain, chainIds array must have same length as vaultAddresses (positional mapping). * * @param vaultAddresses - Array of vault addresses (2-20) * @param chainId - Single chain ID (when all vaults are on same chain) * @param chainIds - Array of chain IDs (for cross-chain, positional mapping with vaultAddresses) * @returns Batch analysis result with all vaults and summary */ analyzeBatch(vaultAddresses: string[], chainId?: number, chainIds?: number[]): Promise; /** * Process a single vault for batch analysis * Creates minimal RiskAnalysisData structure from vault data * Now includes composition data when available */ private processVaultForBatch; /** * Convert numeric score to risk level string */ private scoreToLevel; /** * Format batch risk analysis as markdown */ formatBatchRiskBreakdown(result: BatchRiskAnalysisResult, responseFormat?: 'score' | 'summary' | 'detailed'): string; /** * Convert risk breakdown to structured data for UI block rendering * @param breakdown - Risk analysis breakdown * @param vaultAddress - Vault address * @param chainId - Chain ID * @param vaultName - Vault name (optional) * @returns Structured risk data for frontend */ toStructuredRiskData(breakdown: ExtendedRiskScoreBreakdown, vaultAddress: string, chainId: number, vaultName?: string): StructuredRiskData; /** * Convert camelCase factor name to human-readable format */ private formatFactorName; /** * Convert batch risk result to structured data for UI block rendering * @param result - Batch risk analysis result * @returns Structured batch risk data for frontend */ toStructuredBatchRiskData(result: BatchRiskAnalysisResult): StructuredBatchRiskData; } //# sourceMappingURL=risk.service.d.ts.map