import { Injectable } from '@nestjs/common'; import { investmentResourceMeta } from '../../investment-resource.mapper'; import type { InvestmentResourceMetaDto } from '../../investment-resource.dto'; import { ForecastRepository, type ForecastSourceVersion, } from '../infrastructure/forecast.repository'; @Injectable() export class ForecastService { constructor(private readonly repository: ForecastRepository) {} async result(input: { userId: string; revision: string; membershipRevision: number; scopeKey: string; portfolio: InvestmentResourceMetaDto; sources: { version: ForecastSourceVersion | null; meta: InvestmentResourceMetaDto; }[]; candidate?: T; }) { const dependencies = [ input.portfolio, ...input.sources.map((source) => source.meta), ]; const stored = await this.repository.resolve({ ...input, portfolioProjection: input.portfolio.dataRevision, portfolios: (input.portfolio.dependencies ?? []).flatMap((dependency) => dependency.resourceKey.startsWith('portfolio/') ? [ { accountId: decodeURIComponent( dependency.resourceKey.slice('portfolio/'.length), ), dataRevision: Number( (dependency as InvestmentResourceMetaDto).dataRevision, ), }, ] : [], ), sources: input.sources.flatMap((source) => source.version ? [source.version] : [], ), timestamps: dependencies.flatMap((dependency) => dependency.lastSuccessfulAt ? [ { resourceKey: dependency.resourceKey, at: dependency.lastSuccessfulAt, }, ] : [], ), }); const timestamps = stored?.sourceTimestamps as { resourceKey: string; at: string }[] | undefined; const first = timestamps?.map((entry) => entry.at).sort()[0]; const work = dependencies .map((dependency) => dependency.sync) .find((sync) => sync.state === 'running') ?? dependencies .map((dependency) => dependency.sync) .find((sync) => sync.state === 'failed'); return { data: stored ? (stored.payload as T) : null, meta: investmentResourceMeta({ resourceKey: input.scopeKey, dataRevision: String(stored?.dataRevision ?? 0), availability: stored ? 'complete' : dependencies.some( (dependency) => dependency.availability !== 'missing', ) ? 'partial' : 'missing', fetchedAt: first ? new Date(first) : null, freshForMs: Number.POSITIVE_INFINITY, work: work?.id ? { ...work, id: work.id } : null, dependencies, }), }; } }