| 1 | # 03.2 - Research Readiness, Rule Engine, Refresh Planner & Ephemeral Client Data |
| 2 | |
| 3 | ## Iteration 1 outcome |
| 4 | |
| 5 | This iteration inventories the current refresh architecture and adds an additive migration seam. It does not switch any runtime endpoint, delete any old path, alter a provider adapter, migrate portfolio persistence, calculate the proposed weighted score, deploy code, truncate a table, or delete runtime data. |
| 6 | |
| 7 | Files changed in this iteration: |
| 8 | |
| 9 | - `ai/research-engine/app/research_readiness.py`: requirement, readiness, freshness, authority, conflict, coverage, planner, and future external-tool contracts. |
| 10 | - `ai/research-engine/app/portfolio_context.py`: the future ephemeral portfolio context boundary, with no selected implementation. |
| 11 | - `ai/research-engine/tests/test_research_readiness.py`: focused behavior tests. |
| 12 | - `docs/architecture/03.2-safe-research-reset.sql`: generated, unexecuted reset SQL. |
| 13 | - `services/research-service/src/test/java/com/aiinvestment/research/ResearchFlywayMigrationTest.java`: aligns the existing migration assertion with V7 and verifies the protected market-price table. |
| 14 | |
| 15 | Deployment performed: none. Runtime data mutation performed: none. |
| 16 | |
| 17 | ## 1. Complete old refresh inventory |
| 18 | |
| 19 | The inventory was built by searching all source, test, SQL, documentation, and configuration files under `frontend`, `ai/research-engine`, `services/research-service`, `services/portfolio-service`, `services/api-gateway`, `config`, `infrastructure`, `shared`, and `docs`. Generated output, dependency folders, bytecode, logs, binary fixtures, and backup `.bak` files were excluded. Search phrases included all phrases in the iteration request, followed by endpoint-string and symbol/caller tracing. |
| 20 | |
| 21 | The 104 inventory rows each contain exactly one migration classification. “Delete?” means safe to delete in this iteration. No row is approved for deletion. |
| 22 | |
| 23 | ### Frontend |
| 24 | |
| 25 | | File | Symbol / method / endpoint | Current responsibility | Callers | Side effects | Persisted tables | Provider calls | Delete? | Reuse? | Classification | |
| 26 | |---|---|---|---|---|---|---|---|---|---| |
| 27 | | `frontend/app/lib/portfolio-api.ts` | `MarketIntelligenceResearchState`, `ResearchPrefetchResponse` | Models process-local prefetch states returned by the old ensure endpoint. | `investment-workspace.tsx`, frontend prefetch tests. | None. | None. | None; HTTP contract only. | No; active contract. | Partly, until readiness response replaces it. | `REPLACE_ORCHESTRATOR` | |
| 28 | | `frontend/app/lib/portfolio-api.ts` | `ResearchRefreshJob` | Models portfolio-scoped broad refresh job progress. | Workspace polling/progress UI. | None. | Mirrors `research.research_refresh_jobs`. | None. | No; active contract and replacement absent. | No long term; planner jobs need a requirement-level model. | `REMOVE_OBSOLETE_JOB_MODEL` | |
| 29 | | `frontend/app/lib/portfolio-api.ts` | `researchApi.refresh()` -> `POST /companies/{id}/refresh` | Starts explicit company-wide refresh. | Research view refresh callback. | Network POST. | Indirectly all rebuildable research tables and structured snapshots. | Indirectly Yahoo/NSE/search/SEC/EODHD according to identity. | No; live caller. | Endpoint slot can become readiness/planner backed. | `REPLACE_ORCHESTRATOR` | |
| 30 | | `frontend/app/lib/portfolio-api.ts` | `prefetchVisible()`, `getPrefetchState()` | Submits and polls revision-140 visible-result prefetch. | Sector results effect and research-open flow. | Network POST/GET. | Indirect research persistence. | Indirect provider calls when old freshness gate is not complete. | No; accepted behavior is active. | Yes, as a compatibility shell over readiness/single-flight. | `KEEP_SINGLE_FLIGHT` | |
| 31 | | `frontend/app/lib/portfolio-api.ts` | `refreshPortfolio()`, `getRefreshJob()`, `getActiveRefreshJob()` | Starts and polls broad portfolio research jobs. | Workspace portfolio refresh controls/effects. | Network POST/GET. | Mirrors `research_refresh_jobs`. | Indirect per-instrument provider work. | No; live caller. | Only the polling UX pattern may be reused. | `REPLACE_ORCHESTRATOR` | |
| 32 | | `frontend/app/components/investment-workspace.tsx` | visible-result prefetch effect around `researchApi.prefetchVisible(visibleMarketIntelligenceIds)` | Automatically starts research work after sector results render. | React effect keyed by visible global IDs. | Starts remote background work. | Indirect research writes. | Indirect old prefetch orchestration. | No; revision-140 path must remain until planner wiring exists. | Effect timing and dedupe key can be reused. | `KEEP_SINGLE_FLIGHT` | |
| 33 | | `frontend/app/components/investment-workspace.tsx` | `openMarketIntelligenceResearch()` | Selects a canonical instrument, starts prefetch, ensures a regional watchlist, and adds membership. | Sector performance row click. | Creates/reuses watchlist and membership; starts research prefetch. | Portfolio schema `watchlists`, `watchlist_memberships`; indirect research tables. | Indirect old prefetch providers. | No; multiple active behaviors. | Canonical selection and regional watchlist isolation. | `REPLACE_ORCHESTRATOR` | |
| 34 | | `frontend/app/components/investment-workspace.tsx` | prefetch status polling effect | Observes `REFRESHING`, then reloads durable summary without scheduling a second job. | Selected Market Intelligence instrument. | Network reads only. | None directly. | None. | No. | Yes, polling can observe a planner/single-flight job later. | `KEEP_SINGLE_FLIGHT` | |
| 35 | | `frontend/app/components/investment-workspace.tsx` | company `onRefresh` callback and “Refresh research” control | Runs broad company refresh and replaces the visible summary. | Research view. | Network POST and UI state mutation. | Indirect research writes. | Indirect broad provider path. | No; replacement UI is absent. | Selection/loading handling only. | `REMOVE_OBSOLETE_UI` | |
| 36 | | `frontend/app/components/investment-workspace.tsx` | portfolio `onPortfolioRefresh`, active-job discovery/poll/completion effects, `GlobalResearchRefreshProgress`, “Refresh portfolio research” | Manages portfolio-wide refresh lifecycle and reloads the portfolio summary. | Research view for a portfolio context. | Starts job, polls, updates visible state. | Indirect `research_refresh_jobs` plus research tables. | Indirect broad per-instrument provider calls. | No; active and replacement absent. | Progress presentation can be adapted to requirement targets. | `REMOVE_OBSOLETE_UI` | |
| 37 | | `frontend/app/lib/market-intelligence.ts` | `researchStateLabel()` | Presents prefetch state labels. | Market Intelligence rows. | None. | None. | None. | No. | Replace labels with requirement/area readiness later. | `REPLACE_ORCHESTRATOR` | |
| 38 | |
| 39 | ### API gateway and auth boundary |
| 40 | |
| 41 | | File | Symbol / method / endpoint | Current responsibility | Callers | Side effects | Persisted tables | Provider calls | Delete? | Reuse? | Classification | |
| 42 | |---|---|---|---|---|---|---|---|---|---| |
| 43 | | `services/api-gateway/src/main/java/com/aiinvestment/apigateway/PortfolioRouteController.java` | `routeResearch()` / `/api/v1/research/**` | Transparently routes research requests and adds gateway-verified identity headers. | Frontend research API. | Downstream HTTP call; removes untrusted identity headers before adding trusted ones. | None directly. | None directly. | No. | Yes, unchanged for readiness and planner endpoints. | `KEEP_AUTH_IDENTITY` | |
| 44 | | `services/api-gateway/src/main/java/com/aiinvestment/apigateway/GatewayAuthenticationFilter.java` | authenticated request attributes used by `routeResearch()` | Validates the session/JWT identity before routing. | Gateway route controller. | Request attribute mutation only. | None. | Auth service/JWT validation path, not research providers. | No. | Yes. | `KEEP_AUTH_IDENTITY` | |
| 45 | | `services/api-gateway/src/main/resources/application.yml` | `research.engine.base-url` | Routes the gateway to research-engine. | `PortfolioRouteController`. | Configuration only. | None. | None. | No. | Yes. | `KEEP_AUTH_IDENTITY` | |
| 46 | |
| 47 | ### Research API and composition root |
| 48 | |
| 49 | | File | Symbol / method / endpoint | Current responsibility | Callers | Side effects | Persisted tables | Provider calls | Delete? | Reuse? | Classification | |
| 50 | |---|---|---|---|---|---|---|---|---|---| |
| 51 | | `ai/research-engine/app/main.py` | module-level `repository`, `portfolio_orchestrator`, `refresh_job_manager`, `market_intelligence_research_prefetch` | Wires the old orchestration graph. Manager construction marks orphaned running jobs failed. | ASGI process import. | Opens configured persistence; may update orphaned job status. | `research_refresh_jobs`. | None at construction. | No. | Composition root can wire the new services later. | `REPLACE_ORCHESTRATOR` | |
| 52 | | `ai/research-engine/app/main.py` | `ensure_visible_company_research()` / `POST /api/v1/research/prefetch` | Authenticates and submits up to ten global IDs to revision-140 prefetch. | Frontend `prefetchVisible`. | Creates process-local tasks. | Indirect research writes. | Indirect through `_ensure_one`. | No. | Yes, batch/auth boundary and accepted single-flight semantics. | `KEEP_SINGLE_FLIGHT` | |
| 53 | | `ai/research-engine/app/main.py` | `visible_company_research_state()` / `GET /api/v1/research/prefetch/{instrument_id}` | Reads process-local prefetch status without starting provider work. | Frontend polling. | None. | None. | None. | No. | Yes as an observation endpoint until durable planner jobs exist. | `KEEP_SINGLE_FLIGHT` | |
| 54 | | `ai/research-engine/app/main.py` | `refresh()` / `POST /api/v1/research/companies/{instrument_id}/refresh` | Reconciles canonical profile, restores it, then enters broad structured and document refresh. | Frontend `researchApi.refresh`; API tests. | Identity/mapping reconciliation and research writes. | Portfolio `instrument_master`, `instrument_provider_mappings`; research documents/events/facts/shareholding/runs/structured snapshots. | Yahoo, official NSE, registered sources, and configured search. SEC/EODHD are called by the separate revision-140 prefetch sequence. | No; active and no runtime replacement. | Route can call readiness then planner. | `REPLACE_ORCHESTRATOR` | |
| 55 | | `ai/research-engine/app/main.py` | `backfill_company_research()` / `POST /companies/{instrument_id}/backfill` | Forces deep missing-evidence repair while retaining single-flight and durable reuse. | No repository caller found; API remains externally reachable. | Same writes as repository backfill. | Rebuildable research tables and refresh runs. | Official/search fetch paths. | No; external operational use is unproven. | Potential admin repair action behind planner. | `UNKNOWN_REQUIRES_EVIDENCE` | |
| 56 | | `ai/research-engine/app/main.py` | `refresh_portfolio_research()` / `POST /portfolios/{portfolio_id}/refresh` | Authorizes and creates a portfolio-scoped broad refresh job. | Frontend portfolio refresh. | Reads portfolio positions and creates job. | `research_refresh_jobs`; indirect research tables. | Indirect per-instrument. | No. | Auth/error boundary only. | `REPLACE_ORCHESTRATOR` | |
| 57 | | `ai/research-engine/app/main.py` | `refresh_job_status()`, `active_refresh_job()` | Reads old portfolio refresh job lifecycle. | Frontend job polling/recovery. | DB reads. | `research_refresh_jobs`. | None. | No; callers remain. | No long term; replace with requirement-level plan/run state. | `REMOVE_OBSOLETE_JOB_MODEL` | |
| 58 | | `ai/research-engine/app/main.py` | company/events/documents/summary/presentation and portfolio-summary GET routes | Projects existing durable research and canonical identity. | Frontend research reads. | Reads; profile restoration may read canonical identity. | Research documents/events/facts/shareholding/structured snapshots; portfolio identity/positions for portfolio summary. | No research provider on summary routes. | No. | Durable read projection is reusable after readiness is added. | `KEEP_PERSISTENCE` | |
| 59 | | `ai/research-engine/app/main.py` | `structured_market_snapshot()` / `POST /structured-market/snapshot` | Internal provider-neutral quote/snapshot endpoint used by portfolio-service manual price refresh. | `StructuredMarketClient`. | Persists structured snapshot and price observations. | `global_structured_market_snapshots`, `global_market_price_observations`, possibly `global_financial_facts`. | Yahoo structured provider. | No. | Yes; explicitly outside old broad research removal. | `KEEP_MARKET_DATA` | |
| 60 | | `ai/research-engine/app/main.py` | market-data population/ensure/job endpoints | Starts/observes Nifty universe and historical price population. | Frontend market-data ensure and operational flows. | Durable universe/price writes and process-local jobs. | Portfolio `nifty500_universe`; research `global_market_price_observations`. | Nifty/NSE identity and Yahoo historical prices. | No. | Yes. | `KEEP_MARKET_DATA` | |
| 61 | | `ai/research-engine/app/main.py`, `ai/research-engine/app/scheduler.py` | `schedule()`, `default_schedule_rules()` | Exposes source-type intervals; no in-repository scheduler caller was found. | Public GET endpoint and tests/documentation only. | None. | None. | None. | No; external caller is unknown. | Replace source-wide intervals with requirement policies. | `REPLACE_ORCHESTRATOR` | |
| 62 | |
| 63 | ### Research orchestration, state, persistence, and provider boundaries |
| 64 | |
| 65 | | File | Symbol / method / endpoint | Current responsibility | Callers | Side effects | Persisted tables | Provider calls | Delete? | Reuse? | Classification | |
| 66 | |---|---|---|---|---|---|---|---|---|---| |
| 67 | | `ai/research-engine/app/market_intelligence_research.py` | `VisibleResearchEnsureRequest` | Validates a bounded list of canonical global IDs. | Prefetch POST route. | None. | None. | None. | No. | Yes as a batch request primitive. | `KEEP_SINGLE_FLIGHT` | |
| 68 | | `ai/research-engine/app/market_intelligence_research.py` | `MarketIntelligenceResearchPrefetch.submit/state/wait`, `_tasks`, `_is_fresh` | Revision-140 process-local per-instrument task dedupe and freshness reuse. | Prefetch routes and tests. | Creates bounded background tasks; keeps process-local state. | Reads existing research through repository freshness. | None before the freshness miss. | No. | Yes; planner should reuse this behavior internally. | `KEEP_SINGLE_FLIGHT` | |
| 69 | | `ai/research-engine/app/market_intelligence_research.py` | `_ensure_one()` | Restores/reconciles identity, calls international fundamentals, then invokes broad instrument refresh. | Prefetch task. | Identity mapping and research writes. | Portfolio mappings; research facts/documents/events/shareholding/snapshots/runs. | SEC EDGAR/EODHD and old instrument provider flow. | No. | Single-flight wrapper only; sequencing must be replaced by readiness/plan. | `REPLACE_ORCHESTRATOR` | |
| 70 | | `ai/research-engine/app/refresh_jobs.py` | `ResearchRefreshJobManager` | Creates durable portfolio jobs, runs each portfolio instrument, and records aggregate progress/errors. | Portfolio refresh/status API. | Creates asyncio tasks and job rows; on startup abandons orphaned running rows. | `research_refresh_jobs`; indirect research tables. | Indirect through `refresh_portfolio`. | No; live endpoints depend on it. | Only generic task observation/error hygiene. | `REMOVE_OBSOLETE_JOB_MODEL` | |
| 71 | | `ai/research-engine/app/portfolio_orchestration.py` | `global_instrument_metadata()`, `restore_global_profile()`, `reconcile_global_profile()`, `_resolve_profile()`, `_resolve_etf_profile()` | Resolves the canonical global instrument and hydrates a research profile. | Company refresh/read, prefetch, portfolio refresh. | Reconcile POST can update canonical mappings; profile registration is process-local. | Portfolio `instrument_master`, `instrument_provider_mappings`. | Official mapping clients behind portfolio-service; no research content provider. | No. | Yes; must happen before readiness. | `KEEP_CANONICAL_IDENTITY` | |
| 72 | | `ai/research-engine/app/portfolio_orchestration.py` | `_persist_verified_provider_mapping()`, `_trusted_provider_mapping()`, `_hydrate_verified_exchange_mappings()` | Preserves verified provider ownership in portfolio-service and supplies mappings to adapters. | International fundamentals and position projection. | PUT of verified mapping. | Portfolio `instrument_provider_mappings`. | None beyond mapping endpoint. | No. | Yes. | `KEEP_PROVIDER_MAPPING` | |
| 73 | | `ai/research-engine/app/portfolio_orchestration.py` | `refresh_instrument()` | Always considers structured-market reconciliation, then calls ETF or repository research refresh. | Company endpoint, prefetch, portfolio worker. | Structured snapshot/fact writes followed by document research writes. | `global_structured_market_snapshots`, `global_financial_facts`, research documents/events/shareholding/runs. | Yahoo plus repository official/search path. | No. | Provider calls can be decomposed into requirement executors. | `REPLACE_ORCHESTRATOR` | |
| 74 | | `ai/research-engine/app/portfolio_orchestration.py` | `refresh_portfolio()`, `_refresh_portfolio_instrument()` | Fans broad refresh across every deduplicated holding with bounded concurrency and failure isolation. | Job manager and tests. | Reads customer holdings; writes global research; invokes progress callback. | Indirect portfolio positions and research tables. | Per-instrument structured/official/search providers. | No. | Concurrency/failure isolation can be reused after plans exist. | `REPLACE_ORCHESTRATOR` | |
| 75 | | `ai/research-engine/app/portfolio_orchestration.py` | `prepare_portfolio_refresh()`, `_load_positions()`, `_dedupe_instruments()` | Loads persistent positions, converts local IDs to global IDs, copies verified mappings, and deduplicates. | Job submission and portfolio summary. | GET positions also triggers portfolio-service user upsert, quote lookup, and `ensureMaster`. | Portfolio `app_users`, `portfolio_positions`, `instrument_master`, `instrument_provider_mappings`; quote cache. | Market-data provider may run while formatting positions. | No. | Dedupe/global-ID projection belongs behind `PortfolioContextStore`. | `REPLACE_ORCHESTRATOR` | |
| 76 | | `ai/research-engine/app/portfolio_orchestration.py` | `_reconcile_structured_market()`, `_structured_due_classes()`, `_persist_structured_snapshot()`, `structured_quote()` | Uses market-session and per-class freshness to collect/persist structured market data. | `refresh_instrument`, portfolio worker, internal snapshot route. | Snapshot, observation, and Yahoo statement-fact writes; provider failure state. | `global_structured_market_snapshots`, `global_market_price_observations`, `global_financial_facts`, market schedules. | Yahoo Finance. | No. | Yes; planner should invoke it only for matching facts. | `KEEP_MARKET_DATA` | |
| 77 | | `ai/research-engine/app/portfolio_orchestration.py` | `refresh_international_fundamentals()` | Chooses the existing regional fundamentals adapter and persists normalized facts/mappings. | Revision-140 prefetch. | Financial fact upserts and verified mapping PUT. | `global_financial_facts`; portfolio provider mappings. | SEC EDGAR for USA, EODHD for Europe. | No. | Yes as fact-specific execution behind authority policy. | `KEEP_PROVIDER_ADAPTER` | |
| 78 | | `ai/research-engine/app/portfolio_orchestration.py` | `read_portfolio_summary()`, `_read_company_state*()`, `_finalize*()` | Builds a portfolio-keyed research read model from positions and global durable research. | Portfolio summary API. | Primarily reads; position endpoint has the side effects noted above. | Portfolio positions/identity and research facts/documents/events/shareholding/snapshots. | No research provider. | No. | Company projection is reusable; portfolio acquisition must move behind context store. | `REPLACE_ORCHESTRATOR` | |
| 79 | | `ai/research-engine/app/repository.py` | `_instrument_refresh_flights`, `refresh()` and `backfill()` outer task gates | Shares one process-local refresh task per global instrument, including forced backfill. | Orchestrator and tests. | Creates/cleans asyncio tasks. | None directly. | None before delegated task. | No. | Yes; planner execution should retain it. | `KEEP_SINGLE_FLIGHT` | |
| 80 | | `ai/research-engine/app/repository.py` | `_InstrumentRefreshGate`, `_CATEGORY_STRATEGIES`, `_category_is_eligible_to_check()`, `_category_evidence_timing()`, `_category_is_fresh()`, mark/check methods | Implements existing category-specific evidence/check cadence and distinguishes failed checks from successful no-change checks. | Repository refresh and revision-140 state. | Mutates process-local check timestamps; reads durable evidence. | Documents/events/shareholding. | None. | No. | Yes as inputs while moving policy definitions to the new registry. | `KEEP_FRESHNESS_PRIMITIVE` | |
| 81 | | `ai/research-engine/app/repository.py` | `instrument_refresh_state()` | Collapses all old categories into `FRESH_AND_COMPLETE`, `STALE`, `INCOMPLETE`, or lightweight-check state. | Revision-140 prefetch. | Durable reads only. | Documents/events/shareholding. | None. | No. | Single-flight caller can temporarily adapt; aggregate state is replaced by per-requirement readiness. | `REPLACE_ORCHESTRATOR` | |
| 82 | | `ai/research-engine/app/repository.py` | `_refresh_once()`, `_refresh_live()`, `_refresh_targeted()`, `_missing_categories()` | Uses scorer categories plus a hard-coded structured category set to choose and execute official, registered, and search discovery. | `refresh()` / `backfill()`. | Creates/completes runs; fetches, parses, persists, and updates process-local freshness/errors. | `research_refresh_runs`, documents/events/sources/facts/shareholding. | Registered URLs, NSE official filings/shareholding, configured search. | No. | Provider/extraction subroutines only; orchestration logic is replaced. | `REPLACE_ORCHESTRATOR` | |
| 83 | | `ai/research-engine/app/repository.py` | `_official_filing_flights`, `_fetch_official_filing_single_flight()`, `_reusable_official_document()` | Deduplicates official document fetch/extraction and reuses durable successful documents. | Official filing refresh path. | Process-local tasks plus document/fact/event writes on first fetch. | `research_documents`, `global_financial_facts`, `research_events`, `research_event_sources`. | Official filing attachment URL. | No. | Yes. | `KEEP_SINGLE_FLIGHT` | |
| 84 | | `ai/research-engine/app/repository.py` | `_reconcile_incomplete_persisted_official_financial_facts()` and official fact candidates | Repairs canonical facts from already-durable trusted NSE documents before network work. | `_refresh_live()`. | Reconciles source-owned financial facts. | `global_financial_facts`; reads `research_documents`. | None. | No. | Yes; it is DB-first repair. | `KEEP_RESEARCH_EXTRACTION` | |
| 85 | | `ai/research-engine/app/repository.py` | document/event/shareholding/fact/snapshot read and persist methods, `summary()` | Durable provider-neutral research store facade and read model. | Orchestrators, routes, tests. | Durable upserts and reads. | The 11 non-job Flyway-created data tables: documents, events/sources/runs, shareholding, financial facts, structured snapshots, prices, and market calendars. | None. | No. | Yes; implement the readiness data-source adapter here or beside it. | `KEEP_PERSISTENCE` | |
| 86 | | `ai/research-engine/app/persistence.py`, `ai/research-engine/app/postgres_persistence.py` | `ResearchPersistence`, SQLite/disabled/Postgres implementations | Own durable CRUD, merge semantics, schema checks, refresh audit, and job state. | `ResearchRepository`, job manager, tests. | SQL reads/writes; Postgres fails fast when schema is missing. | All 12 Flyway-created data tables; not Flyway history or the ad hoc backup. | PostgreSQL only. | No. | Yes, except portfolio job CRUD becomes obsolete after migration. | `KEEP_PERSISTENCE` | |
| 87 | | `ai/research-engine/app/models.py` | `ResearchDocument`, `ResearchEvent`, `ResearchEvidenceSource`, `ResearchLifecycleStatus`, `ResearchSummary` | Provider-neutral evidence, provenance, lifecycle, and read contracts. | Repository, extraction, scoring, API. | None by themselves. | Mirror documents/events/event sources. | None. | No. | Yes. | `KEEP_EVIDENCE_PARSING` | |
| 88 | | `ai/research-engine/app/models.py` | `ResearchRefreshJob`, portfolio refresh counters in `PortfolioResearchSummary` | Old portfolio-wide job/status response. | Refresh manager, API, frontend. | None by itself. | Mirrors `research_refresh_jobs`. | None. | No; replacement absent. | No long term. | `REMOVE_OBSOLETE_JOB_MODEL` | |
| 89 | | `ai/research-engine/app/settings.py` | research quarterly/shareholding/catalyst/annual/analyst/search cooldown settings | Existing differentiated category/check TTL primitives. | Repository gate and discovery. | Configuration only. | None. | Controls provider cadence. | No. | Yes during migration; map into fact-specific policy adapter. | `KEEP_FRESHNESS_PRIMITIVE` | |
| 90 | | `ai/research-engine/app/settings.py` | structured price/fundamentals/valuation/analyst and historical/Nifty freshness settings | Existing market-data freshness controls. | Structured reconciliation and market-data population/ensure. | Configuration only. | None. | Controls Yahoo/Nifty cadence. | No. | Yes. | `KEEP_MARKET_DATA` | |
| 91 | | `ai/research-engine/app/settings.py` | `portfolio_refresh_instrument_concurrency` | Bounds broad portfolio job fan-out and revision-140 prefetch semaphore. | Orchestrator and prefetch. | Configuration only. | None. | Limits indirect calls. | No. | Keep a renamed requirement-task limit after old job removal. | `REMOVE_OBSOLETE_JOB_MODEL` | |
| 92 | | `ai/research-engine/app/source_registry.py` | `RegisteredResearchSource`, `registered_sources_for()`, `approved_sources_for_categories()` | Maps approved instrument/category sources without embedding them in UI. | Repository primary and targeted discovery. | None. | None. | Supplies URLs to fetcher. | No. | Yes behind fact-specific authority selection. | `KEEP_PROVIDER_MAPPING` | |
| 93 | | `ai/research-engine/app/source_discovery.py` | search provider protocols/adapters and `SearchDiscoveryService` | Bounded discovery of approved publisher URLs with issuer/date/domain validation. | Repository targeted fallback. | Network search and in-memory stats/cache; no evidence persistence itself. | None. | Brave-compatible, Google-compatible, or SearxNG endpoints. | No. | Yes as approved secondary discovery. | `KEEP_PROVIDER_ADAPTER` | |
| 94 | | `ai/research-engine/app/source_discovery.py` | `OfficialFilingDiscovery`, `OfficialNseShareholdingDiscovery`, NSE subtype/shareholding helpers | Discovers official NSE filings and shareholding/XBRL facts. | Repository targeted refresh. | Network reads and normalized snapshot creation. | Persistence occurs in repository. | NSE corporate announcement/shareholding APIs and XBRL URLs. | No. | Yes, unchanged. | `KEEP_PROVIDER_ADAPTER` | |
| 95 | | `ai/research-engine/app/research_fetching.py` | `ResearchFetcher`, `HttpResearchFetcher`, `PlaywrightResearchFetcher` | Fetches approved original sources with SSRF, redirect, size, timeout, retry, and extraction controls. | Repository source ingestion. | Network I/O; no DB writes itself. | None. | Approved source URLs. | No. | Yes. | `KEEP_PROVIDER_ADAPTER` | |
| 96 | | `ai/research-engine/app/international_fundamentals.py` | `InternationalFundamentalProvider`, `SecEdgarFundamentalProvider`, `EodhdFundamentalProvider`, `international_provider_for()` | Existing regional normalized financial-fact adapters. | Portfolio orchestrator/preload tests. | Returns facts and verified IDs; caller persists. | Caller writes `global_financial_facts` and provider mappings. | SEC company tickers/companyfacts; EODHD fundamentals. | No. | Yes, unchanged. | `KEEP_PROVIDER_ADAPTER` | |
| 97 | | `ai/research-engine/app/structured_market.py` | `StructuredResearchProvider`, `YahooFinanceProvider` | Resolves verified Yahoo identity, fetches quote/fundamental/analyst/news/statement facts, rejects non-finite values, caches briefly. | Portfolio orchestrator and internal structured endpoint. | Network and process cache; caller persists. | Caller writes structured snapshots, prices, and facts. | Yahoo query endpoints and `yfinance`. | No. | Yes, unchanged. | `KEEP_PROVIDER_ADAPTER` | |
| 98 | | `ai/research-engine/app/structured_market.py` | identity candidates, exchange-family checks, `strongest_company_identity()` and mapping validation helpers | Protects verified Yahoo/global identity ownership. | Yahoo adapter. | None. | None. | Yahoo search only after mapping rules allow it. | No. | Yes. | `KEEP_PROVIDER_MAPPING` | |
| 99 | | `ai/research-engine/app/fact_precedence.py` | `FinancialFact`, `FactSourceTier`, `merge_fact()`, `fact_source_authority()` | Canonical fact identity and provider precedence; prevents fallback overwrite of official facts. | Persistence and provider normalizers. | None by itself. | Mirrors `global_financial_facts`. | None. | No. | Yes; authority registry complements rather than replaces durable tier identities. | `KEEP_PERSISTENCE` | |
| 100 | | `ai/research-engine/app/shareholding.py` | NSE XBRL/document parsers | Converts official source payloads into provenanced shareholding snapshots. | Repository and tests. | None until repository persists. | `global_shareholding_snapshots`, child values via caller. | None. | No. | Yes, unchanged. | `KEEP_RESEARCH_EXTRACTION` | |
| 101 | | `ai/research-engine/app/structured_research.py` | financial statement, shareholding, valuation, catalyst parsers/projections | Extracts and projects structured facts from durable evidence. | Repository summary/orchestrator/tests. | None by itself. | Reads document/fact/shareholding models. | None. | No. | Yes, unchanged. | `KEEP_RESEARCH_EXTRACTION` | |
| 102 | | `ai/research-engine/app/extraction.py`, `ai/research-engine/app/normalization.py`, `ai/research-engine/app/deduplication.py` | rule extraction, normalization, content/URL dedupe | Converts fetched documents to canonical evidence/events safely. | Repository fetch pipeline. | In-memory transformation; repository persists results. | Documents/events through caller. | None. | No. | Yes. | `KEEP_EVIDENCE_PARSING` | |
| 103 | | `ai/research-engine/app/entity_resolution.py` | `EntityResolver` | Resolves document evidence to a stable company/instrument using stronger identity than ticker alone. | Repository ingestion. | None. | None. | None. | No. | Yes. | `KEEP_CANONICAL_IDENTITY` | |
| 104 | | `ai/research-engine/app/scoring.py` | `CatalystScorer`, current category aliases and aggregate score | Produces the existing event/catalyst score, which does not match the new 11-area weighted rule contract. | Repository summaries/leaderboard/tests. | None. | Read model only. | None. | No; current product still consumes it. | Evidence/category logic needs an explicit mapping decision. | `UNKNOWN_REQUIRES_EVIDENCE` | |
| 105 | | `ai/research-engine/app/historical_market_data.py` | `YahooHistoricalPriceProvider`, `HistoricalPricePopulationService`, `has_year_historical_coverage()` | Daily historical price acquisition, finite-close filtering, incremental persistence, and coverage checks. | India market population jobs/tests. | Writes durable price observations. | `global_market_price_observations`. | Yahoo historical prices. | No. | Yes, unchanged. | `KEEP_MARKET_DATA` | |
| 106 | | `ai/research-engine/app/market_data_population.py`, `ai/research-engine/app/market_data_ensure.py`, `ai/research-engine/app/market_sessions.py`, `ai/research-engine/app/market_universe.py` | India population jobs, ensure single-flight/cooldowns, session rules, Nifty/global universe | Owns revision-139/140 market coverage behavior independently of research documents. | Market API routes and tests. | Nifty reference refresh, price population, process-local tasks/cooldowns. | Portfolio Nifty/identity tables; research price observations and schedules. | Nifty official CSV/NSE identity/Yahoo historical. | No. | Yes, unchanged. | `KEEP_MARKET_DATA` | |
| 107 | | `ai/research-engine/app/watchlists.py` | watchlist request models and `watchlist_research_projection()` | Composes regional membership with durable global research without refresh or holding mutation. | Watchlist API routes. | Read projection only. | Reads portfolio watchlists/identity and research data. | None. | No. | Yes. | `KEEP_PERSISTENCE` | |
| 108 | |
| 109 | ### Research-service schema, portfolio-service dependencies, and configuration |
| 110 | |
| 111 | | File | Symbol / method / endpoint | Current responsibility | Callers | Side effects | Persisted tables | Provider calls | Delete? | Reuse? | Classification | |
| 112 | |---|---|---|---|---|---|---|---|---|---| |
| 113 | | `services/research-service/src/main/resources/db/migration/V1__research_intelligence.sql` | research documents/events/sources/runs DDL | Creates durable evidence and refresh audit schema. | Flyway. | Schema creation. | Four named tables. | None. | No. | Yes. | `KEEP_PERSISTENCE` | |
| 114 | | `services/research-service/src/main/resources/db/migration/V2__global_shareholding_snapshots.sql` | shareholding DDL | Creates official global shareholding snapshots/values. | Flyway. | Schema creation. | Two shareholding tables. | None. | No. | Yes. | `KEEP_PERSISTENCE` | |
| 115 | | `services/research-service/src/main/resources/db/migration/V3__research_refresh_jobs.sql` | portfolio refresh job DDL | Creates old durable portfolio-scoped job state. | Flyway and Python persistence. | Schema creation. | `research_refresh_jobs`. | None. | No; migration history cannot be edited and runtime uses table. | Table can be cleared, then retired only by a future forward migration. | `REMOVE_OBSOLETE_JOB_MODEL` | |
| 116 | | `services/research-service/src/main/resources/db/migration/V4__global_financial_facts.sql` | normalized fact DDL | Creates canonical provider-neutral financial fact store. | Flyway. | Schema creation. | `global_financial_facts`. | None. | No. | Yes. | `KEEP_PERSISTENCE` | |
| 117 | | `services/research-service/src/main/resources/db/migration/V5__structured_market_snapshots_and_market_sessions.sql`, `services/research-service/src/main/resources/db/migration/V7__global_market_price_observations.sql` | structured snapshot, schedules, calendar, and observations DDL | Creates market-data state and NSE session defaults. | Flyway and market code. | Schema creation/seeding. | Four market-data tables. | None. | No. | Yes, preserve during reset. | `KEEP_MARKET_DATA` | |
| 118 | | `services/research-service/src/main/resources/db/migration/V6__research_document_subtype.sql` | document subtype migration | Adds extraction metadata. | Flyway/repository. | Schema alteration. | `research_documents`. | None. | No. | Yes. | `KEEP_EVIDENCE_PARSING` | |
| 119 | | `services/research-service/src/main/resources/application.yml` | Flyway `research` schema/history configuration | Owns production schema migration and history table. | Research-service startup. | Applies forward migrations. | `flyway_schema_history_research` and migration tables. | PostgreSQL only. | No. | Yes. | `KEEP_PERSISTENCE` | |
| 120 | | `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/api/PortfolioController.java` | `positions()` / `GET /portfolios/{id}/positions` | Returns persisted holdings with quotes and canonical master/mappings. | Research `_load_positions` and frontend. | `AppUserProvisioner.upsert`, quote lookup, `InstrumentMasterService.ensureMaster`. | `app_users`, `portfolio_positions`, instrument master/mappings; quote cache. | Configured market-data provider. | No. | Put behind `PortfolioContextStore`; add identity-only projection later. | `REPLACE_ORCHESTRATOR` | |
| 121 | | `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/application/PortfolioService.java` | `getPositions()`, `getPortfolio()`, repositories | Enforces owner-scoped reads from persistent portfolio rows. | Portfolio controller. | DB reads; other service methods write/sync positions. | `portfolios`, `portfolio_positions` and related broker/history tables. | None in `getPositions`. | No. | DEV Postgres context adapter can wrap it temporarily. | `KEEP_PERSISTENCE` | |
| 122 | | `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/api/GlobalInstrumentController.java`, `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/application/InstrumentMasterService.java` | global lookup/enumeration/reconcile/verified-mapping endpoints and methods | Owns canonical identity and verified provider mappings independently of portfolios. | Research orchestrator, market jobs, portfolio responses. | Canonical/master and mapping upserts during reconcile. | `instrument_master`, `instrument_provider_mappings`. | Official NSE mapping clients where configured. | No. | Yes. | `KEEP_CANONICAL_IDENTITY` | |
| 123 | | `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/application/StructuredMarketClient.java`, `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/application/ManualMarketPriceService.java` | global structured snapshot client and manual public-price refresh | Updates quote cache without changing position quantities/cost. | Manual portfolio price endpoint. | Research snapshot POST and quote-cache update. | Research market tables; quote cache. | Yahoo via research engine. | No. | Yes. | `KEEP_MARKET_DATA` | |
| 124 | | `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/application/Nifty500ReferenceService.java`, `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/api/IndiaMarketUniverseController.java` | Official Nifty universe refresh/read. | Research market universe/population. | Updates global master/mappings and Nifty cache. | `nifty500_universe`, instrument master/mappings. | Nifty constituent CSV and NSE security master. | No. | Yes, unchanged. | `KEEP_MARKET_DATA` | |
| 125 | | `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/api/WatchlistController.java`, `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/application/WatchlistService.java`, `services/portfolio-service/src/main/resources/db/migration/V18__regional_research_watchlists.sql` | Authenticated regional watchlist persistence isolated from portfolios. | Research watchlist proxy and frontend. | Watchlist/membership writes only. | `watchlists`, `watchlist_memberships`. | None. | No. | Yes. | `KEEP_PERSISTENCE` | |
| 126 | | `config/dev/application.env.example`, `config/prd/application.env.example` | research live/search/persistence, cooldown, structured freshness settings | Runtime configuration examples. | Deploy/local operators. | Configuration only. | None. | Enables/configures providers. | No. | Yes; add fact-policy overrides only when operationally needed. | `KEEP_FRESHNESS_PRIMITIVE` | |
| 127 | | `infrastructure/helm/ai-investment-platform/templates/ai-services.yaml`, `infrastructure/helm/ai-investment-platform/values.yaml`, `infrastructure/helm/ai-investment-platform/values-dev.yaml`, `infrastructure/helm/ai-investment-platform/values-prd.yaml` | research deployment, persistence, provider/search limits, cooldown | Deploys research-engine and waits for Flyway readiness. | Helm. | Kubernetes resources/secrets/config. | Indirect research DB. | Enables configured provider endpoints. | No. | Yes. | `KEEP_PERSISTENCE` | |
| 128 | | `docs/research-intelligence.md` | Phase 3/4 refresh pipeline and API description | Documents the current broad refresh architecture. | Engineers/operators. | None. | Names current tables. | Names source paths. | No; historical/current behavior remains relevant. | Update after runtime cutover. | `REPLACE_ORCHESTRATOR` | |
| 129 | |
| 130 | ### Adjacent freshness and portfolio-privacy dependencies |
| 131 | |
| 132 | These files matched the repository-wide terms but do not initiate research refreshes. They are included because they either preserve market-data freshness or carry private portfolio/broker state across the boundary. |
| 133 | |
| 134 | | File | Symbol / method / endpoint | Current responsibility | Callers | Side effects | Persisted tables | Provider calls | Delete? | Reuse? | Classification | |
| 135 | |---|---|---|---|---|---|---|---|---|---| |
| 136 | | `docs/architecture.md`, `docs/architecture/phase-5e-scalable-multi-broker-architecture.md` | broker orchestration and broker/quote freshness architecture | Documents broker ownership, persistent portfolios, and separate quote freshness. | Engineers/operators. | None. | Documents portfolio/broker tables. | Names broker and market adapters. | No. | Preserve as migration context; update only when the privacy design is implemented. | `KEEP_PERSISTENCE` | |
| 137 | | `docs/frontend-ui-ux-requirements.md`, `docs/market-data.md` | freshness vocabulary and display requirements | Defines public market quote provenance/freshness independently of research readiness. | Frontend and market-data implementers. | None. | None. | None. | No. | Yes; research facts must not replace quote freshness. | `KEEP_MARKET_DATA` | |
| 138 | | `services/broker-service/src/main/java/com/aiinvestment/broker/api/BrokerConnectionResponse.java`, `services/broker-service/src/main/java/com/aiinvestment/broker/api/BrokerProviderResponse.java`, `services/broker-service/src/main/java/com/aiinvestment/broker/api/BrokerSnapshotResponse.java`, `services/broker-service/src/main/java/com/aiinvestment/broker/application/BrokerConnectionService.java`, `services/broker-service/src/main/java/com/aiinvestment/broker/domain/BrokerConnection.java`, `services/broker-service/src/main/java/com/aiinvestment/broker/persistence/BrokerConnectionEntity.java`, `services/broker-service/src/main/java/com/aiinvestment/broker/persistence/BrokerConnectionMapper.java`, `services/broker-service/src/main/resources/db/migration/V3__ibkr_read_only_metadata.sql` | broker connection/snapshot freshness and position snapshot transport | Persists connection/session metadata and sends private broker positions to portfolio-service. | Broker APIs and portfolio `BrokerSnapshotClient`. | Broker connection updates and snapshot reads. | Broker `broker_connections`; portfolio persistence occurs downstream. | Configured broker connector, not research providers. | No; outside this iteration. | Later feed an ephemeral portfolio context without entering global research. | `KEEP_PERSISTENCE` | |
| 139 | | `services/broker-service/src/test/java/com/aiinvestment/broker/api/BrokerControllerTest.java`, `services/broker-service/src/test/java/com/aiinvestment/broker/application/BrokerConnectionServiceStatusTest.java` | broker data-freshness contract tests | Protects broker status/snapshot behavior. | Maven. | Test doubles/DB only. | Broker test schema. | Mocked. | No. | Yes. | `KEEP_PERSISTENCE` | |
| 140 | | `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/api/CombinedPortfolioHoldingResponse.java`, `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/api/PortfolioHistoryResponse.java`, `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/api/PortfolioPositionResponse.java`, `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/application/BrokerSnapshotClientResponse.java`, `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/domain/PortfolioPosition.java`, `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/domain/PortfolioValuationPoint.java`, `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/infrastructure/persistence/PortfolioMapper.java`, `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/infrastructure/persistence/PortfolioPositionEntity.java`, `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/infrastructure/persistence/PortfolioValuationSnapshotEntity.java`, `services/portfolio-service/src/main/resources/db/migration/V3__broker_position_provenance.sql`, `services/portfolio-service/src/main/resources/db/migration/V7__portfolio_valuation_snapshots.sql` | private holding quantity/cost/value/P&L and its source freshness | Portfolio service, controller projections, history, and research `_load_positions`. | Persistent position/valuation reads and writes. | `portfolio_positions`, `portfolio_valuation_snapshots` and related portfolio rows. | None directly. | No; portfolio migration is deferred. | These types must move behind or be projected by `PortfolioContextStore`. | `KEEP_PERSISTENCE` | |
| 141 | | `services/portfolio-service/src/test/java/com/aiinvestment/portfolio/application/PortfolioImportServiceIntegrationTest.java`, `services/portfolio-service/src/test/java/com/aiinvestment/portfolio/application/PortfolioServiceIntegrationTest.java` | portfolio persistence, isolation, valuation, and freshness tests | Protects the current Postgres implementation and ownership boundary. | Maven. | H2/test persistence. | Portfolio test schema. | Mocked broker/market paths. | No. | Reuse for the DEV adapter; add shared contract tests for memory/Redis implementations later. | `KEEP_PERSISTENCE` | |
| 142 | | `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/api/QuoteResponse.java`, `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/infrastructure/market/FallbackMarketDataProvider.java`, `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/infrastructure/market/MockMarketDataProvider.java`, `services/portfolio-service/src/main/java/com/aiinvestment/portfolio/infrastructure/market/RedisQuoteCache.java` | current quote provenance, status, cache, and fallback | Portfolio reads/manual public-price refresh. | Quote cache reads/writes. | Redis quote cache. | Configured market-data provider. | No. | Yes, independently of research refresh. | `KEEP_MARKET_DATA` | |
| 143 | | `services/portfolio-service/src/test/java/com/aiinvestment/portfolio/application/PlatformEventSerializationTest.java`, `services/portfolio-service/src/test/java/com/aiinvestment/portfolio/infrastructure/market/FallbackMarketDataProviderTest.java`, `services/portfolio-service/src/test/java/com/aiinvestment/portfolio/infrastructure/market/MockMarketDataProviderTest.java`, `services/portfolio-service/src/test/java/com/aiinvestment/portfolio/infrastructure/market/RedisQuoteCacheTest.java` | market freshness/cache/event contract tests | Protects the public market-data boundary. | Maven. | Test cache/doubles only. | None durable in these tests. | Mocked. | No. | Yes. | `KEEP_MARKET_DATA` | |
| 144 | | `shared/java/domain/src/main/java/com/aiinvestment/shared/domain/event/MarketQuoteUpdatedEvent.java`, `shared/java/domain/src/main/java/com/aiinvestment/shared/domain/market/DataProvenance.java`, `shared/java/domain/src/main/java/com/aiinvestment/shared/domain/market/MarketDataFreshness.java`, `shared/java/domain/src/main/java/com/aiinvestment/shared/domain/market/MarketDataStatus.java`, `shared/java/domain/src/main/java/com/aiinvestment/shared/domain/market/Quote.java` | shared quote freshness/provenance contracts | Portfolio market providers and platform events. | None. | None. | None. | No. | Yes, unchanged. | `KEEP_MARKET_DATA` | |
| 145 | |
| 146 | `frontend/tsconfig.tsbuildinfo` was the only unmatched generated search hit. It is a TypeScript build artifact, not source or configuration, and is excluded from the code inventory. |
| 147 | |
| 148 | ### Relevant regression tests |
| 149 | |
| 150 | | File | Symbol / method / endpoint | Current responsibility | Callers | Side effects | Persisted tables | Provider calls | Delete? | Reuse? | Classification | |
| 151 | |---|---|---|---|---|---|---|---|---|---| |
| 152 | | `ai/research-engine/tests/test_market_intelligence_research_prefetch.py` | prefetch freshness, bounded batch, identity, failure isolation, task dedupe tests | Protects revision-140 single-flight behavior. | Pytest. | Test doubles/process tasks only. | None. | Stubbed. | No. | Yes. | `KEEP_SINGLE_FLIGHT` | |
| 153 | | `ai/research-engine/tests/test_research_engine.py` | repository refresh, targeted discovery, category cadence, official filing single-flight/backfill tests | Protects current provider/persistence primitives and old orchestration. | Pytest. | SQLite/test-double writes. | SQLite mirror. | Mock transports. | No. | Keep primitive assertions; replace broad orchestration assertions after cutover. | `KEEP_FRESHNESS_PRIMITIVE` | |
| 154 | | `ai/research-engine/tests/test_research_api.py`, `ai/research-engine/tests/test_portfolio_orchestration_timeout.py` | company/portfolio refresh routes and orchestration/error behavior | Protects current endpoints and broad workflow. | Pytest. | Test client and doubles. | Test persistence. | Stubbed. | No. | Rewrite against readiness/planner during cutover. | `REPLACE_ORCHESTRATOR` | |
| 155 | | `ai/research-engine/tests/test_official_nse_financial_parsing.py`, `ai/research-engine/tests/test_shareholding.py` | official NSE financial/shareholding freshness, parsing, persistence, and regressions | Protects accepted India provider behavior. | Pytest. | Test persistence. | SQLite mirror. | Fixtures/mocks. | No. | Yes, unchanged. | `KEEP_RESEARCH_EXTRACTION` | |
| 156 | | `ai/research-engine/tests/test_structured_market.py`, `ai/research-engine/tests/test_structured_market_persistence.py`, `ai/research-engine/tests/test_international_mapping_boundary.py` | Yahoo/SEC/EODHD mapping, structured freshness, finite values, persistence | Protects accepted structured/provider behavior. | Pytest. | Test persistence/cache. | SQLite mirror. | Mocked. | No. | Yes, unchanged. | `KEEP_PROVIDER_ADAPTER` | |
| 157 | | `ai/research-engine/tests/test_market_data_ensure.py`, `ai/research-engine/tests/test_india_market_data_population.py`, `ai/research-engine/tests/test_historical_market_data.py`, `ai/research-engine/tests/test_market_sessions.py`, `ai/research-engine/tests/test_global_instrument_universe.py` | Nifty, 481/481 population assumptions, daily history, mapping, and sessions | Protects market-data revisions 139/140. | Pytest. | Test jobs/persistence. | SQLite/test doubles. | Fixtures/mocks. | No. | Yes, unchanged. | `KEEP_MARKET_DATA` | |
| 158 | | `ai/research-engine/tests/test_watchlists.py` | Route separation and no-refresh watchlist projection | Protects regional watchlist isolation. | Pytest. | Test doubles. | None. | None. | No. | Yes. | `KEEP_PERSISTENCE` | |
| 159 | | `frontend/tests/market-intelligence-prefetch.test.mjs` | detached prefetch, backend-owned provider routing, no duplicate scheduling | Protects revision-140 UI behavior. | Node test runner. | Source inspection only. | None. | None. | No. | Yes until planner UI cutover. | `KEEP_SINGLE_FLIGHT` | |
| 160 | | `frontend/tests/public-research-ui.test.mjs`, `frontend/tests/phase5e-ui.test.mjs` | refresh controls/job progress and canonical identity UI behavior | Protects current broad UI and reusable identity rules. | Node test runner. | Source inspection only. | None. | None. | No. | Split identity assertions from obsolete refresh assertions in Iteration 2. | `REMOVE_OBSOLETE_UI` | |
| 161 | | `frontend/tests/market-int-sector-and-watchlist.test.mjs` | click prefetch plus regional watchlist/portfolio isolation | Protects combined current click flow. | Node test runner. | Source inspection only. | None. | None. | No. | Keep watchlist isolation; replace prefetch expectation. | `REPLACE_ORCHESTRATOR` | |
| 162 | | `services/research-service/src/test/java/com/aiinvestment/research/ResearchFlywayMigrationTest.java` | Verifies research schema tables/columns/history. | Maven test. | H2 schema creation. | Research schema. | None. | No. | Yes; expected latest version must track V7. | `KEEP_PERSISTENCE` | |
| 163 | | `services/api-gateway/src/test/java/com/aiinvestment/apigateway/PortfolioRouteControllerTest.java`, `services/api-gateway/src/test/java/com/aiinvestment/apigateway/GatewayAuthenticationFilterTest.java` | Verifies routing and trusted identity propagation. | Maven test. | Mock HTTP only. | None. | None. | No. | Yes. | `KEEP_AUTH_IDENTITY` | |
| 164 | | `services/portfolio-service/src/test/java/com/aiinvestment/portfolio/api/PortfolioControllerTest.java`, `services/portfolio-service/src/test/java/com/aiinvestment/portfolio/api/WatchlistControllerTest.java`, `services/portfolio-service/src/test/java/com/aiinvestment/portfolio/application/GlobalInstrumentReconciliationServiceTest.java`, `services/portfolio-service/src/test/java/com/aiinvestment/portfolio/application/InstrumentMasterServiceTest.java`, `services/portfolio-service/src/test/java/com/aiinvestment/portfolio/application/Nifty500ReferenceServiceTest.java`, `services/portfolio-service/src/test/java/com/aiinvestment/portfolio/application/NseIdentityReconciliationLifecycleTest.java`, `services/portfolio-service/src/test/java/com/aiinvestment/portfolio/application/NseMappingReconciliationServiceTest.java`, `services/portfolio-service/src/test/java/com/aiinvestment/portfolio/application/StructuredMarketClientTest.java` | Protects position ownership, global identity, Nifty, mappings, watchlists, and market data. | Maven test. | Test DB/mocks. | Portfolio test schema. | Mocked. | No. | Yes. | `KEEP_CANONICAL_IDENTITY` | |
| 165 | |
| 166 | ## 2. KEEP / REPLACE / REMOVE decision |
| 167 | |
| 168 | No physical removal is safe yet. Live callers still exist for company refresh, portfolio refresh jobs, and prefetch. The replacement data-source adapter and planner executor are intentionally not wired in this iteration. |
| 169 | |
| 170 | Across 104 inventory rows: 73 are `KEEP_*`, 20 are `REPLACE_ORCHESTRATOR`, 9 are removal candidates after cutover (6 job-model and 3 UI rows), and 2 are `UNKNOWN_REQUIRES_EVIDENCE`. No endpoint is classified `REMOVE_OBSOLETE_ENDPOINT` because every candidate is still externally reachable or has an active caller. |
| 171 | |
| 172 | Keep unchanged: |
| 173 | |
| 174 | - provider adapters and approved-source fetching; |
| 175 | - canonical instrument identity and verified provider mapping ownership; |
| 176 | - durable research persistence, normalized facts, evidence parsing, and NSE/XBRL extraction; |
| 177 | - market data, market sessions, Nifty universe, historical population, and non-finite Yahoo-close handling; |
| 178 | - process-local instrument/filing/prefetch single-flight and existing successful-no-change cadence; |
| 179 | - gateway authentication and regional watchlist isolation. |
| 180 | |
| 181 | Replace behind stable boundaries: |
| 182 | |
| 183 | - scorer-coverage-as-requirements and the hard-coded structured category set; |
| 184 | - company and portfolio broad refresh orchestration; |
| 185 | - prefetch `_ensure_one()` provider sequence; |
| 186 | - direct portfolio-position acquisition in research orchestration; |
| 187 | - source-type schedule policy and broad UI refresh actions. |
| 188 | |
| 189 | Remove only after Iteration 2 cutover and focused caller tests: |
| 190 | |
| 191 | - portfolio-wide `ResearchRefreshJob` model/table API usage; |
| 192 | - broad “Refresh research” and “Refresh portfolio research” UI flows; |
| 193 | - old endpoints only after compatibility routing is no longer needed. |
| 194 | |
| 195 | `backfill_company_research` remains `UNKNOWN_REQUIRES_EVIDENCE`: no repository caller was found, but external operational use cannot be disproved from source. |
| 196 | |
| 197 | ## 3. New contracts added |
| 198 | |
| 199 | `research_readiness.py` adds: |
| 200 | |
| 201 | - `ResearchRequirementRegistry` and `ResearchRequirement`; |
| 202 | - `ResearchRequirementStatus` with `READY_FRESH`, `READY_STALE`, `PARTIAL`, `MISSING`, `CONFLICTING`, `UNSUPPORTED`, `REFRESHING`, and `FAILED`; |
| 203 | - `ResearchReadinessDataSource`, `DurableResearchSnapshot`, `ResearchReadinessService`, `ResearchRequirementReadiness`, and `ResearchReadinessResult`; |
| 204 | - `FreshnessPolicyRegistry`, `FreshnessPolicy`, and explicit policy modes; |
| 205 | - `ResearchRefreshPlanner`, `ResearchRefreshPlan`, and `ResearchRefreshTarget`; |
| 206 | - `ProviderAuthorityRegistry`, `ProviderAuthorityPolicy`, `ProviderAuthority`, and `ProviderFallbackPolicy`; |
| 207 | - `ResearchConflictResolver` and `ResearchCoverageService`; |
| 208 | - `ExternalResearchToolGateway`, an interface only, with no MCP implementation; |
| 209 | - `UPLOAD_EVIDENCE`, `FIND_DATA`, and `RUN_PARTIAL_ANALYSIS` actions. |
| 210 | |
| 211 | Each per-requirement result exposes `requirementId`, `ruleEngineArea`, `mandatory`, `status`, `source`, `sourceTier`, `asOf`, `retrievedAt`, `age`, `freshnessPolicy`, `evidenceIds`, `missingReason`, `conflictReason`, and `supportedActions` in provider-neutral Python form. An API serializer is deferred until the durable adapter and endpoint are wired. |
| 212 | |
| 213 | `portfolio_context.py` adds immutable `PortfolioContext` and `PortfolioHoldingContext` values plus the `PortfolioContextStore` protocol. It selects no storage technology and changes no existing portfolio persistence. |
| 214 | |
| 215 | ## 4. Rule Engine areas and weights |
| 216 | |
| 217 | | Area | Weight | Default requirement IDs | |
| 218 | |---|---:|---| |
| 219 | | Valuation | 18% | `VALUATION_INPUTS` | |
| 220 | | Fundamental / Business Quality | 16% | `BUSINESS_QUALITY_FACTS` | |
| 221 | | Growth | 14% | `GROWTH_FACTS` | |
| 222 | | Balance Sheet | 9% | `BALANCE_SHEET_FACTS` | |
| 223 | | Quarterly / Earnings Trend | 9% | `QUARTERLY_FINANCIALS` | |
| 224 | | Order Book / Capacity / Catalysts | 8% | `ORDER_BOOK_CAPEX_GUIDANCE` | |
| 225 | | Price / Technical | 7% | `LATEST_PRICE`, `HISTORICAL_PRICE_SERIES` | |
| 226 | | News / Geopolitical / Events | 7% | `CURRENT_NEWS` | |
| 227 | | Shareholding | 4% | `SHAREHOLDING` | |
| 228 | | Management / Governance | 5% | `GOVERNANCE_HISTORY` | |
| 229 | | Sector / Macro | 3% | `SECTOR_MACRO` | |
| 230 | |
| 231 | Weights are registry metadata only. No new final score is calculated. |
| 232 | |
| 233 | The default registry treats `ORDER_BOOK_CAPEX_GUIDANCE` and `SHAREHOLDING` as optional because they are not universally applicable across issuers and jurisdictions. Readiness still reports them, but the planner does not spend provider capacity on a missing optional requirement. Iteration 2 can select a jurisdiction/instrument-specific registry profile where either becomes mandatory. |
| 234 | |
| 235 | ## 5. Fact-specific freshness |
| 236 | |
| 237 | | Policy | Mode | Conservative fallback when no calendar/session `validUntil` is supplied | Special rule | |
| 238 | |---|---|---:|---| |
| 239 | | `LATEST_PRICE` | market/session aware | 15 minutes | Adapter may supply an exchange/session-derived `validUntil`. | |
| 240 | | `HISTORICAL_PRICE_SERIES` | daily incremental | 36 hours | Advance the durable daily series; do not rebuild it on a research click. | |
| 241 | | `QUARTERLY_FINANCIALS` | release aware | 120 days | Adapter may supply issuer release-calendar `validUntil`. | |
| 242 | | `ANNUAL_FINANCIALS` | reporting-calendar aware | 400 days | Adapter may supply reporting-calendar `validUntil`. | |
| 243 | | `SHAREHOLDING` | quarterly | 120 days | Official jurisdiction support remains explicit. | |
| 244 | | `ORDER_BOOK_CAPEX_GUIDANCE` | event driven plus bounded periodic check | 30 days | A successful no-change check must not manufacture evidence. | |
| 245 | | `VALUATION_INPUTS` | fact-specific daily | 1 day | Authority remains separate from age. | |
| 246 | | `CURRENT_NEWS` | rolling event/publication window | 30 days | Evidence older than 30 days is excluded from current readiness/score input, but remains durable. Retrieval time cannot make old news current. | |
| 247 | | `GOVERNANCE_HISTORY` | unresolved history | 365-day fallback | Old unresolved evidence remains ready/queryable regardless of age. | |
| 248 | | `SECTOR_MACRO` | fact-specific daily | 1 day | Later adapters can split event and daily subfacts. | |
| 249 | |
| 250 | ## 6. Provider authority and fallback |
| 251 | |
| 252 | Authority is keyed by requirement and jurisdiction: |
| 253 | |
| 254 | - India financial facts: NSE / company filing, then licensed structured, approved secondary, approved external tool, user upload. |
| 255 | - USA financial facts: SEC EDGAR / company filing, then licensed structured, approved secondary, approved external tool, user upload. |
| 256 | - Europe financial facts: regulatory / company filing, EODHD, approved secondary, approved external tool, user upload. |
| 257 | - Current/historical price: configured market-data source selected by exchange/session/freshness, then exchange/licensed/Yahoo fallback as configured. |
| 258 | - India shareholding: NSE / company filing, then licensed structured, approved external tool, user upload; other jurisdictions use their regulatory/company filing policy. |
| 259 | - Order book/capex/guidance: company filing, regulatory filing, approved secondary, approved external tool, user upload. |
| 260 | - Current news: regulatory/company evidence, reputable news, approved external tool, user upload. |
| 261 | - Governance history: regulator/court record, company filing, reputable news, approved external tool, user upload. |
| 262 | - Sector/macro: official statistics or central bank, exchange/index provider, licensed structured, reputable news, approved external tool, user upload. |
| 263 | - Identity: ISIN or permanent ID, then exact exchange plus symbol, then name confirmation. |
| 264 | |
| 265 | Equal-authority evidence conflicts only when it names the same canonical `factKey` and has different normalized `valueFingerprint` values. Lower-authority evidence does not turn a selected official fact into a conflict. `USER_UPLOAD` is below equivalent official/regulatory/structured data by default. |
| 266 | |
| 267 | Every default requirement has an explicit global fact policy, with India/USA/Europe overrides where authority differs. An unregistered requirement has no generic provider ranking and fails closed until its fact-specific policy is configured. |
| 268 | |
| 269 | The fallback policy permits the future external-tool gateway only for `MISSING`, `CONFLICTING`, or evidence below the configured confidence threshold. No LLM chooses fallback timing. No MCP or broker tool is implemented. |
| 270 | |
| 271 | ## 7. Current call graph |
| 272 | |
| 273 | ```mermaid |
| 274 | flowchart TD |
| 275 | UI[Frontend refresh / prefetch action] --> GW[Gateway trusted research route] |
| 276 | GW --> C{Current entry} |
| 277 | C -->|company| CR[Company refresh endpoint] |
| 278 | C -->|portfolio| PJ[Portfolio refresh job] |
| 279 | C -->|visible results| PF[Revision-140 prefetch] |
| 280 | PJ --> POS[Persistent portfolio positions API] |
| 281 | POS --> FAN[Fan out every deduplicated holding] |
| 282 | CR --> ID[Canonical profile reconcile] |
| 283 | PF --> ID |
| 284 | FAN --> ID |
| 285 | ID --> SM[Structured market reconciliation] |
| 286 | SM --> Y[Yahoo structured adapter] |
| 287 | PF --> IF[International fundamentals] |
| 288 | IF --> SEC[SEC EDGAR] |
| 289 | IF --> EOD[EODHD] |
| 290 | SM --> RR[Repository aggregate gate] |
| 291 | RR --> NSE[NSE filings / shareholding] |
| 292 | RR --> SRC[Registered sources] |
| 293 | RR --> SEARCH[Approved search fallback] |
| 294 | Y --> DB[(Durable global research / market DB)] |
| 295 | SEC --> DB |
| 296 | EOD --> DB |
| 297 | NSE --> DB |
| 298 | SRC --> DB |
| 299 | SEARCH --> DB |
| 300 | ``` |
| 301 | |
| 302 | The company path reconciles identity before it evaluates durable category state. The portfolio path obtains every holding first. Structured reconciliation happens before the repository’s aggregate gate, although its own per-class freshness can avoid a Yahoo call. The repository then uses scorer evidence and hard-coded categories to choose targeted document discovery. |
| 303 | |
| 304 | ## 8. Proposed call graph |
| 305 | |
| 306 | ```mermaid |
| 307 | flowchart TD |
| 308 | CLICK[Research click] --> AUTH[Gateway auth identity] |
| 309 | AUTH --> ID[Resolve canonical globalInstrumentId] |
| 310 | ID --> READ[Load durable research once] |
| 311 | READ --> READY[ResearchReadinessService] |
| 312 | REG[Requirement registry] --> READY |
| 313 | FRESH[Fact freshness policies] --> READY |
| 314 | CONFLICT[Conflict / coverage services] --> READY |
| 315 | READY --> VIEW[Return per-area readiness] |
| 316 | READY --> PLAN[ResearchRefreshPlanner] |
| 317 | PLAN --> TARGETS[Only mandatory stale / missing / partial / conflicting / failed requirements] |
| 318 | TARGETS --> SF[Retained per-instrument / per-fact single-flight] |
| 319 | SF --> AUTHORITY[Fact + jurisdiction provider authority] |
| 320 | AUTHORITY --> PROVIDERS[Existing NSE / SEC / EODHD / Yahoo / approved source adapters] |
| 321 | PROVIDERS --> DB[(Durable public-company research)] |
| 322 | DB --> READY2[Re-evaluate readiness] |
| 323 | READY2 --> GATE[Completeness / confidence gate] |
| 324 | GATE --> RULES[Deterministic Rule Engine later] |
| 325 | GATE --> EXPLAIN[LLM / RAG explanation later] |
| 326 | MCP[Future external-tool gateway] -. policy-controlled fallback only .-> AUTHORITY |
| 327 | PCS[Future PortfolioContextStore] -->|global IDs only| ID |
| 328 | ``` |
| 329 | |
| 330 | ## 9. Research database reset inventory |
| 331 | |
| 332 | The running DEV PostgreSQL schema was inspected read-only on 2026-09-10. It contains all V1-V7 Flyway tables plus one manually created IRFC backup table. Counts are evidence for this inventory and will naturally change with runtime activity. |
| 333 | |
| 334 | A read-only cross-schema check also found 481 distinct Nifty-universe instruments with a `VERIFIED` Yahoo mapping and durable price observations for all 481. The full Nifty reference table has 498 rows; the 17 without a verified Yahoo mapping are outside that accepted 481-instrument population set. |
| 335 | |
| 336 | | Table | Live rows observed | Contents | Classification | Reset decision | |
| 337 | |---|---:|---|---|---| |
| 338 | | `research.flyway_schema_history_research` | 8 | Schema creation plus successful migrations V1-V7. | `PRESERVE_SCHEMA_HISTORY` | Never delete/reset. | |
| 339 | | `research.research_documents` | 836 | Normalized research documents, provenance, hashes, status, subtype, normalized text. | `REBUILDABLE_RESEARCH_DATA` | Delete after child evidence/shareholding links. | |
| 340 | | `research.research_events` | 997 | Extracted, validated/rejected event facts and lifecycle. | `REBUILDABLE_RESEARCH_DATA` | Delete after event-source links. | |
| 341 | | `research.research_event_sources` | 997 | Evidence/source links and excerpts. | `REBUILDABLE_RESEARCH_DATA` | Delete first. | |
| 342 | | `research.research_refresh_runs` | 1,941 | Instrument refresh audit/status/counters/errors. | `REBUILDABLE_RESEARCH_DATA` | Delete after writers stop. | |
| 343 | | `research.global_shareholding_snapshots` | 374 | Official/global quarterly shareholding snapshots and provenance. | `REBUILDABLE_RESEARCH_DATA` | Delete after child values; before documents. | |
| 344 | | `research.global_shareholding_snapshot_values` | 2,729 | Category percentages and source evidence. | `REBUILDABLE_RESEARCH_DATA` | Delete before snapshots. | |
| 345 | | `research.research_refresh_jobs` | 25 | Portfolio-scoped broad job/status rows. | `REBUILDABLE_RESEARCH_DATA` | Delete; model retirement requires a later migration. | |
| 346 | | `research.global_financial_facts` | 14,019 | Canonical normalized financial facts and source tiers. | `REBUILDABLE_RESEARCH_DATA` | Delete only when provider rebuild prerequisites are accepted. | |
| 347 | | `research.global_structured_market_snapshots` | 498 | Mixed current price, valuation, fundamentals, analyst facts, failure/freshness state. | `PRESERVE_MARKET_DATA` | Preserve because the table mixes market state with research facts. Split later before considering partial reset. | |
| 348 | | `research.global_market_price_observations` | 132,100 | Durable historical market prices, including accepted India population. | `PRESERVE_MARKET_DATA` | Never include in research reset. | |
| 349 | | `research.market_trading_schedules` | 5 | NSE session rules. | `PRESERVE_MARKET_DATA` | Preserve. | |
| 350 | | `research.market_trading_calendar_exceptions` | 0 | Session holidays/special sessions. | `PRESERVE_MARKET_DATA` | Preserve. | |
| 351 | | `research.irfc_financial_facts_backup_20260902` | 19 | Ad hoc copy of IRFC fact columns; not present in V1-V7 Flyway. | `UNKNOWN` | Preserve until owner, retention need, and restore history are established. | |
| 352 | |
| 353 | No table in the live `research` schema currently owns canonical instrument identity; that reference remains in portfolio-service `instrument_master` and `instrument_provider_mappings`, both outside this reset. |
| 354 | |
| 355 | No dedicated summary or score table exists in the live research schema. Summaries and the current catalyst score are calculated read models over durable facts/evidence, so there is no additional reset target for them. |
| 356 | |
| 357 | The exact generated SQL is [03.2-safe-research-reset.sql](./03.2-safe-research-reset.sql). It takes fail-fast locks, deletes only the eight `REBUILDABLE_RESEARCH_DATA` tables in foreign-key order, and verifies protected table counts in the same transaction. It uses no `TRUNCATE` and no `CASCADE`. It was not executed. |
| 358 | |
| 359 | ## 10. Portfolio persistence coupling points |
| 360 | |
| 361 | | Coupling point | Why it blocks an ephemeral production store | Migration seam | |
| 362 | |---|---|---| |
| 363 | | `PortfolioResearchOrchestrator._load_positions()` calls `/api/v1/portfolios/{portfolioId}/positions`. | Research orchestration knows a persistent portfolio ID and assumes rows can be reloaded later. | Inject `PortfolioContextStore`; planner receives canonical IDs from one short-lived context read. | |
| 364 | | `prepare_portfolio_refresh()` and `read_portfolio_summary()` both call `_load_positions()`. | Both refresh and presentation depend on durable holdings. | Split global company research reads from private context composition. | |
| 365 | | Portfolio positions response includes quantity, average cost, market value, P&L, source metadata, and quote. | More private data crosses into research-engine than readiness needs. | Add an identity-only projection for research, or project `global_instrument_ids` inside the context owner. | |
| 366 | | `PortfolioController.positions()` calls `AppUserProvisioner.upsert()`. | A nominal research read can write a persistent user row. | Authenticate outside the context store and avoid user provisioning on readiness reads. | |
| 367 | | `PortfolioController.positions()` calls `marketDataProvider.getQuote()` and `InstrumentMasterService.ensureMaster()`. | Loading research scope can call market data and mutate canonical mapping state before readiness. | Resolve identity explicitly once, then perform a pure context read and DB-first readiness. | |
| 368 | | `ResearchRefreshJob` and `research_refresh_jobs` store `portfolio_id` and `requested_by`. | Job recovery assumes durable customer/portfolio identity. | Make runs global-instrument/requirement scoped; keep private request correlation in TTL context only. | |
| 369 | | `PortfolioResearchSummary` is keyed by `portfolio_id`; frontend effects recover active jobs by that ID. | UI recovery assumes durable portfolio-scoped jobs. | Return global readiness separately and compose it with session context client-side/service-side. | |
| 370 | | `_dedupe_instruments()` copies `_positionDataFreshness` and private display-name overrides into research input. | Global research execution is influenced by holding-specific metadata. | Pass canonical global ID plus public identity only; keep display/private state in the context projection. | |
| 371 | | Portfolio-service repositories and entities directly persist portfolios, positions, broker holdings, manual-import snapshots, cash balances, position history, and valuation history. | A Redis/in-memory implementation cannot replace these through one interface today. | Later add `PostgresPortfolioContextStore` for DEV, `InMemoryPortfolioContextStore` for tests/local, and `RedisEphemeralPortfolioContextStore` for production, then move callers incrementally. | |
| 372 | | Regional watchlists are durable and user-owned but deliberately contain no quantities/cost/P&L. | They are private preferences, not portfolio financial state. | Keep their isolation; decide retention independently of the portfolio context migration. | |
| 373 | |
| 374 | ## 11. Tests and validation |
| 375 | |
| 376 | The new focused suite contains 20 tests covering: |
| 377 | |
| 378 | 1. fresh mandatory evidence yields an empty plan and zero provider candidates; |
| 379 | 2. stale mandatory evidence yields only that target; |
| 380 | 3. missing mandatory evidence yields a targeted candidate; |
| 381 | 4. incomplete mandatory evidence is `PARTIAL` and targeted; |
| 382 | 5. unrelated fresh data is excluded from the plan; |
| 383 | 6. equal-authority fact disagreement is `CONFLICTING`; |
| 384 | 7. unsupported facts are `UNSUPPORTED` and unscheduled; |
| 385 | 8. mandatory/optional distinctions survive readiness and planning; |
| 386 | 9. current news older than 30 days is excluded from readiness/score input without deletion; |
| 387 | 10. old unresolved governance evidence remains queryable and ready; |
| 388 | 11. canonical non-zero `globalInstrumentId` is required before a durable read; |
| 389 | 12. readiness and portfolio projection do not mutate holding values; |
| 390 | 13. market price and historical series keep separate freshness modes and are not spuriously targeted; |
| 391 | 14. existing Yahoo/SEC/EODHD adapter contracts remain importable; |
| 392 | 15. revision-140 prefetch single-flight methods remain present; |
| 393 | 16. all requested readiness fields and weights are present; |
| 394 | 17. uploaded evidence does not outrank official evidence; |
| 395 | 18. external-tool fallback is deterministic and policy controlled; |
| 396 | 19. provider authority differs by fact and unregistered facts fail closed; |
| 397 | 20. `REFRESHING` and `FAILED` are representable and planned correctly. |
| 398 | |
| 399 | Validation completed on 2026-09-10: |
| 400 | |
| 401 | | Command/scope | Result | |
| 402 | |---|---| |
| 403 | | `python -m py_compile app/research_readiness.py app/portfolio_context.py` | Passed. | |
| 404 | | `python -m pytest -q tests/test_research_readiness.py` | 20 passed. | |
| 405 | | All 26 `ai/research-engine/tests/test_*.py` files | 562 unique cases passed: the final 20 readiness cases, 486 existing provider/orchestration cases from the combined run, and 56 remaining configuration/presentation/watchlist cases; one existing Starlette/httpx deprecation warning. The last authority-policy edit is imported only by the 20-case readiness suite. | |
| 406 | | Research-service Flyway test after aligning its stale V6 assertion with the existing V7 migration | 1 passed. It now verifies `global_market_price_observations` columns as well as schema version 7. | |
| 407 | | Selected portfolio identity, mapping, Nifty, structured-market, watchlist, and portfolio endpoint tests | 76 passed. | |
| 408 | | Gateway authentication tests | 4 passed. | |
| 409 | | Total selected passing Java tests | 81 passed, 0 failures, 0 errors, 0 skipped. | |
| 410 | | Four refresh/prefetch/watchlist frontend source-inspection files | 74 passed; 1 unrelated stale assertion in `frontend/tests/phase5e-ui.test.mjs` still expects the removed source expression `dashboard.combinedHoldings.map`. No frontend file was changed in this iteration. | |
| 411 | | Full `PortfolioRouteControllerTest` diagnostic run | 5 passed; 2 unrelated connector-session fixture errors use `/connector-sessions/test`, while current routing requires a connector UUID. Gateway authentication passed separately. No gateway file was changed. | |
| 412 | |
| 413 | `ruff` is not installed in the available Python environment, so its configured lint command could not be run. Python compilation and the full research-engine pytest suite passed. |
| 414 | |
| 415 | ## 12. Stable paths preserved |
| 416 | |
| 417 | Iteration 1 makes no edits to existing NSE fundamentals, NSE shareholding/XBRL, Nifty universe, historical population, Yahoo quote/history/mapping, SEC EDGAR, EODHD, canonical identity, provider mapping, market observation, watchlist, or single-flight implementation files. The new module imports none of those adapters and never invokes a provider. |
| 418 | |
| 419 | ## 13. Recommended Iteration 2 scope |
| 420 | |
| 421 | 1. Implement a read-only `ResearchReadinessDataSource` adapter over existing durable repository methods. Map documents, events, financial facts, shareholding snapshots, structured snapshots, price observations, and successful-no-change checks to canonical requirement evidence without changing provider code. |
| 422 | 2. Add an authenticated `GET /api/v1/research/companies/{globalInstrumentId}/readiness` endpoint and contract serializer. Prove it makes no provider call and no portfolio mutation. |
| 423 | 3. Add a requirement-level planner execution coordinator that reuses `_instrument_refresh_flights`, official-filing single-flight, revision-140 task dedupe, and existing provider persistence. Start with one low-risk requirement per provider path rather than switching every category together. |
| 424 | 4. Route the Research click through readiness. Present missing/stale/unsupported/conflicting areas and supported actions. Keep old refresh endpoints as compatibility shims until focused caller tests pass. |
| 425 | 5. Enforce the 30-day current-news selection at the Rule Engine input boundary while keeping old governance/litigation/guidance evidence in durable queries. |
| 426 | 6. Add completeness/confidence gate contracts; do not add the final weighted score or LLM/RAG explanation yet. |
| 427 | 7. Introduce an identity-only `PortfolioContextStore` adapter for the research scope. Keep existing Postgres portfolio persistence in DEV; do not move quantities/cost/P&L until its independent privacy migration is designed and tested. |
| 428 | 8. Replace portfolio-scoped refresh job creation with global-instrument/requirement run state. Only after frontend and API callers move should a forward migration retire `research_refresh_jobs` and old controls. |
| 429 | 9. Do not reset data during Iteration 2. If a reset is later approved, re-inventory the live schema and backup table immediately before running the generated transactional SQL. |