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
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:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user