fix: 6 audit issues — missing await, regime range, pct_out scaling, ticker validation, dead code, flag merge

1. app.py: await _update_in_progress (coroutine was silently dropped)
2. models.py + tier1.py: regime_score_adjustment range ±2→±10 (was negligible on 0-100 scale)
3. y_finance.py: pct_out * 100 (was fraction, displayed as percent)
4. app.py: ticker validation accepts dots/hyphens (BRK.B, BF-B)
5. portfolio.py: wire _fetch_peer_basics into theme substitution (was dead code)
6. setup.py: accumulate global_flags across parallel agents (dict.update was dropping them)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dtarkent2-sys 2026-03-09 21:56:38 +00:00
parent ee80a42971
commit 5e8c81e738
6 changed files with 46 additions and 17 deletions

5
app.py
View File

@ -1,6 +1,7 @@
"""FastAPI SSE backend for the structured equity ranking engine."""
import os
import re
import time
import uuid
import asyncio
@ -231,7 +232,7 @@ async def _run_analysis_inner(analysis_id: str, ticker: str, trade_date: str):
await q.put(evt)
# Mark in-progress agents for upcoming stages
_update_in_progress(chunk, emitted_fields, prev_agent_statuses, state, q, start_time)
await _update_in_progress(chunk, emitted_fields, prev_agent_statuses, state, q, start_time)
except Exception as e:
print(f"[ANALYSIS] Stream error: {e}\n{_tb.format_exc()}", flush=True)
@ -396,7 +397,7 @@ async def _start_cleanup():
@app.post("/analyze", dependencies=[Depends(verify_api_key)])
async def start_analysis(req: AnalyzeRequest):
ticker = req.ticker.upper().strip()
if not ticker or len(ticker) > 5 or not ticker.isalpha():
if not ticker or not re.match(r'^[A-Z0-9.\-]{1,6}$', ticker):
raise HTTPException(400, "Invalid ticker")
trade_date = req.date or str(date.today())
analysis_id = str(uuid.uuid4())

View File

@ -104,14 +104,36 @@ def create_theme_substitution_node(llm):
industry = card.get("industry", "")
sector = card.get("sector", "")
# Fetch peer data for comparison
# First, ask LLM to identify theme peers, then we'll fetch their data
# Fetch competitor/peer data to ground the LLM's comparison
competitors = card.get("competitors") or []
peer_data = _fetch_peer_basics(competitors) if competitors else []
peer_summary = ""
if peer_data:
lines = []
for p in peer_data:
if p.get("error"):
continue
rg = p.get("revenue_growth")
rg_str = f"{rg*100:.1f}%" if rg else "N/A"
pm = p.get("profit_margins")
pm_str = f"{pm*100:.1f}%" if pm else "N/A"
lines.append(
f" {p['ticker']}: P/E={p.get('trailing_pe', 'N/A')}, "
f"Fwd P/E={p.get('forward_pe', 'N/A')}, "
f"RevGrowth={rg_str}, "
f"Margins={pm_str}, "
f"52W={p.get('52w_range_pct', 'N/A')}%"
)
peer_summary = "\n".join(lines)
theme_prompt = f"""You are a Theme Substitution Analyst. Your job: determine if {ticker} is the BEST
expression of its investment theme, or if better alternatives exist.
CANDIDATE STOCK:
{summary}
{f'PEER FUNDAMENTALS (live data):{chr(10)}{peer_summary}' if peer_summary else 'No live peer data available — use your knowledge of these companies.'}
INSTRUCTIONS do this in order:
1. IDENTIFY THE THEME: What macro/sector theme does {ticker} express?
@ -120,9 +142,10 @@ INSTRUCTIONS — do this in order:
Name it clearly in theme_name.
2. LIST THEME PEERS: Name 3-6 other publicly traded stocks that express the SAME theme.
These should be the strongest competitors for capital allocation in this theme.
For each peer, estimate a master_score_estimate (0-10) based on your knowledge of
their fundamentals, momentum, and positioning vs {ticker}.
Use the peer data above if available. These should be the strongest competitors
for capital allocation in this theme.
For each peer, score master_score_estimate (0-10) based on fundamentals, momentum,
and positioning vs {ticker}.
3. RANK WITHIN THEME: Rank all stocks (including {ticker}) by investment quality.
The stock with the best combination of: business quality, valuation, momentum,

View File

@ -188,13 +188,13 @@ INSTRUCTIONS:
2. Classify liquidity_regime: "expansion" / "contraction" / "neutral".
- expansion: falling yields, dovish Fed, credit flowing, dollar weakening.
- contraction: rising yields, hawkish Fed, tight credit, dollar strengthening.
3. Set regime_score_adjustment (-2 to +2):
- +2 = strong macro tailwind for this specific stock/sector.
- +1 = mild tailwind.
3. Set regime_score_adjustment (-10 to +10):
- +5 to +10 = strong macro tailwind for this specific stock/sector.
- +1 to +4 = mild tailwind.
- 0 = neutral.
- -1 = mild headwind.
- -2 = severe macro headwind (risk-off + contraction + hostile sector).
This adjustment directly modifies the master score for ALL stocks.
- -1 to -4 = mild headwind.
- -5 to -10 = severe macro headwind (risk-off + contraction + hostile sector).
This adjustment directly modifies the 0-100 master score for ALL stocks.
4. Score macro_alignment_0_to_10: how well macro supports {ticker} specifically.
5. Also provide score_0_to_10 (overall macro health).
6. Set regime_label: descriptive label (e.g., "Late Cycle Risk-Off").

View File

@ -613,7 +613,7 @@ def get_institutional_flow(ticker):
{
"holder": str(r.get("Holder", "")),
"shares": int(r["Shares"]) if r.get("Shares") else None,
"pct_out": float(r["% Out"]) if r.get("% Out") else None,
"pct_out": round(float(r["% Out"]) * 100, 2) if r.get("% Out") else None,
"value": float(r["Value"]) if r.get("Value") else None,
}
for r in top

View File

@ -191,11 +191,16 @@ def _create_parallel_node(agent_fns: List[tuple], label: str):
results = await asyncio.gather(*tasks, return_exceptions=True)
merged: Dict[str, Any] = {}
all_flags: list = []
for (name, _), result in zip(agent_fns, results):
if isinstance(result, Exception):
logger.error("[%s] %s failed: %s", label, name, result)
continue
flags = result.pop("global_flags", [])
all_flags.extend(flags)
merged.update(result)
if all_flags:
merged["global_flags"] = all_flags
logger.info("[%s] completed in %.1fs", label, time.time() - t0)
return merged

View File

@ -121,9 +121,9 @@ class MacroRegimeOutput(AgentBaseOutput):
risk_appetite: str = "neutral" # risk-on / risk-off / transitional
liquidity_regime: str = "neutral" # expansion / contraction / neutral
regime_score_adjustment: float = Field(
default=0.0, ge=-2, le=2,
description="Adjustment applied to all downstream scores. "
"+2 = strong macro tailwind, -2 = severe macro headwind.",
default=0.0, ge=-10, le=10,
description="Adjustment applied to the 0-100 master score. "
"+10 = strong macro tailwind, -10 = severe macro headwind.",
)