fix(coherence): Create centralized portfolio types (E-COH-004, ST1.3)
- Created apps/backend/src/modules/portfolio/types/portfolio.types.ts - Consolidated all portfolio type definitions from DDL - Updated repositories to import from centralized types - Updated service to use centralized types (removed duplicates) - Updated controller to import RiskProfile from types - Removed 73 lines of duplicate type definitions Fixes coherence gap E-COH-004 where portfolio types were absent in backend. All types now align with DDL schema (portfolio.* enums and tables). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
3bb215b51b
commit
1be94f0c1f
@ -4,8 +4,9 @@
|
||||
*/
|
||||
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { portfolioService, RiskProfile } from '../services/portfolio.service';
|
||||
import { portfolioService } from '../services/portfolio.service';
|
||||
import { snapshotRepository } from '../repositories/snapshot.repository';
|
||||
import type { RiskProfile } from '../types/portfolio.types';
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
|
||||
@ -4,13 +4,12 @@
|
||||
*/
|
||||
|
||||
import { db } from '../../../shared/database';
|
||||
import type { GoalStatus, PortfolioGoal } from '../types/portfolio.types';
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
// Database Row Types (snake_case from DB)
|
||||
// ============================================================================
|
||||
|
||||
export type GoalStatus = 'active' | 'completed' | 'abandoned';
|
||||
|
||||
export interface GoalRow {
|
||||
id: string;
|
||||
user_id: string;
|
||||
@ -31,25 +30,7 @@ export interface GoalRow {
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
export interface PortfolioGoal {
|
||||
id: string;
|
||||
userId: string;
|
||||
portfolioId: string | null;
|
||||
name: string;
|
||||
description: string | null;
|
||||
targetAmount: number;
|
||||
currentAmount: number;
|
||||
targetDate: Date;
|
||||
monthlyContribution: number;
|
||||
progress: number;
|
||||
projectedCompletionDate: Date | null;
|
||||
monthsRemaining: number | null;
|
||||
requiredMonthlyContribution: number | null;
|
||||
status: GoalStatus;
|
||||
completedAt: Date | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
// PortfolioGoal interface imported from types/portfolio.types.ts
|
||||
|
||||
export interface CreateGoalInput {
|
||||
userId: string;
|
||||
|
||||
@ -4,14 +4,17 @@
|
||||
*/
|
||||
|
||||
import { db } from '../../../shared/database';
|
||||
import type {
|
||||
RiskProfile,
|
||||
AllocationStatus,
|
||||
Portfolio,
|
||||
PortfolioAllocation,
|
||||
} from '../types/portfolio.types';
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
// Database Row Types (snake_case from DB)
|
||||
// ============================================================================
|
||||
|
||||
export type RiskProfile = 'conservative' | 'moderate' | 'aggressive';
|
||||
export type AllocationStatus = 'active' | 'inactive' | 'rebalancing';
|
||||
|
||||
export interface PortfolioRow {
|
||||
id: string;
|
||||
user_id: string;
|
||||
@ -33,26 +36,7 @@ export interface PortfolioRow {
|
||||
updated_at: Date;
|
||||
}
|
||||
|
||||
export interface Portfolio {
|
||||
id: string;
|
||||
userId: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
riskProfile: RiskProfile;
|
||||
totalValue: number;
|
||||
totalCost: number;
|
||||
unrealizedPnl: number;
|
||||
unrealizedPnlPercent: number;
|
||||
dayChangePercent: number;
|
||||
weekChangePercent: number;
|
||||
monthChangePercent: number;
|
||||
allTimeChangePercent: number;
|
||||
isActive: boolean;
|
||||
isPrimary: boolean;
|
||||
lastRebalancedAt: Date | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
// Portfolio interface imported from types/portfolio.types.ts
|
||||
|
||||
export interface AllocationRow {
|
||||
id: string;
|
||||
@ -73,24 +57,7 @@ export interface AllocationRow {
|
||||
created_at: Date;
|
||||
}
|
||||
|
||||
export interface PortfolioAllocation {
|
||||
id: string;
|
||||
portfolioId: string;
|
||||
asset: string;
|
||||
targetPercent: number;
|
||||
currentPercent: number;
|
||||
deviation: number;
|
||||
quantity: number;
|
||||
avgCost: number;
|
||||
currentPrice: number;
|
||||
value: number;
|
||||
cost: number;
|
||||
pnl: number;
|
||||
pnlPercent: number;
|
||||
status: AllocationStatus;
|
||||
lastUpdatedAt: Date;
|
||||
createdAt: Date;
|
||||
}
|
||||
// PortfolioAllocation interface imported from types/portfolio.types.ts
|
||||
|
||||
export interface CreatePortfolioInput {
|
||||
userId: string;
|
||||
|
||||
@ -4,9 +4,10 @@
|
||||
*/
|
||||
|
||||
import { db } from '../../../shared/database';
|
||||
import type { PortfolioSnapshot, PerformanceDataPoint } from '../types/portfolio.types';
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
// Database Row Types (snake_case from DB)
|
||||
// ============================================================================
|
||||
|
||||
export interface SnapshotRow {
|
||||
@ -23,19 +24,7 @@ export interface SnapshotRow {
|
||||
created_at: Date;
|
||||
}
|
||||
|
||||
export interface PortfolioSnapshot {
|
||||
id: string;
|
||||
portfolioId: string;
|
||||
snapshotDate: Date;
|
||||
totalValue: number;
|
||||
totalCost: number;
|
||||
unrealizedPnl: number;
|
||||
unrealizedPnlPercent: number;
|
||||
dayChange: number;
|
||||
dayChangePercent: number;
|
||||
allocations: Record<string, unknown> | null;
|
||||
createdAt: Date;
|
||||
}
|
||||
// PortfolioSnapshot interface imported from types/portfolio.types.ts
|
||||
|
||||
export interface CreateSnapshotInput {
|
||||
portfolioId: string;
|
||||
@ -49,14 +38,7 @@ export interface CreateSnapshotInput {
|
||||
allocations?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface PerformanceDataPoint {
|
||||
date: string;
|
||||
value: number;
|
||||
pnl: number;
|
||||
pnlPercent: number;
|
||||
change: number;
|
||||
changePercent: number;
|
||||
}
|
||||
// PerformanceDataPoint interface imported from types/portfolio.types.ts
|
||||
|
||||
// ============================================================================
|
||||
// Helper Functions
|
||||
|
||||
@ -7,23 +7,19 @@
|
||||
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { marketService } from '../../trading/services/market.service';
|
||||
import {
|
||||
portfolioRepository,
|
||||
Portfolio as RepoPortfolio,
|
||||
PortfolioAllocation as RepoAllocation,
|
||||
RiskProfile as RepoRiskProfile,
|
||||
} from '../repositories/portfolio.repository';
|
||||
import {
|
||||
goalRepository,
|
||||
PortfolioGoal as RepoGoal,
|
||||
} from '../repositories/goal.repository';
|
||||
import { portfolioRepository } from '../repositories/portfolio.repository';
|
||||
import { goalRepository } from '../repositories/goal.repository';
|
||||
import type {
|
||||
Portfolio as DBPortfolio,
|
||||
PortfolioAllocation as DBAllocation,
|
||||
PortfolioGoal as DBGoal,
|
||||
RiskProfile,
|
||||
} from '../types/portfolio.types';
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
// Service DTOs (with computed/additional fields)
|
||||
// ============================================================================
|
||||
|
||||
export type RiskProfile = 'conservative' | 'moderate' | 'aggressive';
|
||||
|
||||
export interface Portfolio {
|
||||
id: string;
|
||||
userId: string;
|
||||
@ -125,8 +121,8 @@ const DEFAULT_ALLOCATIONS: Record<RiskProfile, { asset: string; percent: number
|
||||
// ============================================================================
|
||||
|
||||
function mapRepoPortfolioToService(
|
||||
repo: RepoPortfolio,
|
||||
allocations: RepoAllocation[]
|
||||
repo: DBPortfolio,
|
||||
allocations: DBAllocation[]
|
||||
): Portfolio {
|
||||
return {
|
||||
id: repo.id,
|
||||
@ -145,7 +141,7 @@ function mapRepoPortfolioToService(
|
||||
};
|
||||
}
|
||||
|
||||
function mapRepoAllocationToService(repo: RepoAllocation): PortfolioAllocation {
|
||||
function mapRepoAllocationToService(repo: DBAllocation): PortfolioAllocation {
|
||||
return {
|
||||
id: repo.id,
|
||||
portfolioId: repo.portfolioId,
|
||||
@ -161,7 +157,7 @@ function mapRepoAllocationToService(repo: RepoAllocation): PortfolioAllocation {
|
||||
};
|
||||
}
|
||||
|
||||
function mapRepoGoalToService(repo: RepoGoal): PortfolioGoal {
|
||||
function mapRepoGoalToService(repo: DBGoal): PortfolioGoal {
|
||||
// Map DB status to service status
|
||||
let status: 'on_track' | 'at_risk' | 'behind' = 'on_track';
|
||||
if (repo.status === 'completed') {
|
||||
@ -223,7 +219,7 @@ class PortfolioService {
|
||||
const repoPortfolio = await portfolioRepository.create({
|
||||
userId,
|
||||
name,
|
||||
riskProfile: riskProfile as RepoRiskProfile,
|
||||
riskProfile,
|
||||
totalValue: initialValue,
|
||||
totalCost: initialValue,
|
||||
});
|
||||
|
||||
224
src/modules/portfolio/types/portfolio.types.ts
Normal file
224
src/modules/portfolio/types/portfolio.types.ts
Normal file
@ -0,0 +1,224 @@
|
||||
/**
|
||||
* Portfolio Module Types
|
||||
* Type definitions for portfolio management, allocations, goals, and rebalancing
|
||||
* Aligned with portfolio schema DDL (00-enums.sql)
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// Portfolio Enums (Alineado con portfolio.* DDL)
|
||||
// ============================================================================
|
||||
|
||||
// Alineado con portfolio.risk_profile (DDL)
|
||||
// Nota: También existe en investment schema
|
||||
export type RiskProfile = 'conservative' | 'moderate' | 'aggressive';
|
||||
|
||||
export enum RiskProfileEnum {
|
||||
CONSERVATIVE = 'conservative',
|
||||
MODERATE = 'moderate',
|
||||
AGGRESSIVE = 'aggressive',
|
||||
}
|
||||
|
||||
// Alineado con portfolio.goal_status (DDL)
|
||||
export type GoalStatus = 'active' | 'completed' | 'abandoned';
|
||||
|
||||
export enum GoalStatusEnum {
|
||||
ACTIVE = 'active',
|
||||
COMPLETED = 'completed',
|
||||
ABANDONED = 'abandoned',
|
||||
}
|
||||
|
||||
// Alineado con portfolio.rebalance_action (DDL)
|
||||
export type RebalanceAction = 'buy' | 'sell' | 'hold';
|
||||
|
||||
export enum RebalanceActionEnum {
|
||||
BUY = 'buy',
|
||||
SELL = 'sell',
|
||||
HOLD = 'hold',
|
||||
}
|
||||
|
||||
// Alineado con portfolio.allocation_status (DDL)
|
||||
export type AllocationStatus = 'active' | 'inactive' | 'rebalancing';
|
||||
|
||||
export enum AllocationStatusEnum {
|
||||
ACTIVE = 'active',
|
||||
INACTIVE = 'inactive',
|
||||
REBALANCING = 'rebalancing',
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Portfolio Interfaces
|
||||
// ============================================================================
|
||||
|
||||
export interface Portfolio {
|
||||
id: string;
|
||||
userId: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
riskProfile: RiskProfile;
|
||||
totalValue: number;
|
||||
totalCost: number;
|
||||
unrealizedPnl: number;
|
||||
unrealizedPnlPercent: number;
|
||||
dayChangePercent: number;
|
||||
weekChangePercent: number;
|
||||
monthChangePercent: number;
|
||||
allTimeChangePercent: number;
|
||||
isActive: boolean;
|
||||
isPrimary: boolean;
|
||||
lastRebalancedAt: Date | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface PortfolioAllocation {
|
||||
id: string;
|
||||
portfolioId: string;
|
||||
asset: string;
|
||||
targetPercent: number;
|
||||
currentPercent: number;
|
||||
deviation: number;
|
||||
quantity: number;
|
||||
avgCost: number;
|
||||
currentPrice: number;
|
||||
value: number;
|
||||
cost: number;
|
||||
pnl: number;
|
||||
pnlPercent: number;
|
||||
status: AllocationStatus;
|
||||
lastUpdatedAt: Date;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface PortfolioGoal {
|
||||
id: string;
|
||||
userId: string;
|
||||
portfolioId: string | null;
|
||||
name: string;
|
||||
description: string | null;
|
||||
targetAmount: number;
|
||||
currentAmount: number;
|
||||
targetDate: Date;
|
||||
monthlyContribution: number;
|
||||
progress: number;
|
||||
projectedCompletionDate: Date | null;
|
||||
monthsRemaining: number | null;
|
||||
requiredMonthlyContribution: number | null;
|
||||
status: GoalStatus;
|
||||
completedAt: Date | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface RebalanceHistory {
|
||||
id: string;
|
||||
portfolioId: string;
|
||||
asset: string;
|
||||
action: RebalanceAction;
|
||||
targetPercent: number;
|
||||
actualPercentBefore: number;
|
||||
actualPercentAfter: number;
|
||||
quantityChange: number;
|
||||
valueChange: number;
|
||||
priceAtRebalance: number;
|
||||
executedAt: Date;
|
||||
executionStatus: string;
|
||||
executionNotes: string | null;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface PortfolioSnapshot {
|
||||
id: string;
|
||||
portfolioId: string;
|
||||
snapshotDate: Date;
|
||||
totalValue: number;
|
||||
totalCost: number;
|
||||
unrealizedPnl: number;
|
||||
unrealizedPnlPercent: number;
|
||||
dayChange: number;
|
||||
dayChangePercent: number;
|
||||
allocations: Record<string, unknown> | null;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Input/Output DTOs
|
||||
// ============================================================================
|
||||
|
||||
export interface CreatePortfolioInput {
|
||||
userId: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
riskProfile: RiskProfile;
|
||||
}
|
||||
|
||||
export interface UpdatePortfolioInput {
|
||||
name?: string;
|
||||
description?: string;
|
||||
riskProfile?: RiskProfile;
|
||||
isActive?: boolean;
|
||||
isPrimary?: boolean;
|
||||
}
|
||||
|
||||
export interface CreateAllocationInput {
|
||||
portfolioId: string;
|
||||
asset: string;
|
||||
targetPercent: number;
|
||||
quantity?: number;
|
||||
avgCost?: number;
|
||||
}
|
||||
|
||||
export interface UpdateAllocationInput {
|
||||
targetPercent?: number;
|
||||
quantity?: number;
|
||||
avgCost?: number;
|
||||
status?: AllocationStatus;
|
||||
}
|
||||
|
||||
export interface CreateGoalInput {
|
||||
userId: string;
|
||||
portfolioId?: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
targetAmount: number;
|
||||
targetDate: Date;
|
||||
monthlyContribution?: number;
|
||||
}
|
||||
|
||||
export interface UpdateGoalInput {
|
||||
name?: string;
|
||||
description?: string;
|
||||
targetAmount?: number;
|
||||
targetDate?: Date;
|
||||
monthlyContribution?: number;
|
||||
currentAmount?: number;
|
||||
status?: GoalStatus;
|
||||
}
|
||||
|
||||
export interface RebalanceRecommendation {
|
||||
asset: string;
|
||||
action: RebalanceAction;
|
||||
currentPercent: number;
|
||||
targetPercent: number;
|
||||
deviation: number;
|
||||
quantityChange: number;
|
||||
valueChange: number;
|
||||
priority: number;
|
||||
}
|
||||
|
||||
export interface PerformanceDataPoint {
|
||||
date: string;
|
||||
value: number;
|
||||
pnl: number;
|
||||
pnlPercent: number;
|
||||
change: number;
|
||||
changePercent: number;
|
||||
}
|
||||
|
||||
export interface PortfolioSummary {
|
||||
portfolio: Portfolio;
|
||||
allocations: PortfolioAllocation[];
|
||||
totalAllocated: number;
|
||||
unallocatedPercent: number;
|
||||
needsRebalance: boolean;
|
||||
lastSnapshot: PortfolioSnapshot | null;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user