Browse Source

Fix duplicate ETF holdings with case-insensitive grouping

pull/6573/head
Aditya Inamdar 2 weeks ago
parent
commit
3027eb983a
  1. 16
      apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts
  2. 40
      apps/client/src/app/pages/portfolio/allocations/top-holdings.util.spec.ts
  3. 7
      apps/client/src/app/pages/portfolio/allocations/top-holdings.util.ts

16
apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts

@ -43,6 +43,11 @@ import {
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
import {
isSameTopHoldingName,
normalizeTopHoldingName
} from './top-holdings.util';
@Component({ @Component({
imports: [ imports: [
GfPortfolioProportionChartComponent, GfPortfolioProportionChartComponent,
@ -408,14 +413,17 @@ export class GfAllocationsPageComponent implements OnInit {
for (const holding of position.holdings) { for (const holding of position.holdings) {
const { allocationInPercentage, name, valueInBaseCurrency } = const { allocationInPercentage, name, valueInBaseCurrency } =
holding; holding;
const topHoldingKey = normalizeTopHoldingName(name);
if (this.topHoldingsMap[name]?.value) { if (this.topHoldingsMap[topHoldingKey]?.value) {
this.topHoldingsMap[name].value += isNumber(valueInBaseCurrency) this.topHoldingsMap[topHoldingKey].value += isNumber(
valueInBaseCurrency
)
? valueInBaseCurrency ? valueInBaseCurrency
: allocationInPercentage * : allocationInPercentage *
this.portfolioDetails.holdings[symbol].valueInPercentage; this.portfolioDetails.holdings[symbol].valueInPercentage;
} else { } else {
this.topHoldingsMap[name] = { this.topHoldingsMap[topHoldingKey] = {
name, name,
value: isNumber(valueInBaseCurrency) value: isNumber(valueInBaseCurrency)
? valueInBaseCurrency ? valueInBaseCurrency
@ -518,7 +526,7 @@ export class GfAllocationsPageComponent implements OnInit {
if (holding.holdings.length > 0) { if (holding.holdings.length > 0) {
const currentParentHolding = holding.holdings.find( const currentParentHolding = holding.holdings.find(
(parentHolding) => { (parentHolding) => {
return parentHolding.name === name; return isSameTopHoldingName(parentHolding.name, name);
} }
); );

40
apps/client/src/app/pages/portfolio/allocations/top-holdings.util.spec.ts

@ -0,0 +1,40 @@
import {
isSameTopHoldingName,
normalizeTopHoldingName
} from './top-holdings.util';
describe('Top Holdings Utility', () => {
it('normalizes top holding names case-insensitively', () => {
expect(normalizeTopHoldingName('NVIDIA Corp')).toEqual('nvidia corp');
expect(normalizeTopHoldingName('NVIDIA CORP')).toEqual('nvidia corp');
});
it('matches top holding names case-insensitively', () => {
expect(isSameTopHoldingName('NVIDIA Corp', 'NVIDIA CORP')).toBe(true);
expect(isSameTopHoldingName('Apple Inc', 'Microsoft Corp')).toBe(false);
});
it('supports aggregation of mixed-case holding names into one entry', () => {
const holdings = [
{ name: 'NVIDIA Corp', valueInBaseCurrency: 50 },
{ name: 'NVIDIA CORP', valueInBaseCurrency: 40 }
];
const topHoldingsMap: Record<string, { name: string; value: number }> = {};
for (const { name, valueInBaseCurrency } of holdings) {
const topHoldingKey = normalizeTopHoldingName(name);
if (topHoldingsMap[topHoldingKey]) {
topHoldingsMap[topHoldingKey].value += valueInBaseCurrency;
} else {
topHoldingsMap[topHoldingKey] = {
name,
value: valueInBaseCurrency
};
}
}
expect(Object.values(topHoldingsMap)).toHaveLength(1);
expect(Object.values(topHoldingsMap)[0].value).toEqual(90);
});
});

7
apps/client/src/app/pages/portfolio/allocations/top-holdings.util.ts

@ -0,0 +1,7 @@
export function normalizeTopHoldingName(name: string) {
return name.trim().toLocaleLowerCase();
}
export function isSameTopHoldingName(aName: string, bName: string) {
return normalizeTopHoldingName(aName) === normalizeTopHoldingName(bName);
}
Loading…
Cancel
Save