refactor: replace ignored numeric literals with constants
Backend security / security (push) Successful in 8s
Backend verify / verify (push) Failing after 2m17s
Conventional commits / commits (pull_request) Successful in 31s
Backend security / security (pull_request) Successful in 8s
Backend verify / verify (pull_request) Failing after 1m56s

This commit is contained in:
2026-09-28 11:24:59 +03:00
parent 9b3558f96c
commit 9cc9fbdcff
60 changed files with 510 additions and 283 deletions
@@ -9,6 +9,9 @@ import {
type ForecastSourceVersion,
} from '../infrastructure/forecast.repository';
const MISSING_DATA_REVISION = 0;
const STALE_FRESHNESS_MS = 0;
@Injectable()
export class ForecastService {
constructor(private readonly repository: ForecastRepository) {}
@@ -89,7 +92,7 @@ export class ForecastService {
data,
meta: investmentResourceMeta({
resourceKey: input.scopeKey,
dataRevision: String(stored?.dataRevision ?? 0),
dataRevision: String(stored?.dataRevision ?? MISSING_DATA_REVISION),
availability: stored ?
'complete' :
dependencies.some(
@@ -100,7 +103,7 @@ export class ForecastService {
fetchedAt: first ? new Date(first) : null,
freshForMs:
data && 'positionIsStale' in data && data.positionIsStale === true ?
0 :
STALE_FRESHNESS_MS :
Number.POSITIVE_INFINITY,
work: work?.id ? { ...work, id: work.id } : null,
dependencies,
@@ -43,6 +43,8 @@ import {
PortfolioPositionDto,
} from '../presentation/portfolio.dto';
const MISSING_DATA_REVISION = 0;
const membershipFreshForMs = tbankSourceTtlMs.membership;
const types: Record<string, string> = {
share: 'STOCK',
@@ -167,7 +169,7 @@ export class PortfolioService {
}
const membershipMeta = investmentResourceMeta({
resourceKey: 'accounts/membership',
dataRevision: String(saved?.membershipRevision ?? 0),
dataRevision: String(saved?.membershipRevision ?? MISSING_DATA_REVISION),
availability: saved?.membershipAt ? 'complete' : 'missing',
fetchedAt: saved?.membershipAt ?? null,
freshForMs: membershipFreshForMs,
@@ -195,7 +197,7 @@ export class PortfolioService {
null,
meta: investmentResourceMeta({
resourceKey: `portfolio/${encodeURIComponent(account.accountId)}`,
dataRevision: String(account.portfolio?.dataRevision ?? 0),
dataRevision: String(account.portfolio?.dataRevision ?? MISSING_DATA_REVISION),
availability: account.portfolio ? 'complete' : 'missing',
fetchedAt: account.portfolio?.fetchedAt ?? null,
freshForMs: portfolioFreshForMs,
@@ -2,10 +2,16 @@ import type { Money } from '../../../../common/money.types';
import {
add,
DECIMAL_RADIX,
decimalFromString,
formatDecimal,
} from '../../../../common/decimal';
const ZERO_AMOUNT = 0n;
const INITIAL_UNVALUED_COUNT = 0;
const POSITION_INCREMENT = 1;
const ROUNDING_DIVISOR = 2n;
const SHARE_PERCENT_DECIMAL_PLACES = 6;
const PERCENT_SCALED_FACTOR = 100_000_000n;
@@ -33,7 +39,7 @@ export function portfolioStructure(
const value = decimalFromString(money.value);
// eslint-disable-next-line security/detect-object-injection -- The numeric index belongs to the fixed portfolio category array.
group.values[index] = add(group.values[index], value);
group.hasNegativeValues ||= value.value < 0n;
group.hasNegativeValues ||= value.value < ZERO_AMOUNT;
currencies.set(money.currency, group);
};
for (const position of positions) {
@@ -41,7 +47,7 @@ export function portfolioStructure(
position.type :
'OTHER';
if (position.currentValue) include(type, position.currentValue);
else unvalued.set(type, (unvalued.get(type) ?? 0) + 1);
else unvalued.set(type, (unvalued.get(type) ?? INITIAL_UNVALUED_COUNT) + POSITION_INCREMENT);
}
for (const amount of cash) include('CASH', amount);
@@ -52,7 +58,7 @@ export function portfolioStructure(
.map(([currency, group]) => {
const total = group.values.reduce(add, decimalFromString('0'));
const canCalculateShares =
!unvalued.size && !group.hasNegativeValues && total.value > 0n;
!unvalued.size && !group.hasNegativeValues && total.value > ZERO_AMOUNT;
const categories = group.values.map((value, index) => ({
// eslint-disable-next-line security/detect-object-injection -- The numeric index belongs to the fixed portfolio category array.
type: categoryTypes[index],
@@ -63,9 +69,9 @@ export function portfolioStructure(
formatDecimal({
value:
(value.value *
10n ** BigInt(total.scale - value.scale) *
DECIMAL_RADIX ** BigInt(total.scale - value.scale) *
PERCENT_SCALED_FACTOR +
total.value / 2n) /
total.value / ROUNDING_DIVISOR) /
total.value,
scale: SHARE_PERCENT_DECIMAL_PLACES,
}) :
@@ -5,6 +5,8 @@ import type { Prisma } from '../../../../../generated/prisma/client';
import { PrismaService } from '../../../../prisma/prisma.service';
const FIRST_BATCH_OFFSET = 0;
export type ForecastSourceVersion = {
dataRevision: number;
fetchedAt: Date;
@@ -82,7 +84,7 @@ export class ForecastRepository {
// Four identity parameters per source: stay below SQLite's parameter limit.
const sources: ForecastSourceVersion[] = [];
for (
let offset = 0;
let offset = FIRST_BATCH_OFFSET;
offset < input.sources.length;
offset += forecastSourceReadBatchSize
) {
@@ -10,6 +10,8 @@ import {
} from '../../../../integrations/tbank/tbank.api';
import { PrismaService } from '../../../../prisma/prisma.service';
const EXPECTED_UPDATED_ACCOUNTS = 1;
type PortfolioAttempt = {
attemptId: string;
id: string;
@@ -152,7 +154,7 @@ export class PortfolioRepository {
},
data: { state: 'failed', errorCode: 'TBANK_UNAVAILABLE' },
});
return result.count === 1;
return result.count === EXPECTED_UPDATED_ACCOUNTS;
}
}