ForecastRepository.resolve: убран ранний возврат первого пересекающегося fallback до validation/upsert кандидата. Latest выбирается детерминированно (calculatedAt desc, scopeKey asc) в пределах account + текущей membership revision, включая непересекающиеся окна (успешный пустой результат вместо null).
Публикует кандидат только запрос с default-окном (hourly и GET без from/to: replacesLatest). Явный from/to публикует лишь при отсутствии сохранённого forecast. Это решение владельца по BE-013.
Failed/неполный кандидат: возвращается previous complete forecast (решение владельца по BE-013), смешанная база не публикуется.
ForecastService.filterEvents добавляет в ответ исходное окно сохранённого forecast.
Ключ scopeKey и схема БД не менялись: миграции нет, старые строки не трогаются.
Stock dividends и bond coupons: их exact-key логика не изменена, регрессия покрыта существующими тестами.
Verification
Падающие на старом коде (5 из 26) тесты test/upcoming-events.e2e-spec.ts: W0→W1 с изменённой quantity, disjoint = пустой результат, narrow GET не заменяет latest, failed calendar после сдвига окна, публикация без зависимостей.
npm run verify: 42 suites / 310 tests, lint, typecheck app+test, build — OK.
npm run test:e2e (все SQLite-suites): OK. upcoming-events.e2e-spec.ts 26/26.
npm run openapi:generate: openapi.json в .gitignore; schema содержит UpcomingEventsDto.window.
Не проверено в runtime: реальный T-Bank/MOEX (live-вызовы запрещены), PostgreSQL/prod-данные.
Breaking Changes (если есть)
Нет. Additive изменение контракта: UpcomingEventsDto.window: { from: 'YYYY-MM-DD', to: 'YYYY-MM-DD' } (required в ответе). Frontend нужно регенерировать типы; текущий клиент не ломается.
## Summary
Новый полный forecast становится latest независимо от пересечения окон; GET с `from`/`to` читает latest и фильтрует, сохраняя исходные `calculatedAt`/окно.
## Linked Issue
Refs WealthLedger/backend#55 (audit 2026-10-11: BE-001, BE-013)
## Changes
- `ForecastRepository.resolve`: убран ранний возврат первого пересекающегося fallback до validation/upsert кандидата. Latest выбирается детерминированно (`calculatedAt desc, scopeKey asc`) в пределах account + текущей membership revision, включая непересекающиеся окна (успешный пустой результат вместо `null`).
- Публикует кандидат только запрос с default-окном (hourly и GET без `from`/`to`: `replacesLatest`). Явный `from`/`to` публикует лишь при отсутствии сохранённого forecast. Это решение владельца по BE-013.
- Failed/неполный кандидат: возвращается previous complete forecast (решение владельца по BE-013), смешанная база не публикуется.
- `ForecastService.filterEvents` добавляет в ответ исходное окно сохранённого forecast.
- Ключ `scopeKey` и схема БД не менялись: миграции нет, старые строки не трогаются.
- Stock dividends и bond coupons: их exact-key логика не изменена, регрессия покрыта существующими тестами.
## Verification
- Падающие на старом коде (5 из 26) тесты `test/upcoming-events.e2e-spec.ts`: W0→W1 с изменённой quantity, disjoint = пустой результат, narrow GET не заменяет latest, failed calendar после сдвига окна, публикация без зависимостей.
- `npm run verify`: 42 suites / 310 tests, lint, typecheck app+test, build — OK.
- `npm run test:e2e` (все SQLite-suites): OK. `upcoming-events.e2e-spec.ts` 26/26.
- `npm run openapi:generate`: `openapi.json` в `.gitignore`; schema содержит `UpcomingEventsDto.window`.
- Не проверено в runtime: реальный T-Bank/MOEX (live-вызовы запрещены), PostgreSQL/prod-данные.
## Breaking Changes (если есть)
Нет. Additive изменение контракта: `UpcomingEventsDto.window: { from: 'YYYY-MM-DD', to: 'YYYY-MM-DD' }` (required в ответе). Frontend нужно регенерировать типы; текущий клиент не ломается.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Новый полный forecast становится latest независимо от пересечения окон; GET с
from/toчитает latest и фильтрует, сохраняя исходныеcalculatedAt/окно.Linked Issue
Refs WealthLedger/backend#55 (audit 2026-10-11: BE-001, BE-013)
Changes
ForecastRepository.resolve: убран ранний возврат первого пересекающегося fallback до validation/upsert кандидата. Latest выбирается детерминированно (calculatedAt desc, scopeKey asc) в пределах account + текущей membership revision, включая непересекающиеся окна (успешный пустой результат вместоnull).from/to:replacesLatest). Явныйfrom/toпубликует лишь при отсутствии сохранённого forecast. Это решение владельца по BE-013.ForecastService.filterEventsдобавляет в ответ исходное окно сохранённого forecast.scopeKeyи схема БД не менялись: миграции нет, старые строки не трогаются.Verification
test/upcoming-events.e2e-spec.ts: W0→W1 с изменённой quantity, disjoint = пустой результат, narrow GET не заменяет latest, failed calendar после сдвига окна, публикация без зависимостей.npm run verify: 42 suites / 310 tests, lint, typecheck app+test, build — OK.npm run test:e2e(все SQLite-suites): OK.upcoming-events.e2e-spec.ts26/26.npm run openapi:generate:openapi.jsonв.gitignore; schema содержитUpcomingEventsDto.window.Breaking Changes (если есть)
Нет. Additive изменение контракта:
UpcomingEventsDto.window: { from: 'YYYY-MM-DD', to: 'YYYY-MM-DD' }(required в ответе). Frontend нужно регенерировать типы; текущий клиент не ломается.