From fa779764667682f4f7b0597485ec5d82ec3fb450 Mon Sep 17 00:00:00 2001 From: tobikugel Date: Tue, 2 Sep 2025 12:18:13 -0300 Subject: [PATCH] implements interface change --- .../src/app/portfolio/portfolio.controller.ts | 4 +- .../src/app/portfolio/portfolio.service.ts | 365 ++++++++++-------- apps/api/src/app/portfolio/rules.service.ts | 2 - .../portfolio/x-ray/x-ray-page.component.ts | 60 ++- .../portfolio-report-rule.interface.ts | 1 - .../responses/portfolio-report.interface.ts | 6 +- 6 files changed, 236 insertions(+), 202 deletions(-) diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index 748cdceb8..d703cf604 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -655,8 +655,8 @@ export class PortfolioController { this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') && this.request.user.subscription.type === 'Basic' ) { - for (const rule in report.xRay.rules) { - report.xRay.rules[rule] = null; + for (const category of report.xRay.categories) { + category.rules = null; } report.xRay.statistics = { diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 1d010bcf7..ceaa9f6fa 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -50,6 +50,7 @@ import { PortfolioPerformanceResponse, PortfolioPosition, PortfolioReportResponse, + PortfolioReportRule, PortfolioSummary, UserSettings } from '@ghostfolio/common/interfaces'; @@ -1231,176 +1232,210 @@ export class PortfolioService { }) ).toNumber(); - const rules: PortfolioReportResponse['xRay']['rules'] = { - accountClusterRisk: - summary.activityCount > 0 - ? await this.rulesService.evaluate( - [ - new AccountClusterRiskCurrentInvestment( - this.exchangeRateDataService, - this.i18nService, - userSettings.language, - accounts - ), - new AccountClusterRiskSingleAccount( - this.exchangeRateDataService, - this.i18nService, - userSettings.language, - accounts - ) - ], - userSettings - ) - : undefined, - assetClassClusterRisk: - summary.activityCount > 0 - ? await this.rulesService.evaluate( - [ - new AssetClassClusterRiskEquity( - this.exchangeRateDataService, - this.i18nService, - userSettings.language, - Object.values(holdings) - ), - new AssetClassClusterRiskFixedIncome( - this.exchangeRateDataService, - this.i18nService, - userSettings.language, - Object.values(holdings) - ) - ], - userSettings - ) - : undefined, - currencyClusterRisk: - summary.activityCount > 0 - ? await this.rulesService.evaluate( - [ - new CurrencyClusterRiskBaseCurrencyCurrentInvestment( - this.exchangeRateDataService, - this.i18nService, - Object.values(holdings), - userSettings.language - ), - new CurrencyClusterRiskCurrentInvestment( - this.exchangeRateDataService, - this.i18nService, - Object.values(holdings), - userSettings.language - ) - ], - userSettings + const categories: PortfolioReportResponse['xRay']['categories'] = [ + { + key: 'accountClusterRisk', + name: 'accountClusterRisk', + rules: + summary.activityCount > 0 + ? await this.rulesService.evaluate( + [ + new AccountClusterRiskCurrentInvestment( + this.exchangeRateDataService, + this.i18nService, + userSettings.language, + accounts + ), + new AccountClusterRiskSingleAccount( + this.exchangeRateDataService, + this.i18nService, + userSettings.language, + accounts + ) + ], + userSettings + ) + : undefined + }, + { + key: 'assetClassClusterRisk', + name: 'assetClassClusterRisk', + rules: + summary.activityCount > 0 + ? await this.rulesService.evaluate( + [ + new AssetClassClusterRiskEquity( + this.exchangeRateDataService, + this.i18nService, + userSettings.language, + Object.values(holdings) + ), + new AssetClassClusterRiskFixedIncome( + this.exchangeRateDataService, + this.i18nService, + userSettings.language, + Object.values(holdings) + ) + ], + userSettings + ) + : undefined + }, + { + key: 'currencyClusterRisk', + name: 'currencyClusterRisk', + rules: + summary.activityCount > 0 + ? await this.rulesService.evaluate( + [ + new CurrencyClusterRiskBaseCurrencyCurrentInvestment( + this.exchangeRateDataService, + this.i18nService, + Object.values(holdings), + userSettings.language + ), + new CurrencyClusterRiskCurrentInvestment( + this.exchangeRateDataService, + this.i18nService, + Object.values(holdings), + userSettings.language + ) + ], + userSettings + ) + : undefined + }, + { + key: 'economicMarketClusterRisk', + name: 'economicMarketClusterRisk', + rules: + summary.activityCount > 0 + ? await this.rulesService.evaluate( + [ + new EconomicMarketClusterRiskDevelopedMarkets( + this.exchangeRateDataService, + this.i18nService, + marketsTotalInBaseCurrency, + markets.developedMarkets.valueInBaseCurrency, + userSettings.language + ), + new EconomicMarketClusterRiskEmergingMarkets( + this.exchangeRateDataService, + this.i18nService, + marketsTotalInBaseCurrency, + markets.emergingMarkets.valueInBaseCurrency, + userSettings.language + ) + ], + userSettings + ) + : undefined + }, + { + key: 'emergencyFund', + name: 'emergencyFund', + rules: await this.rulesService.evaluate( + [ + new EmergencyFundSetup( + this.exchangeRateDataService, + this.i18nService, + userSettings.language, + this.getTotalEmergencyFund({ + userSettings, + emergencyFundHoldingsValueInBaseCurrency: + this.getEmergencyFundHoldingsValueInBaseCurrency({ holdings }) + }).toNumber() ) - : undefined, - economicMarketClusterRisk: - summary.activityCount > 0 - ? await this.rulesService.evaluate( - [ - new EconomicMarketClusterRiskDevelopedMarkets( - this.exchangeRateDataService, - this.i18nService, - marketsTotalInBaseCurrency, - markets.developedMarkets.valueInBaseCurrency, - userSettings.language - ), - new EconomicMarketClusterRiskEmergingMarkets( - this.exchangeRateDataService, - this.i18nService, - marketsTotalInBaseCurrency, - markets.emergingMarkets.valueInBaseCurrency, - userSettings.language - ) - ], - userSettings + ], + userSettings + ) + }, + { + key: 'fees', + name: 'fees', + rules: await this.rulesService.evaluate( + [ + new FeeRatioInitialInvestment( + this.exchangeRateDataService, + this.i18nService, + userSettings.language, + summary.committedFunds, + summary.fees ) - : undefined, - emergencyFund: await this.rulesService.evaluate( - [ - new EmergencyFundSetup( - this.exchangeRateDataService, - this.i18nService, - userSettings.language, - this.getTotalEmergencyFund({ - userSettings, - emergencyFundHoldingsValueInBaseCurrency: - this.getEmergencyFundHoldingsValueInBaseCurrency({ holdings }) - }).toNumber() - ) - ], - userSettings - ), - fees: await this.rulesService.evaluate( - [ - new FeeRatioInitialInvestment( - this.exchangeRateDataService, - this.i18nService, - userSettings.language, - summary.committedFunds, - summary.fees - ) - ], - userSettings - ), - liquidity: await this.rulesService.evaluate( - [ - new BuyingPower( - this.exchangeRateDataService, - this.i18nService, - summary.cash, - userSettings.language - ) - ], - userSettings - ), - regionalMarketClusterRisk: - summary.activityCount > 0 - ? await this.rulesService.evaluate( - [ - new RegionalMarketClusterRiskAsiaPacific( - this.exchangeRateDataService, - this.i18nService, - userSettings.language, - marketsAdvancedTotalInBaseCurrency, - marketsAdvanced.asiaPacific.valueInBaseCurrency - ), - new RegionalMarketClusterRiskEmergingMarkets( - this.exchangeRateDataService, - this.i18nService, - userSettings.language, - marketsAdvancedTotalInBaseCurrency, - marketsAdvanced.emergingMarkets.valueInBaseCurrency - ), - new RegionalMarketClusterRiskEurope( - this.exchangeRateDataService, - this.i18nService, - userSettings.language, - marketsAdvancedTotalInBaseCurrency, - marketsAdvanced.europe.valueInBaseCurrency - ), - new RegionalMarketClusterRiskJapan( - this.exchangeRateDataService, - this.i18nService, - userSettings.language, - marketsAdvancedTotalInBaseCurrency, - marketsAdvanced.japan.valueInBaseCurrency - ), - new RegionalMarketClusterRiskNorthAmerica( - this.exchangeRateDataService, - this.i18nService, - userSettings.language, - marketsAdvancedTotalInBaseCurrency, - marketsAdvanced.northAmerica.valueInBaseCurrency - ) - ], - userSettings + ], + userSettings + ) + }, + { + key: 'liquidity', + name: 'liquidity', + rules: await this.rulesService.evaluate( + [ + new BuyingPower( + this.exchangeRateDataService, + this.i18nService, + summary.cash, + userSettings.language ) - : undefined - }; + ], + userSettings + ) + }, + { + key: 'regionalMarketClusterRisk', + name: 'regionalMarketClusterRisk', + rules: + summary.activityCount > 0 + ? await this.rulesService.evaluate( + [ + new RegionalMarketClusterRiskAsiaPacific( + this.exchangeRateDataService, + this.i18nService, + userSettings.language, + marketsAdvancedTotalInBaseCurrency, + marketsAdvanced.asiaPacific.valueInBaseCurrency + ), + new RegionalMarketClusterRiskEmergingMarkets( + this.exchangeRateDataService, + this.i18nService, + userSettings.language, + marketsAdvancedTotalInBaseCurrency, + marketsAdvanced.emergingMarkets.valueInBaseCurrency + ), + new RegionalMarketClusterRiskEurope( + this.exchangeRateDataService, + this.i18nService, + userSettings.language, + marketsAdvancedTotalInBaseCurrency, + marketsAdvanced.europe.valueInBaseCurrency + ), + new RegionalMarketClusterRiskJapan( + this.exchangeRateDataService, + this.i18nService, + userSettings.language, + marketsAdvancedTotalInBaseCurrency, + marketsAdvanced.japan.valueInBaseCurrency + ), + new RegionalMarketClusterRiskNorthAmerica( + this.exchangeRateDataService, + this.i18nService, + userSettings.language, + marketsAdvancedTotalInBaseCurrency, + marketsAdvanced.northAmerica.valueInBaseCurrency + ) + ], + userSettings + ) + : undefined + } + ]; return { xRay: { - rules, - statistics: this.getReportStatistics(rules) + categories, + statistics: this.getReportStatistics( + categories.flatMap(({ rules }) => rules ?? []) + ) } }; } @@ -1822,7 +1857,7 @@ export class PortfolioService { } private getReportStatistics( - evaluatedRules: PortfolioReportResponse['xRay']['rules'] + evaluatedRules: PortfolioReportRule[] ): PortfolioReportResponse['xRay']['statistics'] { const rulesActiveCount = Object.values(evaluatedRules) .flat() diff --git a/apps/api/src/app/portfolio/rules.service.ts b/apps/api/src/app/portfolio/rules.service.ts index 7f6d964f5..48d1658aa 100644 --- a/apps/api/src/app/portfolio/rules.service.ts +++ b/apps/api/src/app/portfolio/rules.service.ts @@ -22,7 +22,6 @@ export class RulesService { return { evaluation, value, - categoryName: rule.getCategoryName(), configuration: rule.getConfiguration(), isActive: true, key: rule.getKey(), @@ -30,7 +29,6 @@ export class RulesService { }; } else { return { - categoryName: rule.getCategoryName(), isActive: false, key: rule.getKey(), name: rule.getName() diff --git a/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts index 0c8637b05..3ddf9da86 100644 --- a/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts +++ b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts @@ -116,49 +116,49 @@ export class GfXRayPageComponent { this.dataService .fetchPortfolioReport() .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(({ xRay: { rules, statistics } }) => { - this.inactiveRules = this.mergeInactiveRules(rules); + .subscribe(({ xRay: { categories, statistics } }) => { + this.inactiveRules = this.mergeInactiveRules(categories); this.statistics = statistics; this.accountClusterRiskRules = - rules['accountClusterRisk']?.filter(({ isActive }) => { - return isActive; - }) ?? null; + categories + .find(({ key }) => key === 'accountClusterRisk') + ?.rules?.filter(({ isActive }) => isActive) ?? null; this.assetClassClusterRiskRules = - rules['assetClassClusterRisk']?.filter(({ isActive }) => { - return isActive; - }) ?? null; + categories + .find(({ key }) => key === 'assetClassClusterRisk') + ?.rules?.filter(({ isActive }) => isActive) ?? null; this.currencyClusterRiskRules = - rules['currencyClusterRisk']?.filter(({ isActive }) => { - return isActive; - }) ?? null; + categories + .find(({ key }) => key === 'currencyClusterRisk') + ?.rules?.filter(({ isActive }) => isActive) ?? null; this.economicMarketClusterRiskRules = - rules['economicMarketClusterRisk']?.filter(({ isActive }) => { - return isActive; - }) ?? null; + categories + .find(({ key }) => key === 'economicMarketClusterRisk') + ?.rules?.filter(({ isActive }) => isActive) ?? null; this.emergencyFundRules = - rules['emergencyFund']?.filter(({ isActive }) => { - return isActive; - }) ?? null; + categories + .find(({ key }) => key === 'emergencyFund') + ?.rules?.filter(({ isActive }) => isActive) ?? null; this.feeRules = - rules['fees']?.filter(({ isActive }) => { - return isActive; - }) ?? null; + categories + .find(({ key }) => key === 'fees') + ?.rules?.filter(({ isActive }) => isActive) ?? null; this.liquidityRules = - rules['liquidity']?.filter(({ isActive }) => { - return isActive; - }) ?? null; + categories + .find(({ key }) => key === 'liquidity') + ?.rules?.filter(({ isActive }) => isActive) ?? null; this.regionalMarketClusterRiskRules = - rules['regionalMarketClusterRisk']?.filter(({ isActive }) => { - return isActive; - }) ?? null; + categories + .find(({ key }) => key === 'regionalMarketClusterRisk') + ?.rules?.filter(({ isActive }) => isActive) ?? null; this.isLoading = false; @@ -167,15 +167,13 @@ export class GfXRayPageComponent { } private mergeInactiveRules( - rules: PortfolioReportResponse['xRay']['rules'] + categories: PortfolioReportResponse['xRay']['categories'] ): PortfolioReportRule[] { let inactiveRules: PortfolioReportRule[] = []; - for (const category in rules) { - const rulesArray = rules[category] || []; - + for (const category of categories) { inactiveRules = inactiveRules.concat( - rulesArray.filter(({ isActive }) => { + category.rules.filter(({ isActive }) => { return !isActive; }) ); diff --git a/libs/common/src/lib/interfaces/portfolio-report-rule.interface.ts b/libs/common/src/lib/interfaces/portfolio-report-rule.interface.ts index 4df7a8eac..0296606b8 100644 --- a/libs/common/src/lib/interfaces/portfolio-report-rule.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-report-rule.interface.ts @@ -1,5 +1,4 @@ export interface PortfolioReportRule { - categoryName: string; configuration?: { threshold?: { max: number; diff --git a/libs/common/src/lib/interfaces/responses/portfolio-report.interface.ts b/libs/common/src/lib/interfaces/responses/portfolio-report.interface.ts index 985a42311..8146b6086 100644 --- a/libs/common/src/lib/interfaces/responses/portfolio-report.interface.ts +++ b/libs/common/src/lib/interfaces/responses/portfolio-report.interface.ts @@ -2,7 +2,11 @@ import { PortfolioReportRule } from '../portfolio-report-rule.interface'; export interface PortfolioReportResponse { xRay: { - rules: { [group: string]: PortfolioReportRule[] }; + categories: { + key: string; + name: string; + rules: PortfolioReportRule[]; + }[]; statistics: { rulesActiveCount: number; rulesFulfilledCount: number;