refactor!: support RUB only (WealthLedger/main#55)
Backend verify / commits (pull_request) Successful in 1m38s
Backend verify / security (pull_request) Successful in 1m28s
Backend verify / verify (pull_request) Successful in 8m40s

Filter non-RUB data at ingest and collapse per-currency collections
into a single RUB Money. Drop currency from TbankPortfolioValuePoint,
remove NON_RUB_ACTIVITY and the screener currency filter.

BREAKING CHANGE: per-currency API fields replaced with single RUB Money.
This commit is contained in:
2026-11-11 09:40:25 +03:00
parent 85d65f8997
commit 83df5f0be6
61 changed files with 617 additions and 735 deletions
@@ -49,7 +49,8 @@ export class CatalogDiscoveryService {
? this.tbank.getShare(token, uid)
: this.tbank.getBond(token, uid),
);
if (!instrument) throw new NotFoundException();
// RUB-only: a non-RUB instrument line (e.g. USD line of an SPB-traded stock) is not available.
if (!instrument || instrument.currency?.toUpperCase() !== 'RUB') throw new NotFoundException();
return {
data: { ...publicInstrument({ ...instrument, uid }), uid, slug: instrument.ticker?.toUpperCase() ?? uid },
meta: investmentResourceMeta({
@@ -74,10 +75,18 @@ export class CatalogDiscoveryService {
? this.tbank.findShares(token, query)
: this.tbank.findBonds(token, query),
);
const matches = selectCatalogInstruments(
const candidates = selectCatalogInstruments(
found.filter((item) => item.ticker?.trim().toUpperCase() === query),
query,
instrumentType,
);
const details = await this.access.online(userId, ({ token }) => Promise.all(
candidates.map((item) => instrumentType === 'share'
? this.tbank.getShare(token, item.uid)
: this.tbank.getBond(token, item.uid)),
));
const matches = candidates.filter((_item, index) =>
details.at(index)?.currency?.trim().toUpperCase() === 'RUB',
).slice(FIRST_RESULT_OFFSET, tickerAmbiguityLimit);
if (!matches.length) throw new NotFoundException();
if (matches.length > UNAMBIGUOUS_MATCH_COUNT) {
@@ -104,7 +113,7 @@ export class CatalogDiscoveryService {
: found,
input.query,
input.instrumentType,
).slice(FIRST_RESULT_OFFSET, input.exactTicker ? tickerAmbiguityLimit : catalogSearchLimit);
);
const [details, prices] = await Promise.all([
Promise.all(
instruments.map((item) =>
@@ -122,12 +131,13 @@ export class CatalogDiscoveryService {
const fetchedAt = new Date();
const data = instruments.flatMap((item, index) => {
const detail = details.at(index);
if (detail?.currency?.trim().toUpperCase() !== 'RUB') return [];
return [{
...item,
detail: detail ? publicInstrument({ ...detail, uid: item.uid }) : null,
price: prices.find((price) => price.instrumentUid === item.uid) ?? null,
}];
});
}).slice(FIRST_RESULT_OFFSET, input.exactTicker ? tickerAmbiguityLimit : catalogSearchLimit);
return {
data,
meta: investmentResourceMeta({