import { Injectable } from '@nestjs/common'; import type { InvestmentResourceMetaDto } from '../../investment-resource.dto'; import { investmentResourceMeta } from '../../investment-resource.mapper'; import { ForecastRepository, type ForecastSourceVersion, } from '../infrastructure/forecast.repository'; const MISSING_DATA_REVISION = 0; @Injectable() export class ForecastService { constructor(private readonly repository: ForecastRepository) {} async result(input: { candidate?: T; membershipRevision: number; portfolio: InvestmentResourceMetaDto; revision: string; scopeKey: string; sources: { meta: InvestmentResourceMetaDto; version: ForecastSourceVersion | null; }[]; userId: string; }) { 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 undefined | { at: string; resourceKey: string }[]; 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 ?? MISSING_DATA_REVISION), 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, }), }; } }