refactor: centralize domain values and enforce numeric lint

This commit is contained in:
2026-09-13 14:37:39 +03:00
parent 8ba005346d
commit 8637ad2f34
44 changed files with 543 additions and 174 deletions
+15 -9
View File
@@ -2,6 +2,12 @@ import { Injectable, ServiceUnavailableException } from '@nestjs/common';
import { load } from 'cheerio';
import { LRUCache } from 'lru-cache';
const REGISTRY_CACHE_TTL_MS = 21_600_000;
const REQUEST_TIMEOUT_MS = 10_000;
const COLUMN_COUNT = 6;
const ISIN_COLUMN = 3;
const DATE_COLUMN = 5;
const registryUrl = 'https://ratings.ru/ratings/issues/';
export type NcrRating = {
agency: 'NCR';
@@ -18,7 +24,7 @@ export class NcrRatingsClient {
private readonly cache = new LRUCache<
string,
Promise<Map<string, NcrRating>>
>({ max: 1, ttl: 6 * 60 * 60_000 });
>({ max: 1, ttl: REGISTRY_CACHE_TTL_MS });
async rating(isin: string): Promise<NcrRating | null> {
const key = isin.trim().toUpperCase();
@@ -38,7 +44,7 @@ export class NcrRatingsClient {
private async registry(): Promise<Map<string, NcrRating>> {
const response = await fetch(registryUrl, {
signal: AbortSignal.timeout(10_000),
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
});
if (!response.ok) throw new Error('NCR unavailable');
const $ = load(await response.text());
@@ -49,18 +55,18 @@ export class NcrRatingsClient {
.get();
if (
table.length !== 1 ||
headings.length !== 6 ||
headings.length !== COLUMN_COUNT ||
headings[2] !== 'Рейтинг' ||
headings[3] !== 'ISIN' ||
headings[5] !== 'Дата'
headings[ISIN_COLUMN] !== 'ISIN' ||
headings[DATE_COLUMN] !== 'Дата'
)
throw new Error('NCR registry changed');
const observedAt = new Date().toISOString();
const result = new Map<string, NcrRating>();
table.find('tbody tr').each((_, row) => {
const cells = $(row).find('td');
if (cells.length !== 6) throw new Error('NCR row changed');
const isin = cells.eq(3).text().trim().toUpperCase();
if (cells.length !== COLUMN_COUNT) throw new Error('NCR row changed');
const isin = cells.eq(ISIN_COLUMN).text().trim().toUpperCase();
// Unassigned issues cannot safely be matched to a held instrument.
if (!/^[A-Z]{2}[A-Z0-9]{9}\d$/.test(isin)) return;
const raw = cells.eq(2).text().replace(/\s+/g, ' ').trim();
@@ -73,9 +79,9 @@ export class NcrRatingsClient {
)
throw new Error('NCR rating changed');
const dateParts = /^(\d{2})\.(\d{2})\.(\d{4})$/.exec(
cells.eq(5).text().trim(),
cells.eq(DATE_COLUMN).text().trim(),
);
const href = cells.eq(3).find('a').attr('href');
const href = cells.eq(ISIN_COLUMN).find('a').attr('href');
if (!dateParts || !href || !/^\/ratings\/issues\/\d+\/$/.test(href))
throw new Error('NCR identity missing');
const date = `${dateParts[3]}-${dateParts[2]}-${dateParts[1]}`;