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 { 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 { snapshotRepository } from '../repositories/snapshot.repository';
|
||||||
|
import type { RiskProfile } from '../types/portfolio.types';
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Types
|
// Types
|
||||||
|
|||||||
@ -4,13 +4,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { db } from '../../../shared/database';
|
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 {
|
export interface GoalRow {
|
||||||
id: string;
|
id: string;
|
||||||
user_id: string;
|
user_id: string;
|
||||||
@ -31,25 +30,7 @@ export interface GoalRow {
|
|||||||
updated_at: Date;
|
updated_at: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PortfolioGoal {
|
// PortfolioGoal interface imported from types/portfolio.types.ts
|
||||||
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 CreateGoalInput {
|
export interface CreateGoalInput {
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|||||||
@ -4,14 +4,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { db } from '../../../shared/database';
|
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 {
|
export interface PortfolioRow {
|
||||||
id: string;
|
id: string;
|
||||||
user_id: string;
|
user_id: string;
|
||||||
@ -33,26 +36,7 @@ export interface PortfolioRow {
|
|||||||
updated_at: Date;
|
updated_at: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Portfolio {
|
// Portfolio interface imported from types/portfolio.types.ts
|
||||||
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 AllocationRow {
|
export interface AllocationRow {
|
||||||
id: string;
|
id: string;
|
||||||
@ -73,24 +57,7 @@ export interface AllocationRow {
|
|||||||
created_at: Date;
|
created_at: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PortfolioAllocation {
|
// PortfolioAllocation interface imported from types/portfolio.types.ts
|
||||||
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 CreatePortfolioInput {
|
export interface CreatePortfolioInput {
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|||||||
@ -4,9 +4,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { db } from '../../../shared/database';
|
import { db } from '../../../shared/database';
|
||||||
|
import type { PortfolioSnapshot, PerformanceDataPoint } from '../types/portfolio.types';
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Types
|
// Database Row Types (snake_case from DB)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
export interface SnapshotRow {
|
export interface SnapshotRow {
|
||||||
@ -23,19 +24,7 @@ export interface SnapshotRow {
|
|||||||
created_at: Date;
|
created_at: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PortfolioSnapshot {
|
// PortfolioSnapshot interface imported from types/portfolio.types.ts
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreateSnapshotInput {
|
export interface CreateSnapshotInput {
|
||||||
portfolioId: string;
|
portfolioId: string;
|
||||||
@ -49,14 +38,7 @@ export interface CreateSnapshotInput {
|
|||||||
allocations?: Record<string, unknown>;
|
allocations?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PerformanceDataPoint {
|
// PerformanceDataPoint interface imported from types/portfolio.types.ts
|
||||||
date: string;
|
|
||||||
value: number;
|
|
||||||
pnl: number;
|
|
||||||
pnlPercent: number;
|
|
||||||
change: number;
|
|
||||||
changePercent: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Helper Functions
|
// Helper Functions
|
||||||
|
|||||||
@ -7,23 +7,19 @@
|
|||||||
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { marketService } from '../../trading/services/market.service';
|
import { marketService } from '../../trading/services/market.service';
|
||||||
import {
|
import { portfolioRepository } from '../repositories/portfolio.repository';
|
||||||
portfolioRepository,
|
import { goalRepository } from '../repositories/goal.repository';
|
||||||
Portfolio as RepoPortfolio,
|
import type {
|
||||||
PortfolioAllocation as RepoAllocation,
|
Portfolio as DBPortfolio,
|
||||||
RiskProfile as RepoRiskProfile,
|
PortfolioAllocation as DBAllocation,
|
||||||
} from '../repositories/portfolio.repository';
|
PortfolioGoal as DBGoal,
|
||||||
import {
|
RiskProfile,
|
||||||
goalRepository,
|
} from '../types/portfolio.types';
|
||||||
PortfolioGoal as RepoGoal,
|
|
||||||
} from '../repositories/goal.repository';
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Types
|
// Service DTOs (with computed/additional fields)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
export type RiskProfile = 'conservative' | 'moderate' | 'aggressive';
|
|
||||||
|
|
||||||
export interface Portfolio {
|
export interface Portfolio {
|
||||||
id: string;
|
id: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
@ -125,8 +121,8 @@ const DEFAULT_ALLOCATIONS: Record<RiskProfile, { asset: string; percent: number
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
function mapRepoPortfolioToService(
|
function mapRepoPortfolioToService(
|
||||||
repo: RepoPortfolio,
|
repo: DBPortfolio,
|
||||||
allocations: RepoAllocation[]
|
allocations: DBAllocation[]
|
||||||
): Portfolio {
|
): Portfolio {
|
||||||
return {
|
return {
|
||||||
id: repo.id,
|
id: repo.id,
|
||||||
@ -145,7 +141,7 @@ function mapRepoPortfolioToService(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapRepoAllocationToService(repo: RepoAllocation): PortfolioAllocation {
|
function mapRepoAllocationToService(repo: DBAllocation): PortfolioAllocation {
|
||||||
return {
|
return {
|
||||||
id: repo.id,
|
id: repo.id,
|
||||||
portfolioId: repo.portfolioId,
|
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
|
// Map DB status to service status
|
||||||
let status: 'on_track' | 'at_risk' | 'behind' = 'on_track';
|
let status: 'on_track' | 'at_risk' | 'behind' = 'on_track';
|
||||||
if (repo.status === 'completed') {
|
if (repo.status === 'completed') {
|
||||||
@ -223,7 +219,7 @@ class PortfolioService {
|
|||||||
const repoPortfolio = await portfolioRepository.create({
|
const repoPortfolio = await portfolioRepository.create({
|
||||||
userId,
|
userId,
|
||||||
name,
|
name,
|
||||||
riskProfile: riskProfile as RepoRiskProfile,
|
riskProfile,
|
||||||
totalValue: initialValue,
|
totalValue: initialValue,
|
||||||
totalCost: 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