{"openapi":"3.1.0","info":{"title":"Equity Rank API","description":"Stock & Options Valuation SaaS — backend API","version":"0.1.0"},"paths":{"/health":{"get":{"tags":["health"],"summary":"Health Check","description":"Health check endpoint used by Railway to confirm app readiness after deployment.\nReturns immediately — no DB or external calls.\nRailway calls this at deploy time with healthcheckTimeout: 300s.\nIncludes Railway deployment metadata when available.","operationId":"health_check_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Health Check Health Get"}}}}}}},"/health/resilience":{"get":{"tags":["health"],"summary":"Resilience Health","description":"Live circuit-breaker state for every self-healing dependency.\n\nIn-memory only (no DB or external calls) so it stays instant and safe to\npoll. `degraded` lists any dependency whose breaker is not CLOSED — when it\nis non-empty, that external provider is currently being short-circuited and\nserved via fallback. The data-level self-heal report lives at\nGET /cron/self-heal-status (auth-gated, does DB I/O).","operationId":"resilience_health_health_resilience_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Resilience Health Health Resilience Get"}}}}}}},"/api/rates/treasury":{"get":{"tags":["rates"],"summary":"Get Treasury Rate","description":"Returns the current 10-year US Treasury rate (DGS10) from FRED.\n\nResponse: {\"rate\": 4.25, \"source\": \"fred_api\" | \"fred_csv\"}\n\nRate is in percentage form (4.25 = 4.25%, not 0.0425).\nValuation service converts to decimal for CAPM: rate_decimal = rate / 100.","operationId":"get_treasury_rate_api_rates_treasury_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Treasury Rate Api Rates Treasury Get"}}}}}}},"/api/market/pulse":{"get":{"tags":["market"],"summary":"Get Market Pulse","description":"Real-time market pulse using ETF proxies. Route-level 2-min cache so the\ndashboard bar doesn't hammer AlphaVantage on every page load.\nReturns symbol, display label, price, absolute change, and percent change.","operationId":"get_market_pulse_api_market_pulse_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response Get Market Pulse Api Market Pulse Get"}}}}}}},"/api/market/sectors":{"get":{"tags":["market"],"summary":"Get Sector Performance","description":"Sector ETF performance. Route-level 3-min cache — sector rotation is\na multi-day story, sub-minute freshness isn't meaningful.","operationId":"get_sector_performance_api_market_sectors_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response Get Sector Performance Api Market Sectors Get"}}}}}}},"/api/market/quotes":{"get":{"tags":["market"],"summary":"Get Quotes","description":"Batch stock quotes for watchlist. Cached 30 seconds.\nIncludes company_name from screener_cache when available.","operationId":"get_quotes_api_market_quotes_get","parameters":[{"name":"symbols","in":"query","required":true,"schema":{"type":"string","description":"Comma-separated ticker symbols, max 20","title":"Symbols"},"description":"Comma-separated ticker symbols, max 20"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Get Quotes Api Market Quotes Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/market/commodities":{"get":{"tags":["market"],"summary":"Get Commodity Spot","description":"Current spot prices for Gold, Silver, WTI, Brent, Copper, Natural Gas.\nSourced via yfinance futures (GC=F, SI=F, CL=F, BZ=F, HG=F, NG=F). 5-min cache.","operationId":"get_commodity_spot_api_market_commodities_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Commodity Spot Api Market Commodities Get"}}}}}}},"/api/market/commodities/forecast":{"get":{"tags":["market"],"summary":"Get Commodity Forecast","description":"Multi-factor macro model forecasts (bear / base / bull) for all commodities\nat 1M, 3M, and 6M horizons. GARCH vol bands. 4-hour cache — computation\nfetches ~500 days of history across 11 symbols and runs OLS + GARCH.\nEducational only — not investment advice.","operationId":"get_commodity_forecast_api_market_commodities_forecast_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Commodity Forecast Api Market Commodities Forecast Get"}}}}}}},"/api/market/commodities/history/{commodity}":{"get":{"tags":["market"],"summary":"Get Commodity History","description":"Daily OHLCV for one commodity (GC=F futures proxy). Used for price chart.\ncommodity: GOLD | SILVER | WTI | BRENT | COPPER | NATURAL_GAS","operationId":"get_commodity_history_api_market_commodities_history__commodity__get","parameters":[{"name":"commodity","in":"path","required":true,"schema":{"type":"string","title":"Commodity"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":730,"minimum":30,"default":365,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Get Commodity History Api Market Commodities History  Commodity  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/market/commodities/carry":{"get":{"tags":["market"],"summary":"Get Commodity Carry","description":"Latest futures-curve carry / term-structure signal per commodity\n(WTI, Natural Gas) from the front four Yahoo per-contract futures: roll\nyield, slope, z-score and backwardation / contango / flat regime. Refreshed\ndaily by the Supabase-native pg_cron job (migration 062).\nBackwardation = positive carry. Informational only — not investment advice.\n30-min cache.","operationId":"get_commodity_carry_api_market_commodities_carry_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Commodity Carry Api Market Commodities Carry Get"}}}}}}},"/api/admin/users":{"get":{"tags":["admin"],"summary":"List Users","description":"Returns all Supabase auth users with accurate last-active time.\n\nauth.users.last_sign_in_at only updates on new session creation (explicit login).\nUsers who stay logged in via persistent JWT never trigger that update, so the\ncolumn appears stale.  Fix: JOIN auth.sessions and return\nGREATEST(last_sign_in_at, MAX(sessions.updated_at)) — sessions.updated_at is\nbumped on every token refresh, giving true last-activity.\n\nPrimary path: Supabase Management API (has access to auth schema).\nFallback: auth admin SDK (last_sign_in_at only — may be stale).","operationId":"list_users_api_admin_users_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Users Api Admin Users Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["admin"],"summary":"Create User","description":"Create a new Supabase auth user with optional role flags.","operationId":"create_user_api_admin_users_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create User Api Admin Users Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/users/{user_id}":{"delete":{"tags":["admin"],"summary":"Delete User","description":"Permanently delete a Supabase auth user.\nIf the user has an active Stripe subscription:\n  - Calculates a prorated refund for unused days in the current billing period.\n  - Issues the refund against the latest paid invoice charge.\n  - Cancels the subscription immediately.\n  - Updates subscriptions.status to 'canceled'.","operationId":"delete_user_api_admin_users__user_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete User Api Admin Users  User Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/users/{user_id}/profile":{"patch":{"tags":["admin"],"summary":"Update User Profile","description":"Update a user's email and/or display name.","operationId":"update_user_profile_api_admin_users__user_id__profile_patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProfileRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update User Profile Api Admin Users  User Id  Profile Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/users/{user_id}/role":{"patch":{"tags":["admin"],"summary":"Update User Role","description":"Set or clear role flags on a user's metadata.","operationId":"update_user_role_api_admin_users__user_id__role_patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRoleRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update User Role Api Admin Users  User Id  Role Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/run-migrations":{"post":{"tags":["admin"],"summary":"Admin Run Migrations","description":"Manually check and create any missing Supabase tables.\nNOT run automatically on startup — call this after a fresh deploy or schema change.","operationId":"admin_run_migrations_api_admin_run_migrations_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Admin Run Migrations Api Admin Run Migrations Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/token-usage":{"get":{"tags":["admin"],"summary":"Get Claude Token Usage","description":"Returns Claude API token usage and USD cost totals. Requires admin JWT.\nResets on server restart (in-process store).","operationId":"get_claude_token_usage_api_admin_token_usage_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Claude Token Usage Api Admin Token Usage Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/api-calls":{"get":{"tags":["admin"],"summary":"Get Api Call History","description":"Returns external data provider API call history with source breakdown.\n\nTracks all calls to: AlphaVantage, MarketData, FRED, BigQuery, etc.\n\nQuery params:\n- limit: Max calls to return (default 100)\n- source: Filter by provider (alphavantage, marketdata, fred, bigquery, other)\n- symbol: Filter by stock symbol\n\nResponse includes call stats by source and recent call list.\nResets on server restart (in-process store).","operationId":"get_api_call_history_api_admin_api_calls_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"}},{"name":"symbol","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Api Call History Api Admin Api Calls Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/api-calls/av-fallback":{"get":{"tags":["admin"],"summary":"Get Av Fallback Summary","description":"Returns a report of AlphaVantage endpoints/symbols that failed or returned\nno statement data since the last server restart.\n\nUse after a screener or bulk valuation run to identify coverage gaps.\nResponse: derived_symbols (list), failures_by_endpoint (dict), error_detail.","operationId":"get_av_fallback_summary_api_admin_api_calls_av_fallback_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Av Fallback Summary Api Admin Api Calls Av Fallback Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/api-calls/rate-log":{"get":{"tags":["admin"],"summary":"Get Av Rate Log","description":"Query api_rate_log for durable per-minute call counts and rate-limit hits.\n\nSurvives server restarts — use this to verify overnight batch rate usage\nand confirm AV is honoring the 1600 req/min premium contract limit.\n\nQuery params:\n  source         — data provider key (default: alphavantage)\n  lookback_hours — hours of history to return (default: 24)","operationId":"get_av_rate_log_api_admin_api_calls_rate_log_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"source","in":"query","required":false,"schema":{"type":"string","default":"alphavantage","title":"Source"}},{"name":"lookback_hours","in":"query","required":false,"schema":{"type":"integer","default":24,"title":"Lookback Hours"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Av Rate Log Api Admin Api Calls Rate Log Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/sp500":{"get":{"tags":["admin"],"summary":"Get Sp500 Status","description":"Returns the full US equity universe merged with current valuation run status.\nEach entry includes: symbol, name, sector, sub_industry, index,\nvaluation_done (bool), last_run (ISO string | null), consensus_fv, data_quality, price.","operationId":"get_sp500_status_api_admin_sp500_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Sp500 Status Api Admin Sp500 Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/run-valuation/{symbol}":{"post":{"tags":["admin"],"summary":"Admin Run Valuation","description":"Trigger a valuation run for a single symbol. Uses the same logic as\nGET /api/stocks/{symbol}/valuation but bypasses subscription check.\nUpdates the valuation_run_registry on success.","operationId":"admin_run_valuation_api_admin_run_valuation__symbol__post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Admin Run Valuation Api Admin Run Valuation  Symbol  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/run-screener-batch":{"post":{"tags":["admin"],"summary":"Admin Run Screener Batch","description":"Manually trigger a screener batch run.\nScores the full equity universe and upserts results to screener_cache.\nNormally runs automatically Mon–Fri at 06:00 UTC.","operationId":"admin_run_screener_batch_api_admin_run_screener_batch_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Admin Run Screener Batch Api Admin Run Screener Batch Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/backfill-tickers":{"post":{"tags":["admin"],"summary":"Admin Backfill Tickers","description":"Score and upsert specific tickers into screener_cache immediately.\nUsed to add missing tickers without waiting for the nightly batch.","operationId":"admin_backfill_tickers_api_admin_backfill_tickers_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TargetedBackfillBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Admin Backfill Tickers Api Admin Backfill Tickers Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/run-options-screener-batch":{"post":{"tags":["admin"],"summary":"Admin Run Options Screener Batch","description":"Manually trigger the full-universe options screener batch.\nProcesses all scored stocks, persists results to options_screener_cache.\nNormally runs automatically at 02:00 UTC nightly.","operationId":"admin_run_options_screener_batch_api_admin_run_options_screener_batch_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Admin Run Options Screener Batch Api Admin Run Options Screener Batch Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/cron-health":{"get":{"tags":["admin"],"summary":"Admin Cron Health","description":"Options screener cron health status — Phase 57 / OPT-01.\n\nReturns the last 10 options-screener cron runs with:\n  • last_success — most recent successful run record\n  • last_run     — most recent run record (any status)\n  • failure_streak — consecutive failures since the last success\n  • row_count    — rows_processed from last successful run\n  • status       — \"healthy\" | \"warning\" (1–2 failures) | \"degraded\" (>2)\n\nSends an admin alert email via Resend when failure_streak > 2.\nThrottled to one email per 2 hours (in-process cooldown guard).","operationId":"admin_cron_health_api_admin_cron_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Admin Cron Health Api Admin Cron Health Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/screener-picks":{"get":{"tags":["admin"],"summary":"Admin Get Screener Picks","description":"Return the current week's admin-curated picks (if any) plus the full top-10% pool\nso the admin can browse and choose.","operationId":"admin_get_screener_picks_api_admin_screener_picks_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Admin Get Screener Picks Api Admin Screener Picks Get"}}}}},"security":[{"HTTPBearer":[]}]},"put":{"tags":["admin"],"summary":"Admin Set Screener Picks","description":"Save admin-curated picks for the current ISO week.\nPass an empty list to clear overrides and revert to random selection.","operationId":"admin_set_screener_picks_api_admin_screener_picks_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminPicksBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Admin Set Screener Picks Api Admin Screener Picks Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/send-weekly-update-email":{"post":{"tags":["admin"],"summary":"Admin Send Weekly Update Email","description":"Manually trigger the weekly update email.\nNormally runs automatically every Sunday at 07:00 UTC.","operationId":"admin_send_weekly_update_email_api_admin_send_weekly_update_email_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Admin Send Weekly Update Email Api Admin Send Weekly Update Email Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/innovation-blends":{"get":{"tags":["admin"],"summary":"Admin List Blends","description":"List all ticker blend overrides (user-defined + built-in defaults).","operationId":"admin_list_blends_api_admin_innovation_blends_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Admin List Blends Api Admin Innovation Blends Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/innovation-blends/{symbol}":{"put":{"tags":["admin"],"summary":"Admin Set Blend","description":"Create or update a sector blend for a ticker.","operationId":"admin_set_blend_api_admin_innovation_blends__symbol__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SectorBlendBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Admin Set Blend Api Admin Innovation Blends  Symbol  Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["admin"],"summary":"Admin Delete Blend","description":"Remove a user-defined blend (reverts to built-in default or standard sector path).","operationId":"admin_delete_blend_api_admin_innovation_blends__symbol__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Admin Delete Blend Api Admin Innovation Blends  Symbol  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/metrics":{"get":{"tags":["admin"],"summary":"Get Business Metrics","description":"Business health metrics: MRR, trial-to-paid rate, churn, signups.\nPowers the admin dashboard metrics card.","operationId":"get_business_metrics_api_admin_metrics_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Business Metrics Api Admin Metrics Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/upload-iv-history":{"post":{"tags":["admin"],"summary":"Upload Iv History","description":"Upload a CSV or Excel file of historical IV data into iv_daily_snapshot.\n\nAccepts files from any source — ORATS, Market Chameleon, AlphaQuery VolVue,\nOptionSlam, or any custom spreadsheet.\n\nRequired columns (flexible naming):\n  - ticker / symbol / underlying\n  - date / trade_date / snapshot_date\n\nOptional columns (auto-detected):\n  - iv / iv30 / weightedImpliedVolatility\n  - iv_rank / ivRank / impliedVolatilityRank1y\n  - iv_percentile / ivPercentile\n  - put_call_ratio / pcr\n  - total_oi / openInterest\n  - sector / industry\n\nIV values > 5 are auto-converted from percentage to decimal (e.g. 42.0 → 0.420).\nDuplicate (ticker, date) rows within the file are merged before the write.\nThe write replaces the stored row, so a column this file omits is cleared —\nupload complete rows, not partial ones.\n\nReturns: { parsed, upserted, skipped, warnings, sources_detected }","operationId":"upload_iv_history_api_admin_upload_iv_history_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_iv_history_api_admin_upload_iv_history_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Upload Iv History Api Admin Upload Iv History Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/deploy-info":{"get":{"tags":["admin"],"summary":"Deploy Info","description":"Deploy metadata for the admin dashboard header.\n\nCarries the commit MESSAGE, which the unauthenticated /health endpoint no\nlonger exposes (commit subjects here routinely describe internal bugs and\nsecurity fixes — see 2026-09-02 ops audit). No DB or network I/O.","operationId":"deploy_info_api_admin_deploy_info_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Deploy Info Api Admin Deploy Info Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/dashboard/health":{"get":{"tags":["admin"],"summary":"Dashboard Health","description":"Cron job status + screener coverage.","operationId":"dashboard_health_api_admin_dashboard_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/dashboard/metrics":{"get":{"tags":["admin"],"summary":"Dashboard Metrics","description":"Key business metrics.","operationId":"dashboard_metrics_api_admin_dashboard_metrics_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/dashboard/activity":{"get":{"tags":["admin"],"summary":"Dashboard Activity","description":"Recent cron activity — last 20 runs.","operationId":"dashboard_activity_api_admin_dashboard_activity_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/dashboard/subscribers":{"get":{"tags":["admin"],"summary":"Dashboard Subscribers","description":"Email capture funnel — active subscriber count grouped by source.","operationId":"dashboard_subscribers_api_admin_dashboard_subscribers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/dashboard/usage":{"get":{"tags":["admin"],"summary":"Dashboard Usage","description":"First-party product usage analytics — DAU/WAU/MAU, daily-active trend,\nand top events by volume. Sourced from public.usage_events (migration 134)\nvia the usage_* SQL RPC aggregations. Admin self-traffic is excluded by\ndefault (include_admin=true to include owner activity).\n\n`days` bounds the daily-active trend and top-events window (clamped 1..90).","operationId":"dashboard_usage_api_admin_dashboard_usage_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Days"}},{"name":"include_admin","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Admin"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/deploy-cf-worker":{"post":{"tags":["admin"],"summary":"Deploy Cf Worker","description":"Upload backend/static/og-prerender.js to Cloudflare Workers.\n\nReads CLOUDFLARE_API_TOKEN from Railway env vars and uses the CF REST API\ndirectly — no wrangler CLI or Node.js required.\n\nRoutes (equity-rank.com/*) are NOT modified — they persist from the initial\n`wrangler deploy --env production` setup. This endpoint only replaces the\nscript content.\n\nReturns deploy status dict:\n  {ok, worker_name, script_size, deployed_at, account_id}\nOn failure: {ok: False, error: str, details?: str}","operationId":"deploy_cf_worker_api_admin_deploy_cf_worker_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Deploy Cf Worker Api Admin Deploy Cf Worker Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/check-keys":{"post":{"tags":["admin"],"summary":"Check Api Keys","description":"Manually trigger an API key health check.\nProbes AlphaVantage, Anthropic, Stripe, Resend, and Cloudflare.\nSends alert email to chris@equity-rank.com if any key is expired.","operationId":"check_api_keys_api_admin_check_keys_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/schwab/authorize":{"get":{"tags":["admin"],"summary":"Schwab Authorize Url","description":"Return the Schwab OAuth2 authorization URL.\n\nStep 1 of Schwab setup:\n1. Visit the returned URL in your browser\n2. Log in with your Schwab account and approve access\n3. Copy the `code` query parameter from the redirect URL\n4. POST /api/admin/schwab/callback with {\"code\": \"<value>\"}","operationId":"schwab_authorize_url_api_admin_schwab_authorize_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Schwab Authorize Url Api Admin Schwab Authorize Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/schwab/callback":{"post":{"tags":["admin"],"summary":"Schwab Callback","description":"Exchange a Schwab authorization code for tokens.\n\nStep 2 of Schwab setup. Body: {\"code\": \"<auth_code_from_redirect>\"}\nAfter success, copy the refresh_token to Railway env var SCHWAB_REFRESH_TOKEN.","operationId":"schwab_callback_api_admin_schwab_callback_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Schwab Callback Api Admin Schwab Callback Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/schwab/oauth-callback":{"get":{"tags":["admin"],"summary":"Schwab Oauth Callback","description":"Automatic OAuth2 callback — Schwab redirects here after user approval.\n\nNo admin auth required (browser redirect carries no credentials).\nVerifies CSRF state nonce, exchanges the code, and redirects to the admin page.","operationId":"schwab_oauth_callback_api_admin_schwab_oauth_callback_get","parameters":[{"name":"code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"}},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"error","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},{"name":"error_description","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Description"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Schwab Oauth Callback Api Admin Schwab Oauth Callback Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/wheel/positions":{"get":{"tags":["admin"],"summary":"List Wheel Positions","description":"List all manual wheel positions.","operationId":"list_wheel_positions_api_admin_wheel_positions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Wheel Positions Api Admin Wheel Positions Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["admin"],"summary":"Create Wheel Position","description":"Create a manual wheel/options/stock position.","operationId":"create_wheel_position_api_admin_wheel_positions_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Wheel Position Api Admin Wheel Positions Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/wheel/positions/{position_id}":{"patch":{"tags":["admin"],"summary":"Update Wheel Position","description":"Update a manual wheel position (close it, add notes, etc.).","operationId":"update_wheel_position_api_admin_wheel_positions__position_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"position_id","in":"path","required":true,"schema":{"type":"string","title":"Position Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Wheel Position Api Admin Wheel Positions  Position Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["admin"],"summary":"Delete Wheel Position","description":"Delete a manual wheel position.","operationId":"delete_wheel_position_api_admin_wheel_positions__position_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"position_id","in":"path","required":true,"schema":{"type":"string","title":"Position Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Wheel Position Api Admin Wheel Positions  Position Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/analysis-batch":{"post":{"tags":["admin"],"summary":"Batch Fetch Analysis","description":"Batch fetch analysis_cache SAVE data + exit signals for multiple symbols.\n\nReturns {SYMBOL: {save_score, consensus_margin_of_safety, best_fit_method,\nbest_fit_confidence, cached_at, p_win, mos_pct_raw, exit_signal, exit_reason}}.\nexit_signal: 'exit' | 'review' | 'hold' | null.\nAdmin-only. POS-02 / EXIT-01.","operationId":"batch_fetch_analysis_api_admin_analysis_batch_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalysisBatchRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Batch Fetch Analysis Api Admin Analysis Batch Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/iv-rank/{symbol}":{"get":{"tags":["admin"],"summary":"Get Iv Rank","description":"Fetch IV rank for a symbol from options_screener_cache.\n\nReturns {iv_rank: 0-100 or null, label: \"High IV\"|\"Mid IV\"|\"Low IV\"|null}.\nAdmin-only. POS-04.","operationId":"get_iv_rank_api_admin_iv_rank__symbol__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Iv Rank Api Admin Iv Rank  Symbol  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/schwab/portfolio/{account_hash}/greeks":{"get":{"tags":["admin"],"summary":"Get Portfolio Greeks","description":"Aggregate portfolio Greeks across all options positions using options_screener_cache\nATM approximations. Delta and Theta are sourced from options_screener_cache\n(atm_delta, atm_theta); vega is not available in the cache and is always null.\n\nReturns {delta: float|null, theta: float|null, vega: null, options_count: int,\nlabel: \"Portfolio Greeks (ATM approx.)\"}. Admin-only. POS-03.","operationId":"get_portfolio_greeks_api_admin_schwab_portfolio__account_hash__greeks_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"account_hash","in":"path","required":true,"schema":{"type":"string","title":"Account Hash"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Portfolio Greeks Api Admin Schwab Portfolio  Account Hash  Greeks Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/wheel-watchlist":{"get":{"tags":["admin"],"summary":"Wheel Watchlist","description":"Return ranked wheel strategy candidates with SMA trend and VIX gate applied.","operationId":"wheel_watchlist_api_admin_wheel_watchlist_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Wheel Watchlist Api Admin Wheel Watchlist Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/schwab/status":{"get":{"tags":["admin"],"summary":"Schwab Status","description":"Return enriched Schwab token status (SCHWAB-04).\n\nReturns 9 fields: configured, has_tokens, connection_state,\naccess_token_age_sec, access_token_expires_in_sec, last_refreshed_at,\nrefresh_token_issued_at, refresh_token_expires_at, days_until_refresh_expiry.","operationId":"schwab_status_api_admin_schwab_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Schwab Status Api Admin Schwab Status Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/config/erp":{"get":{"tags":["admin"],"summary":"Get Erp Config","description":"Current dynamic ERP, its bounds, and the default. Admin-only.","operationId":"get_erp_config_api_admin_config_erp_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Erp Config Api Admin Config Erp Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["admin"],"summary":"Set Erp Config","description":"Set the platform-wide ERP (clamped to [ERP_MIN, ERP_MAX]). Admin-only.\n\nTakes effect immediately in this process; other processes pick it up at\ntheir next TTL refresh, batch start, or restart. Changing this value moves\nevery DCF/DDM-family fair value on the platform — change deliberately.","operationId":"set_erp_config_api_admin_config_erp_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErpUpdateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Set Erp Config Api Admin Config Erp Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/iv-scanner":{"get":{"tags":["admin"],"summary":"Iv Scanner","description":"Return top CSP and Iron Condor candidates using pre-computed nightly data.\n\nSources: options_screener_cache (IV, chain data) + screener_cache (fundamentals).\nNo live MarketData calls — avoids exhausting daily API quota used by nightly batch.","operationId":"iv_scanner_api_admin_iv_scanner_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Iv Scanner Api Admin Iv Scanner Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/options-chain/{ticker}":{"get":{"tags":["admin"],"summary":"Options Chain Detail","description":"Full option chain for a single ticker with recommended strikes highlighted.\n\nQuery params:\n  strategy — \"csp\" (default) or \"ic\"\n  dte_min  — minimum DTE (default 7)\n  dte_max  — maximum DTE (default 30)","operationId":"options_chain_detail_api_admin_options_chain__ticker__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"strategy","in":"query","required":false,"schema":{"type":"string","default":"csp","title":"Strategy"}},{"name":"dte_min","in":"query","required":false,"schema":{"type":"integer","default":7,"title":"Dte Min"}},{"name":"dte_max","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Dte Max"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Options Chain Detail Api Admin Options Chain  Ticker  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/close-signals":{"get":{"tags":["admin"],"summary":"Close Signals","description":"Analyze open Schwab option positions and signal HOLD / MANAGE / CLOSE.\n\nRules:\n  CLOSE:  pnl_pct >= 50 (profit target) or DTE <= 2 (time risk)\n  MANAGE: pnl_pct <= -100 (doubled against us) or underlying below 20-SMA for short puts\n  HOLD:   everything else","operationId":"close_signals_api_admin_close_signals_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Close Signals Api Admin Close Signals Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/anomalies/recent":{"get":{"tags":["admin"],"summary":"Get Recent Anomalies","description":"Return last `hours` of av_anomaly_log rows + aggregated summary.\n\nResponse shape:\n    {\n        \"rows\": [ {id, created_at, ticker, field_path, rule, raw_value,\n                   fetch_function, severity, root_cause}, ... ],\n        \"summary\": {\n            \"total\": int,\n            \"unexplained\": int,     # rows with root_cause IS NULL\n            \"known_benign\": int,    # rows auto-classified by _derive_root_cause\n            \"root_causes\": {class: count, ...},  # non-null classes only\n            \"categories\": {av_bounds, av_format, calc_failures, zero_weight},\n            \"top_rules\":   [[rule, count], ... up to 10 entries desc],\n            \"top_tickers\": [[ticker, count], ... up to 10 entries desc],\n            \"truncated\": bool,      # True iff the scan cap bounded the read\n            \"scan_cap\": int,        # ANOMALY_SCAN_CAP\n        },\n        \"generated_at\": ISO timestamp,\n        \"hours\": int,\n    }\n\nAdmin-only. Graceful degradation on Supabase failure: returns 200 with\nempty rows + zero counts (see module docstring).","operationId":"get_recent_anomalies_api_admin_anomalies_recent_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"hours","in":"query","required":false,"schema":{"type":"integer","default":24,"title":"Hours"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Recent Anomalies Api Admin Anomalies Recent Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/schwab/portfolio":{"get":{"tags":["admin"],"summary":"Schwab Portfolio","description":"Return all linked Schwab accounts with balances and positions.","operationId":"schwab_portfolio_api_admin_schwab_portfolio_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Schwab Portfolio Api Admin Schwab Portfolio Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/schwab/portfolio/{account_hash}/positions":{"get":{"tags":["admin"],"summary":"Schwab Positions","description":"Return all positions for a specific Schwab account.","operationId":"schwab_positions_api_admin_schwab_portfolio__account_hash__positions_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"account_hash","in":"path","required":true,"schema":{"type":"string","title":"Account Hash"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Schwab Positions Api Admin Schwab Portfolio  Account Hash  Positions Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/schwab/portfolio/{account_hash}/orders":{"post":{"tags":["admin"],"summary":"Schwab Place Order","description":"Place an order on a Schwab account (legacy wheel-CSP shortcut).\n\nGUARD-03: this legacy endpoint writes the immutable order_audit_log on every\nstate transition (DRAFT before send; SENT or REJECTED after), reusing the\nPhase 23 _insert_audit_row / _supabase_audit_headers helpers — so the\nwheel-CSP path no longer bypasses the audit trail. DRAFT-failure is fatal\n(raise 500) so an order can never silently bypass the audit log. The raw\nSchwab body is NOT a PlaceOrderRequest, so the audit row is built inline\n(NOT via _build_audit_base). Returns the raw Schwab result on success\n(AdminPage.tsx wheel shortcut depends on it).","operationId":"schwab_place_order_api_admin_schwab_portfolio__account_hash__orders_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"account_hash","in":"path","required":true,"schema":{"type":"string","title":"Account Hash"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Schwab Place Order Api Admin Schwab Portfolio  Account Hash  Orders Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["admin"],"summary":"Schwab Orders","description":"Return recent working orders for a Schwab account.","operationId":"schwab_orders_api_admin_schwab_portfolio__account_hash__orders_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"account_hash","in":"path","required":true,"schema":{"type":"string","title":"Account Hash"}},{"name":"max_results","in":"query","required":false,"schema":{"type":"integer","default":25,"title":"Max Results"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Schwab Orders Api Admin Schwab Portfolio  Account Hash  Orders Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/schwab/transactions/{account_hash}":{"get":{"tags":["admin"],"summary":"Schwab Transactions","description":"Return transaction history for a Schwab account (default: last 90 days of trades).\n\nQuery params:\n  types      — comma-separated Schwab transaction type(s). Default: TRADE.\n               Options: TRADE, RECEIVE_AND_DELIVER, DIVIDEND_OR_INTEREST,\n               ACH_RECEIPT, ACH_DISBURSEMENT, CASH_RECEIPT, CASH_DISBURSEMENT,\n               ELECTRONIC_FUND, WIRE_OUT, WIRE_IN, JOURNAL, MEMORANDUM,\n               MARGIN_CALL, MONEY_MARKET, SMA_ADJUSTMENT\n  start_date — ISO datetime string (e.g. 2025-01-01T00:00:00.000Z)\n  end_date   — ISO datetime string","operationId":"schwab_transactions_api_admin_schwab_transactions__account_hash__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"account_hash","in":"path","required":true,"schema":{"type":"string","title":"Account Hash"}},{"name":"types","in":"query","required":false,"schema":{"type":"string","default":"TRADE","title":"Types"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Date"}},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Schwab Transactions Api Admin Schwab Transactions  Account Hash  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/schwab/quotes":{"get":{"tags":["admin"],"summary":"Schwab Quotes","description":"Return real-time quotes for one or more symbols (comma-separated).\n\nQuery params:\n  symbols — e.g. AAPL,MSFT,NVDA or AAPL (required)\n\nResponse includes quote (bid/ask/last/volume), reference (exchange, description),\nand fundamental (P/E, EPS, dividend yield) sub-objects per symbol.","operationId":"schwab_quotes_api_admin_schwab_quotes_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbols","in":"query","required":true,"schema":{"type":"string","title":"Symbols"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Schwab Quotes Api Admin Schwab Quotes Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/schwab/price-history/{symbol}":{"get":{"tags":["admin"],"summary":"Schwab Price History","description":"Return OHLCV price history for a symbol.\n\nQuery params:\n  period_type     — day | month | year | ytd\n  period          — number of periods (default: 1)\n  frequency_type  — minute | daily | weekly | monthly\n  frequency       — candle width (e.g. 5 for 5-min bars)\n  need_extended_hours — include pre/post market (default: false)\n  start_date / end_date — ISO date strings (overrides period when set)","operationId":"schwab_price_history_api_admin_schwab_price_history__symbol__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}},{"name":"period_type","in":"query","required":false,"schema":{"type":"string","default":"month","title":"Period Type"}},{"name":"period","in":"query","required":false,"schema":{"type":"integer","default":1,"title":"Period"}},{"name":"frequency_type","in":"query","required":false,"schema":{"type":"string","default":"daily","title":"Frequency Type"}},{"name":"frequency","in":"query","required":false,"schema":{"type":"integer","default":1,"title":"Frequency"}},{"name":"need_extended_hours","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Need Extended Hours"}},{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Date"}},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Schwab Price History Api Admin Schwab Price History  Symbol  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/schwab/movers/{index}":{"get":{"tags":["admin"],"summary":"Schwab Movers","description":"Return top movers for a market index.\n\nPath param:\n  index — $SPX | $COMPX | $DJI | NYSE | NASDAQ | OTCBB |\n          INDEX_ALL | EQUITY_ALL | OPTION_ALL | OPTION_PUT | OPTION_CALL\n\nQuery params:\n  sort      — VOLUME | TRADES | PERCENT_CHANGE_UP | PERCENT_CHANGE_DOWN\n  frequency — 0 (all day) | 1 | 5 | 10 | 30 | 60 (minutes)","operationId":"schwab_movers_api_admin_schwab_movers__index__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"index","in":"path","required":true,"schema":{"type":"string","title":"Index"}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","default":"PERCENT_CHANGE_UP","title":"Sort"}},{"name":"frequency","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Frequency"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Schwab Movers Api Admin Schwab Movers  Index  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/schwab/market-hours":{"get":{"tags":["admin"],"summary":"Schwab Market Hours","description":"Return trading hours for specified markets on a given date.\n\nQuery params:\n  markets — comma-separated: equity | option | bond | future | forex\n  date    — YYYY-MM-DD, defaults to today","operationId":"schwab_market_hours_api_admin_schwab_market_hours_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"markets","in":"query","required":false,"schema":{"type":"string","default":"equity,option","title":"Markets"}},{"name":"date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Date"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Schwab Market Hours Api Admin Schwab Market Hours Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/schwab/instruments":{"get":{"tags":["admin"],"summary":"Schwab Instruments","description":"Search for instrument details by symbol or description.\n\nQuery params:\n  symbol     — search term (symbol, partial symbol, or description keyword)\n  projection — symbol-search | symbol-regex | desc-search | desc-regex |\n               search | fundamental","operationId":"schwab_instruments_api_admin_schwab_instruments_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"query","required":true,"schema":{"type":"string","title":"Symbol"}},{"name":"projection","in":"query","required":false,"schema":{"type":"string","default":"symbol-search","title":"Projection"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Schwab Instruments Api Admin Schwab Instruments Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/schwab/orders/place":{"post":{"tags":["admin"],"summary":"Admin Orders Place","description":"Place a Schwab order with full audit trail.\n\nAppend-only flow:\n  1. INSERT row state=DRAFT (intent captured before any network call)\n  2. Build Schwab payload via build_order_payload\n  3. Call schwab_service.place_order\n     - On 2xx → INSERT row state=SENT (with schwab_order_id) → 200 to caller\n     - On 4xx → INSERT row state=REJECTED (with reason)       → 400 to caller\n     - On 401/403 (SchwabAuthExpiredError) → INSERT REJECTED  → 401 to caller\n\nORDER-01: admin-only endpoint + UI confirmation gate\nORDER-02: immutable audit log — every transition is a new INSERT\nORDER-05: full Schwab order type set (MARKET / LIMIT in 23-01,\n          STOP / STOP_LIMIT in 23-02, TRAILING_STOP in 23-03)","operationId":"admin_orders_place_api_admin_schwab_orders_place_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaceOrderRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Admin Orders Place Api Admin Schwab Orders Place Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/schwab/orders/{order_id}/status":{"get":{"tags":["admin"],"summary":"Admin Order Status","description":"Return the latest audit row for an order; on every poll, detect Schwab\nstate transitions and append the appropriate audit row(s) (TRIGGERED,\nADJUSTED, EXECUTED, CANCELED, REJECTED).\n\nAppend-only: this route NEVER updates or deletes existing audit rows. State\ntransitions are detected via order_state_cache (mutable cache, not audit)\nand written as new INSERT rows.","operationId":"admin_order_status_api_admin_schwab_orders__order_id__status_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"string","title":"Order Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Admin Order Status Api Admin Schwab Orders  Order Id  Status Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/short-term-opportunities":{"get":{"tags":["admin"],"summary":"Get Short Term Opportunities","description":"Ranked short-term (≈20 trading-day) opportunities. Admin-only.\nmode: \"momentum\" (default) or \"conservative\" (70/30 value-tilt).","operationId":"get_short_term_opportunities_api_admin_short_term_opportunities_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":25,"title":"Limit"}},{"name":"mode","in":"query","required":false,"schema":{"type":"string","default":"momentum","title":"Mode"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Short Term Opportunities Api Admin Short Term Opportunities Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/selection-ledger/summary":{"get":{"tags":["admin"],"summary":"Get Selection Ledger Summary","description":"Cohort statistics per selection surface at one horizon. Admin-only.","operationId":"get_selection_ledger_summary_api_admin_selection_ledger_summary_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"horizon","in":"query","required":false,"schema":{"type":"string","pattern":"^(21|63|126|exit)$","default":"63","title":"Horizon"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Selection Ledger Summary Api Admin Selection Ledger Summary Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/selection-ledger/readout":{"get":{"tags":["admin"],"summary":"Get Selection Ledger Readout","description":"Plain readout of every pick list at every horizon. Admin-only.","operationId":"get_selection_ledger_readout_api_admin_selection_ledger_readout_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Selection Ledger Readout Api Admin Selection Ledger Readout Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/cron/notify-owner":{"post":{"tags":["cron"],"summary":"Notify Owner","description":"Send an email via Resend (X-Cron-Secret auth).\n\nDefaults: from settings.admin_email → settings.digest_recipient (owner).\nPass `to` to send to any address; pass `from_addr` to override the sender.","operationId":"notify_owner_cron_notify_owner_post","parameters":[{"name":"X-Cron-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotifyOwnerRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Notify Owner Cron Notify Owner Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/social-trend-brief":{"post":{"tags":["cron"],"summary":"Cron Social Trend Brief","operationId":"cron_social_trend_brief_cron_social_trend_brief_post","parameters":[{"name":"X-Cron-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrendBriefRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Social Trend Brief Cron Social Trend Brief Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/valuations-export":{"post":{"tags":["cron"],"summary":"Cron Valuations Export","description":"Build the per-stock valuations .xlsx and email it to the owner via Resend.\n\nRuns synchronously so a manual caller sees the full result. Scheduled weekly\nby APScheduler (Mondays 13:30 UTC). Auth: X-Cron-Secret.\n\ndry_run=true: build the workbook but do NOT send — returns row/byte counts.","operationId":"cron_valuations_export_cron_valuations_export_post","parameters":[{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Dry Run"}},{"name":"X-Cron-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Valuations Export Cron Valuations Export Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/funnel-report":{"post":{"tags":["cron"],"summary":"Cron Funnel Report","description":"Build the weekly funnel report (sessions->captures->free signups->\nactivation->trial->paid, split by signup_source) and email it to the owner.\n\nRuns synchronously so a manual caller sees the full result. Scheduled weekly\nby APScheduler (Mondays 08:00 UTC, job id funnel_report_weekly). Auth:\nX-Cron-Secret.\n\ndry_run=true: build the report but do NOT send — returns counts/deltas/markdown\n(the markdown key is what backend/scripts/generate_funnel_report.py fetches\nto write the committed .planning/growth/reports/ archive copy).","operationId":"cron_funnel_report_cron_funnel_report_post","parameters":[{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Dry Run"}},{"name":"X-Cron-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Funnel Report Cron Funnel Report Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/mcp-usage-report":{"post":{"tags":["cron"],"summary":"Cron Mcp Usage Report","description":"Weekly MCP usage readout — which assistants connected to POST /mcp and\nwhich tools they called, this week vs last — emailed to the owner.\n\nScheduled by APScheduler (Mondays 08:10 UTC, job id mcp_usage_report_weekly).\nAuth: X-Cron-Secret. dry_run=true builds and returns the summary + markdown\nwithout sending. Like funnel-report, a send Resend does not accept is a\nfailed run: the email is the observable outcome.","operationId":"cron_mcp_usage_report_cron_mcp_usage_report_post","parameters":[{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Dry Run"}},{"name":"X-Cron-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Mcp Usage Report Cron Mcp Usage Report Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/verify-postiz-post":{"post":{"tags":["cron"],"summary":"Cron Verify Postiz Post","description":"Manually-triggerable Postiz publish-state verifier (MEAS-04).\n\nPolls the Postiz posts API for a post matching `post_search_text` created\nsince `since`, returning True once its state reaches PUBLISHED, False if\nthe poll times out first, or None if POSTIZ_BEARER is not configured (a\ndistinct \"not configured\" state -- never an alert on its own). A\ndefinitive False fires exactly one deduped owner alert.\n\nBuilt as a reusable capability for Phase 93's video-publish flow (and,\noptionally, the existing marketing/demo/post_video.mjs Node script) to\ncall after scheduling a Postiz post -- Postiz's own scheduling 201 only\nconfirms the post was QUEUED, never that it reached the platform (the\n154/155 silent Reddit-ERROR lesson this requirement exists to prevent).\nAuth: X-Cron-Secret.","operationId":"cron_verify_postiz_post_cron_verify_postiz_post_post","parameters":[{"name":"X-Cron-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyPostizPostRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Verify Postiz Post Cron Verify Postiz Post Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/portfolio-email":{"post":{"tags":["cron"],"summary":"Cron Portfolio Email","description":"Build + send the owner's Daily Portfolio Update email.\n\nReads owner_portfolio holdings, joins fresh screener_cache signals, and emails\nsettings.digest_recipient. Owner-only; uses the compliant OVERWEIGHT / NEUTRAL /\nUNDERWEIGHT labels (no BUY/SELL). Runs synchronously so a manual caller sees the\nfull result.\n\nSchedule: Mon–Fri 07:00 UTC via APScheduler (after the 06:00 screener batch).\ndry_run=true: render + return a preview WITHOUT sending or persisting last_signal.\nAuth: X-Cron-Secret.","operationId":"cron_portfolio_email_cron_portfolio_email_post","parameters":[{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Dry Run"}},{"name":"X-Cron-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Portfolio Email Cron Portfolio Email Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/free-preview-downgrade":{"post":{"tags":["cron"],"summary":"Cron Free Preview Downgrade","description":"Sweep expired 14-day no-card previews to the Free plan.\n\nUPDATE subscriptions SET status='free', tier='free'\nWHERE tier='free_preview' AND status='trialing' AND trial_end < now().\n\nIdempotent (the WHERE never matches an already-downgraded row). Also runs\ndaily via APScheduler; this HTTP trigger exists for manual sweeps and\nRailway Cron. Auth: X-Cron-Secret.","operationId":"cron_free_preview_downgrade_cron_free_preview_downgrade_post","parameters":[{"name":"X-Cron-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Free Preview Downgrade Cron Free Preview Downgrade Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/score-audit":{"post":{"tags":["cron"],"summary":"Cron Score Audit","description":"Monthly forward-return outcome audit of the composite scores.\n\nRebuilds the point-in-time panel from score_history (trading days only),\nmeasures the D10−D1 forward-return spread + Spearman IC per composite and\nhorizon, persists to score_audit_history (mig 136), and alerts the owner\nvia Resend when the headline 21-trading-day spread is negative. Also runs\nmonthly via APScheduler (`score_audit_monthly`, day 2 05:30 UTC); this\nHTTP trigger exists for manual runs. Auth: X-Cron-Secret.","operationId":"cron_score_audit_cron_score_audit_post","parameters":[{"name":"X-Cron-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Score Audit Cron Score Audit Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/ic-clock-report":{"post":{"tags":["cron"],"summary":"Cron Ic Clock Report","description":"Re-run the monthly cross-signal IC-clock significance report (X-Cron-Secret).\n\nManual trigger for the scheduled job (scheduler_setup, job id\n`ic-clock-monthly-report`, 1st of month 07:30 UTC). Synchronous on\npurpose — same _run_tracked wrapper and job_name as the scheduled path,\nso cron_runs records this run identically. Recomputes a daily\nHAC-corrected significance grid for the legacy composites, the ERS v3\nshadow composites (with sector-conditioned sub-cells), and the v2.4\nexperimental expected-return signals, and persists one summary row to\nscore_audit_history (composite='ic_clock_report').","operationId":"cron_ic_clock_report_cron_ic_clock_report_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Ic Clock Report Cron Ic Clock Report Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/warm-commodity-forecast":{"post":{"tags":["cron"],"summary":"Cron Warm Commodity Forecast","description":"Warm the commodity-forecast cache (X-Cron-Secret). Manual trigger / external scheduler;\nthe APScheduler job runs this every 3h so users hit the cache, not the cold recompute.","operationId":"cron_warm_commodity_forecast_cron_warm_commodity_forecast_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Warm Commodity Forecast Cron Warm Commodity Forecast Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/social-generate":{"post":{"tags":["cron"],"summary":"Cron Social Generate","description":"Fill today's social_posts queue (text + branded cards). X-Cron-Secret.\n\nManual trigger for the daily APScheduler job (social_generate_daily,\n12:30 UTC). Idempotent — skips if rows already exist for today.","operationId":"cron_social_generate_cron_social_generate_post","parameters":[{"name":"X-Cron-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Social Generate Cron Social Generate Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/social-publish":{"post":{"tags":["cron"],"summary":"Cron Social Publish","description":"Publish due social_posts rows on connected platforms. X-Cron-Secret.\n\nManual trigger for the half-hourly APScheduler job. Platforms without\nlive credentials route to needs_manual (surfaced by the daily digest).","operationId":"cron_social_publish_cron_social_publish_post","parameters":[{"name":"X-Cron-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Social Publish Cron Social Publish Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/social-digest":{"post":{"tags":["cron"],"summary":"Cron Social Digest","description":"Email the owner un-notified needs_manual social posts. X-Cron-Secret.","operationId":"cron_social_digest_cron_social_digest_post","parameters":[{"name":"X-Cron-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Social Digest Cron Social Digest Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/screener-batch":{"post":{"tags":["cron"],"summary":"Cron Screener Batch","operationId":"cron_screener_batch_cron_screener_batch_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Screener Batch Cron Screener Batch Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/screener-batch/cancel":{"post":{"tags":["cron"],"summary":"Cancel Screener Batch","description":"Signal the running screener batch to stop after the current pass completes.","operationId":"cancel_screener_batch_cron_screener_batch_cancel_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cancel Screener Batch Cron Screener Batch Cancel Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/reload-screener-cache":{"post":{"tags":["cron"],"summary":"Cron Reload Screener Cache","description":"Re-read `screener_cache` into this process's in-memory `_screener_rows`.\n\nExists for the out-of-process batch (`app/scripts/run_screener_batch_job.py`,\n2026-08-07 batch isolation). When the batch runs in its own Railway container\nit commits rows to Postgres but cannot mutate the WEB process's memory, which\nis what the authenticated `/api/screener` path serves from. It calls this\nafterwards to close that gap.\n\nSynchronous on purpose — the caller wants to know whether the reload landed,\nand load_screener_cache() already carries its own 3x retry + backoff. Safe to\ncall any time; it is exactly what the in-process batch path does at\ncron.py:655. Auth: X-Cron-Secret.","operationId":"cron_reload_screener_cache_cron_reload_screener_cache_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Reload Screener Cache Cron Reload Screener Cache Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/refresh-universe":{"post":{"tags":["cron"],"summary":"Cron Refresh Universe","description":"Tier-1 coverage: rebuild the dynamic screener_universe (weekly Sun 02:00 UTC).\n\nEnumerates AV LISTING_STATUS, market-cap-gates survivors (>=$300M + fundamentals),\nand upserts them to screener_universe. The daily screener batch reads this via\nuniverse_service.load_universe(). Heavy (~6–7k OVERVIEW probes) but flat-rate.\nAuth: X-Cron-Secret.","operationId":"cron_refresh_universe_cron_refresh_universe_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Refresh Universe Cron Refresh Universe Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/universe-dead-feeds":{"post":{"tags":["cron"],"summary":"Cron Universe Dead Feeds","description":"UNI-DEAD-01 (2026-10-12): list — and with ``?apply=true`` retire — the\nscreener_universe rows whose price feed is dead (no traded price_history\nbar AND no screener_cache quote stamp within 14 days of the feed's latest\nsession; first seen >= 21 days ago; manual / manual_include rows exempt).\n\nDRY RUN by default: returns the list, writes nothing. ``apply=true`` sets\nincluded=false (capped 60/run, reversible — not manual_exclude) and\ndeletes their screener_cache / options_screener_cache rows; price_history\nis never touched. The weekly refresh-universe build runs the same pass\nwith apply before its upsert. Synchronous (a few dozen reads). Auth:\nX-Cron-Secret.","operationId":"cron_universe_dead_feeds_cron_universe_dead_feeds_post","parameters":[{"name":"apply","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Apply"}},{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Universe Dead Feeds Cron Universe Dead Feeds Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/av-prefetch":{"post":{"tags":["cron"],"summary":"Cron Av Prefetch","description":"Manual trigger for the overnight AV cache prefetch (daily 03:30 UTC;\nalso the watchdog's staleness re-trigger target).\n\nWarms the full-universe AV fundamentals set (overview, statement histories,\nearnings family, estimates) into the 24h financials_cache so the screener\nbatch and daytime valuations hit cache instead of live AV — and (Phase 89)\npersists statement_history + multiple_history per ticker. Flat-rate — the\ncalls cost $0. `max_tickers` limits scope for smoke tests (e.g. ?max_tickers=25).\nDuplicate-safe: a run started while another is in flight (or while the\nscreener batch runs) returns a skip shape that _run_tracked logs as\ncron_runs status='skipped'. Auth: X-Cron-Secret.","operationId":"cron_av_prefetch_cron_av_prefetch_post","parameters":[{"name":"max_tickers","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Tickers"}},{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Av Prefetch Cron Av Prefetch Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/news-sentiment-daily":{"post":{"tags":["cron"],"summary":"Cron News Sentiment Daily","description":"Nightly news-sentiment incremental (APScheduler: daily 01:00 UTC). Auth: X-Cron-Secret.\n\nRe-fetches the current calendar month for every screener_cache ticker and\nupserts `news_sentiment_daily` + `news_sentiment_coverage` (Phase 94 /\nNEWS-02). Month-aligned on purpose: the coverage table is keyed\n(symbol, window_start), so tonight's run EXTENDS this month's row instead of\nminting a new one per symbol per night. Bounded by NEWS_DAILY_MAX_MINUTES so\na late fire still ends before 02:00 UTC and leaves the AV token bucket clear\nfor av_prefetch_overnight at 03:30. Symbols are worked stalest-first, so a\nbounded run tiles the universe rather than repeating its alphabetical head.","operationId":"cron_news_sentiment_daily_cron_news_sentiment_daily_post","parameters":[{"name":"lookback_days","in":"query","required":false,"schema":{"type":"integer","default":3,"title":"Lookback Days"}},{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron News Sentiment Daily Cron News Sentiment Daily Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/news-sentiment-backfill":{"post":{"tags":["cron"],"summary":"Cron News Sentiment Backfill","description":"One bounded, resumable slice of the 365-day news-sentiment backfill (NEWS-02).\nAuth: X-Cron-Secret.\n\nInvoke repeatedly. Each call derives every symbol's resume point from\n`news_sentiment_coverage` / `news_sentiment_daily` themselves — no cursor\ntable that could disagree with the data — skips the symbols already current,\nand works the rest in ticker order until `chunk_size` symbols are done or\n`max_minutes` elapses. Both bounds exist because the screener batch and the\novernight prefetch own the shared 26.67 req/s AlphaVantage bucket from ~02:00\nto ~23:30 UTC; an unbounded run would not fail, it would make the 06:00 batch\nlate. The returned run reports `stopped_because` — `chunk_size`,\n`max_minutes`, `universe_exhausted` or `universe_exhausted_with_errors`;\nonly `universe_exhausted`, with `symbols_remaining == 0`, means the backfill\nis finished. The `_with_errors` form is terminal too (nothing is left to\nclaim) but it is NOT completion: `symbols_failed` symbols were claimed and\nleft without ground, so it is to be reported, not read as a clean stop.\n\nSizing (measured in plan 94-02): >=13 provider calls per symbol per year,\n~3x that on a name whose every month saturates the 1,000-item cap, over\n~3,300 tickers — so roughly 43k-129k calls across the whole campaign, not\none long run. Duplicate-safe: a second invocation while one is in flight\ngets `cron_runs.status='skipped'` from _run_tracked's atomic claim.","operationId":"cron_news_sentiment_backfill_cron_news_sentiment_backfill_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewsSentimentBackfillRequest","default":{"horizon_days":365}}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron News Sentiment Backfill Cron News Sentiment Backfill Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/news-factor-backfill":{"post":{"tags":["cron"],"summary":"Cron News Factor Backfill","description":"Historical as-of derivation of the seven SHADOW news factors (NEWS-03). Auth: X-Cron-Secret.\n\nWrites one factor_history row per (symbol, weekly NYSE session) carrying\nthe session close, a point-in-time market cap and the seven columns —\nweighted into nothing (D-08). No AlphaVantage calls: it reads\nnews_sentiment_daily / news_sentiment_coverage / price_history only.\nIdempotent (pairs that already carry a news row are skipped) and bounded\nby `max_minutes`; resume with `resume_after` = the previous run's\n`last_symbol_completed`. Stops itself with stopped_because='db_slow' when a\nchunk's reads exceed 45s. Duplicate-safe via _run_tracked's atomic claim.","operationId":"cron_news_factor_backfill_cron_news_factor_backfill_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewsFactorBackfillRequest","default":{"max_minutes":45.0,"pause_seconds":1.0}}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron News Factor Backfill Cron News Factor Backfill Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/edgar-sweep":{"post":{"tags":["cron"],"summary":"Cron Edgar Sweep","description":"Weekly EDGAR batch sweep — Sunday 03:00 UTC (Phase 83 / ALT-03 + ALT-04).\n\nFetches 10-K hiring rate + 8-K layoff severity for the full screener universe,\nwrites to text_signal_cache, projects non-expired values into screener_cache\nshadow columns (experimental_hiring_rate, experimental_layoff_severity), and\nlogs IC folds to ml_model_ic_history.\n\nBoth signals are EXPERIMENTAL at weight 0.0 — excluded from consensus blending\nuntil VALGATE-04 passes (~Q2 2027). Only the OOS IC clock logs them (D-09).\n\nSeparate from the nightly screener batch (Mon-Fri 06:00 UTC):\nEDGAR sweep is heavy (~3000 tickers × 2 SEC calls) and runs once weekly.\nIt MUST NOT be called from within run_screener_batch().\n\nAuth: X-Cron-Secret.\ndry_run=True: fetch + compute signals, do NOT write to DB.","operationId":"cron_edgar_sweep_cron_edgar_sweep_post","parameters":[{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Dry Run"}},{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Edgar Sweep Cron Edgar Sweep Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/iv-snapshot":{"post":{"tags":["cron"],"summary":"Cron Iv Snapshot","operationId":"cron_iv_snapshot_cron_iv_snapshot_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Iv Snapshot Cron Iv Snapshot Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/iv-meanrev":{"post":{"tags":["cron"],"summary":"Cron Iv Meanrev","description":"Re-run the IV mean-reversion z-score refresh + IC scoring (X-Cron-Secret).\n\nManual trigger for the step chained after the 17:00 UTC iv-snapshot job\n(scheduler_setup._iv_snapshot_tracked). Synchronous on purpose: the caller\nis re-running it precisely because the scheduled run did not land, so it\nwants the outcome, not a 202. Same _run_tracked wrapper and job_name as the\nscheduled path, so cron_runs records this run identically — including a\nstatus='failed' row when a step fails, which before 2026-08-18 it never did.","operationId":"cron_iv_meanrev_cron_iv_meanrev_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Iv Meanrev Cron Iv Meanrev Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/iv-backfill":{"post":{"tags":["cron"],"summary":"Cron Iv Backfill","description":"Trigger one chunk of the historical IV backfill.\nAuto-resumes from last completed date — restart-safe.\nAPScheduler calls this every 2 hours until complete.\n\nBody (all optional):\n  end_date:   \"2026-04-14\"  — defaults to yesterday\n  tickers:    [\"AAPL\"]      — defaults to all screener tickers\n  chunk_size: 10            — trading days per run","operationId":"cron_iv_backfill_cron_iv_backfill_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IVBackfillRequest","default":{"chunk_size":10}}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Iv Backfill Cron Iv Backfill Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/iv-null-rank-backfill":{"post":{"tags":["cron"],"summary":"Cron Iv Null Rank Backfill","description":"Targeted backfill for screener_cache rows where iv_rank IS NULL.\n\nCloses the data gap left by the 2026-04-15 AV rate-limit incident.\nDistinct from /cron/iv-backfill (which fills the 5-year iv_daily_snapshot table).\n\nBody (all optional):\n  limit:   integer — cap candidates for staged rollout. None = all NULL-rank tickers.\n  dry_run: bool    — return candidate count without firing AV calls.\n\nIdempotent: re-runs only re-fetch tickers whose iv_rank is still NULL after prior runs.","operationId":"cron_iv_null_rank_backfill_cron_iv_null_rank_backfill_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IVNullRankBackfillRequest","default":{"dry_run":false}}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Iv Null Rank Backfill Cron Iv Null Rank Backfill Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/options-screener":{"post":{"tags":["cron"],"summary":"Cron Options Screener","operationId":"cron_options_screener_cron_options_screener_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Options Screener Cron Options Screener Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/options-screener-midday":{"post":{"tags":["cron"],"summary":"Cron Options Screener Midday","description":"Mid-day options screener refresh — runs at 13:00 CT (19:00 UTC) on market days.\n\nUses the same full-universe build as the nightly job so intra-day IV moves\n(e.g., post-earnings crush, macro data) are reflected in the screener before\nthe US close.","operationId":"cron_options_screener_midday_cron_options_screener_midday_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Options Screener Midday Cron Options Screener Midday Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/options-screener-retry":{"post":{"tags":["cron"],"summary":"Cron Options Screener Retry","description":"OPT-02 (Phase 57): 15-minute auto-recovery job.\n\nRetries tickers that failed all 3 passes of the most recent nightly\nbuild_and_persist() run. Called ~15 min after the main options-screener\njob completes to recover partial-fetch gaps without waiting for the next\nnightly run.\n\nIdempotent: if _partial_fail_tickers is empty (no failures) it returns\nimmediately with recovered=0.","operationId":"cron_options_screener_retry_cron_options_screener_retry_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Options Screener Retry Cron Options Screener Retry Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/earnings-analysis":{"post":{"tags":["cron"],"summary":"Cron Earnings Analysis","operationId":"cron_earnings_analysis_cron_earnings_analysis_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Earnings Analysis Cron Earnings Analysis Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/signal-outcome-scoring":{"post":{"tags":["cron"],"summary":"Cron Signal Outcome Scoring","description":"Closed-loop scoring: score every matured signal_predictions row against the\nrealized forward price and write the outcome to signal_outcomes.\n\nIdempotent — relies on the partial unique index uq_signal_outcomes_pred_horizon\n(prediction_id, horizon_days) to skip rows already scored. Re-running is safe.\n\nQuery params:\n  horizons: optional comma-separated list, e.g. \"30,60,90,180\". Defaults to\n            30/60/90/180 (the 180d run keeps the legacy actual_return_6m column\n            populated for back-compat with improvement_service).\n\nAuth: X-Cron-Secret. Schedule: daily 04:00 UTC (after the 03:00 calibration\ncheck so today's scored outcomes flow into tomorrow's hit-rate snapshot).\n\nUses DB-only price lookups (score_history); does NOT hit AlphaVantage or\ntrigger the screener batch.","operationId":"cron_signal_outcome_scoring_cron_signal_outcome_scoring_post","parameters":[{"name":"horizons","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Horizons"}},{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Signal Outcome Scoring Cron Signal Outcome Scoring Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/corporate-actions-refresh":{"post":{"tags":["cron"],"summary":"Cron Corporate Actions Refresh","description":"Manual trigger for the split_history refresh (daily 04:40 UTC; CAR-01).\n\nAlphaVantage SPLITS for every screener_cache ticker (+ ledger symbols),\ninsert-only into split_history on (symbol, effective_date). Flat-rate AV,\na few minutes for the universe. `max_symbols` bounds the pull for a smoke\ntest (e.g. ?max_symbols=25). Auth: X-Cron-Secret.","operationId":"cron_corporate_actions_refresh_cron_corporate_actions_refresh_post","parameters":[{"name":"max_symbols","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Symbols"}},{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Corporate Actions Refresh Cron Corporate Actions Refresh Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/price-feed-repair":{"post":{"tags":["cron"],"summary":"Cron Price Feed Repair","description":"Manual trigger for the PF-01 dead-feed repair (PFR-01, 2026-10-05).\n\nRuns `scripts/repair_price_feeds.py`'s `run_repair` inside the backend:\nDRY RUN by default (classifies the stale `screener_cache` rows and logs\nthe plan, writes nothing; cron_runs job `price-feed-repair-dry`).\n`?apply=true` writes what the script documents — retire verified\ndelisted/renamed names, backfill provider gaps, and with\n`&purge_contaminated=true` delete the AVB successor-series bars (re-checked\nfirst). `&retire_presumed=true` also retires the presumed-dead class;\noff by default so that list is reported for review, never auto-retired.\n`only=TICK,TICK` limits the run. Auth: X-Cron-Secret.","operationId":"cron_price_feed_repair_cron_price_feed_repair_post","parameters":[{"name":"apply","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Apply"}},{"name":"purge_contaminated","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Purge Contaminated"}},{"name":"retire_presumed","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Retire Presumed"}},{"name":"max_retire","in":"query","required":false,"schema":{"type":"integer","default":60,"title":"Max Retire"}},{"name":"stale_days","in":"query","required":false,"schema":{"type":"integer","default":4,"title":"Stale Days"}},{"name":"only","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Only"}},{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Price Feed Repair Cron Price Feed Repair Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/dividends-refresh":{"post":{"tags":["cron"],"summary":"Cron Dividends Refresh","description":"Manual trigger for the dividend_history refresh (weekly Sun 05:40 UTC; DIV-01).\n\nAlphaVantage DIVIDENDS + SPLITS for every screener_cache ticker (+ ledger\nsymbols), split-restated by scripts/backfill_corporate_actions.build_rows,\nUPSERT (merge-duplicates) into dividend_history on (symbol,\nex_dividend_date). Flat-rate AV, ~10-15 min for the universe.\n`max_symbols` bounds the pull for a smoke test. Auth: X-Cron-Secret.","operationId":"cron_dividends_refresh_cron_dividends_refresh_post","parameters":[{"name":"max_symbols","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Symbols"}},{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Dividends Refresh Cron Dividends Refresh Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/price-history-snapshot":{"post":{"tags":["cron"],"summary":"Cron Price History Snapshot","description":"Daily keep-fresh job for the price_history table — upserts the latest daily\nOHLCV bars for the full active universe (screener_cache.ticker) via yfinance.\n\nSchedule: daily ~22:30 UTC (after the US market close). Fetches a short trailing\nwindow (default 5 calendar days) and upserts ON CONFLICT (symbol, date), so a\nmissed run / weekend / holiday self-heals on the next run with no duplicate rows.\n\nIsolated infrastructure: yfinance ONLY (does NOT touch AlphaVantage / AV quota)\nand does NOT trigger the screener batch.\n\nQuery params:\n  lookback_days: optional override of the trailing fetch window (default 5).\n    NOTE (PF-01 swap guard): a long manual window overlaps far more stored\n    history. A symbol is withheld (logged ERROR \"WITHHELD\", nothing written\n    for it) when EVERY overlapping stored close differs from the fetched\n    series by >25% and not by one uniform split ratio -- e.g. a window that\n    spans two splits while the stored bars reflect only one of them, so\n    every overlapping date is off by 1/2 or 1/4. Repair such names with a\n    targeted backfill (scripts/backfill_price_history_deep.py), not this\n    override.","operationId":"cron_price_history_snapshot_cron_price_history_snapshot_post","parameters":[{"name":"lookback_days","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Lookback Days"}},{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Price History Snapshot Cron Price History Snapshot Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/intraday-price-refresh":{"post":{"tags":["cron"],"summary":"Cron Intraday Price Refresh","description":"Intraday screener_cache price refresh — Schwab batch quotes only.\n\nPulls real-time quotes for the full screener_cache universe from Schwab\n(free, already the primary live-quote source) and applies them via the\napply_intraday_prices proc (mig 135), which recomputes mos_pct from the\nstored consensus_fair_value so price and MoS stay algebraically consistent.\nupdated_at is untouched (batch-freshness semantics preserved); each applied\ntick stamps price_updated_at.\n\nSchedule: every 15 min, 13:30–20:00 UTC Mon–Fri (scheduler_setup.py).\nDegrades gracefully — Schwab unconfigured/auth-dead → in-window top-of-hour\nticks (and in-window force=true) sweep yfinance instead (~39 chunked\nyf.download calls, ~15-min-delayed quotes, same sanity band + proc); the\nquarter ticks in between return status=skipped and the last applied\nprices remain in place.\n\nIsolated infrastructure: Schwab primary, yfinance fallback only (no\nAlphaVantage quota, no screener batch).\n\nQuery params:\n  force: bypass the market-hours window guard (manual/verification runs).","operationId":"cron_intraday_price_refresh_cron_intraday_price_refresh_post","parameters":[{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}},{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Intraday Price Refresh Cron Intraday Price Refresh Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/portfolio-returns-cache":{"post":{"tags":["cron"],"summary":"Cron Portfolio Returns Cache","description":"Rebuild portfolio_returns_cache from price_history (pure DB→DB).\n\nRecomputes the trailing 252-day log-return array + its trading-date grid\nfor every symbol with >= 30 usable returns, via the\nrebuild_portfolio_returns_cache proc (mig 162). No AlphaVantage, no\nyfinance, no screener batch — one statement inside Postgres.\n\nRuns after the nightly 22:30 UTC price-history snapshot and its eod sync\n(scheduler_setup.py), so the arrays always include the close that just\nlanded. Feeds POST /api/portfolio/optimize — the Black-Litterman covariance\nmatrix reads nothing else. Idempotent; a re-run just rewrites the arrays.","operationId":"cron_portfolio_returns_cache_cron_portfolio_returns_cache_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Portfolio Returns Cache Cron Portfolio Returns Cache Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/name-index-model":{"post":{"tags":["cron"],"summary":"Cron Name Index Model","description":"Refit every symbol's single-index risk model into name_index_model.\n\nBKM Ch. 8 / audit 2026-09-30 #3. 252-trading-day OLS of each name's daily\nexcess SIMPLE returns on SPY's: beta_raw, beta_blume, alpha_ann,\nresid_var_ann (σ²(e)), r_squared. Pure DB→DB — one\nprice_history_close_panel RPC (mig 174) in, one upsert on the symbol PK\n(mig 224) out. No AlphaVantage, no yfinance, no screener batch.\n\nNormally runs as the tail of the 22:30 UTC price-history chain\n(scheduler_setup.py) so the fit always includes the close that just\nlanded. This endpoint exists for a manual refit after a backfill.\nIdempotent — the table is one slot per symbol and a re-run rewrites it.","operationId":"cron_name_index_model_cron_name_index_model_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Name Index Model Cron Name Index Model Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/eod-price-sync":{"post":{"tags":["cron"],"summary":"Cron Eod Price Sync","description":"Copy the latest price_history daily closes into screener_cache.\n\nPure DB→DB (no Schwab / yfinance / AlphaVantage): latest-date bars from\nprice_history → apply_intraday_prices proc (price + mos_pct recompute +\nprice_updated_at stamp; updated_at untouched). Sanity band 0.4x–2.5x vs\nthe cached price; refuses bars older than 7 days.\n\nRuns automatically after the nightly 22:30 UTC price-history snapshot\n(scheduler_setup.py) and is the self-heal sweep's recovery action when\nscreener prices are older than the latest close — the backstop for dead\nSchwab auth freezing the intraday refresh (2026-07-31 incident).","operationId":"cron_eod_price_sync_cron_eod_price_sync_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Eod Price Sync Cron Eod Price Sync Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/ai-disruption-refresh":{"post":{"tags":["cron"],"summary":"Cron Ai Disruption Refresh","description":"Quarterly-at-earnings refresh of AI disruption exposure scores.\n\nRuns daily at 09:00 UTC. Picks up any ticker with a fresh earnings report\nwhose exposure row is stale (or missing), plus any ticker whose\nnext_refresh_after has lapsed (~95-day decay fallback). Writes LLM-generated\nscores + rationale to ai_disruption_exposure.","operationId":"cron_ai_disruption_refresh_cron_ai_disruption_refresh_post","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Ai Disruption Refresh Cron Ai Disruption Refresh Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/ai-disruption-initial":{"post":{"tags":["cron"],"summary":"Cron Ai Disruption Initial","description":"One-time initial population of ai_disruption_exposure for all screener_cache tickers.\nSafe to re-run — skips already-scored tickers.","operationId":"cron_ai_disruption_initial_cron_ai_disruption_initial_post","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":900,"title":"Limit"}},{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Ai Disruption Initial Cron Ai Disruption Initial Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/pharma-events":{"post":{"tags":["cron"],"summary":"Cron Pharma Events","description":"Manual / on-demand ClinicalTrials.gov refresh for all pharma-biotech tickers.\n\nMirrors the weekly APScheduler job (pharma_events_refresh, Sun 05:00 UTC).\nScans screener_cache for the granular pharma sectors\n('Biotechnology'/'Pharmaceuticals'/'Drug Manufacturer'), pulls upcoming\nPhase 2/3 primary-completion dates per sponsor, upserts to pharma_events, and\nthen recomputes the signal-neutral pipeline-PoS diagnostic columns on\nscreener_cache. Runs in the background (X-Cron-Secret auth).","operationId":"cron_pharma_events_cron_pharma_events_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Pharma Events Cron Pharma Events Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/warm-valuation-cache":{"post":{"tags":["cron"],"summary":"Cron Warm Valuation Cache","description":"Overnight job — pre-computes and caches valuation + options analysis\nfor every ticker in screener_cache so users see instant tab loads.\n\nRuns Mon-Fri 03:00 UTC (after screener-batch at 06:00 is fine; run at 03:00\nto warm before market open). Concurrency limited to 3 tickers at a time to\navoid overwhelming downstream services. AV rate limiting is handled by\nthe token-bucket in alphavantage_service (26.67 req/sec, 1600/min premium).","operationId":"cron_warm_valuation_cache_cron_warm_valuation_cache_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Warm Valuation Cache Cron Warm Valuation Cache Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/watchdog":{"post":{"tags":["cron"],"summary":"Cron Watchdog","description":"Hourly freshness check. For each job:\n  1. Query cron_runs for last successful completion.\n  2. If last success > FRESHNESS_LIMIT hours ago → re-trigger + send alert email.\nReturns 202 immediately; check runs in background.","operationId":"cron_watchdog_cron_watchdog_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Watchdog Cron Watchdog Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/self-heal":{"post":{"tags":["cron"],"summary":"Cron Self Heal","description":"Inspect critical data tables + circuit breakers and auto-recover broken\nstate (re-trigger the producing batch). Cooldown-guarded — safe to call often.\nPass ?dry_run=true to preview decisions without acting. Runs in background.","operationId":"cron_self_heal_cron_self_heal_post","parameters":[{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Dry Run"}},{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Self Heal Cron Self Heal Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/self-heal-status":{"get":{"tags":["cron"],"summary":"Cron Self Heal Status","description":"Synchronous dry-run report: what self-heal currently sees and would do,\nincluding live circuit-breaker states. Auth: X-Cron-Secret.","operationId":"cron_self_heal_status_cron_self_heal_status_get","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Self Heal Status Cron Self Heal Status Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/metrics-drip":{"post":{"tags":["cron"],"summary":"Cron Metrics Drip","description":"Trigger the Metrics Unpacked daily drip check.\n\nSends the next email in the 5-week educational series (P/E → MoS → FCF → ROIC\n→ Composite Verdict) to subscribed users whose day-offset matches today.\nIdempotent — safe to re-run. Sentinel range 101–105 (distinct from all other\ntrial/onboarding sentinels). Auth: X-Cron-Secret.","operationId":"cron_metrics_drip_cron_metrics_drip_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Metrics Drip Cron Metrics Drip Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/verdict-nudge-48h":{"post":{"tags":["cron"],"summary":"Cron Verdict Nudge 48H","description":"Trigger the 48h behavioral rescue email check.\n\nFires the \"Your first model verdict is ready\" email to trialing users whose\ntrial is ~48h old and who have NOT yet opened a stock verdict\n(first_screener_run_at IS NULL). Idempotent — _claim_send_slot(uid, 48)\nguarantees each user receives it at most once. Respects onboarding_opt_out.\n\nRuns synchronously so a manual/Railway-Cron caller sees the full send result\n(sent / skipped_activated / errors). Also wired into APScheduler daily at\n10:00 UTC (id=verdict_nudge_48h) — this HTTP endpoint adds an out-of-process\ntrigger so the rescue email can be force-run, Railway-Cron-driven, and\nwatchdog-recoverable if in-process scheduling is interrupted by a redeploy.\n\nAuth: X-Cron-Secret.","operationId":"cron_verdict_nudge_48h_cron_verdict_nudge_48h_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Verdict Nudge 48H Cron Verdict Nudge 48H Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/valuation-tracker-publish":{"post":{"tags":["cron"],"summary":"Cron Valuation Tracker Publish","description":"Manual trigger (or Railway Cron) for the monthly valuation tracker publish.\n\nSteps (run synchronously so caller sees full result):\n  1. Snapshot current sector multiples to valuation_tracker_history\n  2. Regenerate PDF via WeasyPrint\n  3. Send email blast to all gated_downloads subscribers (skipped if dry_run)\n  4. Queue a social post via the in-house publisher (skipped if dry_run)\n\nSet dry_run=True to test the pipeline without sending emails or posting social.\nRuns automatically on 3rd Friday of each month at 14:00 UTC (08:00 CST) via APScheduler.","operationId":"cron_valuation_tracker_publish_cron_valuation_tracker_publish_post","parameters":[{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Dry Run"}},{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Valuation Tracker Publish Cron Valuation Tracker Publish Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/sec-human-capital":{"post":{"tags":["cron"],"summary":"Cron Sec Human Capital","description":"Annual batch: employee headcount, CEO pay ratio, turnover rate, tenure,\nunion %, OSHA rate, and diversity from SEC EDGAR. Sector averages computed\nafter completion.\n\nPhase 1 (XBRL): employee count + CEO pay ratio — ~95% large-cap coverage.\nPhase 2 (text):  10-K human capital section parsed by Claude — ~20-40% yield.","operationId":"cron_sec_human_capital_cron_sec_human_capital_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecHumanCapitalRequest","default":{"parse_text":true}}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Sec Human Capital Cron Sec Human Capital Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/founder-slots-reconcile":{"post":{"tags":["cron"],"summary":"Cron Founder Slots Reconcile","description":"Release founder checkout slots leaked by abandoned Stripe checkouts.\n\nSets founder_cohort_slots.used_slots to the count of real founder\nsubscriptions (tier='founder' OR is_founder=true, status trialing/active).\nDownward-only, with a 24h grace window on the last claim so an in-flight\ncheckout is never raced. Idempotent — re-running is a no-op once reconciled.\n\nRuns synchronously so a manual caller sees the full result. Also wired\ninto APScheduler daily at 05:15 UTC (id=founder_slots_reconcile_daily).\nAuth: X-Cron-Secret.","operationId":"cron_founder_slots_reconcile_cron_founder_slots_reconcile_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Founder Slots Reconcile Cron Founder Slots Reconcile Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/status":{"get":{"tags":["cron"],"summary":"Cron Status","description":"Returns last run record for each job. Public for status + timestamps.\nUsed by health checks, admin dashboard, watchdog pre-flight.\n\n`error_message` is raw `str(exc)` from the failing job — Supabase/PostgREST\nerror bodies, table names, URL fragments — so since 2026-09-02 it is only\nincluded when the caller presents a valid X-Cron-Secret; anonymous callers\nget `has_error` instead.","operationId":"cron_status_cron_status_get","parameters":[{"name":"X-Cron-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Status Cron Status Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/anomaly-review":{"get":{"tags":["cron"],"summary":"Cron Anomaly Review Latest","description":"Newest stored daily anomaly review (anomaly_review_daily).\n\nPublic for the headline — review_date, severity, per-check counts — the\nsame way /cron/status is public for job timestamps. The `findings` list\ncarries tickers with fair values and the raw job error flags, so it is\nonly included for a caller presenting a valid X-Cron-Secret.","operationId":"cron_anomaly_review_latest_cron_anomaly_review_get","parameters":[{"name":"X-Cron-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Anomaly Review Latest Cron Anomaly Review Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["cron"],"summary":"Cron Anomaly Review Run","description":"Run the daily anomaly scan now (X-Cron-Secret). Background; lands in\ncron_runs as `anomaly-review`. A re-run on the same UTC day overwrites\nthat day's stored row.","operationId":"cron_anomaly_review_run_cron_anomaly_review_post","parameters":[{"name":"window_hours","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Window Hours"}},{"name":"X-Cron-Secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Anomaly Review Run Cron Anomaly Review Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/ff-feedback-digest":{"post":{"tags":["cron"],"summary":"Cron Ff Feedback Digest","description":"Weekly F&F feedback digest (Phase 52 / FF-03).\n\nPulls every undigested ff_feedback row (digested_at IS NULL), formats\ninto an owner email via Resend, then marks the rows as digested in a\nsecond batch. Idempotent within the digest window — re-running on the\nsame day is a no-op if everything is already marked.\n\nBody params:\n  dry_run=true  — render the digest but do NOT send + do NOT mark\n                  rows as digested. Returns the rendered email for\n                  review.\n\nAuth: X-Cron-Secret. Recipient: settings.digest_recipient.\nSchedule: Mondays 13:00 UTC (07:00 CST) via Railway Cron.","operationId":"cron_ff_feedback_digest_cron_ff_feedback_digest_post","parameters":[{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Dry Run"}},{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Ff Feedback Digest Cron Ff Feedback Digest Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cron/commodity-carry":{"post":{"tags":["cron"],"summary":"Cron Commodity Carry","description":"Manual trigger for the commodity carry / term-structure refresh.\n\nComputes roll yield / term-structure slope / backwardation-contango regime\nfor WTI + Henry Hub from the front four Yahoo per-contract futures and\nupserts commodity_carry. The signal feeds the energy/materials cyclical\nvaluation read alongside long-horizon mean reversion (research E8).\n\nNOTE: the live daily refresh is Supabase-native — `pg_cron` calls the SQL\nfunction `refresh_commodity_carry()` weekdays 23:30 UTC (migration 062),\nbecause EIA discontinued its NYMEX futures feed on 2024-04-05. This endpoint\njust RPC-triggers the same SQL function, so it stays a valid manual refresh.","operationId":"cron_commodity_carry_cron_commodity_carry_post","parameters":[{"name":"x-cron-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Cron-Secret"}}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cron Commodity Carry Cron Commodity Carry Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ingest/edgar-pit":{"post":{"tags":["ingest"],"summary":"Ingest Edgar Pit","operationId":"ingest_edgar_pit_api_ingest_edgar_pit_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestBatch"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/ingest/edgar-pit/delisted-universe":{"get":{"tags":["ingest"],"summary":"Edgar Delisted Universe","description":"Everything the loader's DELISTED universe needs from the DB, so the runner holds\nno DB credential (2026-10-08, migration 237, `research_edgar_delisted_universe_json`):\n  tails       -- per research.px_monthly delisted symbol: n_months, ever_tradeable,\n                 last_obs (what `delisted_fundamentals_pilot.panel_tails` read with\n                 the management token);\n  mapped      -- edgar_cik_map rows with a delisted_on (the `_already_mapped` resume);\n  loaded_ciks -- distinct edgar_fundamentals_pit CIKs (the `_already_loaded_ciks`\n                 resume), so a re-run re-fetches nothing from SEC that is stored.\nOne scalar-jsonb RPC (mig 210's contract: no 1,000-row db-max-rows cap, no paging).\nRead-only; the AV delisted register is fetched on the runner with its own secret.","operationId":"edgar_delisted_universe_api_ingest_edgar_pit_delisted_universe_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DelistedUniverse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/ingest/research-rows":{"post":{"tags":["ingest"],"summary":"Ingest Research Rows","description":"Validate row-by-row, not batch-wide. Before 2026-10-01 a single malformed row\n(e.g. a non-ISO date that slipped past the shaping script) 422'd the ENTIRE\nrequest, and `edgar_pit_ingest_client.post_all` treats any 4xx as fatal and aborts\nthe whole run -- the Owner Daily Desk `insider-history` pull landed only 6 of\n~6,811 symbols because one bad row, thousands of rows in, killed every batch after\nit (the rows already fetched were never lost -- they just never got re-sent,\nbecause the run died). A 422 is now reserved for a structurally wrong request (an\nunknown table, or a batch where EVERY row fails); a batch with some good and some\nbad rows inserts the good ones and reports the rest as `rows_rejected` so the\ncaller can see it without the run dying.","operationId":"ingest_research_rows_api_ingest_research_rows_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResearchRowsBatch"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResearchRowsResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/ingest/research-universe/dividends":{"get":{"tags":["ingest"],"summary":"Research Dividend Universe","description":"The delisted-dividend backfill's symbol list, computed in the DB\n(`public.research_dividend_universe_json()`, migration 210, over migration 209's\nset-returning function): every symbol in the research price panel with no\ndividend_history row. Served to the runner so it never needs a committed list or\nDB access.","operationId":"research_dividend_universe_api_ingest_research_universe_dividends_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DividendUniverse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/ingest/research-universe/{kind}":{"get":{"tags":["ingest"],"summary":"Research Desk Universe","description":"Owner Daily Desk pull universes (migrations 217/230,\n`research_desk_universe_json`): `earnings` = research-panel common stocks with no\nearnings_surprise_history row yet; `splits` = delisted panel names with no\nsplit_history row. Same one-scalar-jsonb contract as the dividends universe above.\n(The `insider` kind was dropped with `insider_transactions_history` in mig 230.)","operationId":"research_desk_universe_api_ingest_research_universe__kind__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"kind","in":"path","required":true,"schema":{"type":"string","title":"Kind"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DividendUniverse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ingest/research-export/{table}":{"get":{"tags":["ingest"],"summary":"Research Export","description":"One keyset page of an allowlisted research-panel input. `next_after` is the last\nrow's keyset (send it back as `after`), or null on a short page (the end).\n`with_total=true` adds the table's exact row count so the runner can prove it read\nevery row.","operationId":"research_export_api_ingest_research_export__table__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"table","in":"path","required":true,"schema":{"type":"string","title":"Table"}},{"name":"after","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"After"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":5000,"title":"Limit"}},{"name":"with_total","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"With Total"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportPage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/reset-password":{"post":{"tags":["auth"],"summary":"Reset Password","description":"Trigger a Supabase password reset email for the given address.\n\nUses supabase.auth.admin.generate_link (magic link / recovery link) so\nthe token is generated server-side without the user being logged in.\n\nImport is lazy — supabase transitively requires pyiceberg which has no\nWindows/Python-3.14 binary wheel; lazy import avoids module-level crash.","operationId":"reset_password_api_auth_reset_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/subscription":{"get":{"tags":["auth"],"summary":"Get Subscription","description":"Returns the subscription status for the authenticated user.\nAdmin and friends-and-family users get a synthetic active status.\nReturns null status fields if no subscription row exists yet.","operationId":"get_subscription_api_auth_subscription_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/auth/capture-referral":{"post":{"tags":["auth"],"summary":"Capture Referral","description":"Persist the er_ref cookie value at signup time so the Stripe webhook can\nauto-redeem on subscription start. Validates code format only — the actual\nredeem (with self-referral / dedup / cap checks) runs in the webhook via\nreferral._redeem_referral_for_user.\n\nIdempotent: upsert by user_id; subsequent calls overwrite the pending code,\nwhich matches \"newest share link wins\" behavior in the cookie write site.","operationId":"capture_referral_api_auth_capture_referral_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaptureReferralRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Capture Referral Api Auth Capture Referral Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/auth/capture-attribution":{"post":{"tags":["auth"],"summary":"Capture Attribution","description":"Persist the visitor's first-touch acquisition source at sign-in so signups\ncan be attributed to a channel (paid ad, programmatic SEO, referral, organic\nsearch, or direct). Without this, ad/SEO links carry utm_source / er_src but\nnothing records them, making paid spend impossible to measure.\n\nWhen the client has no stored attribution (cleared storage, second device),\nthe source is derived server-side from the X-ER-Src header / er_src cookie /\nReferer, so the row always gets at least 'direct' — never NULL.\n\nWrites use DB-level first-touch semantics (see _write_attribution): a real\nchannel label is never overwritten by a later 'direct', fixing the\nsecond-device downgrade path. Idempotent per user via the service-role key\n(bypasses RLS). Best-effort — attribution never blocks the user.","operationId":"capture_attribution_api_auth_capture_attribution_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaptureAttributionRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Capture Attribution Api Auth Capture Attribution Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/auth/preferences":{"get":{"tags":["auth"],"summary":"Get Preferences","description":"Returns the authenticated user's UI preferences.\n\nCurrently exposes `experience_mode` ('beginner' | 'expert' | None). None means\nthe user has not chosen yet, so the frontend shows the first-run prompt. Stored\non the subscriptions row (the same place as digest_opt_out / weekly_picks_email\nand other per-user flags). Best-effort: any read failure returns None so the\nclient falls back to its local value rather than erroring.","operationId":"get_preferences_api_auth_preferences_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Preferences Api Auth Preferences Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["auth"],"summary":"Set Preferences","description":"Persist the user's UI experience level so it follows them across devices.\n\nValidates the enum server-side, then upserts onto the subscriptions row by\nuser_id (creating a minimal row if none exists, mirroring capture-attribution).\nUses the service-role key to bypass RLS.","operationId":"set_preferences_api_auth_preferences_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetExperienceModeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Set Preferences Api Auth Preferences Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/auth/signup-exit-survey":{"post":{"tags":["auth"],"summary":"Signup Exit Survey","description":"Stores an abandoned-signup survey response and notifies survey@equity-rank.com.\nNo authentication required — called from the public signup page.","operationId":"signup_exit_survey_api_auth_signup_exit_survey_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupExitSurveyRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stripe/checkout":{"post":{"tags":["stripe"],"summary":"Create Checkout Session","description":"Creates a Stripe Checkout session with a 7-day free trial.\n\n- Authenticated (JWT required) but does NOT require an active subscription\n  (the user is trying to subscribe for the first time or re-subscribe).\n- Creates a Stripe Customer first, then links it to the Checkout session.\n- Returns {url: 'https://checkout.stripe.com/...'} for the frontend redirect.","operationId":"create_checkout_session_api_stripe_checkout_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/stripe/portal":{"post":{"tags":["stripe"],"summary":"Create Portal Session","description":"Creates a Stripe Customer Portal session for self-serve billing management.\n\n- Requires an active subscription (402 if none).\n- Looks up stripe_customer_id from public.subscriptions.\n- Returns {url: 'https://billing.stripe.com/...'} for frontend redirect.","operationId":"create_portal_session_api_stripe_portal_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/stripe/founder-slots":{"get":{"tags":["stripe"],"summary":"Get Founder Slots","description":"Returns remaining founder cohort slots — public endpoint, no auth required.\nUsed by the pricing page to display the scarcity counter.\nReturns {remaining: int, max: int, active: bool}.\nactive=false when the founder price ID is not configured (feature disabled).\n\nThe displayed count MUST be the true used_slots value. A prior revision\ninflated it with a +63 display baseline; a fabricated \"claimed\" count is\nfalse scarcity (FTC deceptive-practice exposure) and is not permitted on\nany purchase surface.","operationId":"get_founder_slots_api_stripe_founder_slots_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/stripe/webhook":{"post":{"tags":["stripe"],"summary":"Stripe Webhook","description":"Receive and process Stripe webhook events.\n\nSecurity:\n- Verifies Stripe-Signature header using stripe.Webhook.construct_event.\n- Returns 400 on invalid signature (must use raw bytes — request.body()).\n\nIdempotency:\n- Checks processed_webhook_events table before processing.\n- Inserts event_id after success.\n- Returns 200 even if event already processed (Stripe retries on non-2xx).\n\nHandled events:\n- customer.subscription.created  → upsert subscriptions row\n- customer.subscription.updated  → upsert subscriptions row\n- customer.subscription.deleted  → set status='cancelled', owner alert\n- invoice.paid                   → owner payment-received alert\n- invoice.payment_failed         → set status='past_due'\n- payment_method.attached        → set has_payment_method=true","operationId":"stripe_webhook_api_stripe_webhook_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/stripe/subscription":{"delete":{"tags":["stripe"],"summary":"Cancel Subscription","description":"Cancel the user's Stripe subscription at period end.\nThe subscription remains active until current_period_end, then cancels.\nReturns the updated subscription status.","operationId":"cancel_subscription_api_stripe_subscription_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/stripe/account":{"delete":{"tags":["stripe"],"summary":"Delete Account","description":"Permanently delete the user's account.\nSteps:\n  1. Cancel any active Stripe subscription immediately (not at period end)\n  2. Record email in trial_used_emails so re-signup can't get another trial\n  3. Delete the Supabase auth user (cascades to all user data via RLS)\nReturns {ok: True} on success.","operationId":"delete_account_api_stripe_account_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/stocks/search":{"get":{"tags":["stocks"],"summary":"Search Symbols","description":"Search for stocks by ticker symbol or company name.\n\nReturns up to 10 Common Stock / ETP matches from AlphaVantage SYMBOL_SEARCH, ordered with\nexact ticker matches first.  No subscription required — this is a\ndiscovery endpoint used by the search-as-you-type input.\n\n`sector` is set when the query names a sector or industry (\"mining stocks\",\n\"semis\", \"regional banks\"): {label, total, results}, largest companies\nfirst, from screener_cache. It is null otherwise. `results` is unchanged.","operationId":"search_symbols_api_stocks_search_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":50,"description":"Ticker or company name","default":"","title":"Q"},"description":"Ticker or company name"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Search Symbols Api Stocks Search Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stocks/{symbol}/snapshot":{"get":{"tags":["stocks"],"summary":"Get Stock Snapshot","description":"Lightweight: price + score only, no full analysis detail.\nNo auth required — used by the Portfolio Tracker for batch refreshes.","operationId":"get_stock_snapshot_api_stocks__symbol__snapshot_get","parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","description":"US equity ticker, e.g. AAPL","title":"Symbol"},"description":"US equity ticker, e.g. AAPL"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Stock Snapshot Api Stocks  Symbol  Snapshot Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stocks/{symbol}/sentiment":{"get":{"tags":["stocks"],"summary":"Get Stock Sentiment","description":"Returns employee sentiment for a stock, synthesized from Reddit and Glassdoor.\nRequires an active or trialing Stripe subscription.\nResults cached 24h.","operationId":"get_stock_sentiment_api_stocks__symbol__sentiment_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","description":"US equity ticker, e.g. AAPL","title":"Symbol"},"description":"US equity ticker, e.g. AAPL"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Stock Sentiment Api Stocks  Symbol  Sentiment Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stocks/{symbol}/ai-disruption":{"get":{"tags":["stocks"],"summary":"Get Stock Ai Disruption","description":"Returns the LLM-generated AI disruption exposure for a stock.\n\nRegenerated quarterly at each company's earnings. If no LLM row exists yet,\nreturns a minimal payload with only the rule-based fallback score and a\nsource flag so the client can show a \"pending first refresh\" placeholder.\nInformational only, not investment advice.","operationId":"get_stock_ai_disruption_api_stocks__symbol__ai_disruption_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","description":"US equity ticker, e.g. AAPL","title":"Symbol"},"description":"US equity ticker, e.g. AAPL"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Stock Ai Disruption Api Stocks  Symbol  Ai Disruption Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stocks/{symbol}/save":{"post":{"tags":["stocks"],"summary":"Run Save Analysis","description":"Run SAVE (Sentiment-Adjusted Valuation Engine) analysis for a stock.\nMakes 5 real-time web searches via Claude Sonnet + 1 scoring pass.\nResults stored in save_history for prospective backtesting.\n\nQuery param `as_of` (YYYY-MM-DD): frame queries historically for backtesting.\nQuery param `force` (bool): skip the 6-hour staleness cache and always re-run.","operationId":"run_save_analysis_api_stocks__symbol__save_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}},{"name":"as_of","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"As Of"}},{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Run Save Analysis Api Stocks  Symbol  Save Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stocks/{symbol}/save/history":{"get":{"tags":["stocks"],"summary":"Get Save History","description":"Return historical SAVE scores for charting.\nUse `timeframe` for preset windows (1w/1m/3m/1y/3y/5y/all) or\n`days` for an exact day count. `timeframe` takes precedence when provided.","operationId":"get_save_history_api_stocks__symbol__save_history_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":90,"title":"Days"}},{"name":"timeframe","in":"query","required":false,"schema":{"type":"string","description":"Preset window: 1w | 1m | 3m | 1y | 3y | 5y | all","default":"3m","title":"Timeframe"},"description":"Preset window: 1w | 1m | 3m | 1y | 3y | 5y | all"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Save History Api Stocks  Symbol  Save History Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stocks/{symbol}/candles":{"get":{"tags":["stocks"],"summary":"Get Stock Candles","description":"Return OHLCV candlestick data for the PriceChart widget.\nPrimary source: AlphaVantage TIME_SERIES_DAILY.\nResponse shape: {s, t, o, h, l, c, v} (standard OHLCV format), plus an\noptional `benchmark` object ({symbol, t, c}) when `benchmark=SPY|SECTOR`\nis requested — served from a dedicated cache slot (see `_fetch_benchmark_series`).\nWhen the benchmark can't be resolved, `benchmark` is null and\n`benchmark_reason` explains why — never a 500.\nNo subscription required — chart is shown in the free modal header.","operationId":"get_stock_candles_api_stocks__symbol__candles_get","parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","description":"US equity ticker, e.g. AAPL","title":"Symbol"},"description":"US equity ticker, e.g. AAPL"},{"name":"resolution","in":"query","required":false,"schema":{"type":"string","description":"Bar resolution (D = daily)","default":"D","title":"Resolution"},"description":"Bar resolution (D = daily)"},{"name":"from","in":"query","required":true,"schema":{"type":"integer","description":"Start timestamp (Unix seconds)","title":"From"},"description":"Start timestamp (Unix seconds)"},{"name":"to","in":"query","required":true,"schema":{"type":"integer","description":"End timestamp (Unix seconds)","title":"To"},"description":"End timestamp (Unix seconds)"},{"name":"benchmark","in":"query","required":false,"schema":{"anyOf":[{"type":"string","pattern":"^(SPY|SECTOR)$"},{"type":"null"}],"description":"Optional chart overlay: SPY, or SECTOR (resolved from screener_cache)","title":"Benchmark"},"description":"Optional chart overlay: SPY, or SECTOR (resolved from screener_cache)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Stock Candles Api Stocks  Symbol  Candles Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stocks/{symbol}":{"get":{"tags":["stocks"],"summary":"Get Stock Analysis","description":"Returns full EquityRank analysis for a single ticker.\nRequires an active/trialing subscription — OR, when the no-card free tier\nis live, a free-plan account within its 3-distinct-symbols/month quota\n(this is the metering ANCHOR for the stock page).","operationId":"get_stock_analysis_api_stocks__symbol__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Stock Analysis Api Stocks  Symbol  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/options/{symbol}/expirations":{"get":{"tags":["options"],"summary":"Get Expirations","description":"List available option expiry dates for a symbol.","operationId":"get_expirations_api_options__symbol__expirations_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Expirations Api Options  Symbol  Expirations Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/options/{symbol}/chain":{"get":{"tags":["options"],"summary":"Get Chain","description":"Return the options chain for a symbol and expiry date.\nOptionally filter by side ('call' or 'put').\nContracts are sorted by strike ascending.\nIf stock_price is provided, BSM fair value and delta are computed for each contract.","operationId":"get_chain_api_options__symbol__chain_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}},{"name":"expiry","in":"query","required":true,"schema":{"type":"string","description":"Expiry date YYYY-MM-DD","title":"Expiry"},"description":"Expiry date YYYY-MM-DD"},{"name":"side","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"'call' or 'put', omit for both","title":"Side"},"description":"'call' or 'put', omit for both"},{"name":"stock_price","in":"query","required":false,"schema":{"type":"number","description":"Current stock price for BSM enrichment","default":0.0,"title":"Stock Price"},"description":"Current stock price for BSM enrichment"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Chain Api Options  Symbol  Chain Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/options/{symbol}/summary":{"get":{"tags":["options"],"summary":"Get Summary","description":"Return IV rank, IV percentile, put/call ratio, volume/OI totals, and implied move fields.","operationId":"get_summary_api_options__symbol__summary_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}},{"name":"stock_price","in":"query","required":false,"schema":{"type":"number","description":"Current stock price for implied move calc","default":0.0,"title":"Stock Price"},"description":"Current stock price for implied move calc"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Summary Api Options  Symbol  Summary Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/options/{symbol}/earnings-play":{"get":{"tags":["options"],"summary":"Get Earnings Play","description":"Earnings play analysis: next date, IV crush estimate, recommended strategy.","operationId":"get_earnings_play_api_options__symbol__earnings_play_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}},{"name":"stock_price","in":"query","required":false,"schema":{"type":"number","description":"Current stock price for implied move calc","default":0.0,"title":"Stock Price"},"description":"Current stock price for implied move calc"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Earnings Play Api Options  Symbol  Earnings Play Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/options/{symbol}/recommendations":{"get":{"tags":["options"],"summary":"Get Recommendations","description":"Generate an AI strategy recommendation for options on this symbol.\n\nFetches IV summary and chain, finds ATM options, then runs the\nrules-based recommendations engine.\n\nQuery params:\n  stock_price    — required, current stock price\n  rank_score     — EquityRank total score (default 50)\n  momentum_score — momentum pillar score (default 50)\n  quality_score  — quality pillar score (default 50)\n  expiry         — if provided, use this expiry for ATM price lookup","operationId":"get_recommendations_api_options__symbol__recommendations_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}},{"name":"stock_price","in":"query","required":true,"schema":{"type":"number","description":"Current stock price","title":"Stock Price"},"description":"Current stock price"},{"name":"rank_score","in":"query","required":false,"schema":{"type":"number","description":"EquityRank total score 0-100","default":50.0,"title":"Rank Score"},"description":"EquityRank total score 0-100"},{"name":"momentum_score","in":"query","required":false,"schema":{"type":"number","description":"Momentum pillar score 0-100","default":50.0,"title":"Momentum Score"},"description":"Momentum pillar score 0-100"},{"name":"quality_score","in":"query","required":false,"schema":{"type":"number","description":"Quality pillar score 0-100","default":50.0,"title":"Quality Score"},"description":"Quality pillar score 0-100"},{"name":"consensus_margin_of_safety","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Consensus MOS % (negative = overvalued)","title":"Consensus Margin Of Safety"},"description":"Consensus MOS % (negative = overvalued)"},{"name":"expiry","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Target expiry YYYY-MM-DD for ATM data","title":"Expiry"},"description":"Target expiry YYYY-MM-DD for ATM data"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Recommendations Api Options  Symbol  Recommendations Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/options/{symbol}/analysis":{"get":{"tags":["options"],"summary":"Get Analysis","description":"Combined options analysis in a single call:\n- IV rank, PCR, implied move (summary)\n- Strategy recommendation with rationale and payoff math\n- IV mispricing scan (BSM vs market mid, top 5 candidates)\n\nOn success: result is cached to analysis_cache for offline/budget fallback.\nOn failure: serves the most-recent cached result if available.\n\nThe frontend calls this once per symbol load; no further options endpoints needed.","operationId":"get_analysis_api_options__symbol__analysis_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}},{"name":"stock_price","in":"query","required":true,"schema":{"type":"number","description":"Current stock price","title":"Stock Price"},"description":"Current stock price"},{"name":"rank_score","in":"query","required":false,"schema":{"type":"number","description":"EquityRank total score 0-100","default":50.0,"title":"Rank Score"},"description":"EquityRank total score 0-100"},{"name":"momentum_score","in":"query","required":false,"schema":{"type":"number","description":"Momentum pillar score 0-100","default":50.0,"title":"Momentum Score"},"description":"Momentum pillar score 0-100"},{"name":"quality_score","in":"query","required":false,"schema":{"type":"number","description":"Quality pillar score 0-100","default":50.0,"title":"Quality Score"},"description":"Quality pillar score 0-100"},{"name":"consensus_margin_of_safety","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Consensus MOS % (negative = overvalued)","title":"Consensus Margin Of Safety"},"description":"Consensus MOS % (negative = overvalued)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Analysis Api Options  Symbol  Analysis Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/options/{symbol}/iv-crush-estimate":{"get":{"tags":["options"],"summary":"Get Iv Crush Estimate","description":"Return the best available IV crush % estimate for this ticker.\nUses historical per-ticker data (≥3 events), sector average (≥5), or 35% heuristic fallback.","operationId":"get_iv_crush_estimate_api_options__symbol__iv_crush_estimate_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}},{"name":"sector","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Sector for sector-level fallback","title":"Sector"},"description":"Sector for sector-level fallback"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Iv Crush Estimate Api Options  Symbol  Iv Crush Estimate Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/options/admin/capture-pre-earnings-iv":{"post":{"tags":["options"],"summary":"Trigger Pre Earnings Capture","description":"Admin: scan screener_cache for tickers with earnings 1-5 days out and record pre-earnings IV.\nThis runs automatically via the daily cron job but can be triggered manually.","operationId":"trigger_pre_earnings_capture_api_options_admin_capture_pre_earnings_iv_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Trigger Pre Earnings Capture Api Options Admin Capture Pre Earnings Iv Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/options/admin/capture-post-earnings-iv":{"post":{"tags":["options"],"summary":"Trigger Post Earnings Capture","description":"Admin: fill in post-earnings IV for any rows where earnings_date was 1-2 days ago.\nRuns automatically via daily cron but can be triggered manually.","operationId":"trigger_post_earnings_capture_api_options_admin_capture_post_earnings_iv_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Trigger Post Earnings Capture Api Options Admin Capture Post Earnings Iv Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/options/admin/iv-snapshot":{"post":{"tags":["options"],"summary":"Trigger Iv Snapshot","description":"Admin: fetch today's IV rank for all screener tickers from Barchart and store in iv_daily_snapshot.\nRuns automatically at market close via daily cron.","operationId":"trigger_iv_snapshot_api_options_admin_iv_snapshot_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Trigger Iv Snapshot Api Options Admin Iv Snapshot Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/options/admin/iv-backfill":{"post":{"tags":["options"],"summary":"Trigger Iv Backfill","description":"Admin: backfill up to 365 days of historical IV rank from Barchart getEquityOptionsOverviewHistory.\nOne-time operation — safe to re-run (upserts on ticker+date).\nLong-running: may take several minutes for a full screener universe.","operationId":"trigger_iv_backfill_api_options_admin_iv_backfill_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"Number of calendar days to backfill","default":365,"title":"Days"},"description":"Number of calendar days to backfill"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Trigger Iv Backfill Api Options Admin Iv Backfill Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/screener":{"get":{"tags":["screener"],"summary":"Get Screener","description":"Stock screener.\n- Unauthenticated: returns limited fields (ticker, company_name, sector, score, mos_pct) for top 50 stocks.\n- Authenticated with active subscription: returns full screener response (existing behavior).\n\nFull response logic:\n  1. Fetch all scored rows from screener_cache (save_score not null).\n  2. Compute the 90th-percentile score threshold — exposed as `count`\n     so the UI can show pool size for context.\n  3. Return the top 20 stocks by overall_score (falls back to save_score)\n     from the top-decile pool. Admin-curated weekly picks override when\n     present.","operationId":"get_screener_api_screener_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/screener/iv-percentile":{"get":{"tags":["screener"],"summary":"Get Iv Percentile Screener","description":"Free IV Percentile Screener — no auth required.\nReturns the most-recent iv_daily_snapshot rows joined with screener_cache,\nsorted by iv_rank DESC. Results cached 1 hour in memory.","operationId":"get_iv_percentile_screener_api_screener_iv_percentile_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/screener/browse":{"get":{"tags":["screener"],"summary":"Browse Screener","description":"Browse the full screener_cache with optional filtering.\nReturns stocks matching filters, sorted by sort_by descending.\nUsed by the dashboard inline screener panel.\nFiltering and limiting are pushed to Supabase to avoid full-table transfers.","operationId":"browse_screener_api_screener_browse_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"sector","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by sector (partial match)","title":"Sector"},"description":"Filter by sector (partial match)"},{"name":"min_score","in":"query","required":false,"schema":{"type":"number","description":"Minimum Fundamentals Score (0-100)","default":0.0,"title":"Min Score"},"description":"Minimum Fundamentals Score (0-100)"},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","description":"Column to sort by","default":"equity_rank_score","title":"Sort By"},"description":"Column to sort by"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"full_data_only","in":"query","required":false,"schema":{"type":"boolean","description":"When true, exclude rows where has_full_data is false","default":true,"title":"Full Data Only"},"description":"When true, exclude rows where has_full_data is false"},{"name":"min_piotroski_score","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":9,"minimum":0},{"type":"null"}],"description":"Minimum Piotroski F-Score (0–9)","title":"Min Piotroski Score"},"description":"Minimum Piotroski F-Score (0–9)"},{"name":"graham_below_price","in":"query","required":false,"schema":{"type":"boolean","description":"When true, only show stocks trading below their Graham Number","default":false,"title":"Graham Below Price"},"description":"When true, only show stocks trading below their Graham Number"},{"name":"min_sue_score","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":10.0,"minimum":-10.0},{"type":"null"}],"description":"Minimum SUE z-score (BST-01); winsorized to [-10, 10]","title":"Min Sue Score"},"description":"Minimum SUE z-score (BST-01); winsorized to [-10, 10]"},{"name":"min_eps_revision_ratio","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"description":"Minimum EPS revision ratio — upward share of past-30-day analyst revisions [0.0, 1.0] (BST-02)","title":"Min Eps Revision Ratio"},"description":"Minimum EPS revision ratio — upward share of past-30-day analyst revisions [0.0, 1.0] (BST-02)"},{"name":"max_short_ratio","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":100.0,"minimum":0.0},{"type":"null"}],"description":"Maximum days-to-cover short ratio (factual ceiling filter)","title":"Max Short Ratio"},"description":"Maximum days-to-cover short ratio (factual ceiling filter)"},{"name":"max_illiquidity_premium","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":0.05,"minimum":0.0},{"type":"null"}],"description":"Maximum Amihud illiquidity premium (0.0–0.05). Set to 0.01 to filter for liquid names only. Rows without illiquidity data are excluded when this filter is active.","title":"Max Illiquidity Premium"},"description":"Maximum Amihud illiquidity premium (0.0–0.05). Set to 0.01 to filter for liquid names only. Rows without illiquidity data are excluded when this filter is active."},{"name":"min_roic","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":2.0,"minimum":-1.0},{"type":"null"}],"description":"Minimum ROIC, decimal fraction (0.15 = 15%). Rows without ROIC data are excluded when active.","title":"Min Roic"},"description":"Minimum ROIC, decimal fraction (0.15 = 15%). Rows without ROIC data are excluded when active."},{"name":"min_gross_margin","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":100.0,"minimum":-100.0},{"type":"null"}],"description":"Minimum gross margin, PERCENT scale (35.0 = 35%). Rows without gross margin data are excluded when active.","title":"Min Gross Margin"},"description":"Minimum gross margin, PERCENT scale (35.0 = 35%). Rows without gross margin data are excluded when active."},{"name":"min_net_margin","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":150.0,"minimum":-300.0},{"type":"null"}],"description":"Minimum net margin, PERCENT scale (8.0 = 8%). Rows without net margin data are excluded when active.","title":"Min Net Margin"},"description":"Minimum net margin, PERCENT scale (8.0 = 8%). Rows without net margin data are excluded when active."},{"name":"min_revenue_growth","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":1000.0,"minimum":-100.0},{"type":"null"}],"description":"Minimum revenue growth YoY, PERCENT scale (8.0 = 8%). Rows without revenue growth data are excluded when active.","title":"Min Revenue Growth"},"description":"Minimum revenue growth YoY, PERCENT scale (8.0 = 8%). Rows without revenue growth data are excluded when active."},{"name":"min_eps_growth","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":1000.0,"minimum":-100.0},{"type":"null"}],"description":"Minimum EPS growth YoY, PERCENT scale (12.0 = 12%). Rows without EPS growth data are excluded when active.","title":"Min Eps Growth"},"description":"Minimum EPS growth YoY, PERCENT scale (12.0 = 12%). Rows without EPS growth data are excluded when active."},{"name":"min_fcf_yield","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":1.0,"minimum":-1.0},{"type":"null"}],"description":"Minimum FCF yield, decimal fraction (0.05 = 5%). Rows without FCF yield data are excluded when active.","title":"Min Fcf Yield"},"description":"Minimum FCF yield, decimal fraction (0.05 = 5%). Rows without FCF yield data are excluded when active."},{"name":"max_net_debt_to_ebitda","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":50.0,"minimum":-10.0},{"type":"null"}],"description":"Maximum net debt / EBITDA leverage ratio (e.g. 2.5 = 2.5x). Rows without leverage data are excluded when active.","title":"Max Net Debt To Ebitda"},"description":"Maximum net debt / EBITDA leverage ratio (e.g. 2.5 = 2.5x). Rows without leverage data are excluded when active."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrowseResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/screener/demo":{"get":{"tags":["screener"],"summary":"Screener Demo","description":"Public no-auth demo screener (LEAD-01). Runs one of 3 server-defined presets\nagainst the in-memory cache via the SHARED _filter_and_sort_rows helper (LEAD-04),\ncapped at 10 rows server-side (LEAD-02), returning only non-gated public fields.","operationId":"screener_demo_api_screener_demo_get","parameters":[{"name":"preset","in":"query","required":true,"schema":{"type":"string","description":"Preset slug","title":"Preset"},"description":"Preset slug"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/screener/sector/{sector}":{"get":{"tags":["screener"],"summary":"Screener Sector","description":"Public no-auth sector index (SEO-04). Reuses the shared filter helper and\nthe public-safe row allowlist — the same data path as the demo, scoped to one\nsector. A GICS parent name expands to its exact platform-label family via\n_GICS_SECTOR_FAMILY (see above); any other value falls through to\n_filter_and_sort_rows' sanitized partial match, so an unknown sector returns\nan empty list rather than an error and no extra validation is needed.","operationId":"screener_sector_api_screener_sector__sector__get","parameters":[{"name":"sector","in":"path","required":true,"schema":{"type":"string","title":"Sector"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/screener/dividend-growth/{sector}/{band}":{"get":{"tags":["screener"],"summary":"Screener Dividend Growth","description":"Public no-auth dividend-growth sector×band screener (GROW-02).\n\nReturns up to 20 filtered rows for the given sector slug and yield band slug.\nResults are cached 4 hours in memory (33 slots). Uses _demo_public_row allowlist.","operationId":"screener_dividend_growth_api_screener_dividend_growth__sector___band__get","parameters":[{"name":"sector","in":"path","required":true,"schema":{"type":"string","title":"Sector"}},{"name":"band","in":"path","required":true,"schema":{"type":"string","title":"Band"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/screener/ask":{"post":{"tags":["screener"],"summary":"Screener Ask","description":"Conversational screener. NL query -> filters -> existing screen. Always 200.\n\nASK-03 advice-free guarantee is STRUCTURAL: the LLM's only output channel is the\nemit_filters tool schema, so it cannot emit a buy/sell sentence. The only\nuser-facing strings the backend authors are the fixed CLARIFY_* constants.","operationId":"screener_ask_api_screener_ask_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AskRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/stocks/{symbol}/history":{"get":{"tags":["history"],"summary":"Get Score History","description":"Return score history for a symbol — public endpoint, no auth required.","operationId":"get_score_history_api_stocks__symbol__history_get","parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","description":"US equity ticker, e.g. AAPL","title":"Symbol"},"description":"US equity ticker, e.g. AAPL"},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"description":"Number of calendar days to look back","default":90,"title":"Days"},"description":"Number of calendar days to look back"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Score History Api Stocks  Symbol  History Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stocks/{symbol}/valuation-history":{"get":{"tags":["history"],"summary":"Get Valuation History","description":"Return valuation snapshots for a symbol — for accuracy tracking.\n\nEach row contains the fair value estimates and forward-looking predictions\nrecorded at the time of the valuation run, alongside the price at that time.\nCompare forward_pe_fair_value from N days ago against today's price to measure accuracy.","operationId":"get_valuation_history_api_stocks__symbol__valuation_history_get","parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","description":"US equity ticker, e.g. AAPL","title":"Symbol"},"description":"US equity ticker, e.g. AAPL"},{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":730,"minimum":1,"description":"Number of calendar days to look back","default":180,"title":"Days"},"description":"Number of calendar days to look back"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Valuation History Api Stocks  Symbol  Valuation History Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/digest/send":{"post":{"tags":["digest"],"summary":"Send Digest","description":"Build digest for *symbols* and email it to the authenticated user's own address.","operationId":"send_digest_api_digest_send_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_send_digest_api_digest_send_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Send Digest Api Digest Send Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/digest/preview":{"get":{"tags":["digest"],"summary":"Preview Digest","description":"Return digest JSON without sending — for frontend preview.","operationId":"preview_digest_api_digest_preview_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbols","in":"query","required":true,"schema":{"type":"string","description":"Comma-separated tickers, e.g. AAPL,MSFT","title":"Symbols"},"description":"Comma-separated tickers, e.g. AAPL,MSFT"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Preview Digest Api Digest Preview Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/digest/unsubscribe":{"post":{"tags":["digest"],"summary":"Unsubscribe Weekly Picks","description":"Opt the authenticated user out of the weekly watchlist email.","operationId":"unsubscribe_weekly_picks_api_digest_unsubscribe_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Unsubscribe Weekly Picks Api Digest Unsubscribe Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/stocks/{symbol}/valuation":{"get":{"tags":["valuation"],"summary":"Get Stock Valuation","description":"Public valuation endpoint — thin wrapper that records calc_exception\nanomalies (Phase 31 Plan 01, ANOM-02) when the underlying computation\nraises an unexpected error.\n\nHTTPException is re-raised unchanged (it represents a known failure mode\nlike 422 invalid ticker or 503 market data unavailable — those are not\nanomalies). Unexpected exceptions are logged to av_anomaly_log with\nrule='calc_exception', then re-raised so the caller still sees the 500.","operationId":"get_stock_valuation_api_stocks__symbol__valuation_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValuationResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/valuation/{symbol}/reweight":{"post":{"tags":["valuation"],"summary":"Reweight Consensus","description":"Recompute the consensus model estimate under user-supplied method weights.\n\nReuses compute_consensus_valuation(weights_override=...) so the\nreturned number is the canonical engine estimate. The engine normalizes the\nweights and falls back to an equal-weight blend when their sum is zero.","operationId":"reweight_consensus_api_valuation__symbol__reweight_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReweightRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Reweight Consensus Api Valuation  Symbol  Reweight Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/valuation/{symbol}/assumptions":{"post":{"tags":["valuation"],"summary":"Compute Assumptions Valuation","description":"Return the platform's own model estimate alongside a SEPARATE model\nestimate computed under the caller's own growth/margin/discount-rate/\nterminal-growth assumptions. Neither block is advice; both are labeled\nmodel estimates.","operationId":"compute_assumptions_valuation_api_valuation__symbol__assumptions_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssumptionsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Compute Assumptions Valuation Api Valuation  Symbol  Assumptions Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/valuation/{symbol}/assumptions/saved":{"get":{"tags":["valuation"],"summary":"Get Saved Assumptions","description":"Return the caller's saved overrides for `symbol`, or {} if none saved.","operationId":"get_saved_assumptions_api_valuation__symbol__assumptions_saved_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Saved Assumptions Api Valuation  Symbol  Assumptions Saved Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["valuation"],"summary":"Put Saved Assumptions","description":"Upsert the caller's overrides for `symbol`. Persists inputs only.","operationId":"put_saved_assumptions_api_valuation__symbol__assumptions_saved_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssumptionsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Put Saved Assumptions Api Valuation  Symbol  Assumptions Saved Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["valuation"],"summary":"Delete Saved Assumptions","description":"Delete the caller's saved overrides for `symbol`.","operationId":"delete_saved_assumptions_api_valuation__symbol__assumptions_saved_delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/valuation/sector-multiples/{sector}":{"get":{"tags":["valuation"],"summary":"Get Sector Multiple","description":"Returns the user's sector multiples for the given sector.\nFalls back to DEFAULT_SECTOR_MULTIPLES if no custom row exists.\nNo subscription required — this is a config endpoint.","operationId":"get_sector_multiple_api_valuation_sector_multiples__sector__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"sector","in":"path","required":true,"schema":{"type":"string","title":"Sector"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SectorMultiple"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["valuation"],"summary":"Patch Sector Multiple","description":"Partial-update the user's sector multiples for the given sector.\nFetches current values (or defaults) then merges update fields.\nUpserts to Supabase with conflict resolution on (user_id, sector).","operationId":"patch_sector_multiple_api_valuation_sector_multiples__sector__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"sector","in":"path","required":true,"schema":{"type":"string","title":"Sector"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SectorMultipleUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SectorMultiple"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/valuation/sector-calibration":{"get":{"tags":["valuation"],"summary":"Get Sector Calibration","description":"Returns sector-level calibration data for the SAVE multiplier and Innovation alpha.\n\nEach entry explains WHY a sector has its specific multiplier/alpha, backed by the\nempirical IC analysis documented in SAVE_STATISTICAL_THESIS.md. Power users can\nuse this to understand why NVDA gets a different SAVE weight than JPM.\n\nRequires active subscription — this is a premium transparency feature.","operationId":"get_sector_calibration_api_valuation_sector_calibration_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SectorCalibrationResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/valuation/accuracy-stats":{"get":{"tags":["valuation"],"summary":"Get Accuracy Stats","description":"Model accuracy stats endpoint — hit rates WITHHELD (2026-08-03, P0-4).\n\nPublic — no auth required. This endpoint previously served either a\nsynthetic Monte Carlo hit rate (0.891) or, at real_n >= 50, the raw\ndirectional hit rate. The 2026-08-03 verification pack proved the raw\nrate is statistically identical to the market-drift base rate at every\nhorizon — zero information — so no hit rate is published in any form.\ncompute_accuracy_stats() still runs internally (nightly calibration\ntelemetry); only publication is cut. Counters and the timestamp remain\nso consumers can show tracking progress honestly.","operationId":"get_accuracy_stats_api_valuation_accuracy_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Accuracy Stats Api Valuation Accuracy Stats Get"}}}}}}},"/api/valuation/{symbol}/snapshot":{"get":{"tags":["valuation-split"],"summary":"Get Valuation Snapshot","description":"Return the TTM point-in-time valuation snapshot for a stock.\n\nCache-first: reads from the in-memory valuation cache populated by\nGET /api/stocks/{symbol}/valuation. Returns 503 if cache is cold.","operationId":"get_valuation_snapshot_api_valuation__symbol__snapshot_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValuationSnapshot"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/valuation/{symbol}/projection":{"get":{"tags":["valuation-split"],"summary":"Get Valuation Projection","description":"Return 1-year bear/base/bull model scenarios for a stock.\n\nFetches live stock data and runs compute_projected_fair_value_range at a\n1-year model horizon. Returns a partial envelope (data_complete=false) when\nfinancial inputs are insufficient — never returns 500 on data gaps.\n\nModel scenarios only. Not a model fair value range under model scenarios or\ninvestment advice. For informational and educational purposes only.","operationId":"get_valuation_projection_api_valuation__symbol__projection_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectionEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/valuation/{symbol}":{"get":{"tags":["valuation-split"],"summary":"Get Valuation Envelope","description":"Return the combined ValuationEnvelope (snapshot + optional projection).\n\nschema_version=\"2.0\" signals to clients that this is the split-format response.\nProjection is best-effort: if compute fails, snapshot is still returned with\nprojection=null. Returns 503 if the snapshot cache is cold.","operationId":"get_valuation_envelope_api_valuation__symbol__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValuationEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/audit/{ticker}":{"get":{"tags":["audit"],"summary":"Get Audit Trail","description":"Phase 48 / AUDIT-01: structured audit trail for a ticker.\n\nReturns the AuditTrail for the given ticker, reading from screener_cache\n(input snapshot, per-method FVs, consensus, 80% CIs from Phase 47)\n+ av_anomaly_log (data-quality anomalies in the calc window).\n\nNO external fetches — performance target p95 ≤500ms (AUDIT-05).\n\nArgs:\n    ticker: stock symbol (case-insensitive; uppercased internally)\n    days: anomaly window in days (default 7, max 90)\n\nReturns:\n    AuditTrail with versioned schema (schema_version=\"1.0\" in v2.0)\n\nRaises:\n    401 if no JWT and no service bypass\n    403 if JWT lacks `is_admin` or `is_friends_family`\n    404 if ticker not found in screener_cache\n    400 if days outside [1, 90]","operationId":"get_audit_trail_api_audit__ticker__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":7,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditTrail"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/backtest/summary":{"get":{"tags":["backtest"],"summary":"Get Backtest Summary","description":"Return the latest BacktestRunner result.\n\nPublic — no auth. The runner self-caches for 6 hours; the response\nis built from whatever the runner returns (including the empty-shell\n`sufficient_data=False` result when there's < 30 valuation_history rows).","operationId":"get_backtest_summary_api_backtest_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/app__routers__backtest__BacktestSummaryResponse"}}}}}}},"/api/backtest/track-record":{"get":{"tags":["backtest"],"summary":"Get Track Record","description":"Published, point-in-time track record (AWL-08).\n\nPublic — no auth. Built from two real sources and nothing else:\n  - hit_rates: methodology._fetch_realized_outcomes over\n    signal_outcomes, every bucket carrying n / ticker_count / window /\n    horizon; a bucket below the honest minimum is emitted with\n    sufficient=False and hit_rate=None, never a borrowed number.\n  - backtest: this file's own /summary simulation result, with its\n    compliance caveats passed through verbatim.\n\nDegrades to `degraded=True` + empty hit_rates on any Supabase failure —\nnever falls through to a synthetic value.","operationId":"get_track_record_api_backtest_track_record_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrackRecordResponse"}}}}}}},"/api/ff/invites":{"post":{"tags":["friends-and-family"],"summary":"Create Invite","description":"Admin-only: create a new invite code.","operationId":"create_invite_api_ff_invites_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInviteRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteAdmin"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["friends-and-family"],"summary":"List Invites","description":"Admin-only: list recent invites for the /admin/ff dashboard.","operationId":"list_invites_api_ff_invites_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/InviteAdmin"},"title":"Response List Invites Api Ff Invites Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ff/invite/{code}":{"get":{"tags":["friends-and-family"],"summary":"Preview Invite","description":"Public, no-auth: preview an invite by code.\n\nUsed by /invite/:code landing page to render the F&F memo before\nasking the user to sign in + redeem. Does NOT leak created_by_email\nor use counts.","operationId":"preview_invite_api_ff_invite__code__get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvitePreview"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ff/redeem/{code}":{"post":{"tags":["friends-and-family"],"summary":"Redeem Invite","description":"Authenticated user: redeem an invite code.\n\nIdempotent in the sense that if the user is already F&F, redemption\nstill bumps use_count by 1 (an audit trail of who clicked the link).\nThe Supabase admin API call to set app_metadata is fail-soft — if\nthe supabase auth call fails, the redemption row is still written\nso the owner can manually backfill via /api/admin/users/{id}/role.","operationId":"redeem_invite_api_ff_redeem__code__post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedeemInviteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedeemInviteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ff/feedback":{"post":{"tags":["friends-and-family"],"summary":"Submit Feedback","description":"F&F (or admin): submit feedback.\n\nCaptures user-agent from the request headers as debug context.\nService layer normalizes ticker to uppercase + validates body length.","operationId":"submit_feedback_api_ff_feedback_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["friends-and-family"],"summary":"List Feedback","description":"Admin-only: list feedback for /admin/ff dashboard + FF-03 digest.","operationId":"list_feedback_api_ff_feedback_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":200,"title":"Limit"}},{"name":"undigested_only","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Undigested Only"}},{"name":"unresolved_only","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Unresolved Only"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FeedbackAdmin"},"title":"Response List Feedback Api Ff Feedback Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/investor/view":{"post":{"tags":["investor"],"summary":"Record View","description":"Public, no-auth: record one page view of /investor.\n\nBest-effort — if Supabase is unreachable we silently swallow the\nerror and return recorded=False rather than 5xx-ing the public page.","operationId":"record_view_api_investor_view_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewPingRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewPingResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/investor/views":{"get":{"tags":["investor"],"summary":"View Summary","description":"Admin-only aggregate: 7d / 30d / all-time view counts, broken down by referrer.","operationId":"view_summary_api_investor_views_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewSummaryResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/investor/lead":{"post":{"tags":["investor"],"summary":"Capture Lead","description":"Public, no-auth: capture an investor lead from /investor page.\n\nWrites to investor_leads (separate from newsletter subscribers).\nDuplicate emails are silently ignored (unique-index on-conflict ignore).","operationId":"capture_lead_api_investor_lead_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadCaptureRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadCaptureResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/investor/calendly-click":{"post":{"tags":["investor"],"summary":"Log Calendly Click","description":"Public no-auth: log a Calendly link click.\n\nFrontend calls this first, then opens the returned calendly_url in a new tab.\nBest-effort — failure does not block the user from reaching Calendly.","operationId":"log_calendly_click_api_investor_calendly_click_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalendlyClickResponse"}}}}}}},"/api/investor/funnel":{"get":{"tags":["investor"],"summary":"Investor Funnel","description":"Admin-only: investor conversion funnel — views → email-captures → Calendly-clicks.","operationId":"investor_funnel_api_investor_funnel_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvestorFunnelResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/ai/analyze":{"post":{"tags":["ai"],"summary":"Analyze Stock","description":"POST /api/ai/analyze — streams expert AI analysis as Server-Sent Events.\n\nDefense layers (in order):\n  L1: slowapi IP rate limit — 10/min per IP (burst cap)\n  L2: platform daily cost ceiling — global $/day circuit breaker\n  L3: force_refresh cooldown — 1 per user per hour\n  L4: atomic reserve_ai_call RPC — fixes TOCTOU + disconnect abuse\n      (includes per-user concurrency cap)\n  L5: release_ai_call in finally — refunds on upstream error, frees slot","operationId":"analyze_stock_api_ai_analyze_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AIAnalysisRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/save/weights/{sector}":{"get":{"tags":["save"],"summary":"Get Save Weights","description":"Return user's custom weights for a sector, or sector defaults if none saved.","operationId":"get_save_weights_api_save_weights__sector__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"sector","in":"path","required":true,"schema":{"type":"string","title":"Sector"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SAVEWeights"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["save"],"summary":"Patch Save Weights","description":"Upsert user's custom weights for a sector. Weights must sum to 1.0 (±0.01).","operationId":"patch_save_weights_api_save_weights__sector__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"sector","in":"path","required":true,"schema":{"type":"string","title":"Sector"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SAVEWeightsUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SAVEWeights"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["save"],"summary":"Reset Save Weights","description":"Delete user's custom weights for a sector (reverts to sector defaults).","operationId":"reset_save_weights_api_save_weights__sector__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"sector","in":"path","required":true,"schema":{"type":"string","title":"Sector"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/save/batch":{"post":{"tags":["save"],"summary":"Trigger Save Batch","description":"Trigger a background SAVE batch run across 100 diverse symbols.\nReturns immediately with {\"status\": \"started\", \"count\": 100}.\nThe batch runs asynchronously in the background (max 5 concurrent).","operationId":"trigger_save_batch_api_save_batch_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Trigger Save Batch Api Save Batch Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/errors/report":{"post":{"tags":["errors"],"summary":"Report Frontend Error","description":"Accept an error report from the frontend and log it to Supabase.","operationId":"report_frontend_error_api_errors_report_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FrontendError"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Report Frontend Error Api Errors Report Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/errors/digest":{"get":{"tags":["errors"],"summary":"Send Error Digest","description":"Fetch all unresolved errors from the last 24 h, send a Resend email\ndigest, then mark the errors as resolved.\nAdmin-only — always sends to the configured admin email.","operationId":"send_error_digest_api_errors_digest_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Send Error Digest Api Errors Digest Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/support/inbound":{"post":{"tags":["support"],"summary":"Inbound Email","description":"Receive inbound email from Resend webhook.\nParses sender/subject/body, generates Claude reply, sends via Resend, saves ticket.","operationId":"inbound_email_api_support_inbound_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/support/tickets":{"get":{"tags":["support"],"summary":"List Tickets","description":"List recent support tickets. Admin JWT required.","operationId":"list_tickets_api_support_tickets_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"unresolved_only","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Unresolved Only"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/support/tickets/{ticket_id}/resolve":{"patch":{"tags":["support"],"summary":"Resolve Ticket","description":"Mark a support ticket as resolved. Admin JWT required.","operationId":"resolve_ticket_api_support_tickets__ticket_id__resolve_patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"ticket_id","in":"path","required":true,"schema":{"type":"string","title":"Ticket Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/support/forward":{"post":{"tags":["support"],"summary":"Forward Email","description":"Receive inbound emails addressed to chris@equity-rank.com via Resend webhook\nand forward them to the admin_email address (chris@equity-rank.com).\nRequires valid Resend HMAC signature when RESEND_WEBHOOK_SECRET is configured.","operationId":"forward_email_api_support_forward_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/watchlist":{"get":{"tags":["watchlist"],"summary":"Get Watchlist","description":"Return all symbols in the user's watchlist with enriched score/trend data.\nAlso returns legacy `symbols` list for backwards compatibility.","operationId":"get_watchlist_api_watchlist_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Watchlist Api Watchlist Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["watchlist"],"summary":"Add To Watchlist","description":"Add a symbol to the user's watchlist (idempotent — duplicate silently ignored).\n\nFree-plan accounts (no-card free tier, post-preview) are capped at\nFREE_WATCHLIST_TICKER_CAP tickers. All other users keep existing behavior.","operationId":"add_to_watchlist_api_watchlist_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddSymbolRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Add To Watchlist Api Watchlist Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/watchlist/{symbol}":{"delete":{"tags":["watchlist"],"summary":"Remove From Watchlist","description":"Remove a symbol from the user's watchlist.","operationId":"remove_from_watchlist_api_watchlist__symbol__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Remove From Watchlist Api Watchlist  Symbol  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/watchlist/{symbol}/alerts":{"patch":{"tags":["watchlist"],"summary":"Update Alert Prefs","description":"Update alert preferences for a specific watchlist entry.","operationId":"update_alert_prefs_api_watchlist__symbol__alerts_patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlertPrefsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Alert Prefs Api Watchlist  Symbol  Alerts Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/watchlist/alerts/disable-all":{"post":{"tags":["watchlist"],"summary":"Disable All Alerts","description":"Disable alerts for every watchlist entry belonging to the user (one-click unsubscribe).","operationId":"disable_all_alerts_api_watchlist_alerts_disable_all_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Disable All Alerts Api Watchlist Alerts Disable All Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/watchlist/{symbol}/threshold":{"patch":{"tags":["watchlist"],"summary":"Update Alert Threshold","description":"Update the daily move alert threshold for a watchlist entry.","operationId":"update_alert_threshold_api_watchlist__symbol__threshold_patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateThresholdRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Alert Threshold Api Watchlist  Symbol  Threshold Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/watchlist/{symbol}/note":{"get":{"tags":["watchlist"],"summary":"Get Watchlist Note","description":"Return the caller's private note for a symbol; note: null if none exists.\n\nRLS (`auth.uid() = user_id`) is the primary isolation boundary. This query\nALSO carries an explicit `user_id=eq.{caller}` filter as defense-in-depth\n(T-89-11-01) — the service-role key used by `_supabase_headers()` bypasses\nRLS, so the router-level filter is what actually scopes this query today.","operationId":"get_watchlist_note_api_watchlist__symbol__note_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Watchlist Note Api Watchlist  Symbol  Note Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["watchlist"],"summary":"Upsert Watchlist Note","description":"Upsert the caller's private note for a symbol (defense-in-depth: user_id\nis always the authenticated caller's id, never client-supplied).","operationId":"upsert_watchlist_note_api_watchlist__symbol__note_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NoteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Upsert Watchlist Note Api Watchlist  Symbol  Note Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["watchlist"],"summary":"Delete Watchlist Note","description":"Remove the caller's private note for a symbol. Scoped by user_id=eq.{caller}\nas defense-in-depth alongside RLS (T-89-11-01).","operationId":"delete_watchlist_note_api_watchlist__symbol__note_delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Watchlist Note Api Watchlist  Symbol  Note Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/watchlist/share":{"post":{"tags":["watchlist"],"summary":"Toggle Share","description":"SHARE-01 + SHARE-05: toggle the user's `public.watchlists` row to shared/unshared.\nOn enabling, generates a 12-char URL-safe slug if one does not yet exist.\nOn disabling, sets is_shared=false (slug remains in DB to preserve uniqueness).\nTargets `public.watchlists` (plural) — the user-keyed table extended by 16-01.","operationId":"toggle_share_api_watchlist_share_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShareToggleRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Toggle Share Api Watchlist Share Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/watchlist/share-state":{"get":{"tags":["watchlist"],"summary":"Get Share State","description":"Returns the current share state for the authenticated user's watchlist.\nUsed by the frontend WatchlistPage useEffect on mount to render the correct\ntoggle label (\"Share watchlist\" vs \"Stop sharing\") + URL pill.","operationId":"get_share_state_api_watchlist_share_state_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Share State Api Watchlist Share State Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/watchlists/public/{slug}":{"get":{"tags":["watchlists-public"],"summary":"Get Public Watchlist","description":"SHARE-04 + SHARE-05: public read-only fetch of a shared watchlist.\nNo auth required. Returns 404 for is_shared=false or unknown slug.\n60s in-process cache.\nExcludes: per-ticker fair value, AI analysis, options data, alerts.\nIncludes: owner_referral_code so the frontend can append ?ref=... to signup CTAs.","operationId":"get_public_watchlist_api_watchlists_public__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Public Watchlist Api Watchlists Public  Slug  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/valuation-views":{"post":{"tags":["valuation-views"],"summary":"Save View","description":"Save a custom-weighted valuation view and return a shareable permalink.\n\nStores the RAW custom_weights JSONB (reproducible source of truth — not\nnormalized; the engine renormalizes at read time). Generates a 12-char\nURL-safe slug with a 5-attempt collision retry against saved_valuation_views.","operationId":"save_view_api_valuation_views_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveViewRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Save View Api Valuation Views Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/valuation-views/public/{slug}":{"get":{"tags":["valuation-views-public"],"summary":"Get Public View","description":"Public read-only fetch of a saved custom-weighted view. No auth required.\n\nReturns ONLY the public allowlist:\n    symbol, company_name, price, consensus_fair_value,\n    consensus_margin_of_safety, custom_weights, owner_handle,\n    owner_referral_code\n\nThe consensus is recomputed from the stored custom_weights against the LIVE\nscreener_cache price via compute_consensus_valuation_extended. No per-method\nbreakdown, AI, or projection field is ever exposed (RESEARCH Pitfall 1).\n404 on unknown / non-shared slug or a malformed slug (guard runs pre-DB).","operationId":"get_public_view_api_valuation_views_public__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Public View Api Valuation Views Public  Slug  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/subscribe":{"post":{"tags":["subscribe"],"summary":"Subscribe","description":"Add an email to the marketing subscriber list.\n\nOptional first-touch attribution (channel/utm_*/referrer, from the client's\nstored `er_attr` record) is persisted so lead yield is reportable per\nacquisition channel. Keys are only included when non-empty, so a repeat\nsubscribe without attribution never clobbers the original first touch.","operationId":"subscribe_api_subscribe_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_subscribe_api_subscribe_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Subscribe Api Subscribe Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/unsubscribe":{"post":{"tags":["subscribe"],"summary":"Unsubscribe Post","description":"Opt an email out of marketing emails (no auth required).","operationId":"unsubscribe_post_api_unsubscribe_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_unsubscribe_post_api_unsubscribe_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Unsubscribe Post Api Unsubscribe Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/subscription/nudge":{"get":{"tags":["subscribe"],"summary":"Annual plan upgrade nudge data","description":"Returns nudge data for the annual plan upgrade banner.\nshow_nudge=True only when user is on a monthly plan (active or trialing).\nAnnual and cancelled users get show_nudge=False.","operationId":"get_subscription_nudge_api_subscription_nudge_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Subscription Nudge Api Subscription Nudge Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/subscription/free-tier-status":{"get":{"tags":["subscription"],"summary":"Get Free Tier Status","description":"Returns whether the no-card free tier is live — public endpoint, no auth.\nUsed by the signup and pricing pages. Returns {\"enabled\": bool}.","operationId":"get_free_tier_status_api_subscription_free_tier_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Free Tier Status Api Subscription Free Tier Status Get"}}}}}}},"/api/subscription/start-preview":{"post":{"tags":["subscription"],"summary":"Start Preview","description":"Start the 14-day full-access preview for a brand-new account (no card).\n\n- 403 when FREE_TIER_ENABLED is off (feature not live).\n- 409 when the user already has a subscriptions row with history (paid,\n  trialing, cancelled, a Stripe customer, or a previous preview) — the\n  preview is strictly once, for accounts with no subscription history. The\n  blank row capture-attribution creates on first sign-in is not history;\n  it is promoted in place (see _is_blank_shell_row).\n- Otherwise inserts {status:'trialing', tier:'free_preview', plan:NULL,\n  trial_end: now+14d} and returns {ok, trial_end}.","operationId":"start_preview_api_subscription_start_preview_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Start Preview Api Subscription Start Preview Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/webhooks/email-received":{"post":{"tags":["inbox"],"summary":"Receive Email Webhook","description":"Resend inbound email webhook.\nCalled by Resend when claude@equity-rank.com receives a message.\nFetches full body from Resend API and stores in claude_inbox.","operationId":"receive_email_webhook_api_webhooks_email_received_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Receive Email Webhook Api Webhooks Email Received Post"}}}}}}},"/api/admin/inbox":{"get":{"tags":["inbox"],"summary":"List Inbox","description":"List received emails in claude_inbox.","operationId":"list_inbox_api_admin_inbox_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":20,"title":"Limit"}},{"name":"status_filter","in":"query","required":false,"schema":{"type":"string","default":"all","title":"Status Filter"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Inbox Api Admin Inbox Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/inbox/{inbox_id}/reply":{"post":{"tags":["inbox"],"summary":"Send Reply","description":"Send a reply to a received email via Resend.\nMarks the original message as replied in claude_inbox.","operationId":"send_reply_api_admin_inbox__inbox_id__reply_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"inbox_id","in":"path","required":true,"schema":{"type":"integer","title":"Inbox Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_send_reply_api_admin_inbox__inbox_id__reply_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Send Reply Api Admin Inbox  Inbox Id  Reply Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/options-screener/sectors":{"get":{"tags":["options-screener"],"summary":"Get Screener Sectors","description":"Return unique sectors present in the cached screener. Used to populate the sector filter.","operationId":"get_screener_sectors_api_options_screener_sectors_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Screener Sectors Api Options Screener Sectors Get"}}}}}}},"/api/options-screener/covered-calls":{"get":{"tags":["options-screener"],"summary":"Covered Calls","description":"Public no-auth covered-call index (SEO-03).\n\nLists undervalued underlyings (positive margin of safety) with covered-call\nincome metrics: if-called return and downside protection. Premiums come from\nreal option chains; when no real chain is available an ATM Black-Scholes\nestimate is used and labeled a model estimate. Reads the same cached data the\nauthed options screener uses (no new query path); returns only non-gated\npublic fields and is rate-limited per IP via the shared limiter.","operationId":"covered_calls_api_options_screener_covered_calls_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/options-screener/earnings-implied-moves":{"get":{"tags":["options-screener"],"summary":"Earnings Implied Moves","description":"Public no-auth list: names reporting within 14 days, with the move the\noption market currently prices in, IV rank/percentile, and open-interest\ncontext.\n\n`implied_move_pct` is the ATM straddle on the first expiry at or after the\nreport whenever one is quotable (`implied_move_basis == \"straddle\"`), and a\n7-day Black-Scholes figure off the 30-day ATM IV snapshot otherwise\n(`\"iv_model\"`, flagged by `implied_move_is_model_estimate`). Informational\nmarket data — not a forecast, recommendation, or directional view.","operationId":"earnings_implied_moves_api_options_screener_earnings_implied_moves_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/options-screener":{"get":{"tags":["options-screener"],"summary":"Get Options Screener","description":"Return ranked options strategy opportunities.\n\nEach result includes a SAVE-score-derived directional signal, recommended\noptions strategy, IV rank, IV percentile, put/call ratio, total OI,\nimplied 7-day move, conviction score, margin of safety, and rationale bullets.\n\nWhen wheel_preset=true, results are filtered for the cash-secured put /\ncovered-call wheel strategy: bullish/neutral direction, IV rank ≥ 40,\nearnings > 14 days out, and covered-call premium yield ≥ 1.5%/month.\nUse wheel_max_price to gate by account size (e.g. 50 for Standard, 200 for Enhanced).","operationId":"get_options_screener_api_options_screener_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"direction","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by direction: bullish | bearish | neutral","title":"Direction"},"description":"Filter by direction: bullish | bearish | neutral"},{"name":"sector","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by sector name, e.g. 'Technology'","title":"Sector"},"description":"Filter by sector name, e.g. 'Technology'"},{"name":"min_iv_rank","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":100,"minimum":0},{"type":"null"}],"description":"Minimum IV rank (0-100)","title":"Min Iv Rank"},"description":"Minimum IV rank (0-100)"},{"name":"max_iv_rank","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":100,"minimum":0},{"type":"null"}],"description":"Maximum IV rank (0-100)","title":"Max Iv Rank"},"description":"Maximum IV rank (0-100)"},{"name":"earnings_within_14d","in":"query","required":false,"schema":{"type":"boolean","description":"Only show options where underlying has earnings within 14 days","default":false,"title":"Earnings Within 14D"},"description":"Only show options where underlying has earnings within 14 days"},{"name":"sort_by","in":"query","required":false,"schema":{"type":"string","description":"Sort field: conviction | iv_rank | save_score | implied_move","default":"conviction","title":"Sort By"},"description":"Sort field: conviction | iv_rank | save_score | implied_move"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Maximum results to return","default":30,"title":"Limit"},"description":"Maximum results to return"},{"name":"force_refresh","in":"query","required":false,"schema":{"type":"boolean","description":"Bypass cache and re-fetch","default":false,"title":"Force Refresh"},"description":"Bypass cache and re-fetch"},{"name":"wheel_preset","in":"query","required":false,"schema":{"type":"boolean","description":"Filter for wheel-strategy candidates (iv_rank≥40, earnings>14d, cc_yield≥1.5%/mo)","default":false,"title":"Wheel Preset"},"description":"Filter for wheel-strategy candidates (iv_rank≥40, earnings>14d, cc_yield≥1.5%/mo)"},{"name":"wheel_max_price","in":"query","required":false,"schema":{"anyOf":[{"type":"number","maximum":5000,"minimum":1},{"type":"null"}],"description":"Max stock price for account-size gating (e.g. 50 or 200)","title":"Wheel Max Price"},"description":"Max stock price for account-size gating (e.g. 50 or 200)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Options Screener Api Options Screener Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/options-screener/rate":{"post":{"tags":["options-screener"],"summary":"Rate Option","description":"Admin-only. Upsert a good/bad rating for an options opportunity.","operationId":"rate_option_api_options_screener_rate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Rate Option Api Options Screener Rate Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/stocks/{symbol}/innovation":{"get":{"tags":["innovation"],"summary":"Sector-specific innovation / implementation score","description":"Compute and return an innovation score for the given symbol.\n\n- R&D-heavy sectors (Technology, Healthcare, Communication Services,\n  Consumer Discretionary, Industrials, Materials): scored on R&D intensity,\n  R&D growth, patent activity, and patent velocity.\n- CapEx-intensive sectors (Energy, Utilities, Real Estate, Consumer Staples):\n  scored on CapEx intensity, CapEx growth, and revenue growth.\n- Financials: returns a not_applicable result (no meaningful R&D or CapEx proxy).\n\nResults are cached for 1 year.  Cache key includes the deploy version so\na new production deploy automatically evicts stale entries.","operationId":"get_innovation_score_api_stocks__symbol__innovation_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InnovationResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/options/{symbol}/backtest":{"get":{"tags":["options-backtest"],"summary":"Get Options Backtest","description":"5-year simulated options backtest using reconstructed MA + HV signals.\n\nSignals are derived purely from price history — no stored user data required.\nResults cached 6 hours per symbol.","operationId":"get_options_backtest_api_options__symbol__backtest_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Options Backtest Api Options  Symbol  Backtest Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/options-edge/scan":{"get":{"tags":["admin-options-edge"],"summary":"Scan Universe","description":"Rank the screener universe by options edge score, descending.","operationId":"scan_universe_api_admin_options_edge_scan_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":10,"description":"How many universe rows to evaluate","default":50,"title":"Limit"},"description":"How many universe rows to evaluate"},{"name":"min_edge","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":0,"description":"Drop results below this score","default":60,"title":"Min Edge"},"description":"Drop results below this score"},{"name":"confidence","in":"query","required":false,"schema":{"type":"string","description":"\"any\" | \"high\" | \"medium\"","default":"any","title":"Confidence"},"description":"\"any\" | \"high\" | \"medium\""}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Scan Universe Api Admin Options Edge Scan Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/options-edge/backtest/{symbol}":{"get":{"tags":["admin-options-edge"],"summary":"Backtest Symbol","description":"Run the realistic v2 backtest for a single symbol.","operationId":"backtest_symbol_api_admin_options_edge_backtest__symbol__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}},{"name":"threshold","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":0,"default":60,"title":"Threshold"}},{"name":"years","in":"query","required":false,"schema":{"type":"integer","maximum":15,"minimum":1,"default":10,"title":"Years"}},{"name":"skip_earnings","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Skip Earnings"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Backtest Symbol Api Admin Options Edge Backtest  Symbol  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/auto-trader/status":{"get":{"tags":["admin-auto-trader"],"summary":"Status","description":"Auto-trader mode, halt, owner confirmation, open-position count.","operationId":"status_api_admin_auto_trader_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Status Api Admin Auto Trader Status Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/auto-trader/autonomous":{"post":{"tags":["admin-auto-trader"],"summary":"Set Autonomous","description":"Enable / disable AUTONOMOUS_MODE.\n\nBody: {\"enabled\": true | false, \"confirm\": \"ENABLE_AUTONOMOUS\"}.\n\nWhen enabled, the daily OWNER_CONFIRMED check is bypassed -- the engine\ncan trade live every day without daily acknowledgment. Halt flag still\nworks as the manual kill switch.\n\nEnabling requires the literal confirm string to prevent accidental\nactivation.","operationId":"set_autonomous_api_admin_auto_trader_autonomous_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Set Autonomous Api Admin Auto Trader Autonomous Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/auto-trader/set-primary-account":{"post":{"tags":["admin-auto-trader"],"summary":"Set Primary Account","description":"Designate which Schwab account receives auto-trader orders.\n\nBody: {\"account_hash\": \"<full hash>\"}\n\nThe hash must already be in AUTO_TRADER_ACCOUNT_HASH_WHITELIST or the\nrequest is rejected. Takes effect on the next cycle — no restart needed.","operationId":"set_primary_account_api_admin_auto_trader_set_primary_account_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Set Primary Account Api Admin Auto Trader Set Primary Account Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/auto-trader/set-config":{"post":{"tags":["admin-auto-trader"],"summary":"Set Config","description":"Update portfolio sizing config at runtime.\n\nAccepted keys (all optional, pass only what you want to change):\n  portfolio_value      — total capital to deploy (float, min $500)\n  max_position_pct     — max fraction per trade (0.01–0.20, e.g. 0.05)\n\nChanges take effect on the next cycle — no restart needed.","operationId":"set_config_api_admin_auto_trader_set_config_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Set Config Api Admin Auto Trader Set Config Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/auto-trader/signals":{"get":{"tags":["admin-auto-trader"],"summary":"Signals","description":"Recent decisions across all symbols.","operationId":"signals_api_admin_auto_trader_signals_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Signals Api Admin Auto Trader Signals Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/auto-trader/halt":{"post":{"tags":["admin-auto-trader"],"summary":"Halt","description":"Flip the halt flag to true. New entries blocked immediately. Existing\nopen positions are NEVER force-closed by this endpoint.","operationId":"halt_api_admin_auto_trader_halt_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Halt Api Admin Auto Trader Halt Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/auto-trader/resume":{"post":{"tags":["admin-auto-trader"],"summary":"Resume","description":"Clear the halt flag. Requires {\"confirm\": \"RESUME\"} in the body to\navoid accidental flips.","operationId":"resume_api_admin_auto_trader_resume_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Resume Api Admin Auto Trader Resume Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/auto-trader/run-once":{"post":{"tags":["admin-auto-trader"],"summary":"Run Once","description":"Run one v7.9 signal-generation cycle. Paper-only — v79 does not place\nreal orders (live routing is Phase 5, behind an owner flag).\n\ndry_run defaults to True for safe manual previewing: full pipeline runs\nbut the v79_sleeve_positions state + auto_trader_signals log writes are\nskipped. Pass dry_run=false to make the cycle persist (the scheduler\nfires the non-dry path daily at 13:35 UTC).","operationId":"run_once_api_admin_auto_trader_run_once_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"portfolio_value","in":"query","required":false,"schema":{"type":"number","minimum":1000.0,"default":100000.0,"title":"Portfolio Value"}},{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Dry Run"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Run Once Api Admin Auto Trader Run Once Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/auto-trader/exit-scan":{"post":{"tags":["admin-auto-trader"],"summary":"Exit Scan Endpoint","description":"Scan all open auto-trader stock positions for exit signals.\n\nChecks each open position against MoS and P(win) exit gates.\nIn paper mode: logs 'observed' close signals (no real orders).\nIn live mode: routes SELL orders through Schwab when a gate fires.\n\nSafe to call at any time — idempotent, does not affect buy evaluations.","operationId":"exit_scan_endpoint_api_admin_auto_trader_exit_scan_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"portfolio_value","in":"query","required":false,"schema":{"type":"number","minimum":1000.0,"default":100000.0,"title":"Portfolio Value"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Exit Scan Endpoint Api Admin Auto Trader Exit Scan Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/auto-trader/reconcile-pnl":{"post":{"tags":["admin-auto-trader"],"summary":"Reconcile Pnl Endpoint","description":"Reconcile auto-trader open positions against live Schwab positions.\n\nUpdates pnl_dollars / pnl_pct for open positions still in Schwab.\nMarks outcome='closed' + exit_date for positions no longer in Schwab.\n\nNo-op in paper mode. Requires live mode + valid Schwab token.","operationId":"reconcile_pnl_endpoint_api_admin_auto_trader_reconcile_pnl_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"account_hash","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Hash"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Reconcile Pnl Endpoint Api Admin Auto Trader Reconcile Pnl Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/auto-trader/set-mode":{"post":{"tags":["admin-auto-trader"],"summary":"Set Mode","description":"Switch the engine between paper (simulation) and live (real orders).\n\nBody: {\"mode\": \"live\" | \"paper\"}\nSwitching to live additionally requires {\"confirm\": \"GO_LIVE\"}.\nPaper switch requires no confirm — always safe.","operationId":"set_mode_api_admin_auto_trader_set_mode_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Set Mode Api Admin Auto Trader Set Mode Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/auto-trader/set-live-approved":{"post":{"tags":["admin-auto-trader"],"summary":"Set Live Approved","description":"Enable or revoke AUTO_TRADER_SCHWAB_LIVE_APPROVED (GUARD-01 gate 2).\n\nApprove: {\"approved\": true, \"confirm\": \"APPROVE_LIVE_ORDERS\"}\nRevoke:  {\"approved\": false}\n\nWhen approved, the engine is allowed to send real orders to Schwab.\nRevoking blocks real orders at GUARD-01. Existing open positions are\nnever force-closed by this endpoint.","operationId":"set_live_approved_api_admin_auto_trader_set_live_approved_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Set Live Approved Api Admin Auto Trader Set Live Approved Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/auto-trader/activate-live":{"post":{"tags":["admin-auto-trader"],"summary":"Activate Live","description":"All-in-one live activation: mode=live, halt=false, autonomous=true,\nSchwab live approved=true — in one atomic operation.\n\nBody: {\"confirm\": \"ACTIVATE_LIVE_TRADING\"}\n\nRequires a primary account to be selected. All four flags must write\nsuccessfully or the endpoint returns 500.\n\nIMPORTANT: After activation the engine will place real Schwab orders\non the next scheduled cycle. Use /deactivate-live or /halt to stop.","operationId":"activate_live_api_admin_auto_trader_activate_live_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Activate Live Api Admin Auto Trader Activate Live Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/auto-trader/set-active-accounts":{"post":{"tags":["admin-auto-trader"],"summary":"Set Active Accounts","description":"Set which accounts receive auto-trader orders simultaneously.\n\nBody: {\"account_hashes\": [\"hash1\", \"hash2\"]}\n\nAll hashes must be in AUTO_TRADER_ACCOUNT_HASH_WHITELIST. Empty list\nclears the override and falls back to the primary account.","operationId":"set_active_accounts_api_admin_auto_trader_set_active_accounts_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Set Active Accounts Api Admin Auto Trader Set Active Accounts Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/auto-trader/set-account-config":{"post":{"tags":["admin-auto-trader"],"summary":"Set Account Config","description":"Set a per-account portfolio value override.\n\nBody: {\"account_hash\": \"<full hash>\", \"portfolio_value\": 25000}\n\nUses AUTO_TRADER_PORTFOLIO_VALUE_{hash8} in app_config.\nPass portfolio_value=null to clear the per-account override.","operationId":"set_account_config_api_admin_auto_trader_set_account_config_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Set Account Config Api Admin Auto Trader Set Account Config Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/auto-trader/deactivate-live":{"post":{"tags":["admin-auto-trader"],"summary":"Deactivate Live","description":"Deactivate live trading: mode=paper, halt=true, Schwab approval revoked.\n\nSafe to call at any time — no confirmation required.\nDoes NOT force-close existing open positions.","operationId":"deactivate_live_api_admin_auto_trader_deactivate_live_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Deactivate Live Api Admin Auto Trader Deactivate Live Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/auto-trader/snapshot-now":{"post":{"tags":["admin-auto-trader"],"summary":"Snapshot Now","description":"Trigger an immediate valuation snapshot — copies current screener_cache\nconsensus_fair_value + per-method fair values into valuation_history.\n\nRun this once to seed, then daily via the scheduler to accumulate the\nreal fair-value time series the auto-trader needs.","operationId":"snapshot_now_api_admin_auto_trader_snapshot_now_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Snapshot Now Api Admin Auto Trader Snapshot Now Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/auto-trader/backfill-eps":{"post":{"tags":["admin-auto-trader"],"summary":"Backfill Eps Endpoint","description":"Backfill EPS actuals from AlphaVantage EARNINGS into earnings_dates.\n\nUnblocks the PEAD (post-earnings-announcement-drift) signal in the\nprobability engine. Idempotent — re-running only fills new gaps.\n\nBudget ~14 min per 1,000 symbols at the default throttle. The queue read\nused to truncate at 1,000, which is where the old \"~800 symbols\" estimate\ncame from; the real depth was 8,053 on 2026-09-02. The response now reports\n`symbols_matching` (the whole queue) next to `symbols_processed`, so pass a\n`limit` and work it down across passes rather than expecting one to finish.","operationId":"backfill_eps_endpoint_api_admin_auto_trader_backfill_eps_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":0,"description":"0 = all symbols, else cap to N for test runs","default":0,"title":"Limit"},"description":"0 = all symbols, else cap to N for test runs"},{"name":"throttle_ms","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":820,"description":"ms between AV calls (820 = 73/min AV premium)","default":820,"title":"Throttle Ms"},"description":"ms between AV calls (820 = 73/min AV premium)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Backfill Eps Endpoint Api Admin Auto Trader Backfill Eps Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/auto-trader/blended-allocation":{"get":{"tags":["admin-auto-trader"],"summary":"Blended Allocation","description":"Compute today's recommended capital split between the stocks book and\nthe options book based on relative edge in each asset class.\n\nReturns top-N picks per book + cross-class weights + sector caps applied.\nDoes not place orders — this is the planning layer the auto-trader\nconsumes.","operationId":"blended_allocation_api_admin_auto_trader_blended_allocation_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"portfolio_value","in":"query","required":false,"schema":{"type":"number","minimum":1000,"default":100000.0,"title":"Portfolio Value"}},{"name":"top_stocks","in":"query","required":false,"schema":{"type":"integer","maximum":25,"minimum":3,"default":10,"title":"Top Stocks"}},{"name":"top_options","in":"query","required":false,"schema":{"type":"integer","maximum":15,"minimum":1,"default":5,"title":"Top Options"}},{"name":"evaluate_options","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Evaluate Options"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Blended Allocation Api Admin Auto Trader Blended Allocation Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/auto-trader/backfill-eps-yfinance":{"post":{"tags":["admin-auto-trader"],"summary":"Backfill Eps Yfinance Endpoint","description":"Backfill EPS actuals via YFINANCE (free, no daytime cap).\n\nUse this when AlphaVantage is rate-saturated by the screener cron.\nBudget ~2-3 minutes per 1,000 symbols at the default throttle. Shares the\nAlphaVantage backfiller's queue read, so `symbols_matching` here is the same\n8,053-deep queue — see `/backfill-eps` for why one pass will not clear it.","operationId":"backfill_eps_yfinance_endpoint_api_admin_auto_trader_backfill_eps_yfinance_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":0,"default":0,"title":"Limit"}},{"name":"throttle_ms","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":50,"default":150,"title":"Throttle Ms"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Backfill Eps Yfinance Endpoint Api Admin Auto Trader Backfill Eps Yfinance Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/auto-trader/smart-money-snapshot-now":{"post":{"tags":["admin-auto-trader"],"summary":"Smart Money Snapshot Now","description":"Daily snapshot of insider + analyst + short-interest signals.\n\nPulls from yfinance (free) for top_n highest-MoS symbols. Writes to\nsmart_money_history. After ~30 days the time series enables backtests\non these factors. Takes ~3-5 minutes for 200 symbols.","operationId":"smart_money_snapshot_now_api_admin_auto_trader_smart_money_snapshot_now_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"top_n","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":20,"default":200,"title":"Top N"}},{"name":"throttle_ms","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":50,"default":150,"title":"Throttle Ms"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Smart Money Snapshot Now Api Admin Auto Trader Smart Money Snapshot Now Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/auto-trader/sentiment-snapshot-now":{"post":{"tags":["admin-auto-trader"],"summary":"Sentiment Snapshot Now","description":"Snapshot today's news + sentiment into sentiment_history.\n\nPrimary source: save_history (Equity Rank's AI narrative composite).\nOptional: AlphaVantage NEWS_SENTIMENT for tickers save_history misses.","operationId":"sentiment_snapshot_now_api_admin_auto_trader_sentiment_snapshot_now_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"top_n","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":20,"default":200,"title":"Top N"}},{"name":"use_av","in":"query","required":false,"schema":{"type":"boolean","description":"Pull from AV NEWS_SENTIMENT for symbols not covered by save_history","default":false,"title":"Use Av"},"description":"Pull from AV NEWS_SENTIMENT for symbols not covered by save_history"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Sentiment Snapshot Now Api Admin Auto Trader Sentiment Snapshot Now Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/auto-trader/factor-snapshot-now":{"post":{"tags":["admin-auto-trader"],"summary":"Factor Snapshot Now","description":"Trigger an immediate factor snapshot — copies the full screener_cache\nfeature set (Piotroski, accruals, Beneish, AI displacement, momentum\nsub-scores, etc.) into factor_history. Companion to /snapshot-now.\n\nRun daily so the multifactor backtest gets richer feature history than\nscore_history's four sub-scores. After 30-60 days the multifactor\nprobability engine can be backtested on its full feature surface.","operationId":"factor_snapshot_now_api_admin_auto_trader_factor_snapshot_now_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Factor Snapshot Now Api Admin Auto Trader Factor Snapshot Now Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/v79/dashboard":{"get":{"tags":["admin-v79"],"summary":"Dashboard","description":"Full v7.9 engine state for the admin dashboard. Computes current MTM\non every holding and option. One round-trip, no client-side pricing.","operationId":"dashboard_api_admin_v79_dashboard_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Dashboard Api Admin V79 Dashboard Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/v79/signals":{"get":{"tags":["admin-v79"],"summary":"Signals","description":"Last N v79-tagged signals from auto_trader_signals (strategy_label\nstarts with 'v79:'). Ordered newest-first.","operationId":"signals_api_admin_v79_signals_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Signals Api Admin V79 Signals Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/v79/nav-history":{"get":{"tags":["admin-v79"],"summary":"Nav History","description":"Time series of total NAV + SPY/QQQ benchmark for the validation\nchart. One row per run_v79_cycle invocation, ordered oldest-first.","operationId":"nav_history_api_admin_v79_nav_history_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"default":30,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Nav History Api Admin V79 Nav History Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/v79/snapshot-eps-now":{"post":{"tags":["admin-v79"],"summary":"Snapshot Eps Now","description":"Manually trigger the EPS-surprise backfill for the v79 universe.\nSame code path as the daily 13:25 UTC scheduled job. Populates\nearnings_dates.eps_surprise_pct so the earnings_momentum sleeve can\nfire on fresh post-earnings drift.","operationId":"snapshot_eps_now_api_admin_v79_snapshot_eps_now_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"lookback_days","in":"query","required":false,"schema":{"type":"integer","maximum":60,"minimum":1,"default":14,"title":"Lookback Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Snapshot Eps Now Api Admin V79 Snapshot Eps Now Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/v79/hmm-fit":{"post":{"tags":["admin-v79"],"summary":"Hmm Fit Endpoint","description":"One-time HMM training. Fits a 3-state Gaussian HMM on all\navailable joint (VIX, SPY) history and persists the model\nparameters. Run this BEFORE /hmm-compute the first time.","operationId":"hmm_fit_endpoint_api_admin_v79_hmm_fit_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Hmm Fit Endpoint Api Admin V79 Hmm Fit Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/v79/hmm-compute":{"post":{"tags":["admin-v79"],"summary":"Hmm Compute Endpoint","description":"Apply the latest trained HMM model to every historical date and\nUPSERT hmm_regime_history. Idempotent on (date).","operationId":"hmm_compute_endpoint_api_admin_v79_hmm_compute_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Hmm Compute Endpoint Api Admin V79 Hmm Compute Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/v79/hmm-latest":{"get":{"tags":["admin-v79"],"summary":"Hmm Latest Endpoint","description":"Return the most recent HMM regime label + posterior probabilities.","operationId":"hmm_latest_endpoint_api_admin_v79_hmm_latest_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Hmm Latest Endpoint Api Admin V79 Hmm Latest Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/v79/backfill-earnings-calendar":{"post":{"tags":["admin-v79"],"summary":"Backfill Earnings Calendar Endpoint","description":"One-time deep backfill: pull AV EARNINGS history for every v79\nticker (~879) and insert all historical (symbol, report_date) rows\ninto earnings_dates. Idempotent — ON CONFLICT DO NOTHING skips\nexisting rows. Takes ~60-90 sec at AV's 1600/min rate.\n\nAfter this completes, the earnings_iv_history backfill (run separately)\ncan extract pre/post IV from iv_daily_snapshot's 18-year archive for\nevery ticker × quarter → tens of thousands of crush observations\ninstead of the ~500 we have today.","operationId":"backfill_earnings_calendar_endpoint_api_admin_v79_backfill_earnings_calendar_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Backfill Earnings Calendar Endpoint Api Admin V79 Backfill Earnings Calendar Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/v79/run-once":{"post":{"tags":["admin-v79"],"summary":"Manual Run Once","description":"Trigger one v7.9 paper-mode cycle on demand. Same code path as the\ndaily 13:35 UTC scheduler. dry_run=True runs the full pipeline without\npersisting state or logging signals (safe preview).\nportfolio_value is the fallback if Schwab equity cannot be fetched.","operationId":"manual_run_once_api_admin_v79_run_once_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Dry Run"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Manual Run Once Api Admin V79 Run Once Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/v79/pins":{"get":{"tags":["admin-v79"],"summary":"List Pins","description":"Return the list of tickers the engine will never sell.","operationId":"list_pins_api_admin_v79_pins_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Pins Api Admin V79 Pins Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["admin-v79"],"summary":"Pin Ticker","description":"Add a ticker to the do-not-sell list.\n\nBody: {\"ticker\": \"PM\"}\nThe engine will hold this position across all sleeves until explicitly unpinned.","operationId":"pin_ticker_api_admin_v79_pins_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Pin Ticker Api Admin V79 Pins Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/v79/pins/{ticker}":{"delete":{"tags":["admin-v79"],"summary":"Unpin Ticker","description":"Remove a ticker from the do-not-sell list — engine may sell it again.","operationId":"unpin_ticker_api_admin_v79_pins__ticker__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Unpin Ticker Api Admin V79 Pins  Ticker  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/owner-portfolio":{"get":{"tags":["admin-owner-portfolio"],"summary":"List Holdings","operationId":"list_holdings_api_admin_owner_portfolio_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Holdings Api Admin Owner Portfolio Get"}}}}},"security":[{"HTTPBearer":[]}]},"put":{"tags":["admin-owner-portfolio"],"summary":"Replace Holdings","description":"Replace the entire holdings set. Deduplicates by symbol (last wins).","operationId":"replace_holdings_api_admin_owner_portfolio_put","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HoldingsReplace"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Replace Holdings Api Admin Owner Portfolio Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["admin-owner-portfolio"],"summary":"Upsert Holding","description":"Add or update a single holding (upsert on symbol).","operationId":"upsert_holding_api_admin_owner_portfolio_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Holding"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Upsert Holding Api Admin Owner Portfolio Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/owner-portfolio/{symbol}":{"delete":{"tags":["admin-owner-portfolio"],"summary":"Delete Holding","operationId":"delete_holding_api_admin_owner_portfolio__symbol__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Holding Api Admin Owner Portfolio  Symbol  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/owner-portfolio/preview":{"post":{"tags":["admin-owner-portfolio"],"summary":"Preview Email","description":"Render the daily portfolio email without sending (dry run). JWT or X-Cron-Secret.","operationId":"preview_email_api_admin_owner_portfolio_preview_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Preview Email Api Admin Owner Portfolio Preview Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/owner-portfolio/send":{"post":{"tags":["admin-owner-portfolio"],"summary":"Send Email Now","description":"Build + send the daily portfolio email immediately. JWT or X-Cron-Secret.","operationId":"send_email_now_api_admin_owner_portfolio_send_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Send Email Now Api Admin Owner Portfolio Send Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/admin/owner-portfolio/sync":{"post":{"tags":["admin-owner-portfolio"],"summary":"Sync From Schwab","description":"Mirror the owner's live Schwab equity positions into owner_portfolio.\n\nRead-only against Schwab (positions only — never places orders). Upserts by\nsymbol (preserving last_signal) and prunes holdings no longer held. JWT or\nX-Cron-Secret. Used for initial population + manual refresh; the daily email\nalso auto-syncs before sending.","operationId":"sync_from_schwab_api_admin_owner_portfolio_sync_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Sync From Schwab Api Admin Owner Portfolio Sync Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/methodology/backtest-summary":{"get":{"tags":["methodology"],"summary":"Get Backtest Summary","description":"Return combined signal backtest proof statistics.\n\nNo authentication required. Designed for public viewing on /methodology.\n\nChecks Supabase methodology_stats for real data (real_n >= 50); if unavailable\nor insufficient, returns the hardcoded proof numbers from the synthetic simulation.","operationId":"get_backtest_summary_api_methodology_backtest_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/app__routers__methodology__BacktestSummaryResponse"}}}}}}},"/api/methodology/pdf":{"get":{"tags":["methodology"],"summary":"Get Methodology Pdf","description":"Download the Equity Rank methodology white paper as a PDF.\n\nNo authentication required — this is a public, informational document.\nThe PDF mirrors the /methodology page content with additional detail on\nvaluation methods, sector weights, and the SAVE score pillars.\n\nGenerated fresh on each request; build-stamped with RAILWAY_GIT_COMMIT_SHA.","operationId":"get_methodology_pdf_api_methodology_pdf_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/stocks/{ticker}/public":{"get":{"tags":["public"],"summary":"Get Public Stock","description":"Public teaser data for a stock — no auth required.","operationId":"get_public_stock_api_stocks__ticker__public_get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicStockResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stocks/{ticker}/fundamentals-history":{"get":{"tags":["public"],"summary":"Get Fundamentals History","description":"Public historical fundamentals (annual, point-in-time) for trend charts.\n\nFactual financial-statement data only (revenue, net income, FCF, margins,\nROE, etc.) — informational, never a projection, target, or advice.","operationId":"get_fundamentals_history_api_stocks__ticker__fundamentals_history_get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Fundamentals History Api Stocks  Ticker  Fundamentals History Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stocks/prices-batch":{"get":{"tags":["public"],"summary":"Get Prices Batch","description":"Return price + mos_pct + company_name for up to 50 comma-separated tickers.\nNo auth required. Used by the AI Roadmap to show live prices on ticker cards.","operationId":"get_prices_batch_api_stocks_prices_batch_get","parameters":[{"name":"tickers","in":"query","required":true,"schema":{"type":"string","title":"Tickers"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Get Prices Batch Api Stocks Prices Batch Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stocks/{ticker}/sector-rank":{"get":{"tags":["public"],"summary":"Get Sector Rank","description":"Returns the ticker's percentile rank within its sector by MoS%.\nNo auth required. Pulls from screener_cache.","operationId":"get_sector_rank_api_stocks__ticker__sector_rank_get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Sector Rank Api Stocks  Ticker  Sector Rank Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stocks/{ticker}/card.png":{"get":{"tags":["public"],"summary":"Get Stock Card","description":"Branded 800x420 PNG share card — no auth required. Cached 24 hours.","operationId":"get_stock_card_api_stocks__ticker__card_png_get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/og/watchlist/{slug}":{"get":{"tags":["public"],"summary":"Og Watchlist Card","description":"SHARE-03: Returns a 1200x630 PNG OG card for a shared watchlist.\nReturns 404 when slug is unknown or is_shared=false.\nTargets `public.watchlists` (plural) directly — no separate share table exists.\nCached 24 hours in-process to mirror the existing stock card pattern.","operationId":"og_watchlist_card_api_og_watchlist__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/og/valuation-view/{slug}":{"get":{"tags":["public"],"summary":"Og Valuation View Card","description":"MODEL-03: 1200x630 PNG OG card for a shared custom valuation view.\nReturns 404 when the slug is unknown. Cached 24 hours in-process.","operationId":"og_valuation_view_card_api_og_valuation_view__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stocks-index":{"get":{"tags":["public"],"summary":"Get Stocks Index","description":"Lightweight public index of every scored ticker — powers the /stocks hub.\n\nReturns [{ticker, company_name, sector, mos_pct, methods_used}] for all\nscreener_cache rows, alphabetical by ticker. No auth. 1-hour in-process cache.\nDistinct literal path (not /api/stocks/{ticker}) so it never collides with the\nper-ticker routes.\n\n`methods_used` is included so the public /stocks hub can apply the A-7 coverage\nfloor to its client-side margin-of-safety ranking (thin-coverage MoS is\nstructurally upward-biased — method-analysis-2026-07-10 / A finding 7).","operationId":"get_stocks_index_api_stocks_index_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response Get Stocks Index Api Stocks Index Get"}}}}}}},"/api/sitemap.xml":{"get":{"tags":["public"],"summary":"Get Sitemap","description":"XML sitemap of all tickers in screener_cache — no auth required.","operationId":"get_sitemap_api_sitemap_xml_get","responses":{"200":{"description":"Successful Response"}}}},"/mcp":{"get":{"tags":["mcp"],"summary":"Mcp No Stream","description":"No server-initiated SSE stream and no sessions — the spec's answer for\nboth is 405. The body tells a human who opened the URL in a browser what\nit is.","operationId":"mcp_no_stream_mcp_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["mcp"],"summary":"Mcp Endpoint","description":"Streamable HTTP transport, stateless: every message is self-contained.","operationId":"mcp_endpoint_mcp_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"delete":{"tags":["mcp"],"summary":"Mcp No Stream","description":"No server-initiated SSE stream and no sessions — the spec's answer for\nboth is 405. The body tells a human who opened the URL in a browser what\nit is.","operationId":"mcp_no_stream_mcp_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/.well-known/mcp-registry-auth":{"get":{"tags":["well-known"],"summary":"Mcp Registry Auth","operationId":"mcp_registry_auth__well_known_mcp_registry_auth_get","responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/api/datasets":{"get":{"tags":["public-datasets"],"summary":"Datasets Index","description":"What is offered, with the column dictionary and terms — no rows.","operationId":"datasets_index_api_datasets_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/datasets/valuations-latest.csv":{"get":{"tags":["public-datasets"],"summary":"Valuations Csv","operationId":"valuations_csv_api_datasets_valuations_latest_csv_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/datasets/valuations-latest.json":{"get":{"tags":["public-datasets"],"summary":"Valuations Json","operationId":"valuations_json_api_datasets_valuations_latest_json_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/alerts":{"get":{"tags":["alerts"],"summary":"List Alerts","description":"Return all alerts for the authenticated user (active and triggered history).","operationId":"list_alerts_api_alerts_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Alerts Api Alerts Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["alerts"],"summary":"Create Alert","description":"Create a new price or MoS% alert.\nEnforces MAX_ACTIVE_ALERTS (20) per user — returns 402 if exceeded.","operationId":"create_alert_api_alerts_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAlertRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Alert Api Alerts Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/alerts/{alert_id}":{"delete":{"tags":["alerts"],"summary":"Delete Alert","description":"Delete an alert. Ownership is enforced — users can only delete their own alerts.","operationId":"delete_alert_api_alerts__alert_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"alert_id","in":"path","required":true,"schema":{"type":"string","title":"Alert Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/referral/code":{"get":{"tags":["referral"],"summary":"Get Or Create Code","description":"Return the current user's referral code and link.\nCreates one if none exists yet.\nAlso returns total_referred and bonus_days_earned for display.","operationId":"get_or_create_code_api_referral_code_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Or Create Code Api Referral Code Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/referral/redeem":{"post":{"tags":["referral"],"summary":"Redeem Code","description":"Redeem a referral code.\nCalled by a new user during or after trial start (or auto-fired by the\nStripe customer.subscription.created webhook via _redeem_referral_for_user).\n\nRules:\n- Code must exist and have redeemed_at IS NULL (the template row)\n- Referrer cannot redeem their own code\n- Each user can only redeem one code ever\n- If referrer already has 30 bonus days, mark as redeemed but do not extend Stripe trial","operationId":"redeem_code_api_referral_redeem_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RedeemRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Redeem Code Api Referral Redeem Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/referral/stats":{"get":{"tags":["referral"],"summary":"Get Referral Stats","description":"Return the referrer's code, count of successful referrals, and total bonus days.","operationId":"get_referral_stats_api_referral_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Referral Stats Api Referral Stats Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/referral/activation-event":{"post":{"tags":["referral"],"summary":"Activation Event","description":"Record an ACT-03 activation event server-side (survives ad-blockers).\n\nOn 'initiated', also tag the AUTHENTICATED caller's unredeemed referral row\nsource='activation_trigger' — this inviter-row PATCH is the AUTHORITATIVE\nK-factor attribution. The deep-link's `er_src=activation` marker (Plan 01) is\na secondary, forward-compat signal only and is NOT relied on here.\n\nBody: {\"event\": \"shown\" | \"initiated\"}. user_id comes from the verified\nsession (get_current_user), never the body — the body's only field is the\nevent name, validated against a fixed allowlist (T-32-07 / T-32-08).","operationId":"activation_event_api_referral_activation_event_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivationEventRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Activation Event Api Referral Activation Event Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/earnings-analysis/{symbol}":{"get":{"tags":["earnings"],"summary":"Get Earnings Analysis","description":"Get latest earnings call analysis for a symbol.\n\nReturns:\n{\n  \"symbol\": \"AAPL\",\n  \"report_date\": \"2024-01-25\",\n  \"eps_actual\": 2.18,\n  \"eps_estimate\": 2.15,\n  \"eps_surprise_pct\": 1.39,\n  \"key_takeaways\": [\"...\", \"...\", \"...\"],\n  \"forward_guidance\": \"...\",\n  \"risk_factors\": [\"...\", \"...\"],\n  \"sentiment\": \"bullish\",\n  \"confidence\": 82,\n  \"transcript_summary\": \"...\",\n  \"analyzed_at\": \"2024-01-25T...\"\n}\n\nReturns 404 if no recent earnings call transcript available.","operationId":"get_earnings_analysis_earnings_analysis__symbol__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","description":"Stock ticker symbol","title":"Symbol"},"description":"Stock ticker symbol"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Earnings Analysis Earnings Analysis  Symbol  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/cs-agent/chat":{"post":{"tags":["cs-agent"],"summary":"Cs Chat","description":"POST /api/cs-agent/chat\n\nSends a user message to the Equity Rank CS managed agent.\nReturns the agent's answer and whether it classified the request as\nSUPPORT (answered inline) or FEATURE (logged to product queue).\n\nRequires valid Supabase JWT (any authenticated user — no active sub required).","operationId":"cs_chat_api_cs_agent_chat_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/demo/analyze/{ticker}":{"get":{"tags":["demo"],"summary":"Demo Analyze","operationId":"demo_analyze_api_demo_analyze__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DemoAnalysisResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/demo/email":{"post":{"tags":["demo"],"summary":"Demo Email Capture","description":"Save demo visitor email to marketing_subscribers.","operationId":"demo_email_capture_api_demo_email_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailCaptureRequest"}}},"required":true},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/projection/{ticker}":{"get":{"tags":["projection"],"summary":"Get Projected Fair Value Range","description":"Return bear/base/bull projected fair value model scenarios for a ticker.\n\n- horizon: 1, 3, or 5 years only. Any other value returns 400.\n- Missing financial data returns data_complete=false with partial info.\n- Never returns 500 on legitimate data gaps.\n\nModel scenarios only. Not a price forecast or investment advice.","operationId":"get_projected_fair_value_range_api_projection__ticker__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","description":"US equity ticker, e.g. NVDA","title":"Ticker"},"description":"US equity ticker, e.g. NVDA"},{"name":"horizon","in":"query","required":false,"schema":{"type":"integer","maximum":5,"minimum":1,"description":"Model horizon in years. Accepted values: 1, 3, 5.","default":3,"title":"Horizon"},"description":"Model horizon in years. Accepted values: 1, 3, 5."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectionResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/valuation/{ticker}/scenarios":{"get":{"tags":["scenarios"],"summary":"Get Scenario Analysis","description":"Return Bear/Base/Bull DCF scenario model estimates, 5x5 sensitivity matrix,\nand rate sensitivity table for a ticker.\n\n- Missing financial data returns data_complete=false with partial info.\n- Never returns 500 on legitimate data gaps.\n- recession_scenario: sector-specific recession model estimate (wired).\n\nModel estimates only. Not a price forecast or investment advice.","operationId":"get_scenario_analysis_api_valuation__ticker__scenarios_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","description":"US equity ticker, e.g. NVDA","title":"Ticker"},"description":"US equity ticker, e.g. NVDA"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScenarioAnalysisResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/tracking/linkedin-event":{"post":{"tags":["tracking"],"summary":"Post Linkedin Event","description":"Send a LinkedIn CAPI conversion event.\n\nAccepts: { \"event_type\": \"SIGN_UP\", \"email\": \"user@example.com\", \"metadata\": {} }\n\nThe email is SHA-256 hashed inside the service before transmission.\nmetadata keys may include: first_name, last_name, linkedin_id, amount, currencyCode.","operationId":"post_linkedin_event_api_tracking_linkedin_event_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkedInEventRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Post Linkedin Event Api Tracking Linkedin Event Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/tracking/health":{"get":{"tags":["tracking"],"summary":"Tracking Health","description":"Returns service health status including whether LinkedIn and Reddit CAPI are configured.","operationId":"tracking_health_api_tracking_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Tracking Health Api Tracking Health Get"}}}}}}},"/api/pharma-events/ticker/{ticker}":{"get":{"tags":["pharma-events"],"summary":"Events For Ticker","description":"Upcoming Phase 2/3 clinical trial completion dates for a single ticker.","operationId":"events_for_ticker_api_pharma_events_ticker__ticker__get","parameters":[{"name":"ticker","in":"path","required":true,"schema":{"type":"string","title":"Ticker"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/pharma-events/upcoming":{"get":{"tags":["pharma-events"],"summary":"Upcoming Events","description":"All upcoming pharma binary events across all tickers within `days` horizon.","operationId":"upcoming_events_api_pharma_events_upcoming_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":548,"minimum":7,"default":90,"title":"Days"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/pharma-events/refresh":{"post":{"tags":["pharma-events"],"summary":"Trigger Refresh","description":"Admin: manually trigger a ClinicalTrials.gov refresh for all pharma tickers.\n\nAdmin-gated: this fans out to the external ClinicalTrials.gov API across all\npharma tickers and writes the DB, so it must not be callable anonymously.","operationId":"trigger_refresh_api_pharma_events_refresh_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/screeners/earnings-surprise":{"get":{"tags":["screeners"],"summary":"Get Earnings Surprise Screener","description":"Stocks reporting earnings in the next 7 days, sorted by Fundamentals score (save_score).\nIncludes EPS estimate for the upcoming report and surprise history\nfrom prior quarters so users can see consistency patterns.","operationId":"get_earnings_surprise_screener_api_screeners_earnings_surprise_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response Get Earnings Surprise Screener Api Screeners Earnings Surprise Get"}}}}}}},"/api/screeners/earnings-calendar":{"get":{"tags":["screeners"],"summary":"Get Earnings Calendar","description":"Upcoming earnings over the next `days` days (default 30, clamped 1–45),\njoined with screener_cache for Fundamentals score (save_score) and valuation context. Sorted\nby report_date asc, then Fundamentals score desc (nulls last). Public — no auth.\nOnly scored-universe names are returned so every row has model context.","operationId":"get_earnings_calendar_api_screeners_earnings_calendar_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Get Earnings Calendar Api Screeners Earnings Calendar Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/cancel/reason":{"post":{"tags":["cancel"],"summary":"Record Cancel Reason","description":"Step 1 of cancel flow: record reason, cancel at period end via Stripe,\nand log outcome in cancellation_reasons.\nCalled by CancelPage.tsx handleCancel() for users who proceed to cancel\n(switching/other paths, missing_feature direct cancel, or users who bypass save offers).","operationId":"record_cancel_reason_api_cancel_reason_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelReasonRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/cancel/accept-offer":{"post":{"tags":["cancel"],"summary":"Accept Save Offer","description":"Save-path endpoint. Three actions:\n  pause    — pause Stripe subscription (behavior=void) for N days; update subscriptions.paused_until;\n             send pause confirmation email via Resend.\n             NOTE: Stripe pause_collection keeps status='active' — screener access unaffected (intentional).\n  coupon   — apply RETAIN50_3MO coupon (50% off 3 invoices); log outcome=saved_with_offer\n  downsell — switch to starter_monthly_price_id; log outcome=downsell","operationId":"accept_save_offer_api_cancel_accept_offer_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AcceptOfferRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/store/products":{"get":{"tags":["store"],"summary":"List Products","operationId":"list_products_api_store_products_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/store/checkout":{"post":{"tags":["store"],"summary":"Create Store Checkout","operationId":"create_store_checkout_api_store_checkout_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreCheckoutRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/store/order/{session_id}":{"get":{"tags":["store"],"summary":"Get Order","operationId":"get_order_api_store_order__session_id__get","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/store/download/{session_id}/{file_key}":{"get":{"tags":["store"],"summary":"Download File","operationId":"download_file_api_store_download__session_id___file_key__get","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"file_key","in":"path","required":true,"schema":{"type":"string","title":"File Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/research/valuation-multiple-summary":{"get":{"tags":["research"],"summary":"Get Valuation Multiple Summary","description":"Public leaderboard data — no authentication required.\nReturns per-sector median P/E, P/B, EV/EBITDA with percentile bands and divergence flags.\nDivergence flag is True when a sector's current multiple diverges from historical median\nby more than 20%. Response is cached for 1 hour.","operationId":"get_valuation_multiple_summary_api_research_valuation_multiple_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response Get Valuation Multiple Summary Api Research Valuation Multiple Summary Get"}}}}}}},"/api/research/download-pdf":{"post":{"tags":["research"],"summary":"Download Tracker Pdf","description":"Email-gated PDF download — CONTENT-03.\nCaptures visitor email into gated_downloads, generates WeasyPrint PDF with sector\nleaderboard, and streams as application/pdf attachment.","operationId":"download_tracker_pdf_api_research_download_pdf_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GatedDownloadRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/portfolio/analyze":{"post":{"tags":["portfolio"],"summary":"Analyze Portfolio","description":"Score a list of tickers from the screener_cache.\nUnauthenticated callers receive score + basic info only.\nAuthenticated callers receive the full screener row.","operationId":"analyze_portfolio_api_portfolio_analyze_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyzeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Analyze Portfolio Api Portfolio Analyze Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/portfolio/save-watchlist":{"post":{"tags":["portfolio"],"summary":"Save To Watchlist","description":"Bulk-add a list of tickers to the authenticated user's watchlist.\nSilently skips duplicates (upsert semantics).\nReturns added count and any tickers that failed.","operationId":"save_to_watchlist_api_portfolio_save_watchlist_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveWatchlistRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Save To Watchlist Api Portfolio Save Watchlist Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/portfolio/optimize":{"post":{"tags":["portfolio"],"summary":"Optimize Portfolio","description":"Black-Litterman portfolio model. F&F-only v1 (legal gate).\nReturns model-derived weights for holdings used as model inputs.\nNot investment advice. Educational use only.","operationId":"optimize_portfolio_api_portfolio_optimize_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortfolioOptimizeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortfolioOptimizeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/portfolio/holdings":{"get":{"tags":["portfolio"],"summary":"Get Holdings","description":"Return the user's holdings enriched with the latest cached price and model\ncontext, plus per-position and total unrealized P&L (computed from the\nuser's entered cost basis — factual tracking, not advice).","operationId":"get_holdings_api_portfolio_holdings_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Holdings Api Portfolio Holdings Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["portfolio"],"summary":"Upsert Holding","description":"Add a holding, or update it if the symbol already exists for this user.","operationId":"upsert_holding_api_portfolio_holdings_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HoldingRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Upsert Holding Api Portfolio Holdings Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/portfolio/holdings/{symbol}":{"patch":{"tags":["portfolio"],"summary":"Update Holding","description":"Update an existing holding's shares, cost basis, or notes.","operationId":"update_holding_api_portfolio_holdings__symbol__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HoldingUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Holding Api Portfolio Holdings  Symbol  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["portfolio"],"summary":"Delete Holding","description":"Remove a holding from the user's portfolio.","operationId":"delete_holding_api_portfolio_holdings__symbol__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Holding Api Portfolio Holdings  Symbol  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/wheel-screener/public":{"get":{"tags":["wheel-screener"],"summary":"Get Wheel Screener Public","description":"Public preview — top 10 wheel candidates by wheel_score, limited fields.\n\nNo auth required. Serves as the data layer for the\n/wheel-strategy-stocks SEO landing page.\n\nExposes: ticker, company_name, sector, price, strategy,\n         equity_rank_score, combined_mos_pct, prob_of_profit.\nAll premium fields (wheel_score, vrp_pct, atm_delta,\nreward_risk_ratio, days_to_earnings) are excluded.","operationId":"get_wheel_screener_public_api_wheel_screener_public_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Wheel Screener Public Api Wheel Screener Public Get"}}}}}}},"/api/wheel-screener":{"get":{"tags":["wheel-screener"],"summary":"Get Wheel Screener","description":"Return top wheel-strategy candidates ranked by composite wheel_score.\n\nwheel_score = 0.4 * (equity_rank_score/100)\n            + 0.3 * (prob_of_profit as fraction)\n            + 0.2 * (reward_risk_ratio, capped at 5, normalised)\n            + 0.1 * (earnings buffer: 1 when earnings ≥30d out or absent, 0 when today)\n\nCandidate gates applied at build time:\n  - direction IN (bullish, neutral)\n  - total_oi >= 500\n  - no earnings within 14 days\n  - ATM call premium yield >= 1.5%/month\n  - equity_rank_score IS NOT NULL\nEach row's strategy is the wheel-entry structure fitting the row (bullish →\nCash-Secured Put, bullish high-priced → Bull Put Spread, neutral → Covered\nCall), with prob_of_profit and reward/risk computed for that structure.","operationId":"get_wheel_screener_api_wheel_screener_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"strategy","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: 'Cash-Secured Put' | 'Covered Call' | 'Bull Put Spread'","title":"Strategy"},"description":"Filter: 'Cash-Secured Put' | 'Covered Call' | 'Bull Put Spread'"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Max rows","default":100,"title":"Limit"},"description":"Max rows"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Wheel Screener Api Wheel Screener Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/money/bootstrap":{"get":{"tags":["money"],"summary":"Bootstrap","operationId":"bootstrap_api_money_bootstrap_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Bootstrap Api Money Bootstrap Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/money/overview":{"get":{"tags":["money"],"summary":"Overview","operationId":"overview_api_money_overview_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"month","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Month"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Overview Api Money Overview Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/money/cashflow":{"get":{"tags":["money"],"summary":"Cashflow","operationId":"cashflow_api_money_cashflow_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"months","in":"query","required":false,"schema":{"type":"integer","default":6,"title":"Months"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Cashflow Api Money Cashflow Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/money/accounts":{"post":{"tags":["money"],"summary":"Create Account","operationId":"create_account_api_money_accounts_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Account Api Money Accounts Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/money/accounts/{account_id}":{"patch":{"tags":["money"],"summary":"Update Account","operationId":"update_account_api_money_accounts__account_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Account Api Money Accounts  Account Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["money"],"summary":"Delete Account","operationId":"delete_account_api_money_accounts__account_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Account Api Money Accounts  Account Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/money/transactions":{"get":{"tags":["money"],"summary":"List Transactions","operationId":"list_transactions_api_money_transactions_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"from_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From Date"}},{"name":"to_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To Date"}},{"name":"account_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Id"}},{"name":"category_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category Id"}},{"name":"q","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Q"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Transactions Api Money Transactions Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["money"],"summary":"Create Transaction","operationId":"create_transaction_api_money_transactions_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TxnCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create Transaction Api Money Transactions Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/money/transactions/{txn_id}":{"patch":{"tags":["money"],"summary":"Update Transaction","operationId":"update_transaction_api_money_transactions__txn_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"txn_id","in":"path","required":true,"schema":{"type":"string","title":"Txn Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TxnPatch"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update Transaction Api Money Transactions  Txn Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["money"],"summary":"Delete Transaction","operationId":"delete_transaction_api_money_transactions__txn_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"txn_id","in":"path","required":true,"schema":{"type":"string","title":"Txn Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Transaction Api Money Transactions  Txn Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/money/transactions/import-csv":{"post":{"tags":["money"],"summary":"Import Csv","operationId":"import_csv_api_money_transactions_import_csv_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CsvImportRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Import Csv Api Money Transactions Import Csv Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/money/categories":{"post":{"tags":["money"],"summary":"Create Category","operationId":"create_category_api_money_categories_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CategoryCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Category Api Money Categories Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/money/categories/{category_id}":{"delete":{"tags":["money"],"summary":"Delete Category","operationId":"delete_category_api_money_categories__category_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"category_id","in":"path","required":true,"schema":{"type":"string","title":"Category Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Category Api Money Categories  Category Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/money/budgets":{"get":{"tags":["money"],"summary":"Get Budgets","operationId":"get_budgets_api_money_budgets_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"month","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Month"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Budgets Api Money Budgets Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["money"],"summary":"Put Budgets","operationId":"put_budgets_api_money_budgets_put","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BudgetsPut"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Put Budgets Api Money Budgets Put"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/money/budgets/{category_id}":{"delete":{"tags":["money"],"summary":"Delete Budget","operationId":"delete_budget_api_money_budgets__category_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"category_id","in":"path","required":true,"schema":{"type":"string","title":"Category Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Budget Api Money Budgets  Category Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/money/rules":{"get":{"tags":["money"],"summary":"List Rules","operationId":"list_rules_api_money_rules_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Rules Api Money Rules Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["money"],"summary":"Create Rule","operationId":"create_rule_api_money_rules_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RuleCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Rule Api Money Rules Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/money/rules/{rule_id}":{"delete":{"tags":["money"],"summary":"Delete Rule","operationId":"delete_rule_api_money_rules__rule_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","title":"Rule Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete Rule Api Money Rules  Rule Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/money/recurring":{"get":{"tags":["money"],"summary":"Recurring","operationId":"recurring_api_money_recurring_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Recurring Api Money Recurring Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/money/net-worth-history":{"get":{"tags":["money"],"summary":"Net Worth History","description":"Month-end net worth for the last `months` months, plus today.\n\nPoints carry `estimated`: false means a stored snapshot (what the balances\nactually were on the day the cron ran), true means a reconstruction from the\ntransaction ledger for a date before snapshots began. The distinction is\nsurfaced rather than smoothed over — a reconstruction is only as complete as\nthe transactions behind it, and an account with no imported history\nreconstructs as a flat line at its current balance.","operationId":"net_worth_history_api_money_net_worth_history_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"months","in":"query","required":false,"schema":{"type":"integer","default":12,"title":"Months"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Net Worth History Api Money Net Worth History Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/money/plaid/status":{"get":{"tags":["money-plaid"],"summary":"Plaid Status","description":"Feature status for the accounts page: hidden (not configured), upsell\n(configured but caller not on a paid plan), or connect UI + items.","operationId":"plaid_status_api_money_plaid_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Plaid Status Api Money Plaid Status Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/money/plaid/link-token":{"post":{"tags":["money-plaid"],"summary":"Link Token","operationId":"link_token_api_money_plaid_link_token_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkTokenRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Link Token Api Money Plaid Link Token Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/money/plaid/exchange":{"post":{"tags":["money-plaid"],"summary":"Exchange","operationId":"exchange_api_money_plaid_exchange_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExchangeRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Exchange Api Money Plaid Exchange Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/money/plaid/sync":{"post":{"tags":["money-plaid"],"summary":"Sync Now","operationId":"sync_now_api_money_plaid_sync_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Sync Now Api Money Plaid Sync Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/money/plaid/items/{item_row_id}":{"delete":{"tags":["money-plaid"],"summary":"Disconnect Item","description":"Disconnect a bank: /item/remove at Plaid FIRST (this is what stops the\nper-Item billing), then vault + row cleanup. Accounts survive as manual\naccounts and every synced transaction is kept — it's the user's data.","operationId":"disconnect_item_api_money_plaid_items__item_row_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"item_row_id","in":"path","required":true,"schema":{"type":"string","title":"Item Row Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Disconnect Item Api Money Plaid Items  Item Row Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/saved-screeners":{"get":{"tags":["saved-screeners"],"summary":"List Saved Screeners","description":"Return all saved screener filter sets for the authenticated user.","operationId":"list_saved_screeners_api_saved_screeners_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response List Saved Screeners Api Saved Screeners Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["saved-screeners"],"summary":"Save Screener Filter","description":"Save a new screener filter set. Max 10 per user.","operationId":"save_screener_filter_api_saved_screeners_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveFilterRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Save Screener Filter Api Saved Screeners Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/saved-screeners/{filter_id}":{"delete":{"tags":["saved-screeners"],"summary":"Delete Saved Screener","description":"Delete a saved screener filter. Ownership enforced — user_id must match.","operationId":"delete_saved_screener_api_saved_screeners__filter_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"filter_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Filter Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["saved-screeners"],"summary":"Rename Saved Screener","description":"Rename a saved screener filter.","operationId":"rename_saved_screener_api_saved_screeners__filter_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"filter_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Filter Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenameFilterRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Rename Saved Screener Api Saved Screeners  Filter Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stocks/{symbol}/ownership":{"get":{"tags":["ownership"],"summary":"Get Ownership","description":"Factual insider, short-interest, filing, and earnings-call data the\nnightly batch already ingests. No aggregate ratio, no direction label,\nno interpretive framing — this reports what was filed and when.","operationId":"get_ownership_api_stocks__symbol__ownership_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Ownership Api Stocks  Symbol  Ownership Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/analytics/event":{"post":{"tags":["analytics"],"summary":"Post Usage Event","description":"Record one first-party usage event.\n\nAuth is optional — anonymous events are stored with user_id NULL. The write\nis synchronous but wrapped so it can never fail the request; the response\nreports whether the event was accepted.","operationId":"post_usage_event_api_analytics_event_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageEventRequest"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Post Usage Event Api Analytics Event Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stocks/{symbol}/statements":{"get":{"tags":["statements"],"summary":"Get Statements","description":"Income statement, balance sheet, and cash-flow statement (annual +\nquarterly) for `symbol`, read from the durable `statement_history` table.\n\nA symbol with no persisted rows returns 200 with empty arrays and\n`as_of: null` — never a 500. This is a paid analyst surface, matching\nAWL-01's premium framing (statements are gated the same as the rest of\nthe deep-dive stock modal).","operationId":"get_statements_api_stocks__symbol__statements_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}},{"name":"period","in":"query","required":false,"schema":{"enum":["annual","quarterly","both"],"type":"string","description":"Filter to annual, quarterly, or both period sets.","default":"both","title":"Period"},"description":"Filter to annual, quarterly, or both period sets."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Statements Api Stocks  Symbol  Statements Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/stocks/{symbol}/multiple-history":{"get":{"tags":["multiples"],"summary":"Get Multiple History","description":"Derived trailing P/E, EV/EBITDA, P/S, and P/B history for `symbol`,\nread from the persisted `multiple_history` table.\n\nA cold symbol (zero persisted rows) returns 200 with\nstatus: \"computing\" and next_refresh: \"overnight prefetch\" — never a\n500, and never a live outbound data-provider call from this request\npath. A symbol's series is warmed exclusively by the overnight prefetch\nservice, never by this route.","operationId":"get_multiple_history_api_stocks__symbol__multiple_history_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"symbol","in":"path","required":true,"schema":{"type":"string","title":"Symbol"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Multiple History Api Stocks  Symbol  Multiple History Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/screener/export.csv":{"post":{"tags":["screener-export"],"summary":"Export Screener Csv","description":"Export the caller's current screener filter set as CSV.","operationId":"export_screener_csv_api_screener_export_csv_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreenerExportRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/screener/export.xlsx":{"post":{"tags":["screener-export"],"summary":"Export Screener Xlsx","description":"Export the caller's current screener filter set as XLSX.","operationId":"export_screener_xlsx_api_screener_export_xlsx_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreenerExportRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}}},"components":{"schemas":{"AIAnalysisRequest":{"properties":{"symbol":{"type":"string","title":"Symbol"},"model":{"type":"string","title":"Model","default":"auto"},"force_refresh":{"type":"boolean","title":"Force Refresh","default":false}},"type":"object","required":["symbol"],"title":"AIAnalysisRequest","description":"Request body for the AI stock analysis streaming endpoint."},"AbstainedStep":{"properties":{"entry_date":{"type":"string","title":"Entry Date"},"exit_date":{"type":"string","title":"Exit Date"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"},"entry_universe":{"type":"integer","title":"Entry Universe","default":0},"exit_universe":{"type":"integer","title":"Exit Universe","default":0},"basket_size":{"type":"integer","title":"Basket Size","default":0},"priced":{"type":"integer","title":"Priced","default":0}},"type":"object","required":["entry_date","exit_date"],"title":"AbstainedStep","description":"One rebalance step left out of every curve and figure (MDD-01a/b)."},"AcceptOfferRequest":{"properties":{"action":{"type":"string","enum":["pause","coupon","downsell"],"title":"Action"},"reason":{"type":"string","title":"Reason","default":"too_expensive"},"days":{"type":"integer","title":"Days","default":30},"feature_request":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Feature Request"}},"type":"object","required":["action"],"title":"AcceptOfferRequest"},"AccountCreate":{"properties":{"name":{"type":"string","maxLength":60,"minLength":1,"title":"Name"},"account_type":{"type":"string","title":"Account Type","default":"checking"},"institution":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Institution"},"balance":{"type":"number","title":"Balance","default":0.0},"include_in_net_worth":{"type":"boolean","title":"Include In Net Worth","default":true}},"type":"object","required":["name"],"title":"AccountCreate"},"AccountPatch":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":60,"minLength":1},{"type":"null"}],"title":"Name"},"account_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Type"},"institution":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Institution"},"balance":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Balance"},"include_in_net_worth":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Include In Net Worth"},"is_archived":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Archived"}},"type":"object","title":"AccountPatch"},"ActivationEventRequest":{"properties":{"event":{"type":"string","title":"Event"}},"type":"object","required":["event"],"title":"ActivationEventRequest"},"AddSymbolRequest":{"properties":{"symbol":{"type":"string","title":"Symbol"}},"type":"object","required":["symbol"],"title":"AddSymbolRequest"},"AdminPicksBody":{"properties":{"tickers":{"items":{"type":"string"},"type":"array","title":"Tickers"}},"type":"object","required":["tickers"],"title":"AdminPicksBody"},"AlertPrefsRequest":{"properties":{"alerts_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Alerts Enabled"},"alert_threshold":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Alert Threshold"}},"type":"object","title":"AlertPrefsRequest"},"AnalysisBatchRequest":{"properties":{"symbols":{"items":{"type":"string"},"type":"array","title":"Symbols","default":[]}},"type":"object","title":"AnalysisBatchRequest"},"AnalyzeRequest":{"properties":{"tickers":{"items":{"type":"string"},"type":"array","title":"Tickers"}},"type":"object","required":["tickers"],"title":"AnalyzeRequest"},"AskRequest":{"properties":{"query":{"type":"string","title":"Query"}},"type":"object","required":["query"],"title":"AskRequest"},"AssumptionsRequest":{"properties":{"growth_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Growth Rate"},"margin":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Margin"},"discount_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Discount Rate"},"terminal_growth_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Terminal Growth Rate"}},"additionalProperties":false,"type":"object","title":"AssumptionsRequest"},"AuditAnomaly":{"properties":{"field_path":{"type":"string","title":"Field Path"},"rule":{"type":"string","title":"Rule"},"raw_value":{"title":"Raw Value"},"fetch_function":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fetch Function"},"severity":{"type":"string","title":"Severity","default":"error"},"root_cause":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Root Cause"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"}},"type":"object","required":["field_path","rule"],"title":"AuditAnomaly","description":"One row from `av_anomaly_log` within the audit window. Lets the user\nsee if data quality issues affected this ticker's valuation."},"AuditConsensusMath":{"properties":{"consensus_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Consensus Fair Value"},"consensus_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Consensus Margin Of Safety"},"methods_used":{"type":"integer","title":"Methods Used","default":0},"method_contributions":{"items":{"$ref":"#/components/schemas/AuditMethodValue"},"type":"array","title":"Method Contributions"},"interval_low":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Interval Low"},"interval_high":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Interval High"},"confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Confidence"},"model_confidence_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Model Confidence Score"}},"type":"object","title":"AuditConsensusMath","description":"The consensus blend's intermediate values: methods included + their\nweights, the weighted average math, and the resulting consensus FV.\nIncludes the 80% CI on the consensus when Phase 47 data is present."},"AuditInputSnapshot":{"properties":{"price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Price"},"eps_ttm":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Eps Ttm"},"bvps":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Bvps"},"ps_ratio":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ps Ratio"},"ev_ebitda_ratio":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ev Ebitda Ratio"},"beta":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Beta"},"revenue_growth_yoy":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Revenue Growth Yoy"},"eps_growth_yoy":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Eps Growth Yoy"},"dividend_yield":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dividend Yield"},"forward_pe_ratio":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Forward Pe Ratio"},"realized_vol_60d":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Realized Vol 60D"},"market_cap":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Market Cap"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At"}},"type":"object","title":"AuditInputSnapshot","description":"Snapshot of inputs that drove the valuation, with timestamps.\n\nThese are the SAME values that compute_* functions consumed. No re-fetch\n— they come from screener_cache's already-persisted columns."},"AuditMethodValue":{"properties":{"method":{"type":"string","title":"Method"},"fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fair Value"},"weight":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Weight"},"interval_low":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Interval Low"},"interval_high":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Interval High"},"confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Confidence"}},"type":"object","required":["method"],"title":"AuditMethodValue","description":"One method's contribution to the consensus.\n\n`weight` is the SECTOR_METHOD_WEIGHTS entry; `fair_value` is the\ncompute_*_fair_value output; `interval_low/high` are the 80% CI bounds\nfrom Phase 47 (Optional — rows pre-Phase-47 won't have them)."},"AuditSectorMultiples":{"properties":{"sector":{"type":"string","title":"Sector"},"pe_multiple":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pe Multiple"},"pb_multiple":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pb Multiple"},"ps_multiple":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ps Multiple"},"ev_ebitda_multiple":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ev Ebitda Multiple"},"ev_sales_multiple":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ev Sales Multiple"},"roe_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Roe Pct"},"pfcf_multiple":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pfcf Multiple"},"evfcf_multiple":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Evfcf Multiple"},"ev_ebit_multiple":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ev Ebit Multiple"}},"type":"object","required":["sector"],"title":"AuditSectorMultiples","description":"Sector multiples applied during this calc — the DEFAULT_SECTOR_MULTIPLES\ndict entry for this ticker's sector, surfaced for transparency."},"AuditTrail":{"properties":{"schema_version":{"type":"string","title":"Schema Version","default":"1.0"},"ticker":{"type":"string","title":"Ticker"},"sector":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sector"},"model_horizon":{"type":"string","title":"Model Horizon","default":"~1 year"},"inputs":{"$ref":"#/components/schemas/AuditInputSnapshot"},"sector_multiples":{"anyOf":[{"$ref":"#/components/schemas/AuditSectorMultiples"},{"type":"null"}]},"consensus":{"$ref":"#/components/schemas/AuditConsensusMath"},"anomalies":{"items":{"$ref":"#/components/schemas/AuditAnomaly"},"type":"array","title":"Anomalies"},"anomaly_window_days":{"type":"integer","title":"Anomaly Window Days","default":7},"disclaimer":{"type":"string","title":"Disclaimer","default":"These values are model scenarios under stated assumptions — not a forecast, price target, or recommendation. For informational use only."},"generated_at":{"type":"string","title":"Generated At"},"compute_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Compute Ms"}},"type":"object","required":["ticker"],"title":"AuditTrail","description":"Phase 48 / AUDIT-01..05 — versioned, structured \"show your work\" trail.\n\nSchema-stable: breaking changes MUST bump `schema_version` AND update the\npinned fixture in `backend/tests/test_audit_trail.py`.\n\nCompliance:\n- \"model_horizon\" defaults to \"~1 year\" (matches Phase 46 Model Horizon\n  badge on ValuationPanel projected section).\n- `disclaimer` field always contains the LEGAL-OK negation pattern.\n  LEGAL-OK: disclaimer field text NEGATES \"forecast\" and \"price target\"."},"BacktestComplianceBlock":{"properties":{"framing":{"type":"string","title":"Framing","default":"Historical model performance under stated assumptions. Not a forecast, recommendation, or guarantee of future returns."},"look_ahead_controls":{"type":"string","title":"Look Ahead Controls","default":"At each rebalance date the strategy uses ONLY data already in valuation_history as of that date. No forward-looking model values used to make past selections."},"live_history_disclaimer":{"type":"string","title":"Live History Disclaimer","default":"Model live since March 2026. Track record covers a limited period. Historical output does not predict future returns."},"survivorship_bias_note":{"type":"string","title":"Survivorship Bias Note","default":"IMPORTANT — Survivorship bias: this backtest selects from stocks currently in the Equity Rank universe, which excludes companies that were delisted, acquired, or went bankrupt during the historical period. This inflates displayed results by an amount that varies with the window and cannot currently be stated precisely, but which can be large relative to the figures shown — prior internal point-in-time work on an earlier window estimated the effect in the tens of percentage points. A point-in-time universe reconstruction is planned to bound it for the current window. Past model performance is not indicative of future results; provided for informational purposes only."}},"type":"object","title":"BacktestComplianceBlock","description":"Compliance copy surfaced to the frontend (BACK-05).\n\nAll language locks to \"historical model performance\" framing and\nexplicitly negates \"forecast\" / \"recommendation\" / \"guarantee\"."},"BacktestMetricsBlock":{"properties":{"cagr":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cagr"},"sharpe":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sharpe"},"max_drawdown":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Max Drawdown"},"total_return":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Return"},"alpha_vs_spy":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Alpha Vs Spy"},"alpha_vs_qqq":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Alpha Vs Qqq"}},"type":"object","title":"BacktestMetricsBlock","description":"Headline metrics — None when sufficient_data=False.\n\nP0-3: cagr / sharpe / alpha_vs_spy are additionally None until\nn_rebalances >= 24 (annualizing a ~4-month window is statistically\nmeaningless — df=3). MDD-01 (2026-10-09): max_drawdown is gated the same\nway (a sample maximum is most biased on a short panel); the response's\n`max_drawdown_suppressed` says so. `alpha_vs_spy` is a CAGR DIFFERENCE, not\nrisk-adjusted alpha; surfaces must label it \"CAGR gap vs SPY\"."},"BearBaseBullResult":{"properties":{"bear":{"anyOf":[{"$ref":"#/components/schemas/ScenarioPoint"},{"type":"null"}]},"base":{"anyOf":[{"$ref":"#/components/schemas/ScenarioPoint"},{"type":"null"}]},"bull":{"anyOf":[{"$ref":"#/components/schemas/ScenarioPoint"},{"type":"null"}]},"data_complete":{"type":"boolean","title":"Data Complete","default":true}},"type":"object","required":["bear","base","bull"],"title":"BearBaseBullResult","description":"Container for the three Bear/Base/Bull DCF scenario points."},"BenchmarkBlock":{"properties":{"total_return":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Total Return"},"max_drawdown":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Max Drawdown"}},"type":"object","title":"BenchmarkBlock","description":"Per-benchmark figures over the SAME measured steps as the strategy.\n\nMDD-01e (2026-10-09): `max_drawdown` is published exactly when the\nstrategy's is (n_rebalances >= 24 measured steps) so the strategy drawdown\nis never shown without its comparator; None otherwise, and None for SPY\nwhen the feed degraded."},"BestFit":{"properties":{"method":{"type":"string","title":"Method"},"label":{"type":"string","title":"Label"},"fair_value":{"type":"number","title":"Fair Value"},"margin_of_safety":{"type":"number","title":"Margin Of Safety"},"rationale":{"type":"string","title":"Rationale"},"confidence":{"type":"string","title":"Confidence"},"method_no_leverage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Method No Leverage"},"label_no_leverage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label No Leverage"},"fair_value_no_leverage":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fair Value No Leverage"},"margin_of_safety_no_leverage":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Margin Of Safety No Leverage"},"confidence_no_leverage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Confidence No Leverage"},"leverage_fired":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Leverage Fired"},"leverage_debt_equity":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Leverage Debt Equity"},"bridge_observed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Bridge Observed"},"rule_sha":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rule Sha"}},"type":"object","required":["method","label","fair_value","margin_of_safety","rationale","confidence"],"title":"BestFit","description":"The single best-fitting valuation method for this specific stock."},"Body_send_digest_api_digest_send_post":{"properties":{"symbols":{"items":{"type":"string"},"type":"array","title":"Symbols"}},"type":"object","required":["symbols"],"title":"Body_send_digest_api_digest_send_post"},"Body_send_reply_api_admin_inbox__inbox_id__reply_post":{"properties":{"reply_html":{"type":"string","title":"Reply Html"},"reply_text":{"type":"string","title":"Reply Text","default":""}},"type":"object","required":["reply_html"],"title":"Body_send_reply_api_admin_inbox__inbox_id__reply_post"},"Body_subscribe_api_subscribe_post":{"properties":{"email":{"type":"string","title":"Email"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"channel":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Channel"},"utm_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Utm Source"},"utm_medium":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Utm Medium"},"utm_campaign":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Utm Campaign"},"referrer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Referrer"}},"type":"object","required":["email"],"title":"Body_subscribe_api_subscribe_post"},"Body_unsubscribe_post_api_unsubscribe_post":{"properties":{"email":{"type":"string","title":"Email"}},"type":"object","required":["email"],"title":"Body_unsubscribe_post_api_unsubscribe_post"},"Body_upload_iv_history_api_admin_upload_iv_history_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_iv_history_api_admin_upload_iv_history_post"},"BrowseResponse":{"properties":{"stocks":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Stocks"},"count":{"type":"integer","title":"Count"},"universe_size":{"type":"integer","title":"Universe Size"},"show_activation_invite":{"type":"boolean","title":"Show Activation Invite","default":false}},"type":"object","required":["stocks","count","universe_size"],"title":"BrowseResponse"},"BudgetItem":{"properties":{"category_id":{"type":"string","format":"uuid","title":"Category Id"},"amount":{"type":"number","maximum":1000000000.0,"exclusiveMinimum":0.0,"title":"Amount"}},"type":"object","required":["category_id","amount"],"title":"BudgetItem"},"BudgetsPut":{"properties":{"items":{"items":{"$ref":"#/components/schemas/BudgetItem"},"type":"array","maxItems":100,"title":"Items"}},"type":"object","required":["items"],"title":"BudgetsPut"},"CalendlyClickResponse":{"properties":{"logged":{"type":"boolean","title":"Logged","default":true},"calendly_url":{"type":"string","title":"Calendly Url","default":"https://calendly.com/equity-rank/investor-call?utm_source=investor_page"}},"type":"object","title":"CalendlyClickResponse"},"CancelReasonRequest":{"properties":{"reason":{"type":"string","title":"Reason"},"outcome":{"type":"string","title":"Outcome","default":"canceled"},"feature_request":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Feature Request"},"price_point":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Price Point"}},"type":"object","required":["reason"],"title":"CancelReasonRequest"},"CaptureAttributionRequest":{"properties":{"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"utm_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Utm Source"},"utm_medium":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Utm Medium"},"utm_campaign":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Utm Campaign"},"utm_content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Utm Content"},"referrer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Referrer"}},"type":"object","title":"CaptureAttributionRequest"},"CaptureReferralRequest":{"properties":{"code":{"type":"string","title":"Code"}},"type":"object","required":["code"],"title":"CaptureReferralRequest"},"CategoryCreate":{"properties":{"name":{"type":"string","maxLength":40,"minLength":1,"title":"Name"},"parent_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Parent Id"},"kind":{"type":"string","title":"Kind","default":"expense"},"icon":{"anyOf":[{"type":"string","maxLength":8},{"type":"null"}],"title":"Icon"},"color":{"anyOf":[{"type":"string","maxLength":9},{"type":"null"}],"title":"Color"}},"type":"object","required":["name"],"title":"CategoryCreate"},"ChatRequest":{"properties":{"message":{"type":"string","title":"Message"}},"type":"object","required":["message"],"title":"ChatRequest"},"ChatResponse":{"properties":{"answer":{"type":"string","title":"Answer"},"classification":{"type":"string","title":"Classification"}},"type":"object","required":["answer","classification"],"title":"ChatResponse"},"CheckoutRequest":{"properties":{"plan":{"type":"string","enum":["monthly","annual"],"title":"Plan","default":"monthly"},"tier":{"type":"string","enum":["starter","plus","pro","advisor","founder"],"title":"Tier","default":"pro"},"success_url":{"type":"string","title":"Success Url","default":""},"cancel_url":{"type":"string","title":"Cancel Url","default":""}},"type":"object","title":"CheckoutRequest"},"CikMapRow":{"properties":{"ticker":{"type":"string","maxLength":12,"minLength":1,"title":"Ticker"},"cik":{"type":"string","title":"Cik"},"entity_name":{"type":"string","maxLength":300,"minLength":1,"title":"Entity Name"},"delisted_on":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Delisted On"},"match_method":{"type":"string","maxLength":64,"minLength":1,"title":"Match Method"},"confidence":{"type":"string","enum":["high","medium","low"],"title":"Confidence"}},"type":"object","required":["ticker","cik","entity_name","match_method","confidence"],"title":"CikMapRow"},"CoverageBlock":{"properties":{"min_snapshot_universe":{"type":"integer","title":"Min Snapshot Universe","default":0},"min_priced_fraction":{"type":"number","title":"Min Priced Fraction","default":0.0},"min_basket_names":{"type":"integer","title":"Min Basket Names","default":0},"steps_attempted":{"type":"integer","title":"Steps Attempted","default":0},"steps_measured":{"type":"integer","title":"Steps Measured","default":0},"steps_abstained":{"type":"integer","title":"Steps Abstained","default":0},"measured_steps_contiguous":{"type":"boolean","title":"Measured Steps Contiguous","default":true},"abstained":{"items":{"$ref":"#/components/schemas/AbstainedStep"},"type":"array","title":"Abstained"}},"type":"object","title":"CoverageBlock","description":"MDD-01 (2026-10-09): per-step coverage floor and what it removed.\n\nA step is measured only when both of its monthly snapshots hold at least\n`min_snapshot_universe` distinct scored symbols AND at least\nmax(`min_basket_names`, `min_priced_fraction` x basket) of the basket is\npriced at both ends. Any other step abstains: it is absent from the curves\nand from every figure (never published as a flat 0.00% month) and is\nlisted in `abstained`. `measured_steps_contiguous` is False when an\nabstained step sits BETWEEN measured ones, i.e. the curve joins\nnon-adjacent periods."},"CreateAlertRequest":{"properties":{"ticker":{"type":"string","title":"Ticker"},"alert_type":{"type":"string","title":"Alert Type"},"threshold":{"type":"number","title":"Threshold"}},"type":"object","required":["ticker","alert_type","threshold"],"title":"CreateAlertRequest"},"CreateInviteRequest":{"properties":{"label":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Label"},"max_uses":{"type":"integer","maximum":1000.0,"minimum":1.0,"title":"Max Uses","default":1},"acknowledgment_required":{"type":"boolean","title":"Acknowledgment Required","default":true},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"}},"type":"object","title":"CreateInviteRequest"},"CreateUserRequest":{"properties":{"email":{"type":"string","title":"Email"},"password":{"type":"string","title":"Password"},"is_admin":{"type":"boolean","title":"Is Admin","default":false},"is_friends_family":{"type":"boolean","title":"Is Friends Family","default":false}},"type":"object","required":["email","password"],"title":"CreateUserRequest"},"CsvImportRequest":{"properties":{"account_id":{"type":"string","format":"uuid","title":"Account Id"},"csv_text":{"type":"string","title":"Csv Text"},"mapping":{"additionalProperties":{"anyOf":[{"type":"string"},{"type":"null"}]},"type":"object","title":"Mapping"},"flip_sign":{"type":"boolean","title":"Flip Sign","default":false}},"type":"object","required":["account_id","csv_text","mapping"],"title":"CsvImportRequest"},"DelistedUniverse":{"properties":{"tails":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Tails"},"mapped":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Mapped"},"loaded_ciks":{"items":{"type":"string"},"type":"array","title":"Loaded Ciks"}},"type":"object","required":["tails","mapped","loaded_ciks"],"title":"DelistedUniverse"},"DemoAnalysisResponse":{"properties":{"ticker":{"type":"string","title":"Ticker"},"company_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Name"},"sector":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sector"},"price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Price"},"save_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Save Score"},"mos_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mos Pct"},"pe_ttm":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pe Ttm"},"fwd_pe":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fwd Pe"},"revenue_growth":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Revenue Growth"},"gross_margin":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gross Margin"},"market_cap":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Market Cap"},"fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fair Value"},"qualifying_question":{"type":"string","title":"Qualifying Question"}},"type":"object","required":["ticker","company_name","sector","price","save_score","mos_pct","pe_ttm","fwd_pe","revenue_growth","gross_margin","market_cap","fair_value","qualifying_question"],"title":"DemoAnalysisResponse"},"DividendUniverse":{"properties":{"symbols":{"items":{"type":"string"},"type":"array","title":"Symbols"},"delisted":{"items":{"type":"string"},"type":"array","title":"Delisted"}},"type":"object","required":["symbols","delisted"],"title":"DividendUniverse"},"EmailCaptureRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"ticker":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ticker"}},"type":"object","required":["email"],"title":"EmailCaptureRequest"},"EquityCurveBlock":{"properties":{"dates":{"items":{"type":"string"},"type":"array","title":"Dates"},"strategy":{"items":{"type":"number"},"type":"array","title":"Strategy"},"spy":{"items":{"type":"number"},"type":"array","title":"Spy"},"qqq":{"items":{"type":"number"},"type":"array","title":"Qqq"},"universe":{"items":{"type":"number"},"type":"array","title":"Universe"}},"type":"object","title":"EquityCurveBlock","description":"Per-series equity curve segment — normalized to 100.0 at index 0.\n\nP0-3 (2026-08-03): `universe` is the equal-weight mean of every scored\nsymbol over the same rebalance dates — the like-for-like comparator.\n`qqq` is retired (always empty, kept for response-shape compatibility).\nAn empty `spy` with `benchmark_degraded=True` on the response means the\nexternal feed failed — never rendered as a flat 0% line."},"ErpUpdateRequest":{"properties":{"value":{"type":"number","title":"Value"}},"type":"object","required":["value"],"title":"ErpUpdateRequest","description":"Admin write for the platform-wide equity risk premium (decimal, e.g. 0.047)."},"ExchangeRequest":{"properties":{"public_token":{"type":"string","maxLength":200,"minLength":10,"title":"Public Token"},"institution_name":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Institution Name"},"institution_id":{"anyOf":[{"type":"string","maxLength":40},{"type":"null"}],"title":"Institution Id"}},"type":"object","required":["public_token"],"title":"ExchangeRequest"},"ExportPage":{"properties":{"table":{"type":"string","title":"Table"},"rows":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Rows"},"next_after":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Next After"},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total"}},"type":"object","required":["table","rows","next_after"],"title":"ExportPage"},"FactRow":{"properties":{"cik":{"type":"string","title":"Cik"},"ticker":{"type":"string","maxLength":12,"minLength":1,"title":"Ticker"},"entity_name":{"type":"string","maxLength":300,"minLength":1,"title":"Entity Name"},"concept":{"type":"string","title":"Concept"},"xbrl_tag":{"type":"string","maxLength":200,"minLength":3,"title":"Xbrl Tag"},"unit":{"type":"string","maxLength":64,"minLength":1,"title":"Unit"},"period_start":{"type":"string","format":"date","title":"Period Start"},"period_end":{"type":"string","format":"date","title":"Period End"},"fiscal_year":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Fiscal Year"},"fiscal_period":{"anyOf":[{"type":"string","maxLength":8},{"type":"null"}],"title":"Fiscal Period"},"form":{"type":"string","maxLength":16,"minLength":1,"title":"Form"},"filed":{"type":"string","format":"date","title":"Filed"},"value":{"type":"number","title":"Value"},"accession":{"type":"string","maxLength":40,"minLength":1,"title":"Accession"},"frame":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Frame"},"source":{"type":"string","const":"edgar","title":"Source","default":"edgar"}},"type":"object","required":["cik","ticker","entity_name","concept","xbrl_tag","unit","period_start","period_end","form","filed","value","accession"],"title":"FactRow"},"FactorICSeries":{"properties":{"factor_name":{"type":"string","title":"Factor Name"},"horizon_days":{"type":"integer","title":"Horizon Days"},"points":{"items":{"$ref":"#/components/schemas/FactorICSeriesPoint"},"type":"array","title":"Points"}},"type":"object","required":["factor_name","horizon_days","points"],"title":"FactorICSeries","description":"Time series of rolling-20d IC for one factor at one horizon (B5).\n\nPowers the per-factor sparklines on /methodology. Points ascend by date and\nare capped server-side (~90) — this is a trend glyph, not an export feed."},"FactorICSeriesPoint":{"properties":{"date":{"type":"string","title":"Date"},"ic":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ic"}},"type":"object","required":["date","ic"],"title":"FactorICSeriesPoint","description":"One dated rolling-IC reading for the sparkline series (B5)."},"FairValueInterval":{"properties":{"low":{"type":"number","title":"Low"},"high":{"type":"number","title":"High"},"confidence":{"type":"number","title":"Confidence","default":0.8}},"type":"object","required":["low","high"],"title":"FairValueInterval","description":"Phase 47 / CI-01..04: 80% confidence band on a fair value point estimate.\n\nThe band combines two uncertainty sources:\n- Cross-method disagreement (IQR-based, robust to DCF tail outliers)\n- Sector-vol within-method spread (decile-bucketed multiplier × FV)\n\n`confidence` is always 0.80 in v2.0; reserved for v2.1+ scenario weights.\nCopy framing in UI must use \"80% model interval\" — NEVER \"forecast\",\n\"expected range\", or \"price target\" (banned per CLAUDE.md)."},"FeedbackAdmin":{"properties":{"id":{"type":"integer","title":"Id"},"user_id":{"type":"string","title":"User Id"},"user_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Email"},"surface":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Surface"},"ticker":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ticker"},"rating":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rating"},"body":{"type":"string","title":"Body"},"submitted_at":{"type":"string","title":"Submitted At"},"digested_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Digested At"},"resolved_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved At"},"resolved_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resolved Note"}},"type":"object","required":["id","user_id","user_email","surface","ticker","rating","body","submitted_at","digested_at","resolved_at","resolved_note"],"title":"FeedbackAdmin"},"FeedbackRequest":{"properties":{"body":{"type":"string","maxLength":8000,"minLength":3,"title":"Body"},"surface":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Surface"},"ticker":{"anyOf":[{"type":"string","maxLength":12},{"type":"null"}],"title":"Ticker"},"rating":{"anyOf":[{"type":"integer","maximum":5.0,"minimum":1.0},{"type":"null"}],"title":"Rating"}},"type":"object","required":["body"],"title":"FeedbackRequest"},"FeedbackResponse":{"properties":{"id":{"type":"integer","title":"Id"},"submitted_at":{"type":"string","title":"Submitted At"},"received":{"type":"boolean","title":"Received","default":true},"next_steps":{"type":"string","title":"Next Steps","default":"Thanks for the feedback — the owner reviews these weekly."}},"type":"object","required":["id","submitted_at"],"title":"FeedbackResponse"},"FrontendError":{"properties":{"error_type":{"type":"string","title":"Error Type","default":"frontend_error"},"message":{"type":"string","title":"Message"},"symbol":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Symbol"},"endpoint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Endpoint"},"stack":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Stack"},"context":{"additionalProperties":true,"type":"object","title":"Context","default":{}}},"type":"object","required":["message"],"title":"FrontendError"},"FunnelStepBlock":{"properties":{"count_7d":{"type":"integer","title":"Count 7D"},"count_30d":{"type":"integer","title":"Count 30D"},"count_all":{"type":"integer","title":"Count All"}},"type":"object","required":["count_7d","count_30d","count_all"],"title":"FunnelStepBlock"},"GatedDownloadRequest":{"properties":{"email":{"type":"string","title":"Email"},"source":{"type":"string","title":"Source","default":"valuation-tracker"}},"type":"object","required":["email"],"title":"GatedDownloadRequest"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"Holding":{"properties":{"symbol":{"type":"string","title":"Symbol"},"shares":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Shares"},"cost_basis":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cost Basis"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["symbol"],"title":"Holding"},"HoldingRequest":{"properties":{"symbol":{"type":"string","title":"Symbol"},"shares":{"type":"number","title":"Shares"},"cost_basis":{"type":"number","title":"Cost Basis"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["symbol","shares","cost_basis"],"title":"HoldingRequest"},"HoldingUpdate":{"properties":{"shares":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Shares"},"cost_basis":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cost Basis"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"HoldingUpdate"},"HoldingsReplace":{"properties":{"holdings":{"items":{"$ref":"#/components/schemas/Holding"},"type":"array","title":"Holdings"}},"type":"object","required":["holdings"],"title":"HoldingsReplace"},"IVBackfillRequest":{"properties":{"end_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date"},"tickers":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tickers"},"chunk_size":{"type":"integer","title":"Chunk Size","default":10}},"type":"object","title":"IVBackfillRequest"},"IVNullRankBackfillRequest":{"properties":{"limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Limit"},"dry_run":{"type":"boolean","title":"Dry Run","default":false}},"type":"object","title":"IVNullRankBackfillRequest"},"IngestBatch":{"properties":{"cik_map":{"items":{"$ref":"#/components/schemas/CikMapRow"},"type":"array","maxItems":1000,"title":"Cik Map"},"facts":{"items":{"$ref":"#/components/schemas/FactRow"},"type":"array","maxItems":1000,"title":"Facts"}},"type":"object","title":"IngestBatch"},"IngestResult":{"properties":{"cik_map_sent":{"type":"integer","title":"Cik Map Sent"},"facts_sent":{"type":"integer","title":"Facts Sent"},"run_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Run Id"},"ref":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ref"}},"type":"object","required":["cik_map_sent","facts_sent","run_id","ref"],"title":"IngestResult"},"InnovationComponent":{"properties":{"label":{"type":"string","title":"Label"},"value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Value"},"score":{"type":"number","title":"Score"},"weight":{"type":"number","title":"Weight"},"description":{"type":"string","title":"Description"}},"type":"object","required":["label","score","weight","description"],"title":"InnovationComponent"},"InnovationResult":{"properties":{"symbol":{"type":"string","title":"Symbol"},"sector":{"type":"string","title":"Sector"},"score_type":{"type":"string","title":"Score Type"},"total_score":{"type":"number","title":"Total Score"},"label":{"type":"string","title":"Label"},"components":{"items":{"$ref":"#/components/schemas/InnovationComponent"},"type":"array","title":"Components"},"patents_2y":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Patents 2Y"},"rd_intensity_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rd Intensity Pct"},"rd_growth_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rd Growth Pct"},"capex_intensity_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Capex Intensity Pct"},"capex_growth_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Capex Growth Pct"},"sector_benchmark":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sector Benchmark"},"data_as_of":{"type":"string","title":"Data As Of"},"data_source":{"type":"string","title":"Data Source"}},"type":"object","required":["symbol","sector","score_type","total_score","label","components","data_as_of","data_source"],"title":"InnovationResult"},"InvestorFunnelResponse":{"properties":{"page_views":{"$ref":"#/components/schemas/FunnelStepBlock"},"email_captures":{"$ref":"#/components/schemas/FunnelStepBlock"},"calendly_clicks":{"$ref":"#/components/schemas/FunnelStepBlock"},"conversion_rates":{"additionalProperties":true,"type":"object","title":"Conversion Rates"}},"type":"object","required":["page_views","email_captures","calendly_clicks","conversion_rates"],"title":"InvestorFunnelResponse"},"InviteAdmin":{"properties":{"id":{"type":"integer","title":"Id"},"code":{"type":"string","title":"Code"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"max_uses":{"type":"integer","title":"Max Uses"},"use_count":{"type":"integer","title":"Use Count"},"acknowledgment_required":{"type":"boolean","title":"Acknowledgment Required"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"},"created_by_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By Email"},"created_at":{"type":"string","title":"Created At"},"disabled_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disabled At"},"is_redeemable":{"type":"boolean","title":"Is Redeemable"}},"type":"object","required":["id","code","label","max_uses","use_count","acknowledgment_required","expires_at","created_by_email","created_at","disabled_at","is_redeemable"],"title":"InviteAdmin","description":"Full invite view for /admin/ff dashboard."},"InvitePreview":{"properties":{"code":{"type":"string","title":"Code"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"is_redeemable":{"type":"boolean","title":"Is Redeemable"},"acknowledgment_required":{"type":"boolean","title":"Acknowledgment Required"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At"}},"type":"object","required":["code","is_redeemable","acknowledgment_required"],"title":"InvitePreview","description":"Public-safe subset of FFInvite — no created_by_email, no internal IDs."},"LeadCaptureRequest":{"properties":{"email":{"type":"string","maxLength":320,"title":"Email"},"utm_source":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Utm Source"},"lead_type":{"type":"string","maxLength":32,"title":"Lead Type","default":"email_capture"}},"type":"object","required":["email"],"title":"LeadCaptureRequest"},"LeadCaptureResponse":{"properties":{"captured":{"type":"boolean","title":"Captured","default":true},"calendly_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Calendly Url"}},"type":"object","title":"LeadCaptureResponse"},"LinkTokenRequest":{"properties":{"item_row_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Item Row Id"}},"type":"object","title":"LinkTokenRequest"},"LinkedInEventRequest":{"properties":{"event_type":{"type":"string","title":"Event Type"},"email":{"type":"string","format":"email","title":"Email"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata","default":{}}},"type":"object","required":["event_type","email"],"title":"LinkedInEventRequest"},"LiveFactorIC":{"properties":{"factor_name":{"type":"string","title":"Factor Name"},"horizon_days":{"type":"integer","title":"Horizon Days"},"ic_rolling_20d":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ic Rolling 20D"},"as_of":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"As Of"},"mean_ic":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mean Ic"},"t_hac":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"T Hac"},"eff_n":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Eff N"},"significant":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Significant"},"passes_rule":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Passes Rule"},"adequate_eff_n":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Adequate Eff N"}},"type":"object","required":["factor_name","horizon_days","ic_rolling_20d","as_of"],"title":"LiveFactorIC","description":"One live information-coefficient cell from factor_ic_history.\n\n2026-08-03 (P1-8): carries the overlap-corrected significance verdict\nalongside the legacy rolling mean, so no consumer can over-read a raw\nrolling IC again. `passes_rule` is the Newey-West HAC + BH-FDR +\neffective-N decision from factor_ic_significance; `adequate_eff_n` is\nFalse when the cell's overlap-corrected sample is still too thin to\ndecide anything."},"NewsFactorBackfillRequest":{"properties":{"start_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End Date"},"tickers":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tickers"},"max_minutes":{"type":"number","title":"Max Minutes","default":45.0},"resume_after":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Resume After"},"pause_seconds":{"type":"number","title":"Pause Seconds","default":1.0}},"type":"object","title":"NewsFactorBackfillRequest","description":"Body for one bounded slice of the Phase 94 historical news-factor derivation."},"NewsSentimentBackfillRequest":{"properties":{"chunk_size":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Chunk Size"},"max_minutes":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Max Minutes"},"tickers":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tickers"},"horizon_days":{"type":"integer","title":"Horizon Days","default":365},"concurrency":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Concurrency"}},"type":"object","title":"NewsSentimentBackfillRequest","description":"Body for one bounded slice of the Phase 94 news-sentiment backfill.\n\nEvery field is optional; omitting one takes the Railway-tunable default\n(NEWS_BACKFILL_CHUNK_SIZE / NEWS_BACKFILL_MAX_MINUTES / NEWS_INGEST_CONCURRENCY)."},"NoteRequest":{"properties":{"note":{"type":"string","maxLength":5000,"title":"Note"}},"type":"object","required":["note"],"title":"NoteRequest"},"NotifyOwnerRequest":{"properties":{"subject":{"type":"string","title":"Subject"},"body":{"type":"string","title":"Body"},"to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"To"},"from_addr":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"From Addr"},"from_name":{"type":"string","title":"From Name","default":"Equity Rank"}},"type":"object","required":["subject","body"],"title":"NotifyOwnerRequest"},"PlaceOrderRequest":{"properties":{"account_hash":{"type":"string","title":"Account Hash"},"symbol":{"type":"string","title":"Symbol"},"side":{"type":"string","title":"Side"},"quantity":{"type":"number","title":"Quantity"},"order_type":{"type":"string","enum":["MARKET","LIMIT","STOP","STOP_LIMIT","TRAILING_STOP"],"title":"Order Type"},"asset_type":{"type":"string","enum":["EQUITY","OPTION"],"title":"Asset Type","default":"EQUITY"},"limit_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Limit Price"},"stop_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Stop Price"},"trailing_offset":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Trailing Offset"},"trailing_basis":{"type":"string","enum":["PERCENT","VALUE"],"title":"Trailing Basis","default":"PERCENT"}},"type":"object","required":["account_hash","symbol","side","quantity","order_type"],"title":"PlaceOrderRequest","description":"Request body for POST /api/admin/schwab/orders/place.\n\nValidates required price/offset fields per order_type at the FastAPI 422\nlayer, before any Schwab call."},"PortalRequest":{"properties":{"return_url":{"type":"string","title":"Return Url","default":""}},"type":"object","title":"PortalRequest"},"PortfolioOptimizeRequest":{"properties":{"tickers":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tickers"},"positions":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Positions"}},"type":"object","title":"PortfolioOptimizeRequest"},"PortfolioOptimizeResponse":{"properties":{"holdings_used":{"type":"integer","title":"Holdings Used"},"weights":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Weights"},"portfolio_stats":{"additionalProperties":true,"type":"object","title":"Portfolio Stats"},"disclaimer":{"type":"string","title":"Disclaimer"},"cache_age_minutes":{"type":"integer","title":"Cache Age Minutes"}},"type":"object","required":["holdings_used","weights","portfolio_stats","disclaimer","cache_age_minutes"],"title":"PortfolioOptimizeResponse"},"ProjectionEnvelope":{"properties":{"bear":{"anyOf":[{"$ref":"#/components/schemas/ValuationProjection"},{"type":"null"}]},"base":{"anyOf":[{"$ref":"#/components/schemas/ValuationProjection"},{"type":"null"}]},"bull":{"anyOf":[{"$ref":"#/components/schemas/ValuationProjection"},{"type":"null"}]},"data_complete":{"type":"boolean","title":"Data Complete","default":false},"horizon_years":{"type":"integer","title":"Horizon Years","default":1},"range_low":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Range Low"},"range_high":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Range High"},"disclaimer":{"type":"string","title":"Disclaimer","default":""}},"type":"object","title":"ProjectionEnvelope","description":"All three bear/base/bull scenarios for a given ticker + horizon."},"ProjectionResult":{"properties":{"ticker":{"type":"string","title":"Ticker"},"horizon_years":{"type":"integer","title":"Horizon Years"},"data_complete":{"type":"boolean","title":"Data Complete"},"bear":{"anyOf":[{"$ref":"#/components/schemas/ProjectionScenario"},{"type":"null"}]},"base":{"anyOf":[{"$ref":"#/components/schemas/ProjectionScenario"},{"type":"null"}]},"bull":{"anyOf":[{"$ref":"#/components/schemas/ProjectionScenario"},{"type":"null"}]},"projected_fair_value_range_low":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Projected Fair Value Range Low"},"projected_fair_value_range_high":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Projected Fair Value Range High"},"method_notes":{"items":{"type":"string"},"type":"array","title":"Method Notes","default":[]},"disclaimer":{"type":"string","title":"Disclaimer","default":"Projected Fair Value Range — Model Scenarios Only\n\nThis section presents algorithmic model scenarios — not price targets, price forecasts, or investment recommendations. Scenarios are generated by applying Equity Rank's valuation models to projected future fundamentals under bear, base, and bull growth assumptions. Inputs include analyst consensus EPS growth estimates and are disclosed for each scenario. Actual market prices are determined by factors that no model can fully capture and may differ materially from these estimates. All financial models contain assumptions that may be incorrect. This output is for informational and educational purposes only. Equity Rank is not a registered investment adviser. Consult a licensed financial advisor before making any investment decision."}},"type":"object","required":["ticker","horizon_years","data_complete"],"title":"ProjectionResult"},"ProjectionScenario":{"properties":{"label":{"type":"string","title":"Label"},"projected_fair_value":{"type":"number","title":"Projected Fair Value"},"assumed_growth_rate":{"type":"number","title":"Assumed Growth Rate"},"model_fair_value_differential_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Model Fair Value Differential Pct"},"assumptions_note":{"type":"string","title":"Assumptions Note"}},"type":"object","required":["label","projected_fair_value","assumed_growth_rate","assumptions_note"],"title":"ProjectionScenario"},"PublicStockResponse":{"properties":{"ticker":{"type":"string","title":"Ticker"},"company_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Name"},"sector":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sector"},"consensus_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Consensus Fair Value"},"mos_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mos Pct"},"price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Price"},"last_updated":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Updated"},"save_score_30d_delta":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Save Score 30D Delta"},"roe_3yr_slope":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Roe 3Yr Slope"},"fcf_3yr_cagr":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fcf 3Yr Cagr"},"gross_margin_3yr_change":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Gross Margin 3Yr Change"},"fundamental_strength_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fundamental Strength Score"},"fundamental_strength_grade":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fundamental Strength Grade"},"fundamental_strength_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Fundamental Strength Label"},"fundamental_strength":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Fundamental Strength"},"method_agreement_below":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Method Agreement Below"},"method_agreement_above":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Method Agreement Above"},"method_agreement_total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Method Agreement Total"},"next_earnings_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Earnings Date"},"next_earnings_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Earnings Time"},"next_earnings_eps_estimate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Next Earnings Eps Estimate"},"noindex":{"type":"boolean","title":"Noindex","default":false},"fair_value_status":{"type":"string","title":"Fair Value Status","default":"available"},"reporting_currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reporting Currency"}},"type":"object","required":["ticker"],"title":"PublicStockResponse"},"RateRequest":{"properties":{"ticker":{"type":"string","title":"Ticker"},"strategy":{"type":"string","title":"Strategy"},"direction":{"type":"string","title":"Direction"},"rating":{"type":"string","title":"Rating"}},"type":"object","required":["ticker","strategy","direction","rating"],"title":"RateRequest"},"RateSensitivityRow":{"properties":{"rate_shock_bps":{"type":"integer","title":"Rate Shock Bps"},"delta_fv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Delta Fv"},"stressed_fv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Stressed Fv"}},"type":"object","required":["rate_shock_bps","delta_fv","stressed_fv"],"title":"RateSensitivityRow","description":"One row of the rate-sensitivity table."},"RateSensitivityTable":{"properties":{"equity_duration":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Equity Duration"},"rows":{"items":{"$ref":"#/components/schemas/RateSensitivityRow"},"type":"array","title":"Rows"}},"type":"object","required":["equity_duration","rows"],"title":"RateSensitivityTable","description":"Rate-sensitivity table: five rate-shock rows + equity duration (SA-02)."},"RealizedOutcomeBucket":{"properties":{"signal_source":{"type":"string","title":"Signal Source"},"horizon_days":{"type":"integer","title":"Horizon Days"},"n":{"type":"integer","title":"N"},"hits":{"type":"integer","title":"Hits"},"hit_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Hit Rate"},"base_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Base Rate"},"excess_hit_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Excess Hit Rate"},"excess_n":{"type":"integer","title":"Excess N","default":0},"excess_ci_low":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Excess Ci Low"},"excess_ci_high":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Excess Ci High"},"ticker_count":{"type":"integer","title":"Ticker Count","default":0},"sufficient":{"type":"boolean","title":"Sufficient","default":true},"first_prediction":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Prediction"},"last_evaluated":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Evaluated"}},"type":"object","required":["signal_source","horizon_days","n","hits","first_prediction","last_evaluated"],"title":"RealizedOutcomeBucket","description":"Aggregated realized directional outcomes for one (signal, horizon) (B5).\n\nComputed at request time from signal_outcomes.hit_flag (the outcome\nrecorder's own direction-agreement flag), never from the stale\nmethodology_stats keys. Buckets publish only at n >= 50 by default;\npublished verbatim — including readings below coin-flip.\n\n`ticker_count` / `sufficient` (AWL-08, Phase 89-14): ticker_count is the\ndistinct-symbol count within the bucket (a bare hit-rate percentage over\na handful of tickers is exactly what the track-record surface must never\npresent bare). `sufficient` is False only when the caller opts in via\n`include_insufficient=True` (the track-record surface) — the default\n/backtest-summary caller never sees a sub-threshold bucket, unchanged.\n\n2026-08-03 (P0-4): `hit_rate` is now ALWAYS None. The verification pack\nproved it identical to the drift base rate at every horizon — zero\ninformation. Published instead: `base_rate` (share of positive realized\nmoves in the bucket — explicitly labeled market drift, so readers see WHY\nthe raw rate is uninformative) and `excess_hit_rate` (mean of\nsignal_outcomes.excess_hit_flag: direction agreement measured against the\nsame-horizon universe median move, the drift-corrected metric), which is\nNone until enough excess evaluations accumulate."},"RecessionScenario":{"properties":{"sector":{"type":"string","title":"Sector"},"revenue_haircut_pct":{"type":"number","title":"Revenue Haircut Pct"},"margin_compression_bps":{"type":"number","title":"Margin Compression Bps"},"fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fair Value"},"assumptions_note":{"type":"string","title":"Assumptions Note"}},"type":"object","required":["sector","revenue_haircut_pct","margin_compression_bps","fair_value","assumptions_note"],"title":"RecessionScenario","description":"Recession stress scenario using sector-specific haircut table (SA-04)."},"RedeemInviteRequest":{"properties":{"acknowledgment_displayed":{"type":"boolean","title":"Acknowledgment Displayed","description":"MUST be true when the invite has acknowledgment_required=true. Frontend sets this AFTER displaying the F&F memo and capturing user assent."}},"type":"object","required":["acknowledgment_displayed"],"title":"RedeemInviteRequest"},"RedeemInviteResponse":{"properties":{"redeemed":{"type":"boolean","title":"Redeemed"},"invite_id":{"type":"integer","title":"Invite Id"},"user_id":{"type":"string","title":"User Id"},"next_steps":{"type":"string","title":"Next Steps","default":"Sign out and back in to refresh your JWT. Your is_friends_family flag is now set; the paid funnel is bypassed."}},"type":"object","required":["redeemed","invite_id","user_id"],"title":"RedeemInviteResponse"},"RedeemRequest":{"properties":{"code":{"type":"string","title":"Code"}},"type":"object","required":["code"],"title":"RedeemRequest"},"RenameFilterRequest":{"properties":{"name":{"type":"string","title":"Name"}},"type":"object","required":["name"],"title":"RenameFilterRequest"},"ResearchRowsBatch":{"properties":{"table":{"type":"string","enum":["listing_status_snapshot","dividend_history","split_history","earnings_surprise_history","benchmark_price_history"],"title":"Table"},"rows":{"items":{"additionalProperties":true,"type":"object"},"type":"array","maxItems":1000,"title":"Rows"}},"type":"object","required":["table"],"title":"ResearchRowsBatch"},"ResearchRowsResult":{"properties":{"table":{"type":"string","title":"Table"},"rows_sent":{"type":"integer","title":"Rows Sent"},"rows_rejected":{"type":"integer","title":"Rows Rejected"},"run_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Run Id"}},"type":"object","required":["table","rows_sent","rows_rejected","run_id"],"title":"ResearchRowsResult"},"ResetPasswordRequest":{"properties":{"email":{"type":"string","title":"Email"}},"type":"object","required":["email"],"title":"ResetPasswordRequest"},"ReweightRequest":{"properties":{"weights":{"additionalProperties":{"type":"number"},"type":"object","title":"Weights"}},"type":"object","required":["weights"],"title":"ReweightRequest"},"RuleCreate":{"properties":{"pattern":{"type":"string","maxLength":80,"minLength":1,"title":"Pattern"},"category_id":{"type":"string","format":"uuid","title":"Category Id"},"priority":{"type":"integer","maximum":1000.0,"minimum":0.0,"title":"Priority","default":0}},"type":"object","required":["pattern","category_id"],"title":"RuleCreate"},"SAVEWeights":{"properties":{"sector":{"type":"string","title":"Sector"},"mentionVelocity":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Mentionvelocity"},"consumerSentiment":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Consumersentiment"},"employeeSentiment":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Employeesentiment"},"newsMomentum":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Newsmomentum"},"searchTrend":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Searchtrend"},"is_default":{"type":"boolean","title":"Is Default","default":false}},"type":"object","required":["sector","mentionVelocity","consumerSentiment","employeeSentiment","newsMomentum","searchTrend"],"title":"SAVEWeights"},"SAVEWeightsUpdate":{"properties":{"mentionVelocity":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Mentionvelocity"},"consumerSentiment":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Consumersentiment"},"employeeSentiment":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Employeesentiment"},"newsMomentum":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Newsmomentum"},"searchTrend":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Searchtrend"}},"type":"object","title":"SAVEWeightsUpdate"},"SaveFilterRequest":{"properties":{"name":{"type":"string","title":"Name"},"filters":{"$ref":"#/components/schemas/ScreenerFilters"}},"type":"object","required":["name","filters"],"title":"SaveFilterRequest"},"SaveViewRequest":{"properties":{"symbol":{"type":"string","title":"Symbol"},"custom_weights":{"additionalProperties":{"type":"number"},"type":"object","title":"Custom Weights"}},"type":"object","required":["symbol","custom_weights"],"title":"SaveViewRequest"},"SaveWatchlistRequest":{"properties":{"tickers":{"items":{"type":"string"},"type":"array","title":"Tickers"}},"type":"object","required":["tickers"],"title":"SaveWatchlistRequest"},"ScenarioAnalysisResult":{"properties":{"ticker":{"type":"string","title":"Ticker"},"data_complete":{"type":"boolean","title":"Data Complete"},"bear_base_bull":{"$ref":"#/components/schemas/BearBaseBullResult"},"sensitivity_matrix":{"anyOf":[{"$ref":"#/components/schemas/SensitivityMatrix"},{"type":"null"}]},"rate_sensitivity":{"anyOf":[{"$ref":"#/components/schemas/RateSensitivityTable"},{"type":"null"}]},"recession_scenario":{"anyOf":[{"$ref":"#/components/schemas/RecessionScenario"},{"type":"null"}]},"disclaimer":{"type":"string","title":"Disclaimer","default":""}},"type":"object","required":["ticker","data_complete","bear_base_bull"],"title":"ScenarioAnalysisResult","description":"Top-level API response for GET /api/valuation/{ticker}/scenarios."},"ScenarioPoint":{"properties":{"label":{"type":"string","title":"Label"},"fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fair Value"},"revenue_delta_pp":{"type":"number","title":"Revenue Delta Pp"},"margin_delta_pp":{"type":"number","title":"Margin Delta Pp"},"coe_delta_bps":{"type":"number","title":"Coe Delta Bps"},"assumptions_note":{"type":"string","title":"Assumptions Note"}},"type":"object","required":["label","fair_value","revenue_delta_pp","margin_delta_pp","coe_delta_bps","assumptions_note"],"title":"ScenarioPoint","description":"A single Bear, Base, or Bull DCF scenario result."},"ScreenerExportRequest":{"properties":{"sector":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sector"},"min_score":{"type":"number","title":"Min Score","default":0.0},"sort_by":{"type":"string","title":"Sort By","default":"equity_rank_score"},"min_piotroski_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Piotroski Score"},"graham_below_price":{"type":"boolean","title":"Graham Below Price","default":false},"max_illiquidity_premium":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Max Illiquidity Premium"},"min_roic":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Roic"},"min_gross_margin":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Gross Margin"},"min_net_margin":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Net Margin"},"min_revenue_growth":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Revenue Growth"},"min_eps_growth":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Eps Growth"},"min_fcf_yield":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Fcf Yield"},"max_net_debt_to_ebitda":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Max Net Debt To Ebitda"},"full_data_only":{"type":"boolean","title":"Full Data Only","default":true},"min_sue_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Sue Score"},"min_eps_revision_ratio":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Eps Revision Ratio"},"max_short_ratio":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Max Short Ratio"},"symbol":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Symbol"}},"type":"object","title":"ScreenerExportRequest","description":"The export request body mirrors /browse's filter params.\n\nExtends ScreenerFilters (imported, not redeclared) so every field it\nalready carries — sector, min_score, sort_by, min_piotroski_score,\ngraham_below_price, max_illiquidity_premium, and the six AWL-07\ngrowth/leverage/quality filters — reuses ScreenerFilters' own clamp\nvalidators verbatim. Only the handful of /browse params ScreenerFilters\ndoesn't carry are added here, with bounds mirroring the live /browse\nQuery(...) bounds in screener.py."},"ScreenerFilters":{"properties":{"sector":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sector"},"min_score":{"type":"number","title":"Min Score","default":0.0},"sort_by":{"type":"string","title":"Sort By","default":"equity_rank_score"},"min_piotroski_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Min Piotroski Score"},"graham_below_price":{"type":"boolean","title":"Graham Below Price","default":false},"max_illiquidity_premium":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Max Illiquidity Premium"},"min_roic":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Roic"},"min_gross_margin":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Gross Margin"},"min_net_margin":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Net Margin"},"min_revenue_growth":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Revenue Growth"},"min_eps_growth":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Eps Growth"},"min_fcf_yield":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Fcf Yield"},"max_net_debt_to_ebitda":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Max Net Debt To Ebitda"}},"type":"object","title":"ScreenerFilters"},"SecHumanCapitalRequest":{"properties":{"tickers":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tickers"},"parse_text":{"type":"boolean","title":"Parse Text","default":true},"max_companies":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Max Companies"}},"type":"object","title":"SecHumanCapitalRequest"},"SectorBlendBody":{"properties":{"label":{"type":"string","title":"Label"},"rd_weight":{"type":"number","title":"Rd Weight"},"capex_weight":{"type":"number","title":"Capex Weight"},"rd_benchmark":{"type":"number","title":"Rd Benchmark","default":10.0},"capex_benchmark":{"type":"number","title":"Capex Benchmark","default":8.0}},"type":"object","required":["label","rd_weight","capex_weight"],"title":"SectorBlendBody"},"SectorCalibrationEntry":{"properties":{"sector":{"type":"string","title":"Sector"},"save_multiplier":{"type":"number","title":"Save Multiplier"},"innovation_alpha":{"type":"number","title":"Innovation Alpha"},"save_rationale":{"type":"string","title":"Save Rationale"},"innovation_rationale":{"type":"string","title":"Innovation Rationale"},"calibration_date":{"type":"string","title":"Calibration Date","default":"2026-04-01"}},"type":"object","required":["sector","save_multiplier","innovation_alpha","save_rationale","innovation_rationale"],"title":"SectorCalibrationEntry"},"SectorCalibrationResponse":{"properties":{"sectors":{"items":{"$ref":"#/components/schemas/SectorCalibrationEntry"},"type":"array","title":"Sectors"},"methodology_note":{"type":"string","title":"Methodology Note"},"last_calibrated":{"type":"string","title":"Last Calibrated","default":"2026-04-01"}},"type":"object","required":["sectors","methodology_note"],"title":"SectorCalibrationResponse"},"SectorMultiple":{"properties":{"sector":{"type":"string","title":"Sector"},"pe_multiple":{"type":"number","exclusiveMinimum":0.0,"title":"Pe Multiple"},"pb_multiple":{"type":"number","exclusiveMinimum":0.0,"title":"Pb Multiple"},"ps_multiple":{"type":"number","exclusiveMinimum":0.0,"title":"Ps Multiple"},"ev_ebitda_multiple":{"type":"number","exclusiveMinimum":0.0,"title":"Ev Ebitda Multiple"},"roe_pct":{"type":"number","minimum":0.0,"title":"Roe Pct"}},"type":"object","required":["sector","pe_multiple","pb_multiple","ps_multiple","ev_ebitda_multiple","roe_pct"],"title":"SectorMultiple"},"SectorMultipleUpdate":{"properties":{"pe_multiple":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Pe Multiple"},"pb_multiple":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Pb Multiple"},"ps_multiple":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Ps Multiple"},"ev_ebitda_multiple":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"null"}],"title":"Ev Ebitda Multiple"},"roe_pct":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Roe Pct"}},"type":"object","title":"SectorMultipleUpdate"},"SensitivityMatrix":{"properties":{"revenue_deltas_pp":{"items":{"type":"number"},"type":"array","title":"Revenue Deltas Pp"},"margin_deltas_pp":{"items":{"type":"number"},"type":"array","title":"Margin Deltas Pp"},"grid":{"items":{"items":{"anyOf":[{"type":"number"},{"type":"null"}]},"type":"array"},"type":"array","title":"Grid"}},"type":"object","required":["revenue_deltas_pp","margin_deltas_pp","grid"],"title":"SensitivityMatrix","description":"5x5 revenue x margin sensitivity grid (SA-02).\n\ngrid[row][col] corresponds to margin_deltas_pp[row] and\nrevenue_deltas_pp[col]. None means DataUnavailableError for that cell."},"SetExperienceModeRequest":{"properties":{"experience_mode":{"type":"string","title":"Experience Mode"}},"type":"object","required":["experience_mode"],"title":"SetExperienceModeRequest","description":"Body for POST /api/auth/preferences — the user's UI experience level."},"ShareToggleRequest":{"properties":{"is_shared":{"type":"boolean","title":"Is Shared"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"}},"type":"object","required":["is_shared"],"title":"ShareToggleRequest"},"SignupExitSurveyRequest":{"properties":{"reason":{"type":"string","title":"Reason"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"partial_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Partial Email"}},"type":"object","required":["reason"],"title":"SignupExitSurveyRequest"},"StoreCheckoutRequest":{"properties":{"product_id":{"type":"string","title":"Product Id"}},"type":"object","required":["product_id"],"title":"StoreCheckoutRequest"},"TargetedBackfillBody":{"properties":{"tickers":{"items":{"type":"string"},"type":"array","title":"Tickers"}},"type":"object","required":["tickers"],"title":"TargetedBackfillBody"},"TrackRecordBacktestBlock":{"properties":{"sufficient_data":{"type":"boolean","title":"Sufficient Data"},"real_data_since":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Real Data Since"},"period_start":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period Start"},"period_end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period End"},"n_rebalances":{"type":"integer","title":"N Rebalances","default":0},"max_drawdown_suppressed":{"type":"boolean","title":"Max Drawdown Suppressed","default":false},"coverage":{"$ref":"#/components/schemas/CoverageBlock"},"strategy":{"$ref":"#/components/schemas/BacktestMetricsBlock"},"spy":{"$ref":"#/components/schemas/BenchmarkBlock"},"qqq":{"$ref":"#/components/schemas/BenchmarkBlock"},"validation":{"$ref":"#/components/schemas/ValidationGateBlock"},"caveats":{"$ref":"#/components/schemas/BacktestComplianceBlock"}},"type":"object","required":["sufficient_data"],"title":"TrackRecordBacktestBlock","description":"The existing /backtest simulation, passed through with its\ncompliance caveats verbatim (byte-identical to what /summary serves)."},"TrackRecordHitRate":{"properties":{"signal":{"type":"string","title":"Signal"},"horizon_days":{"type":"integer","title":"Horizon Days"},"n":{"type":"integer","title":"N"},"ticker_count":{"type":"integer","title":"Ticker Count"},"window_start":{"type":"string","title":"Window Start"},"window_end":{"type":"string","title":"Window End"},"hit_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Hit Rate"},"base_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Base Rate"},"excess_hit_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Excess Hit Rate"},"excess_n":{"type":"integer","title":"Excess N","default":0},"excess_ci_low":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Excess Ci Low"},"excess_ci_high":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Excess Ci High"},"sufficient":{"type":"boolean","title":"Sufficient"}},"type":"object","required":["signal","horizon_days","n","ticker_count","window_start","window_end","sufficient"],"title":"TrackRecordHitRate","description":"One realized-outcome bucket, always carrying its full sample context.\n\n`hit_rate` is None (never a synthetic or borrowed number) whenever\n`sufficient` is False — i.e. the bucket has fewer than the honest\nminimum (`_OUTCOME_BUCKET_MIN_N`) evaluated outcomes."},"TrackRecordResponse":{"properties":{"live_tracking_started":{"type":"string","title":"Live Tracking Started"},"hit_rates":{"items":{"$ref":"#/components/schemas/TrackRecordHitRate"},"type":"array","title":"Hit Rates"},"backtest":{"$ref":"#/components/schemas/TrackRecordBacktestBlock"},"limitations":{"items":{"type":"string"},"type":"array","title":"Limitations"},"total_realized_outcomes":{"type":"integer","title":"Total Realized Outcomes","default":0},"total_ticker_count":{"type":"integer","title":"Total Ticker Count","default":0},"degraded":{"type":"boolean","title":"Degraded","default":false}},"type":"object","required":["live_tracking_started","backtest"],"title":"TrackRecordResponse","description":"Response model for GET /api/backtest/track-record.\n\n`total_realized_outcomes` / `total_ticker_count` are the aggregate live\ncounts (across every evaluated signal_outcomes row, not just the\npublished buckets) — the exact numbers the UI-SPEC's mandatory\ninsufficient-data copy substitutes into \"{n} realized outcomes across\n{ticker_count} tickers\". Kept as explicit numeric fields (not just prose\ninside `limitations`) so the frontend never has to parse text to render\nthe mandated string."},"TrendBriefRequest":{"properties":{"themes":{"items":{},"type":"array","title":"Themes"}},"type":"object","required":["themes"],"title":"TrendBriefRequest"},"TxnCreate":{"properties":{"account_id":{"type":"string","format":"uuid","title":"Account Id"},"txn_date":{"type":"string","title":"Txn Date"},"amount":{"type":"number","title":"Amount"},"payee":{"type":"string","maxLength":120,"title":"Payee","default":""},"note":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Note"},"category_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Category Id"},"is_transfer":{"type":"boolean","title":"Is Transfer","default":false}},"type":"object","required":["account_id","txn_date","amount"],"title":"TxnCreate"},"TxnPatch":{"properties":{"txn_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Txn Date"},"amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Amount"},"payee":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Payee"},"note":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Note"},"category_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Category Id"},"clear_category":{"type":"boolean","title":"Clear Category","default":false},"is_transfer":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Transfer"},"create_rule":{"type":"boolean","title":"Create Rule","default":false},"rule_pattern":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Rule Pattern"}},"type":"object","title":"TxnPatch"},"UpdateProfileRequest":{"properties":{"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"}},"type":"object","title":"UpdateProfileRequest"},"UpdateRoleRequest":{"properties":{"is_admin":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Admin"},"is_friends_family":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Friends Family"}},"type":"object","title":"UpdateRoleRequest"},"UpdateThresholdRequest":{"properties":{"alert_threshold":{"type":"number","title":"Alert Threshold"}},"type":"object","required":["alert_threshold"],"title":"UpdateThresholdRequest"},"UsageEventRequest":{"properties":{"event_name":{"type":"string","maxLength":128,"minLength":1,"title":"Event Name"},"path":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"title":"Path"},"session_id":{"anyOf":[{"type":"string","maxLength":128},{"type":"null"}],"title":"Session Id"},"properties":{"additionalProperties":true,"type":"object","title":"Properties"}},"type":"object","required":["event_name"],"title":"UsageEventRequest"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"ValidationGateBlock":{"properties":{"dsr":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dsr"},"pbo":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pbo"},"pbo_applicable":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Pbo Applicable"},"pbo_n_candidates":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pbo N Candidates"},"passes_gates":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Passes Gates"},"validation_status":{"type":"string","title":"Validation Status","default":"not_evaluated"}},"type":"object","title":"ValidationGateBlock","description":"D3 / VALGATE hard gates (item 37, .planning/PRICE-PREDICTION-TIMING-PLAN-2026-09-03.md).\n\nDeflated Sharpe Ratio + Probability of Backtest Overfitting (Bailey &\nLópez de Prado 2014 — computed once in\napp/services/factor_ic_significance.py and never re-derived here) applied to\nthe persisted strategy result. `dsr`/`pbo`/`passes_gates` are all None and\n`validation_status` stays 'not_evaluated' until the annualized Sharpe itself\nis populated (fewer than MIN_REBALANCES_FOR_ANNUALIZED periods — nothing to\ndeflate yet). `passes_gates` is true only when dsr >= 0.95 AND, where PBO is\napplicable, pbo < 0.35; a False or None value here means this result must\nNOT be read as validated, regardless of how the headline metrics look.\n\n`pbo_applicable` (2026-10-08): PBO measures selection among >= 2 candidate\nconfigurations. This strategy is one fixed configuration, so `pbo` is null\nand `pbo_applicable` is false: no overfitting-of-selection test was run,\nand the gate rests on the DSR alone. It is never published as 0.0.\n`pbo_n_candidates` is the number of configurations compared.\n\n`dsr` IS A PROBABILITY IN [0, 1] AS OF 2026-10-07, NOT A RATIO. It estimates\nP(true Sharpe > the Sharpe the best of n_trials null configurations would be\nexpected to show by luck), so 0.95 is a one-sided 95% confidence level. The\nfield previously carried a ratio-valued statistic whose documented 1.5\nthreshold was unreachable at any trial count of 4 or more (DSR-01); it was\nnever non-null on this surface, because the live result has had fewer than\nMIN_REBALANCES_FOR_ANNUALIZED rebalances throughout, so no published value\nchanged. Both fields stay None rather than 0.0 so \"not measurable\" can never\nread as \"measured\"."},"ValuationEnvelope":{"properties":{"snapshot":{"$ref":"#/components/schemas/ValuationSnapshot"},"projection":{"anyOf":[{"$ref":"#/components/schemas/ProjectionEnvelope"},{"type":"null"}]},"schema_version":{"type":"string","title":"Schema Version","default":"2.0"}},"type":"object","required":["snapshot"],"title":"ValuationEnvelope","description":"Combined snapshot + projection response for GET /api/valuation/{symbol}.\nschema_version field lets clients detect the new split format."},"ValuationProjection":{"properties":{"ticker":{"type":"string","title":"Ticker"},"scenario":{"type":"string","title":"Scenario"},"horizon_years":{"type":"integer","title":"Horizon Years"},"projected_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Projected Fair Value"},"assumed_growth_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Assumed Growth Rate"},"model_fair_value_differential_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Model Fair Value Differential Pct"},"assumptions":{"additionalProperties":true,"type":"object","title":"Assumptions"},"assumptions_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Assumptions Note"},"disclaimer":{"type":"string","title":"Disclaimer","default":""}},"type":"object","required":["ticker","scenario","horizon_years"],"title":"ValuationProjection","description":"Single forward scenario (bear / base / bull) for a given model horizon.\n\nProduced by projection_service.compute_projected_fair_value_range() and\nconverted via projection_from_scenario(). One row per scenario.\n\nNot a projected fair value range under model scenarios or investment advice.\nFor informational and educational purposes only."},"ValuationResult":{"properties":{"symbol":{"type":"string","title":"Symbol"},"price":{"type":"number","title":"Price"},"sector":{"type":"string","title":"Sector"},"best_fit":{"anyOf":[{"$ref":"#/components/schemas/BestFit"},{"type":"null"}]},"dcf_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dcf Fair Value"},"dcf_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dcf Margin Of Safety"},"dcf_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dcf Unavailable Reason"},"pe_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pe Fair Value"},"pe_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pe Margin Of Safety"},"pe_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pe Unavailable Reason"},"pb_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pb Fair Value"},"pb_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pb Margin Of Safety"},"pb_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pb Unavailable Reason"},"ps_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ps Fair Value"},"ps_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ps Margin Of Safety"},"ps_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ps Unavailable Reason"},"ev_ebitda_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ev Ebitda Fair Value"},"ev_ebitda_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ev Ebitda Margin Of Safety"},"ev_ebitda_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ev Ebitda Unavailable Reason"},"graham_number":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Graham Number"},"graham_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Graham Margin Of Safety"},"graham_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Graham Unavailable Reason"},"ddm_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ddm Fair Value"},"ddm_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ddm Margin Of Safety"},"ddm_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ddm Unavailable Reason"},"peg_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Peg Fair Value"},"peg_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Peg Margin Of Safety"},"peg_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Peg Unavailable Reason"},"consensus_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Consensus Fair Value"},"consensus_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Consensus Margin Of Safety"},"methods_used":{"type":"integer","title":"Methods Used"},"best_method":{"type":"string","title":"Best Method"},"last_updated":{"type":"string","title":"Last Updated"},"data_quality":{"type":"string","title":"Data Quality","description":"'full' | 'partial' | 'limited' — indicates data completeness","default":"full"},"yfinance_fallback":{"type":"boolean","title":"Yfinance Fallback","description":"True when financials were sourced from yfinance fallback","default":false},"derived_fields":{"items":{},"type":"array","title":"Derived Fields","description":"Fields estimated via mathematical derivation"},"calculated_growth_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Calculated Growth Rate","description":"Forward earnings growth rate (decimal). DTL/causal model when sector driver available; DuPont or historical blend fallback."},"growth_rate_model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Growth Rate Model","description":"Which growth model tier was used, e.g. 'DTL/Gold spot', 'DTL/revenue', 'DuPont', 'historical_blend'"},"eps_beat_probability":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Eps Beat Probability","description":"Fraction of last 8 quarters beating consensus EPS estimate (0.0–1.0). None when fewer than 4 quarters of data available."},"accruals_ratio":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Accruals Ratio","description":"Sloan accruals ratio: (NetIncome - OCF) / TotalAssets. >0.10 suggests earnings inflation risk; <-0.10 is conservative."},"piotroski_f_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Piotroski F Score","description":"Piotroski F-Score 0-9. >=7 = strong fundamentals; <=3 = weak."},"beneish_m_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Beneish M Score","description":"Beneish M-Score. >-1.78 = probable manipulation risk. NULL for banks, insurance, REITs, utilities (model not applicable)."},"beneish_flag":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Beneish Flag","description":"True when beneish_m_score > -1.78. NULL for suppressed sectors."},"roic_trend":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Roic Trend","description":"ROIC trajectory: 'improving' | 'flat' | 'declining'."},"margin_trend":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Margin Trend","description":"Operating margin trajectory: 'expanding' | 'stable' | 'compressing'."},"momentum_12m_1m":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Momentum 12M 1M","description":"12m-1m price momentum factor: (price_1m_ago / price_12m_ago) - 1. Positive = upward trend; excludes last month per Jegadeesh-Titman."},"implied_earnings_move":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Implied Earnings Move","description":"ATM straddle / current_price for nearest post-earnings expiry. Market-implied expected move magnitude (e.g. 0.08 = 8%). Only populated when a future earnings date is within 90 days."},"pmi_multiplier_applied":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Pmi Multiplier Applied","description":"True when the ISM Manufacturing PMI multiplier adjusted the Tier 1 or Tier 2 earnings growth estimate for this sector. Indicated by '+PMI' suffix in growth_rate_model label."},"forward_eps":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Forward Eps","description":"Forward EPS (analyst consensus when available; otherwise TTM EPS x (1 + blended growth))"},"forward_eps_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Forward Eps Source","description":"'analyst' when forward EPS is sourced from consensus estimates; 'analyst_fy2' when the second forward fiscal year replaced a trough FY1 (see forward_eps_note); 'modelled' when synthetic"},"forward_eps_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Forward Eps Note","description":"Input-selection code for the forward P/E method when it ran on something other than the FY1 consensus (e.g. 'fy1_trough_fy2_fallback'); None otherwise"},"forward_pe_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Forward Pe Fair Value","description":"Forward EPS x Sector P/E multiple"},"forward_pe_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Forward Pe Margin Of Safety","description":"MoS% using forward P/E fair value"},"forward_pe_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Forward Pe Unavailable Reason","description":"Abstention code when forward P/E produced no fair value"},"implied_growth_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Implied Growth Rate","description":"Reverse DCF implied growth rate (decimal)"},"trailing_eps":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Trailing Eps","description":"TTM EPS used by trailing P/E fair value"},"trailing_pe_ratio":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Trailing Pe Ratio","description":"Trailing P/E ratio (price / TTM EPS)"},"forward_pe_ratio":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Forward Pe Ratio","description":"Forward P/E ratio from AV OVERVIEW"},"analyst_consensus_target":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Analyst Consensus Target","description":"Street consensus price target (AV AnalystTargetPrice)"},"eps_estimate_current_year":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Eps Estimate Current Year","description":"Consensus EPS estimate for current fiscal year"},"eps_estimate_next_year":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Eps Estimate Next Year","description":"Consensus EPS estimate for next fiscal year"},"revenue_estimate_current_year":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Revenue Estimate Current Year","description":"Consensus revenue estimate for current fiscal year"},"revenue_estimate_next_year":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Revenue Estimate Next Year","description":"Consensus revenue estimate for next fiscal year"},"forward_ps_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Forward Ps Fair Value","description":"Forward revenue per share x sector P/S multiple"},"forward_ps_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Forward Ps Margin Of Safety","description":"MoS% using forward P/S fair value"},"forward_ps_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Forward Ps Unavailable Reason"},"forward_ev_ebitda_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Forward Ev Ebitda Fair Value","description":"Forward EBITDA per share x sector EV/EBITDA multiple"},"forward_ev_ebitda_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Forward Ev Ebitda Margin Of Safety","description":"MoS% using forward EV/EBITDA fair value"},"forward_ev_ebitda_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Forward Ev Ebitda Unavailable Reason"},"forward_pfcf_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Forward Pfcf Fair Value","description":"Forward FCF per share x sector P/FCF multiple"},"forward_pfcf_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Forward Pfcf Margin Of Safety","description":"MoS% using forward P/FCF fair value"},"forward_pfcf_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Forward Pfcf Unavailable Reason"},"projected_best_fit_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Projected Best Fit Fair Value","description":"NTM fair value using the forward version of the best-fit method"},"projected_best_fit_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Projected Best Fit Margin Of Safety","description":"MoS% at projected best-fit fair value"},"projected_best_fit_method":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Projected Best Fit Method","description":"Forward method used, e.g. 'forward_ps', 'forward_pe', 'forward_ev_ebitda'"},"pfcf_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pfcf Fair Value"},"pfcf_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pfcf Margin Of Safety"},"pfcf_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pfcf Unavailable Reason"},"evfcf_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Evfcf Fair Value"},"evfcf_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Evfcf Margin Of Safety"},"evfcf_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Evfcf Unavailable Reason"},"justified_pb_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Justified Pb Fair Value"},"justified_pb_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Justified Pb Margin Of Safety"},"justified_pb_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Justified Pb Unavailable Reason"},"epv_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Epv Fair Value"},"epv_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Epv Margin Of Safety"},"epv_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Epv Unavailable Reason"},"ev_ebit_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ev Ebit Fair Value"},"ev_ebit_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ev Ebit Margin Of Safety"},"ev_ebit_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ev Ebit Unavailable Reason"},"three_stage_dcf_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Three Stage Dcf Fair Value"},"three_stage_dcf_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Three Stage Dcf Margin Of Safety"},"three_stage_dcf_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Three Stage Dcf Unavailable Reason"},"ffo_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ffo Fair Value","description":"FFO-based fair value (REIT-specific)"},"ffo_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ffo Margin Of Safety"},"ffo_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ffo Unavailable Reason"},"ffo_per_share":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ffo Per Share","description":"FFO per share (Net Income + D&A, per share)"},"pffo_multiple_used":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pffo Multiple Used","description":"P/FFO multiple applied"},"paffo_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Paffo Fair Value","description":"P/AFFO fair value (estimated AFFO = FFO - 0.20*capex)"},"paffo_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Paffo Margin Of Safety"},"paffo_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Paffo Unavailable Reason"},"affo_payout_coverage":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Affo Payout Coverage","description":"AFFO_ps / dividend_ps ratio; None if dividend_ps unavailable"},"affo_payout_coverage_flag":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Affo Payout Coverage Flag","description":"True when coverage < 1.0 (distributions exceed estimated AFFO)"},"shareholder_yield_total":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Shareholder Yield Total","description":"Boudoukh et al. (2007): (div + buybacks - net_debt_issuance) / market_cap; decimal"},"capital_allocation_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Capital Allocation Score","description":"Capital allocation quality score 0-100 (50% spread / 30% SGR / 20% goodwill trend); None if all inputs missing"},"roic":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Roic","description":"NOPAT / invested_capital (decimal); None if invested_capital <= 0"},"economic_spread":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Economic Spread","description":"ROIC - cost_of_equity (CAPM rf+beta*ERP; ERP dynamic via app_config, default 0.055); positive = returns exceed cost of capital"},"goodwill_heavy_acquisition_flag":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Goodwill Heavy Acquisition Flag","description":"True when goodwill/assets > 0.30 AND economic_spread < 0 (factual capital allocation signal)"},"ohlson_o_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ohlson O Score","description":"Ohlson (1980) logit O-Score; None for Financials/Banks/Insurance/REIT"},"default_probability":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Default Probability","description":"P(default within 2yr) = 1/(1+exp(-O)); range [0,1]"},"cash_conversion_ratio":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cash Conversion Ratio","description":"OCF / Net_Income; None when NI <= 0; < 0.7 = weak cash backing"},"revenue_quality_ratio":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Revenue Quality Ratio","description":"DeltaAR / DeltaRevenue; None when flat revenue; > 1.2 = receivables growing faster than revenue"},"dio_trend":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dio Trend","description":"DIO_t - DIO_t-1 (days); None when COGS <= 0; positive = inventory building"},"ev_sales_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ev Sales Fair Value","description":"EV/Sales-based fair value"},"ev_sales_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ev Sales Margin Of Safety"},"ev_sales_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ev Sales Unavailable Reason"},"ev_sales_multiple_used":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ev Sales Multiple Used","description":"EV/Sales multiple applied"},"high_multiple_model":{"type":"boolean","title":"High Multiple Model","description":"True when valuation relies primarily on revenue-multiple methods","default":false},"rnpv_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rnpv Fair Value","description":"rNPV per-share equity model estimate (base case): pipeline value + net cash, per share. Model estimate under stated assumptions — not advice or a return promise; clinical-stage drug outcomes are binary and uncertain, and a program can fail with its modeled value falling toward zero."},"rnpv_fair_value_low":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rnpv Fair Value Low","description":"rNPV per-share estimate under the low peak-sales band (0.4x prior)"},"rnpv_fair_value_high":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rnpv Fair Value High","description":"rNPV per-share estimate under the high peak-sales band (2.5x prior)"},"rnpv_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rnpv Margin Of Safety"},"rnpv_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rnpv Unavailable Reason"},"rnpv_program_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rnpv Program Count","description":"Count of unique clinical programs valued in the rNPV"},"rnpv_lead_phase":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rnpv Lead Phase","description":"Canonical phase of the most-advanced program (PHASE1/PHASE2/PHASE3/FILED/PHASE4)"},"rnpv_pipeline_value_per_share":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rnpv Pipeline Value Per Share","description":"Pipeline-only rNPV per share (before net cash)"},"rnpv_net_cash_per_share":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rnpv Net Cash Per Share","description":"Net cash (cash − total debt) per share added to pipeline value"},"rnpv_in_consensus":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Rnpv In Consensus","description":"True when rNPV carried non-zero weight in the consensus blend (shadow gate live)"},"nav_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Nav Fair Value","description":"NAV proxy fair value for commodity producers — normalized FCF / depletion-adjusted discount rate"},"nav_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Nav Margin Of Safety"},"nav_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Nav Unavailable Reason"},"pe_midcycle_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pe Midcycle Fair Value","description":"P/E fair value using normalized mid-cycle EPS (commodity price ratio adjusted)"},"pe_midcycle_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pe Midcycle Margin Of Safety"},"pe_midcycle_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pe Midcycle Unavailable Reason"},"commodity_scenarios":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Commodity Scenarios","description":"Bear/base/bull fair value scenarios for commodity producers using forecast service price scenarios"},"commodity_price_vs_ma252":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Commodity Price Vs Ma252","description":"Current commodity spot price vs 1-year moving average (>1.0 = above mid-cycle)"},"ri_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ri Fair Value","description":"Residual Income fair value (Ohlson 1995 intrinsic value model)"},"ri_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ri Margin Of Safety"},"ri_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ri Unavailable Reason"},"ptbv_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ptbv Fair Value","description":"Justified P/TBV fair value: TBVPS × (ROTE − g)/(CoE − g); bank sectors only"},"ptbv_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ptbv Margin Of Safety"},"ptbv_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ptbv Unavailable Reason"},"ev_gp_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ev Gp Fair Value","description":"EV/Gross-Profit fair value: GP/share × (sector EV/Sales multiple ÷ 0.70) − net debt/share; software sectors only"},"ev_gp_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ev Gp Margin Of Safety"},"ev_gp_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ev Gp Unavailable Reason"},"eva_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Eva Fair Value","description":"EVA firm-level fair value (NOPAT - WACC × IC, Stern-Stewart); audit companion only"},"eva_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Eva Margin Of Safety"},"eva_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Eva Unavailable Reason"},"cape_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cape Fair Value","description":"Shiller CAPE fair value: real_EPS_avg (10yr CPI-adjusted) × sector long-run CAPE multiple"},"cape_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Cape Margin Of Safety"},"cape_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cape Unavailable Reason"},"ev_ebitdax_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ev Ebitdax Fair Value","description":"EV/EBITDA (E&P) fair value: (EBITDA/share + exploration_expense/share) × E&P sector multiple (5.5x) − net_debt/share. Exploration add-back requires future data source (always None from AV as of Phase 70)."},"ev_ebitdax_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ev Ebitdax Margin Of Safety"},"ev_ebitdax_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ev Ebitdax Unavailable Reason"},"ri_intangible_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ri Intangible Fair Value","description":"Residual-Income fair value on intangible-adjusted book equity (Adjusted_BVPS = BVPS + KC/share + OC/share − Goodwill/share); Tech/Software/Semis/Pharma/Biotech/MedDev only; shadow lens at weight 0.0."},"ri_intangible_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ri Intangible Margin Of Safety"},"ri_intangible_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ri Intangible Unavailable Reason"},"pb_intangible_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pb Intangible Fair Value","description":"Price-to-Book fair value on intangible-adjusted book equity × sector-median intangible-adjusted P/B (calibrated from live universe); Tech/Software/Semis/Pharma/Biotech/MedDev only; shadow lens at weight 0.0."},"pb_intangible_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pb Intangible Margin Of Safety"},"pb_intangible_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pb Intangible Unavailable Reason"},"merton_distance_to_default":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Merton Distance To Default","description":"Std deviations from default; >3=safe, 1-3=watch, <1=distress"},"merton_default_probability":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Merton Default Probability","description":"Risk-neutral probability of default over 1 year"},"real_options_flag":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Real Options Flag","description":"'optionality_premium' when price >> DCF and growth > 20%"},"options_fwd_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Options Fwd Fair Value"},"options_fwd_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Options Fwd Margin Of Safety"},"options_fwd_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Options Fwd Unavailable Reason"},"sector_bubble_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Sector Bubble Score","description":"0–100 bubble score for the sector (0=cheap, 100=extreme bubble)"},"sector_bubble_signal":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sector Bubble Signal","description":"e.g. 'elevated', 'bubble_risk', 'fairly_valued'"},"sector_bubble_premium_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sector Bubble Premium Pct","description":"How far current sector P/E is above/below 20Y historical mean (%)"},"save_composite":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Save Composite","description":"Latest SAVE composite score [-1, +1]"},"save_multiplier_weight":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Save Multiplier Weight","description":"Sector-calibrated SAVE multiplier (0.10–0.25)"},"save_adjusted_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Save Adjusted Fair Value","description":"Consensus fair value adjusted by SAVE multiplier"},"save_adjusted_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Save Adjusted Margin Of Safety","description":"Margin of safety at SAVE-adjusted fair value"},"innovation_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Innovation Score","description":"Innovation/implementation score (0–100); 50 = sector average"},"innovation_multiplier_alpha":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Innovation Multiplier Alpha","description":"Sector-calibrated innovation alpha (sensitivity of FV to score)"},"innovation_adjusted_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Innovation Adjusted Fair Value","description":"Consensus fair value adjusted by innovation multiplier"},"innovation_adjusted_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Innovation Adjusted Margin Of Safety","description":"Margin of safety at innovation-adjusted fair value"},"combined_adjusted_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Combined Adjusted Fair Value","description":"Consensus FV adjusted by both SAVE composite and innovation score"},"combined_adjusted_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Combined Adjusted Margin Of Safety","description":"Margin of safety at combined SAVE+Innovation fair value"},"excluded_from_consensus":{"items":{},"type":"array","title":"Excluded From Consensus","description":"Method keys with 0 sector weight — computed but not blended"},"company_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Type","description":"Classified company type (e.g. 'profitable_saas', 'financial_bank', 'deep_value')"},"profile_confidence":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Profile Confidence","description":"Classification confidence: 'high' | 'medium' | 'low'"},"classification_notes":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Classification Notes","description":"Edge-case flags applied during classification (e.g. 'extreme_leverage', 'dividend_sustainability_risk')"},"stage_gated_methods":{"items":{},"type":"array","title":"Stage Gated Methods","description":"Method keys disabled by stage-based gating for this company_type"},"method_weights_used":{"anyOf":[{"additionalProperties":{"type":"number"},"type":"object"},{"type":"null"}],"title":"Method Weights Used","description":"Normalized per-method weights actually used in this consensus blend (post overrides/outlier-trim/renormalization)"},"consensus_overrides_applied":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Consensus Overrides Applied","description":"Factual trail of weight/FV adjustments that fired for this computation (e.g. high_pe_override, ttm_contamination_guard, sector_cape_haircut, outlier_trim, coverage_shrink, dispersion_shrink)"},"valuation_assumptions":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Valuation Assumptions","description":"Model parameters behind this valuation: risk-free rate, beta (raw + cap), equity risk premium, cost of equity, terminal growth, sector multiples, growth rates, illiquidity premium, micro-cap discount"},"method_sensitivity":{"anyOf":[{"items":{"additionalProperties":true,"type":"object"},"type":"array"},{"type":"null"}],"title":"Method Sensitivity","description":"Ceteris-paribus per-method fair-value sensitivity to ±1pp input changes (DCF terminal growth / discount rate; DDM discount rate). Educational what-if, not a projection."},"cluster_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Cluster Count","description":"Number of valuation clusters with at least 1 method contributing"},"clusters_used":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Clusters Used","description":"Cluster names that contributed to the consensus blend"},"intra_cluster_method_counts":{"anyOf":[{"additionalProperties":{"type":"integer"},"type":"object"},{"type":"null"}],"title":"Intra Cluster Method Counts","description":"Mapping of cluster name to number of methods in that cluster"},"low_cluster_count_flag":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Low Cluster Count Flag","description":"True when only 2 clusters contributed (reduced diversification)"},"model_confidence_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Model Confidence Score","description":"0–100: reliability of the fair value estimate"},"model_confidence_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Confidence Label","description":"'Strong' | 'Moderate' | 'Limited' | 'Weak'"},"model_confidence_breakdown":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Model Confidence Breakdown","description":"Pillar sub-scores: convergence, coverage, data_quality, best_fit_certainty"},"model_confidence_diagnostics":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Model Confidence Diagnostics","description":"Plain-English reasons why confidence is limited"},"fair_value_low":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fair Value Low","description":"Lowest fair value across all applicable methods"},"fair_value_high":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fair Value High","description":"Highest fair value across all applicable methods"},"fair_value_range_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fair Value Range Pct","description":"Range spread as % of low FV — measures method divergence"},"required_mos":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Required Mos","description":"Minimum MoS% this stock should clear given its risk profile"},"mos_excess":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mos Excess","description":"mos_mid - required_mos; positive = clears hurdle"},"page_blend_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Page Blend Fair Value","description":"This page's own multi-method blend; the headline consensus uses the screener's value when its row is fresh (PARITY-01)"},"consensus_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Consensus Source","description":"'screener' when the headline consensus is the screener batch value, 'page' when it is this page's own blend, 'screener_withheld' when the fresh screener row publishes no fair value so the page publishes none either"},"fv_low_band":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fv Low Band","description":"Weighted 25th percentile fair value across methods"},"fv_high_band":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fv High Band","description":"Weighted 75th percentile fair value across methods"},"mos_low":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mos Low","description":"MoS% at fv_low_band (conservative)"},"mos_mid":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mos Mid","description":"MoS% at consensus FV after dispersion shrinkage"},"mos_high":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mos High","description":"MoS% at fv_high_band (optimistic)"},"mos_mid_raw":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mos Mid Raw","description":"MoS% before dispersion shrinkage (unshrunk)"},"dispersion_cv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dispersion Cv","description":"Coefficient of variation across method FVs"},"dispersion_factor":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Dispersion Factor","description":"Shrinkage factor applied to MoS (0..0.6)"},"trimmed_methods":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Trimmed Methods","description":"Method keys dropped by the robust outlier trim (median-anchored MAD filter)"},"coverage_shrink_factor":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Coverage Shrink Factor","description":"Factor applied to shrink fair value toward price when fewer than 5 methods carry weight in the blend (1.0 = no shrink)"},"forward_consensus_fv":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Forward Consensus Fv","description":"Forward (NTM) consensus fair value blend"},"forward_consensus_mos":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Forward Consensus Mos","description":"MoS% using forward consensus FV"},"forward_consensus_methods":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Forward Consensus Methods","description":"Methods used in forward consensus"},"quality_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quality Label","description":"'high' | 'medium' | 'low' | 'distressed' | 'unknown'"},"mos_quality_bucket":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mos Quality Bucket","description":"'cheap_quality' | 'value_trap_risk' | 'premium_quality' | 'fair_value'"},"mos_regime":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mos Regime","description":"'persistent_cheap' | 'freshly_cheap' | 'persistent_premium' | 'freshly_premium' | 'transitioning'"},"weeks_at_current_band":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Weeks At Current Band","description":"Consecutive weeks the MoS sign has been the same"},"mos_4w_avg":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mos 4W Avg","description":"Average consensus MoS over the last 4 weekly snapshots"},"mos_drift_30d":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mos Drift 30D","description":"mos_now - mos_30d_ago; positive = model increasingly undervalued"},"convergence_low_weeks":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Convergence Low Weeks","description":"Optimistic re-rating horizon in weeks (OU p25)"},"convergence_mid_weeks":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Convergence Mid Weeks","description":"Base-case re-rating horizon in weeks (OU halflife × catalyst)"},"convergence_high_weeks":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Convergence High Weeks","description":"Pessimistic re-rating horizon in weeks (OU p75)"},"convergence_probability_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Convergence Probability Label","description":"Human-readable horizon: 'Near-term' | 'Medium-term' | 'Patient' | 'Long-dated'"},"catalyst_acceleration":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Catalyst Acceleration","description":"Multiplier on OU halflife from earnings/SAVE/momentum. <1 = faster, >1 = slower"},"ou_halflife_weeks":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ou Halflife Weeks","description":"Raw OU mean-reversion halflife in weeks before catalyst adjustment"},"ou_r2":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Ou R2","description":"AR(1) goodness-of-fit for OU regression (0–1)"},"ou_confidence":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ou Confidence","description":"'high' | 'medium' | 'low' | 'insufficient' based on history length and R²"},"company_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Name","description":"Full company name"},"company_description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Description","description":"Business description narrative from AV OVERVIEW"},"company_website":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Website","description":"Official company website"},"company_employees":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Company Employees","description":"Full-time employees"},"company_country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Country","description":"HQ country"},"company_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Address","description":"HQ street address"},"company_fiscal_year_end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Fiscal Year End","description":"Fiscal year-end month"},"company_exchange":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Exchange","description":"Listing exchange"},"company_industry":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Industry","description":"Sub-industry"},"earnings_quality_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Earnings Quality Score","description":"Forensic risk score 0-100 (Earnings Quality badge)"},"earnings_quality_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Earnings Quality Label","description":"Earnings Quality label: Excellent | Adequate | Caution | At-Risk"},"macro_regime":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Macro Regime","description":"Macro regime: RISK_ON | LATE_CYCLE | STAGFLATION | RECESSION"},"consensus_fair_value_macro_adjusted":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Consensus Fair Value Macro Adjusted","description":"Macro-adjusted consensus FV"},"macro_haircut":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Macro Haircut","description":"Fractional haircut applied by macro regime overlay"},"mgmt_quality_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mgmt Quality Score","description":"Management Quality Score 0-100 (4 governance signals + drop-renorm + 25 baseline). None when all signals absent."},"mgmt_quality_grade":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mgmt Quality Grade","description":"Management Quality neutral-tier label — pending financial-legal-advisor sign-off. None when score None."},"consensus_fair_value_mgmt_adjusted":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Consensus Fair Value Mgmt Adjusted","description":"consensus_fair_value × (1+mgmt_haircut[±2.5%]). DISTINCT column off RAW consensus — not stacked on macro_adjusted."},"illiquidity_premium_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Illiquidity Premium Pct","description":"Amihud (2002) illiquidity model parameter — cost-of-equity add-on in decimal (e.g. 0.02 = 2pp). Factual computation input, not investment advice. None when history < 253 daily rows (252 daily returns) or liquidity is unknown."},"micro_cap_discount_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Micro Cap Discount Pct","description":"Market-cap tier discount: 0.25 (<$100M) / 0.15 (<$300M) / 0.05 (<$500M) / 0.0 (>=500M). Applied post-consensus as distinct multiplier off RAW consensus_fair_value. Factual model parameter, not investment advice."},"consensus_fair_value_liquidity_adjusted":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Consensus Fair Value Liquidity Adjusted","description":"Model estimate under [liquidity] assumptions. Distinct column off RAW consensus_fair_value — not stacked on macro_adjusted or mgmt_adjusted."},"inventory_turns_ratio":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Inventory Turns Ratio","description":"Inventory turns = COGS / average inventory (retail cohort only). Factual operational efficiency metric, not investment advice. None for non-cohort names or when inputs are missing / non-positive."},"brand_equity_premium_pp":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Brand Equity Premium Pp","description":"Company gross margin minus peer-subgroup median GM, in percentage points (retail cohort). Factual margin comparison, not investment advice. None below the 4-member subgroup quorum or for non-cohort names."},"sssg_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sssg Pct","description":"Most recent reported same-store / comparable-store sales growth (decimal), from a curated cited seed. Reported historical metric, not a forecast. None for unseeded / stale / e-commerce / non-cohort names."},"consensus_fair_value_retail_adjusted":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Consensus Fair Value Retail Adjusted","description":"Model estimate under [retail] assumptions. Distinct column off RAW consensus_fair_value — not stacked on macro_adjusted, mgmt_adjusted, or liquidity_adjusted. Equals RAW consensus when no retail signal is available."},"semis_ev_ebitda_midcycle_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Semis Ev Ebitda Midcycle Fair Value","description":"EV/EBITDA mid-cycle fair value: EBITDA/share normalized by GM ratio (current/5yr-avg) × sub-sector multiple minus net_debt/share. Abstains if avg GM unavailable or |GM deviation| <= 5pp."},"semis_ev_ebitda_midcycle_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Semis Ev Ebitda Midcycle Margin Of Safety"},"semis_ev_ebitda_midcycle_unavailable_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Semis Ev Ebitda Midcycle Unavailable Reason"},"cycle_position":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cycle Position","description":"Semiconductor cycle-position indicator: 'early' | 'mid' | 'late' | 'trough'. Derived from DOH trend and GM deviation. Factual industry term only. Does NOT read book_to_bill."},"inventory_doh":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Inventory Doh","description":"Days of Inventory On Hand = inventory / (COGS/365). Factual balance-sheet metric from most-recent annual period."},"rd_intensity":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rd Intensity","description":"R&D as percentage of revenue (R&D expense / total revenue × 100). Factual metric derived from income statement."},"book_to_bill":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Book To Bill","description":"Book-to-bill ratio. ALWAYS None in Phase 73 — SIA association data not wired (abstain). T-73-03 integrity gate."},"book_to_bill_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Book To Bill Reason","description":"Reason book_to_bill is None. Value: 'semi_association_data_not_wired'."},"backlog_to_revenue_ratio":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Backlog To Revenue Ratio","description":"Backlog / TTM revenue ratio. >3.0 = very high; <0.8 = flag. Factual. Abstains when unseeded."},"contract_mix_risk_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Contract Mix Risk Score","description":"Contract mix risk score 0-100 (fixed-price-heavy = higher). Only for directly-disclosed tickers (LMT/RTX/NOC/BA). Factual — not investment advice."},"aftermarket_revenue_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Aftermarket Revenue Pct","description":"Aftermarket / services revenue as decimal fraction of total revenue. From seed (public filings)."},"backlog_adjusted_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Backlog Adjusted Fair Value","description":"Model estimate under [backlog_as_of_date] backlog assumptions. DISTINCT field off RAW consensus — clamped +/-5%. Not a revenue forecast."},"backlog_as_of_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Backlog As Of Date","description":"As-of date of the backlog figures used (e.g. 'FY2024 10-K'). Always displayed alongside backlog_adjusted_fair_value."},"fair_value_ci_low":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fair Value Ci Low","description":"Lower bound of the 80% model confidence interval on consensus fair value"},"fair_value_ci_high":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fair Value Ci High","description":"Upper bound of the 80% model confidence interval on consensus fair value"},"sue_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Sue Score","description":"BST-01: SUE z-score (Bernard & Thomas 1989); winsorized to [-10, 10]; sourced from screener batch"},"eps_revision_ratio":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Eps Revision Ratio","description":"BST-02: EPS estimate revision ratio — upward revisions / total revisions (past 30 days); range [0.0, 1.0]"},"short_ratio":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Short Ratio","description":"Days-to-cover short ratio (factual count); sourced from screener batch"},"short_percent_of_float":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Short Percent Of Float","description":"Short interest as decimal fraction of float; sourced from screener batch"},"pead_signal":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pead Signal","description":"BST-01: PEAD proxy — most recent EPS surprise bounded to [-1.0, 1.0]"},"eps_surprise_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Eps Surprise Rate","description":"Mean EPS surprise % across last 8 quarters (decimal fraction)"}},"type":"object","required":["symbol","price","sector","consensus_fair_value","consensus_margin_of_safety","methods_used","best_method"],"title":"ValuationResult"},"ValuationSnapshot":{"properties":{"symbol":{"type":"string","title":"Symbol"},"price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Price"},"sector":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sector"},"company_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Type"},"consensus_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Consensus Fair Value"},"consensus_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Consensus Margin Of Safety"},"methods_used":{"items":{"type":"string"},"type":"array","title":"Methods Used"},"best_method":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Best Method"},"best_fit":{"anyOf":[{"$ref":"#/components/schemas/BestFit"},{"type":"null"}]},"model_confidence_score":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Model Confidence Score"},"model_confidence_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Confidence Label"},"fair_value_low":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fair Value Low"},"fair_value_high":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fair Value High"},"combined_adjusted_fair_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Combined Adjusted Fair Value"},"combined_adjusted_margin_of_safety":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Combined Adjusted Margin Of Safety"},"fair_value_interval":{"anyOf":[{"$ref":"#/components/schemas/FairValueInterval"},{"type":"null"}]},"data_quality":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Data Quality","default":"full"},"data_as_of":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Data As Of"},"last_updated":{"type":"string","title":"Last Updated"}},"type":"object","required":["symbol"],"title":"ValuationSnapshot","description":"Point-in-time valuation — TTM/most-recent fundamentals only.\n\nDerived from ValuationResult via snapshot_from_valuation_result().\nDoes NOT include forward-scenario outputs — those belong in ValuationProjection.\nValuationResult is unchanged; this is a curated view of it for new endpoints."},"VerifyPostizPostRequest":{"properties":{"post_search_text":{"type":"string","title":"Post Search Text"},"since":{"type":"string","title":"Since"}},"type":"object","required":["post_search_text","since"],"title":"VerifyPostizPostRequest"},"ViewPingRequest":{"properties":{"utm_source":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Utm Source"},"path":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Path","default":"/investor"}},"type":"object","title":"ViewPingRequest"},"ViewPingResponse":{"properties":{"recorded":{"type":"boolean","title":"Recorded","default":true}},"type":"object","title":"ViewPingResponse"},"ViewSummaryEntry":{"properties":{"referrer":{"type":"string","title":"Referrer"},"views_7d":{"type":"integer","title":"Views 7D"},"views_30d":{"type":"integer","title":"Views 30D"}},"type":"object","required":["referrer","views_7d","views_30d"],"title":"ViewSummaryEntry"},"ViewSummaryResponse":{"properties":{"total_views_7d":{"type":"integer","title":"Total Views 7D"},"total_views_30d":{"type":"integer","title":"Total Views 30D"},"total_views_all":{"type":"integer","title":"Total Views All"},"by_referrer":{"items":{"$ref":"#/components/schemas/ViewSummaryEntry"},"type":"array","title":"By Referrer"}},"type":"object","required":["total_views_7d","total_views_30d","total_views_all","by_referrer"],"title":"ViewSummaryResponse"},"app__routers__backtest__BacktestSummaryResponse":{"properties":{"schema_version":{"type":"string","title":"Schema Version","default":"2.0"},"sufficient_data":{"type":"boolean","title":"Sufficient Data"},"real_data_since":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Real Data Since"},"period_start":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period Start"},"period_end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Period End"},"n_rebalances":{"type":"integer","title":"N Rebalances","default":0},"annualized_metrics_suppressed":{"type":"boolean","title":"Annualized Metrics Suppressed","default":false},"max_drawdown_suppressed":{"type":"boolean","title":"Max Drawdown Suppressed","default":false},"benchmark_degraded":{"type":"boolean","title":"Benchmark Degraded","default":false},"price_basis":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Price Basis"},"coverage":{"$ref":"#/components/schemas/CoverageBlock"},"equity_curve":{"$ref":"#/components/schemas/EquityCurveBlock"},"strategy":{"$ref":"#/components/schemas/BacktestMetricsBlock"},"spy":{"$ref":"#/components/schemas/BenchmarkBlock"},"qqq":{"$ref":"#/components/schemas/BenchmarkBlock"},"universe":{"$ref":"#/components/schemas/BenchmarkBlock"},"validation":{"$ref":"#/components/schemas/ValidationGateBlock"},"compliance":{"$ref":"#/components/schemas/BacktestComplianceBlock"}},"type":"object","required":["sufficient_data"],"title":"BacktestSummaryResponse","description":"Response model for GET /api/backtest/summary."},"app__routers__methodology__BacktestSummaryResponse":{"properties":{"combined_hit_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Combined Hit Rate"},"combined_r2":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Combined R2"},"save_hit_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Save Hit Rate"},"innovation_hit_rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Innovation Hit Rate"},"signal_correlation":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Signal Correlation"},"n_simulations":{"type":"integer","title":"N Simulations","default":0},"data_source":{"type":"string","title":"Data Source","default":"withheld_uninformative_metric"},"real_data_since":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Real Data Since"},"real_n":{"type":"integer","title":"Real N","default":0},"live_factor_ic":{"items":{"$ref":"#/components/schemas/LiveFactorIC"},"type":"array","title":"Live Factor Ic","default":[]},"live_ic_as_of":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Live Ic As Of"},"ic_series":{"items":{"$ref":"#/components/schemas/FactorICSeries"},"type":"array","title":"Ic Series","default":[]},"realized_outcomes":{"items":{"$ref":"#/components/schemas/RealizedOutcomeBucket"},"type":"array","title":"Realized Outcomes","default":[]},"realized_outcomes_total_n":{"type":"integer","title":"Realized Outcomes Total N","default":0},"realized_outcomes_as_of":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Realized Outcomes As Of"}},"type":"object","title":"BacktestSummaryResponse","description":"Response model for GET /api/methodology/backtest-summary.\n\nTopline hit-rate fields are permanently null (see _WITHHELD_DATA_SOURCE\nnote above) — kept in the model so existing consumers deserialize\ncleanly; every consumer must treat null as \"withheld\", not zero."}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"}}}}