Three root causes addressed:
1. nginx routing gap — bare GET /api (no trailing slash) fell through
`location /api/` to the Vite dev proxy, which forwarded it to the
backend as an unmatched path. Added `location = /api` exact-match
block before the prefix block to catch it and proxy directly to
the backend health handler.
2. AppController root handler — added @Get() (maps to GET /api with
global prefix) so bare /api requests return a clean 200 instead of
a 404 that registers as a phantom NR transaction.
3. New Relic transaction naming — NestJS's setGlobalPrefix('api')
causes NR's Express instrumentation to bucket ALL requests into the
generic "Expressjs/GET/api$" segment, making per-endpoint APM data
completely useless. The new NewRelicTransactionInterceptor calls
newrelic.setTransactionName() with "METHOD /route/pattern" for
every request (after routing, so req.route is populated with the
matched template). Gracefully no-ops in dev where NR is not loaded.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Make all AI endpoints (health scores + investment recommendations)
fire-and-forget: POST returns immediately, frontend polls for results
- Extend AI API timeout from 2-5 min to 10 min for both services
- Add "last analysis failed — showing cached data" message to the
Investment Recommendations panel (matches health score widgets)
- Add status/error_message columns to ai_recommendations table
- Remove nginx AI timeout overrides (no longer needed)
- Users can now navigate away during AI processing without interruption
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The AI recommendation endpoint calls a large language model (397B params)
which can take 60-120 seconds to respond. Nginx's default 60s proxy_read_timeout
was killing the connection before the response arrived. Added a dedicated
location block with 180s timeout for the recommendations endpoint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>