Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions api/intelligence.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ def intelligence_status():
"bank_health": True, # never 503s -- returns data: null for a ticker with no FDIC-mapped lead subsidiary
"agriculture": True, # never 503s -- returns data: null for a ticker with no USDA commodity linkage
"real_estate": True, # never 503s -- returns data: null for a ticker with no housing-market linkage
"supply_chain": True, # never 503s -- returns data: null for a ticker with no freight/logistics linkage
"consumer_demand": True, # never 503s -- returns data: null for a ticker with no consumer-spending linkage
"webhooks": True, # management endpoints, never 503 -- Pro-tier gated (403 for free keys), see services/webhook_service.py
})

Expand All @@ -278,6 +280,14 @@ def intelligence_status():
# changes programmatically) and rendered on intelligence-api.html#changelog.
# ---------------------------------------------------------------------------
INTELLIGENCE_CHANGELOG = [
{
"date": "2026-08-31",
"changes": [
{"type": "added", "text": "GET /v1/supply-chain/{ticker} -- FRED inventory/sales ratio, manufacturing new orders, durable goods orders, industrial production, and manufacturing employment for freight/logistics-linked tickers (carriers, railroads, transportation ETFs). Second of 3 cross-industry expansion candidates."},
{"type": "added", "text": "GET /v1/consumer-demand/{ticker} -- FRED retail sales, personal consumption expenditures, and durable goods consumption for consumer-spending-linked tickers (large retailers, e-commerce, consumer-discretionary ETFs). Third of 3 cross-industry expansion candidates -- not Google Trends search-interest data, see that endpoint's docs for why."},
{"type": "fixed", "text": "News search (/v1/events, /v1/sentiment) now resolves bare ticker queries to their real company name before searching headlines, and widens the GDELT lookback window -- fixes zero-results for well-known large-cap tickers whose headlines almost never contain the bare ticker symbol."},
],
},
{
"date": "2026-08-30",
"changes": [
Expand Down Expand Up @@ -1221,6 +1231,60 @@ def intelligence_real_estate(
return _envelope(data=result, meta={"ticker": ticker})


@router.get("/intelligence/v1/supply-chain/{ticker}")
def intelligence_supply_chain(
response: Response,
ticker: str,
x_api_key: str = Header(None, alias="X-API-Key"),
):
"""FRED US manufacturing/supply-chain context for `ticker` (services/
supply_chain_service.py -- inventory/sales ratio, manufacturing new
orders, durable goods orders, industrial production, manufacturing
employment). Only populated for tickers with a real freight/logistics
linkage (carriers, railroads, transportation ETFs -- see that
module's _TICKER_TO_NAME) -- any other ticker returns `data: null`,
never a fabricated reading for an unrelated symbol."""
auth = _require_api_key(x_api_key)
_check_and_spend_quota(x_api_key, auth["tier"], "supply_chain", response, ticker=ticker.upper())

from services.supply_chain_service import get_supply_chain_context_for_ticker

ticker = ticker.upper().strip()
result = get_supply_chain_context_for_ticker(ticker)
if not result:
return _envelope(data=None, error=f"No supply-chain linkage for {ticker}")

return _envelope(data=result, meta={"ticker": ticker})


@router.get("/intelligence/v1/consumer-demand/{ticker}")
def intelligence_consumer_demand(
response: Response,
ticker: str,
x_api_key: str = Header(None, alias="X-API-Key"),
):
"""FRED US consumer-spending context for `ticker` (services/
consumer_demand_service.py -- retail sales, personal consumption
expenditures, durable goods consumption). NOT Google Trends search-
interest data -- see that module's docstring for why (no officially
licensed, commercial-use-safe search-trends API exists). Only
populated for tickers with a real consumer-spending linkage (large
retailers, e-commerce, consumer-discretionary ETFs -- see that
module's _TICKER_TO_NAME) -- any other ticker returns `data: null`,
never a fabricated reading for an unrelated symbol."""
auth = _require_api_key(x_api_key)
_check_and_spend_quota(x_api_key, auth["tier"], "consumer_demand", response, ticker=ticker.upper())

from services.consumer_demand_service import get_consumer_demand_context_for_ticker

ticker = ticker.upper().strip()
result = get_consumer_demand_context_for_ticker(ticker)
if not result:
return _envelope(data=None, error=f"No consumer-spending linkage for {ticker}")

return _envelope(data=result, meta={"ticker": ticker})


@router.get("/intelligence/v1/exchange/{ticker}")
def intelligence_exchange(
response: Response,
Expand Down Expand Up @@ -1435,6 +1499,8 @@ def intelligence_webhooks_unsubscribe(
"/intelligence/v1/bank-health/{ticker}",
"/intelligence/v1/agriculture/{ticker}",
"/intelligence/v1/real-estate/{ticker}",
"/intelligence/v1/supply-chain/{ticker}",
"/intelligence/v1/consumer-demand/{ticker}",
"/intelligence/v1/webhooks/subscribe",
"/intelligence/v1/webhooks",
"/intelligence/v1/webhooks/{webhook_id}",
Expand Down
42 changes: 42 additions & 0 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,48 @@ def _run_real_estate_refresh_job():
)


# 2026-08-31 -- same pre-warm reasoning as _run_real_estate_refresh_job
# above, for services/supply_chain_service.py's 5 series.
def _run_supply_chain_refresh_job():
try:
from services import supply_chain_service
if supply_chain_service.is_available():
for meta in supply_chain_service._SERIES.values():
supply_chain_service._fetch_series(meta["series_id"], n_obs=1)
except Exception:
pass

_push_scheduler.add_job(
_run_supply_chain_refresh_job,
"cron",
hour=3,
minute=20,
id="supply_chain_refresh",
replace_existing=True,
)


# 2026-08-31 -- same pre-warm reasoning as _run_real_estate_refresh_job
# above, for services/consumer_demand_service.py's 4 series.
def _run_consumer_demand_refresh_job():
try:
from services import consumer_demand_service
if consumer_demand_service.is_available():
for meta in consumer_demand_service._SERIES.values():
consumer_demand_service._fetch_series(meta["series_id"], n_obs=1)
except Exception:
pass

_push_scheduler.add_job(
_run_consumer_demand_refresh_job,
"cron",
hour=3,
minute=25,
id="consumer_demand_refresh",
replace_existing=True,
)


def _run_cftc_cot_refresh_job():
try:
from services.cftc_cot_service import get_snapshot
Expand Down
75 changes: 74 additions & 1 deletion intelligence-api.html
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@
<div class="hero">
<span class="badge-pill" data-i18n="ia_badge">EARLY ACCESS</span>
<h1 data-i18n="ia_hero_title">Real market intelligence, structured for developers</h1>
<p data-i18n="ia_hero_p">Seven JSON endpoints — market events, FinBERT sentiment, multi-agent AI debate, structured intelligence feed, technical/market-structure analysis, Monte Carlo stress testing, and regime-aware signals — built on the same real data and anti-fabrication principles behind XFINLAB's own product. Free tier keys are issued instantly and automatically; Pro/Enterprise are still set up personally.</p>
<p data-i18n="ia_hero_p">20+ JSON endpoints — market events, FinBERT sentiment, multi-agent AI debate, company network intelligence, fundamentals, and cross-industry macro context (energy, agriculture, real estate, supply chain, consumer demand) — built on the same real data and anti-fabrication principles behind XFINLAB's own product. Free tier keys are issued instantly and automatically; Pro/Enterprise are still set up personally.</p>
<div class="hero-ctas">
<a href="#quickstart" class="btn-primary" data-i18n="ia_cta_quickstart">Quickstart →</a>
<a href="#access" class="btn-ghost" data-i18n="ia_cta_access">Request Early Access</a>
Expand Down Expand Up @@ -273,6 +273,9 @@ <h1 data-i18n="ia_hero_title">Real market intelligence, structured for developer
<option value="vix-term-structure" data-i18n="ia_ep14_name">VIX Term Structure</option>
<option value="bank-health" data-i18n="ia_ep15_name">Bank Health</option>
<option value="agriculture" data-i18n="ia_ep16_name">Agriculture Prices</option>
<option value="real-estate" data-i18n="ia_ep18_name">Real Estate</option>
<option value="supply-chain" data-i18n="ia_ep19_name">Supply Chain</option>
<option value="consumer-demand" data-i18n="ia_ep20_name">Consumer Demand</option>
</select>
<span class="try-path-preview" id="tryPathPreview">GET /v1/events</span>
</div>
Expand Down Expand Up @@ -710,6 +713,71 @@ <h1 data-i18n="ia_hero_title">Real market intelligence, structured for developer
<p class="schema-note" data-i18n="ia_schema_note_webhooks">A subscription auto-deactivates after 5 consecutive delivery failures (check GET /webhooks for fail_count) -- re-subscribe once your endpoint is back up. Delivery is best-effort and fire-and-forget: a slow/dead receiver never blocks or retries indefinitely.</p>
</details>
</div>
<!-- 2026-08-30/31 cross-industry expansion (AJ: "由1開始順住做"):
real estate, supply chain, consumer demand -- 3 new industries
of ticker-linked FRED context, same soft-fail-to-null posture
as every card above. Documented together since they shipped
in quick succession and share one pattern. -->
<div class="ep-card">
<div class="ep-head">
<div class="ep-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 11l9-8 9 8"/><path d="M5 10v10h14V10"/><path d="M9 20v-6h6v6"/></svg></div>
<span class="ep-weight">2x</span>
</div>
<span class="ep-tag">GET /v1/real-estate/{ticker}</span>
<div class="ep-name" data-i18n="ia_ep18_name">Real Estate</div>
<div class="ep-desc" data-i18n="ia_ep18_desc">FRED US housing-market context -- 30-year fixed mortgage rate, Case-Shiller home price index, housing starts, existing home sales. Only populated for housing-linked tickers (homebuilders, REITs, a mortgage originator, housing-sector ETFs).</div>
<details class="schema-details">
<summary data-i18n="ia_schema_toggle">Response fields</summary>
<div class="schema-fields">
<div class="schema-field"><code>data.matched_ticker</code>, <code>data.matched_name</code>, <code>data.attribution</code> <span class="schema-type">string</span></div>
<div class="schema-field"><code>data.indicators</code> <span class="schema-type">object</span> -- dynamically keyed (mortgage_rate_30y_pct, home_price_index, housing_starts_thousands, existing_home_sales_thousands), each value or null</div>
<div class="schema-field">&nbsp;&nbsp;<code>label</code>, <code>unit</code>, <code>date</code> <span class="schema-type">string</span></div>
<div class="schema-field">&nbsp;&nbsp;<code>value</code> <span class="schema-type">number</span></div>
<div class="schema-field"><code>meta.ticker</code> <span class="schema-type">string</span></div>
</div>
<p class="schema-note" data-i18n="ia_schema_note_real_estate">Returns data:null for any ticker without a real housing-market linkage -- never a fabricated reading for an unrelated symbol. Coverage: DHI, LEN, PHM, NVR, TOL, KBH, MTH, O, SPG, PLD, PSA, AVB, EQR, RKT, VNQ, XHB, ITB today.</p>
</details>
</div>
<div class="ep-card">
<div class="ep-head">
<div class="ep-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="7" width="15" height="10" rx="1"/><path d="M16 10h4l3 3v4h-7z"/><circle cx="5.5" cy="19.5" r="1.5"/><circle cx="18.5" cy="19.5" r="1.5"/></svg></div>
<span class="ep-weight">2x</span>
</div>
<span class="ep-tag">GET /v1/supply-chain/{ticker}</span>
<div class="ep-name" data-i18n="ia_ep19_name">Supply Chain</div>
<div class="ep-desc" data-i18n="ia_ep19_desc">FRED US manufacturing/supply-chain context -- inventory/sales ratio, manufacturing new orders, durable goods orders, industrial production, manufacturing employment. Only populated for freight/logistics-linked tickers (carriers, railroads, transportation ETFs).</div>
<details class="schema-details">
<summary data-i18n="ia_schema_toggle">Response fields</summary>
<div class="schema-fields">
<div class="schema-field"><code>data.matched_ticker</code>, <code>data.matched_name</code>, <code>data.attribution</code> <span class="schema-type">string</span></div>
<div class="schema-field"><code>data.indicators</code> <span class="schema-type">object</span> -- dynamically keyed (inventory_sales_ratio, manufacturing_new_orders_musd, durable_goods_orders_musd, industrial_production_manufacturing_index, manufacturing_employment_thousands), each value or null</div>
<div class="schema-field">&nbsp;&nbsp;<code>label</code>, <code>unit</code>, <code>date</code> <span class="schema-type">string</span></div>
<div class="schema-field">&nbsp;&nbsp;<code>value</code> <span class="schema-type">number</span></div>
<div class="schema-field"><code>meta.ticker</code> <span class="schema-type">string</span></div>
</div>
<p class="schema-note" data-i18n="ia_schema_note_supply_chain">Returns data:null for any ticker without a real freight/logistics linkage -- never a fabricated reading for an unrelated symbol. Coverage: FDX, UPS, XPO, JBHT, CHRW, ODFL, GXO, EXPD, CSX, UNP, NSC, IYT, XTN today.</p>
</details>
</div>
<div class="ep-card">
<div class="ep-head">
<div class="ep-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="21" r="1"/><circle cx="18" cy="21" r="1"/><path d="M2 3h2l2.4 12.4a2 2 0 0 0 2 1.6h8.7a2 2 0 0 0 2-1.6L21 7H6"/></svg></div>
<span class="ep-weight">2x</span>
</div>
<span class="ep-tag">GET /v1/consumer-demand/{ticker}</span>
<div class="ep-name" data-i18n="ia_ep20_name">Consumer Demand</div>
<div class="ep-desc" data-i18n="ia_ep20_desc">FRED US consumer-spending context -- retail sales, personal consumption expenditures, durable goods consumption. Not Google Trends search-interest data -- no officially licensed, commercial-use-safe search-trends API exists; real spending data is the more reliable proxy. Only populated for consumer-spending-linked tickers (large retailers, e-commerce, consumer-discretionary ETFs).</div>
<details class="schema-details">
<summary data-i18n="ia_schema_toggle">Response fields</summary>
<div class="schema-fields">
<div class="schema-field"><code>data.matched_ticker</code>, <code>data.matched_name</code>, <code>data.attribution</code> <span class="schema-type">string</span></div>
<div class="schema-field"><code>data.indicators</code> <span class="schema-type">object</span> -- dynamically keyed (retail_sales_total_musd, retail_sales_goods_only_musd, personal_consumption_expenditures_busd, durable_goods_consumption_busd), each value or null</div>
<div class="schema-field">&nbsp;&nbsp;<code>label</code>, <code>unit</code>, <code>date</code> <span class="schema-type">string</span></div>
<div class="schema-field">&nbsp;&nbsp;<code>value</code> <span class="schema-type">number</span></div>
<div class="schema-field"><code>meta.ticker</code> <span class="schema-type">string</span></div>
</div>
<p class="schema-note" data-i18n="ia_schema_note_consumer_demand">Returns data:null for any ticker without a real consumer-spending linkage -- never a fabricated reading for an unrelated symbol. Coverage: WMT, TGT, COST, HD, LOW, AMZN, BBY, TJX, ROST, XRT, XLY today.</p>
</details>
</div>
</div>
</div>

Expand Down Expand Up @@ -1062,6 +1130,11 @@ <h1 data-i18n="ia_hero_title">Real market intelligence, structured for developer
'vix-term-structure':{ method: 'GET', needsTicker: false, needsAmount: false, path: function() { return '/intelligence/v1/vix-term-structure'; } },
'bank-health': { method: 'GET', needsTicker: true, needsAmount: false, path: function(tk) { return '/intelligence/v1/bank-health/' + encodeURIComponent(tk); } },
'agriculture': { method: 'GET', needsTicker: true, needsAmount: false, path: function(tk) { return '/intelligence/v1/agriculture/' + encodeURIComponent(tk); } },
// 2026-08-30/31 cross-industry expansion -- all three are simple
// GET-by-ticker, same shape as energy/agriculture above.
'real-estate': { method: 'GET', needsTicker: true, needsAmount: false, path: function(tk) { return '/intelligence/v1/real-estate/' + encodeURIComponent(tk); } },
'supply-chain': { method: 'GET', needsTicker: true, needsAmount: false, path: function(tk) { return '/intelligence/v1/supply-chain/' + encodeURIComponent(tk); } },
'consumer-demand': { method: 'GET', needsTicker: true, needsAmount: false, path: function(tk) { return '/intelligence/v1/consumer-demand/' + encodeURIComponent(tk); } },
};

function tryUpdateFields() {
Expand Down
14 changes: 14 additions & 0 deletions sdk/js/xfinlab.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,20 @@
return this._get('/intelligence/v1/agriculture/' + encodeURIComponent(ticker));
};

// 2026-08-30/31: cross-industry expansion -- real estate, supply
// chain, consumer demand. Same shape as agriculture()/energy() above.
XfinlabClient.prototype.realEstate = function (ticker) {
return this._get('/intelligence/v1/real-estate/' + encodeURIComponent(ticker));
};

XfinlabClient.prototype.supplyChain = function (ticker) {
return this._get('/intelligence/v1/supply-chain/' + encodeURIComponent(ticker));
};

XfinlabClient.prototype.consumerDemand = function (ticker) {
return this._get('/intelligence/v1/consumer-demand/' + encodeURIComponent(ticker));
};

// 2026-08-28: Pro-tier webhooks (push instead of polling). See
// services/webhook_service.py's VALID_EVENT_TYPES for the exact
// eventType values ('vix_regime_change' market-wide, 'new_13d_filing'
Expand Down
24 changes: 24 additions & 0 deletions sdk/python/xfinlab_intelligence/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,30 @@ def agriculture(self, ticker: str) -> dict:
populated for CORN/WEAT/SOYB."""
return self._get(f"/intelligence/v1/agriculture/{ticker}")

# 2026-08-30/31: cross-industry expansion -- real estate, supply
# chain, consumer demand. Same shape as agriculture()/energy() above.
def real_estate(self, ticker: str) -> dict:
"""FRED US housing-market context (30-year mortgage rate,
Case-Shiller home price index, housing starts, existing home
sales) -- only populated for homebuilders, REITs, a mortgage
originator, and housing-sector ETFs."""
return self._get(f"/intelligence/v1/real-estate/{ticker}")

def supply_chain(self, ticker: str) -> dict:
"""FRED US manufacturing/supply-chain context (inventory/sales
ratio, manufacturing new orders, durable goods orders, industrial
production, manufacturing employment) -- only populated for
freight carriers, railroads, and transportation ETFs."""
return self._get(f"/intelligence/v1/supply-chain/{ticker}")

def consumer_demand(self, ticker: str) -> dict:
"""FRED US consumer-spending context (retail sales, personal
consumption expenditures, durable goods consumption) -- only
populated for large retailers, e-commerce, and consumer-
discretionary ETFs. Not Google Trends data -- see the endpoint
docs for why."""
return self._get(f"/intelligence/v1/consumer-demand/{ticker}")

# 2026-08-28: Pro-tier webhooks (push instead of polling). See
# services/webhook_service.py's VALID_EVENT_TYPES for the exact
# event_type values ("vix_regime_change" market-wide, "new_13d_filing"
Expand Down
Loading
Loading