Spaces:
Running
Running
Sync from GitHub (tests passed)
Browse files- app/commentary.py +34 -2
app/commentary.py
CHANGED
|
@@ -138,6 +138,23 @@ def _deterministic_stance_from_inputs(
|
|
| 138 |
return "NEUTRAL"
|
| 139 |
|
| 140 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
def _build_sentiment_regime_line(sentiment_index: float, sentiment_label: str) -> str:
|
| 142 |
"""One risk line describing the sentiment regime without inventing polarity.
|
| 143 |
|
|
@@ -861,6 +878,7 @@ DATA INPUTS:
|
|
| 861 |
Return valid JSON with exactly two keys: "stance" and "commentary"
|
| 862 |
|
| 863 |
**stance**: Must be one of: BULLISH, BEARISH, NEUTRAL
|
|
|
|
| 864 |
- Derive the stance from the news sentiment regime: Bullish -> BULLISH, Bearish -> BEARISH, Neutral (balanced) -> NEUTRAL. The 5-day TFT-ASRO outlook informs the commentary and synthesis, never the stance.
|
| 865 |
- Where both models describe the same thing, the TFT-ASRO 5-day view takes priority; the XGBoost next-day view is a diagnostic and must be labelled as such.
|
| 866 |
|
|
@@ -995,7 +1013,14 @@ Write as a seasoned commodities strategist would for institutional clients—pre
|
|
| 995 |
try:
|
| 996 |
stance, commentary = _parse_commentary_payload(content)
|
| 997 |
logger.info("AI commentary generated successfully (%s chars)", len(commentary))
|
| 998 |
-
return CommentaryGenerationResult(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 999 |
except Exception as parse_exc:
|
| 1000 |
logger.warning("Commentary JSON parse failed, attempting repair: %s", parse_exc)
|
| 1001 |
repaired, repair_data = await _repair_commentary(content)
|
|
@@ -1008,7 +1033,14 @@ Write as a seasoned commodities strategist would for institutional clients—pre
|
|
| 1008 |
)
|
| 1009 |
attempts = [*attempts, *(repair_meta.get("attempts") or [])]
|
| 1010 |
logger.info("AI commentary generated via JSON repair (%s chars)", len(commentary))
|
| 1011 |
-
return CommentaryGenerationResult(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1012 |
except Exception as exc:
|
| 1013 |
logger.warning("Commentary generation failed, using deterministic fallback: %s", exc)
|
| 1014 |
return CommentaryGenerationResult(
|
|
|
|
| 138 |
return "NEUTRAL"
|
| 139 |
|
| 140 |
|
| 141 |
+
def _resolve_stance(llm_stance: str, derived_stance: str) -> str:
|
| 142 |
+
"""The news regime owns the stance; the model's answer is advisory only.
|
| 143 |
+
|
| 144 |
+
Prompt rules cannot guarantee the model's JSON, and a stance that disagrees
|
| 145 |
+
with the label rendered next to the same sentiment index would put the pet
|
| 146 |
+
in a mood the dashboard contradicts. Divergences are logged so prompt
|
| 147 |
+
regressions stay visible.
|
| 148 |
+
"""
|
| 149 |
+
if llm_stance != derived_stance:
|
| 150 |
+
logger.warning(
|
| 151 |
+
"LLM stance %s disagrees with the news-derived %s; publishing the derived stance",
|
| 152 |
+
llm_stance,
|
| 153 |
+
derived_stance,
|
| 154 |
+
)
|
| 155 |
+
return derived_stance
|
| 156 |
+
|
| 157 |
+
|
| 158 |
def _build_sentiment_regime_line(sentiment_index: float, sentiment_label: str) -> str:
|
| 159 |
"""One risk line describing the sentiment regime without inventing polarity.
|
| 160 |
|
|
|
|
| 878 |
Return valid JSON with exactly two keys: "stance" and "commentary"
|
| 879 |
|
| 880 |
**stance**: Must be one of: BULLISH, BEARISH, NEUTRAL
|
| 881 |
+
- Stance for this run is fixed at {deterministic_stance}: the news sentiment regime above decides it, not price direction. Do not contradict it anywhere in the commentary.
|
| 882 |
- Derive the stance from the news sentiment regime: Bullish -> BULLISH, Bearish -> BEARISH, Neutral (balanced) -> NEUTRAL. The 5-day TFT-ASRO outlook informs the commentary and synthesis, never the stance.
|
| 883 |
- Where both models describe the same thing, the TFT-ASRO 5-day view takes priority; the XGBoost next-day view is a diagnostic and must be labelled as such.
|
| 884 |
|
|
|
|
| 1013 |
try:
|
| 1014 |
stance, commentary = _parse_commentary_payload(content)
|
| 1015 |
logger.info("AI commentary generated successfully (%s chars)", len(commentary))
|
| 1016 |
+
return CommentaryGenerationResult(
|
| 1017 |
+
commentary,
|
| 1018 |
+
_resolve_stance(stance, deterministic_stance),
|
| 1019 |
+
"llm",
|
| 1020 |
+
actual_model,
|
| 1021 |
+
None,
|
| 1022 |
+
attempts,
|
| 1023 |
+
)
|
| 1024 |
except Exception as parse_exc:
|
| 1025 |
logger.warning("Commentary JSON parse failed, attempting repair: %s", parse_exc)
|
| 1026 |
repaired, repair_data = await _repair_commentary(content)
|
|
|
|
| 1033 |
)
|
| 1034 |
attempts = [*attempts, *(repair_meta.get("attempts") or [])]
|
| 1035 |
logger.info("AI commentary generated via JSON repair (%s chars)", len(commentary))
|
| 1036 |
+
return CommentaryGenerationResult(
|
| 1037 |
+
commentary,
|
| 1038 |
+
_resolve_stance(stance, deterministic_stance),
|
| 1039 |
+
"llm_repaired",
|
| 1040 |
+
actual_model,
|
| 1041 |
+
None,
|
| 1042 |
+
attempts,
|
| 1043 |
+
)
|
| 1044 |
except Exception as exc:
|
| 1045 |
logger.warning("Commentary generation failed, using deterministic fallback: %s", exc)
|
| 1046 |
return CommentaryGenerationResult(
|