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
@@ -27,6 +27,11 @@ import { CandleRepository } from '../infrastructure/candle.repository';
import { CatalogEnrichmentService } from './catalog-enrichment.service';
import { timestamp } from './instrument-market';
const FIRST_BATCH_OFFSET = 0;
const BACKGROUND_SYNC_PRIORITY = 2;
const REPLACED_WINDOW_COUNT = 1;
const NEXT_WINDOW_OFFSET = 1;
type CandleInput = {
instrumentType: 'bond' | 'share';
period: 'all' | 'halfYear' | 'month' | 'year';
@@ -246,7 +251,7 @@ export class CandleService {
)
.map(([uid]) => uid);
for (
let offset = 0;
let offset = FIRST_BATCH_OFFSET;
offset < uids.length;
offset += prefetchReadBatchSize
) {
@@ -297,7 +302,7 @@ export class CandleService {
now,
).length
) {
await this.request(userId, revision, input, now, false, 2);
await this.request(userId, revision, input, now, false, BACKGROUND_SYNC_PRIORITY);
}
}
}
@@ -393,7 +398,7 @@ export class CandleService {
const gaps = candleGaps(window, latest.coverage, now)
.flatMap(candleWindows)
.reverse();
ranges.splice(index, 1, ...gaps);
ranges.splice(index, REPLACED_WINDOW_COUNT, ...gaps);
// eslint-disable-next-line security/detect-object-injection -- The index is the internal numeric window cursor.
window = ranges[index];
}
@@ -430,7 +435,7 @@ export class CandleService {
) {
return false;
}
pass.nextWindow = index + 1;
pass.nextWindow = index + NEXT_WINDOW_OFFSET;
if (pass.nextWindow < ranges.length) return null;
}
return true;
@@ -512,7 +517,7 @@ export class CandleService {
{ ...producer.input, period: 'all' },
producer.now,
false,
2,
BACKGROUND_SYNC_PRIORITY,
);
}
}
@@ -26,6 +26,10 @@ import {
selectCatalogInstruments,
} from './catalog-selection';
const UNAMBIGUOUS_MATCH_COUNT = 1;
const NO_MATCHES = 0;
const FIRST_RESULT_OFFSET = 0;
const tickerAmbiguityLimit = 2;
type Producer = {
@@ -160,7 +164,7 @@ export class CatalogDiscoveryService {
{ instrumentType, query: instrument.ticker, exactTicker: true },
observe,
);
if (alias.data?.length === 1 && alias.data[0].uid === uid) {
if (alias.data?.length === UNAMBIGUOUS_MATCH_COUNT && alias.data[0].uid === uid) {
slug = instrument.ticker.toUpperCase();
}
dependencies.push(alias.meta);
@@ -200,8 +204,8 @@ export class CatalogDiscoveryService {
{ instrumentType, query: ticker, exactTicker: true },
observe,
);
if (lookup.data?.length === 0) throw new NotFoundException();
if (lookup.data && lookup.data.length > 1) {
if (lookup.data?.length === NO_MATCHES) throw new NotFoundException();
if (lookup.data && lookup.data.length > UNAMBIGUOUS_MATCH_COUNT) {
throw new ConflictException({
code:
instrumentType === 'share' ?
@@ -346,7 +350,7 @@ export class CatalogDiscoveryService {
input.query,
input.instrumentType,
).slice(
0,
FIRST_RESULT_OFFSET,
input.exactTicker ? tickerAmbiguityLimit : catalogSearchLimit,
),
new Date(),
@@ -364,7 +368,7 @@ export class CatalogDiscoveryService {
if (success) {
const saved = await this.repository.read(input);
for (const observer of [attempt, ...producer.observers]) {
if (!input.exactTicker || saved.data?.length === 1) {
if (!input.exactTicker || saved.data?.length === UNAMBIGUOUS_MATCH_COUNT) {
await this.enrichment.requestMany(
observer.userId,
observer.revision,
@@ -39,6 +39,10 @@ import {
import { catalogSearchLimit } from './catalog-selection';
import { previousClose, timestamp } from './instrument-market';
const MISSING_DATA_REVISION = 0;
const FIRST_QUEUE_INDEX = 0;
const DEQUEUED_ITEM_COUNT = 1;
type Producer = {
attempt: TbankSyncAttempt;
input: CatalogSource;
@@ -147,7 +151,7 @@ export class CatalogEnrichmentService {
price: payload?.price ?? null,
meta: investmentResourceMeta({
resourceKey: catalogScope(input),
dataRevision: String(row?.dataRevision ?? 0),
dataRevision: String(row?.dataRevision ?? MISSING_DATA_REVISION),
availability: row ? 'complete' : 'missing',
fetchedAt: row?.fetchedAt ?? null,
freshForMs: tbankSourceTtlMs[sourcePolicy(input.kind)],
@@ -485,7 +489,7 @@ export class CatalogEnrichmentService {
// One batch RPC for queued prices sharing the same authorized producer.
if (producer.input.kind === 'last-price') {
for (
let i = 0;
let i = FIRST_QUEUE_INDEX;
i < this.queue.length && batch.length < catalogSearchLimit;
) {
// eslint-disable-next-line security/detect-object-injection -- Numeric queue indexes and validated instrument UIDs select internal data only.
@@ -496,7 +500,7 @@ export class CatalogEnrichmentService {
next.attempt.revision === producer.attempt.revision
) {
batch.push(next);
this.queue.splice(i, 1);
this.queue.splice(i, DEQUEUED_ITEM_COUNT);
} else i++;
}
}
@@ -3,19 +3,21 @@ import type {
TbankTimestamp,
} from '../../../../integrations/tbank/tbank.api';
import { MILLISECONDS_PER_SECOND } from '../../../../common/time';
import { ISO_DATE_LENGTH, ISO_DATE_START, MILLISECONDS_PER_SECOND } from '../../../../common/time';
const MIN_TIMESTAMP_SECONDS_EXCLUSIVE = 0;
const MOSCOW_OFFSET_MS = 10_800_000;
export function timestamp(value?: TbankTimestamp): null | string {
const seconds = Number(value?.seconds);
return Number.isFinite(seconds) && seconds > 0 ?
return Number.isFinite(seconds) && seconds > MIN_TIMESTAMP_SECONDS_EXCLUSIVE ?
new Date(seconds * MILLISECONDS_PER_SECOND).toISOString() :
null;
}
export function moscowDay(iso: string): string {
return new Date(Date.parse(iso) + MOSCOW_OFFSET_MS)
.toISOString()
.slice(0, 10);
.slice(ISO_DATE_START, ISO_DATE_LENGTH);
}
export function previousClose(
candles: TbankCandle[],
@@ -29,7 +31,7 @@ export function previousClose(
candle.isComplete &&
candle.close &&
time &&
time.slice(0, 10) < moscowDay(priceAt)
time.slice(ISO_DATE_START, ISO_DATE_LENGTH) < moscowDay(priceAt)
);
})
.sort((a, b) => Number(b.time?.seconds) - Number(a.time?.seconds))[0] ??
@@ -12,6 +12,10 @@ import {
} from '../../portfolio/portfolio.api';
import { StockPositionDto } from '../stocks/presentation/stock.dto';
const EMPTY_POSITIONS_COUNT = 0;
const ZERO_QUANTITY = 0n;
const ZERO_AMOUNT = 0n;
const PRICE_DECIMAL_PLACES = 9;
const RETURN_PERCENT_DECIMAL_PLACES = 6;
@@ -32,8 +36,8 @@ export function instrumentPosition(
const currentValue = sumComplete(positions.map((p) => p.currentValue));
const absoluteReturn = sumComplete(positions.map((p) => p.absoluteReturn));
const positive =
positions.length > 0 &&
positions.every((p) => decimalFromString(p.quantity).value > 0n);
positions.length > EMPTY_POSITIONS_COUNT &&
positions.every((p) => decimalFromString(p.quantity).value > ZERO_QUANTITY);
return {
quantity: formatDecimal(quantity),
currentValue,
@@ -55,7 +59,7 @@ export function instrumentPosition(
accounts: positions.map((p) => ({
...p,
returnPercent:
decimalFromString(p.quantity).value > 0n ?
decimalFromString(p.quantity).value > ZERO_QUANTITY ?
percent(p.absoluteReturn, cost(p)) :
null,
})),
@@ -103,7 +107,7 @@ function percent(
!base ||
!result ||
base.currency !== result.currency ||
decimalFromString(base.value).value <= 0n
decimalFromString(base.value).value <= ZERO_AMOUNT
) {
return null;
}