feat: Specialist Desks — per asset-class fundamental configs + report catalogue
- 7 pre-seeded desks (Forex, Metals, Agri, Energy, Indices, Crypto, Bonds) each with default fundamental drivers, macro regime sensitivities and price delta thresholds - Global report catalogue (specialist_reports) fully manual — add any report including non-calendar ones (e.g. Cocoa Grinding Report, ICCO) - Many-to-many report ↔ desk linking (report_desk_links table) - 12 default reports pre-seeded (COT, EIA, WASDE, FOMC, ECB, CPI, NFP…) - AI scorer injects SPECIALIST DESK context block for asset classes present in each scoring batch (upcoming reports, key drivers, regime sensitivity) - /specialist-desks page: desk sidebar + fundamentals editor + macro sensitivity tag editor + reports tab + global reports catalogue + modal to create/edit any report with desk assignment Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -376,6 +376,54 @@ SCORE DISTRIBUTION RULE (MANDATORY):
|
||||
- A pattern with 3+ relevant news AND favorable macro can reach 70-85/100"""
|
||||
|
||||
|
||||
def _build_specialist_context_block(asset_classes: set) -> str:
|
||||
"""Build a SPECIALIST DESK CONTEXT block for the given asset classes."""
|
||||
try:
|
||||
from services.database import get_asset_class_config, get_desk_reports
|
||||
from datetime import datetime as _dt
|
||||
blocks = []
|
||||
for ac in sorted(asset_classes):
|
||||
cfg = get_asset_class_config(ac)
|
||||
if not cfg:
|
||||
continue
|
||||
reports = get_desk_reports(ac)
|
||||
lines = [f"\n## SPECIALIST DESK — {cfg['display_name'].upper()} {cfg.get('icon','')}"]
|
||||
if cfg.get("fundamentals"):
|
||||
lines.append(f"Key drivers: {cfg['fundamentals'][:300]}")
|
||||
# Macro sensitivity
|
||||
sensitivities = cfg.get("macro_sensitivity") or []
|
||||
if sensitivities:
|
||||
sens_str = " | ".join(
|
||||
f"{s.get('regime','?')} → {s.get('effect','?')}"
|
||||
for s in sensitivities[:4]
|
||||
)
|
||||
lines.append(f"Regime sensitivity: {sens_str}")
|
||||
# Price thresholds
|
||||
thresh = cfg.get("price_delta_thresholds") or {}
|
||||
if thresh:
|
||||
lines.append(
|
||||
f"Price thresholds: significant_week={thresh.get('significant_week','?')}% "
|
||||
f"extreme_week={thresh.get('extreme_week','?')}%"
|
||||
)
|
||||
# Upcoming reports
|
||||
today = _dt.utcnow().strftime("%Y-%m-%d")
|
||||
upcoming = sorted(
|
||||
[r for r in reports if r.get("next_date") and r["next_date"] >= today],
|
||||
key=lambda r: r["next_date"]
|
||||
)[:5]
|
||||
if upcoming:
|
||||
lines.append("Upcoming reports:")
|
||||
for r in upcoming:
|
||||
lines.append(
|
||||
f" • {r['name']} ({r['source']}) — {r['next_date']} [{r['cadence']}]"
|
||||
+ (f" ⭐×{r['importance']}" if r.get("importance", 1) >= 3 else "")
|
||||
)
|
||||
blocks.append("\n".join(lines))
|
||||
return "\n".join(blocks) + "\n" if blocks else ""
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def score_patterns_with_context(
|
||||
patterns: List[Dict],
|
||||
recent_news: List[Dict],
|
||||
@@ -783,12 +831,18 @@ Lessons: {' | '.join(str(l)[:80] for l in lessons[:3])}
|
||||
|
||||
"""
|
||||
|
||||
# Build specialist-desk blocks for asset classes present in this pattern set
|
||||
_all_pattern_asset_classes = {
|
||||
p.get("asset_class", "") for p in patterns if p.get("asset_class")
|
||||
}
|
||||
specialist_block = _build_specialist_context_block(_all_pattern_asset_classes)
|
||||
|
||||
# Build the per-batch prompt template (static parts)
|
||||
prompt_header = f"""GLOBAL CONTEXT:
|
||||
- Geopolitical risk score: {geo_score.get('score', 50)}/100 ({geo_score.get('level', 'medium')})
|
||||
- Top risks: {geo_score.get('top_risks', [])}
|
||||
{macro_section}{lessons_header}{iv_context}
|
||||
{risk_context}
|
||||
{risk_context}{specialist_block}
|
||||
SCORING TEMPLATE:
|
||||
{scoring_template}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user