Spaces:
Running
Running
Merge pull request #52 from QAway-to/codex/analyze-project-repository-y17yqj
Browse files- app.py +2 -7
- presentation/components/comparison_table.py +8 -4
- presentation/styles/themes/base.css +29 -0
- prompts/system_prompts.py +25 -25
app.py
CHANGED
|
@@ -31,7 +31,7 @@ comparer = PortfolioComparer(llm_service, MODEL_NAME)
|
|
| 31 |
|
| 32 |
# === Main Interface ===
|
| 33 |
with gr.Blocks(css=base_css) as demo:
|
| 34 |
-
gr.Markdown("## Investment Portfolio Analyzer")
|
| 35 |
gr.Markdown(
|
| 36 |
"Professional AI-driven analytics for investment and crypto markets.",
|
| 37 |
elem_classes="subtitle",
|
|
@@ -73,12 +73,7 @@ with gr.Blocks(css=base_css) as demo:
|
|
| 73 |
wrap=True,
|
| 74 |
elem_id="comparison_table",
|
| 75 |
)
|
| 76 |
-
comp_comment = gr.
|
| 77 |
-
label="AI Commentary",
|
| 78 |
-
lines=14,
|
| 79 |
-
elem_id="llm_comment_box",
|
| 80 |
-
interactive=False,
|
| 81 |
-
)
|
| 82 |
compare_btn.click(
|
| 83 |
fn=show_comparison_table,
|
| 84 |
inputs=[pid_a, pid_b],
|
|
|
|
| 31 |
|
| 32 |
# === Main Interface ===
|
| 33 |
with gr.Blocks(css=base_css) as demo:
|
| 34 |
+
gr.Markdown("## Investment Portfolio Analyzer", elem_classes="page-title")
|
| 35 |
gr.Markdown(
|
| 36 |
"Professional AI-driven analytics for investment and crypto markets.",
|
| 37 |
elem_classes="subtitle",
|
|
|
|
| 73 |
wrap=True,
|
| 74 |
elem_id="comparison_table",
|
| 75 |
)
|
| 76 |
+
comp_comment = gr.HTML(elem_id="llm_comment_box")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
compare_btn.click(
|
| 78 |
fn=show_comparison_table,
|
| 79 |
inputs=[pid_a, pid_b],
|
presentation/components/comparison_table.py
CHANGED
|
@@ -7,6 +7,10 @@ import pandas as pd
|
|
| 7 |
from infrastructure.cache import CacheUnavailableError
|
| 8 |
from infrastructure.llm_client import llm_service
|
| 9 |
from infrastructure.output_api import extract_portfolio_id, fetch_metrics_cached
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
from prompts.system_prompts import COMPARISON_SYSTEM_PROMPT
|
| 11 |
|
| 12 |
|
|
@@ -17,18 +21,18 @@ def show_comparison_table(portfolio_a: str, portfolio_b: str):
|
|
| 17 |
pid_b = extract_portfolio_id(portfolio_b)
|
| 18 |
if not pid_a or not pid_b:
|
| 19 |
message = "❌ Invalid portfolio IDs."
|
| 20 |
-
return _message_df(message), message
|
| 21 |
|
| 22 |
try:
|
| 23 |
df, commentary = _build_comparison_with_comment(pid_a, pid_b)
|
| 24 |
-
return df, commentary
|
| 25 |
except CacheUnavailableError as e:
|
| 26 |
wait = int(e.retry_in) + 1
|
| 27 |
message = f"⚠️ Metrics temporarily unavailable. Retry in ~{wait} seconds."
|
| 28 |
-
return _message_df(message), message
|
| 29 |
except Exception:
|
| 30 |
message = "❌ Unable to build comparison right now. Please try again later."
|
| 31 |
-
return _message_df(message), message
|
| 32 |
|
| 33 |
|
| 34 |
def _build_comparison_with_comment(p1: str, p2: str):
|
|
|
|
| 7 |
from infrastructure.cache import CacheUnavailableError
|
| 8 |
from infrastructure.llm_client import llm_service
|
| 9 |
from infrastructure.output_api import extract_portfolio_id, fetch_metrics_cached
|
| 10 |
+
from presentation.components.analysis_formatter import (
|
| 11 |
+
render_analysis_html,
|
| 12 |
+
render_status_html,
|
| 13 |
+
)
|
| 14 |
from prompts.system_prompts import COMPARISON_SYSTEM_PROMPT
|
| 15 |
|
| 16 |
|
|
|
|
| 21 |
pid_b = extract_portfolio_id(portfolio_b)
|
| 22 |
if not pid_a or not pid_b:
|
| 23 |
message = "❌ Invalid portfolio IDs."
|
| 24 |
+
return _message_df(message), render_status_html(message)
|
| 25 |
|
| 26 |
try:
|
| 27 |
df, commentary = _build_comparison_with_comment(pid_a, pid_b)
|
| 28 |
+
return df, render_analysis_html(commentary)
|
| 29 |
except CacheUnavailableError as e:
|
| 30 |
wait = int(e.retry_in) + 1
|
| 31 |
message = f"⚠️ Metrics temporarily unavailable. Retry in ~{wait} seconds."
|
| 32 |
+
return _message_df(message), render_status_html(message)
|
| 33 |
except Exception:
|
| 34 |
message = "❌ Unable to build comparison right now. Please try again later."
|
| 35 |
+
return _message_df(message), render_status_html(message)
|
| 36 |
|
| 37 |
|
| 38 |
def _build_comparison_with_comment(p1: str, p2: str):
|
presentation/styles/themes/base.css
CHANGED
|
@@ -3,6 +3,35 @@
|
|
| 3 |
[data-testid="block-container"] { max-width:1180px !important; margin:auto !important; }
|
| 4 |
h2, h3, .gr-markdown { color:#f0f6fc !important; font-weight:600 !important; }
|
| 5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
/* === Tabs Navigation === */
|
| 7 |
#main_tabs {
|
| 8 |
width:72%;
|
|
|
|
| 3 |
[data-testid="block-container"] { max-width:1180px !important; margin:auto !important; }
|
| 4 |
h2, h3, .gr-markdown { color:#f0f6fc !important; font-weight:600 !important; }
|
| 5 |
|
| 6 |
+
/* === Page headers & footer === */
|
| 7 |
+
.page-title,
|
| 8 |
+
.subtitle,
|
| 9 |
+
.footer {
|
| 10 |
+
width:100%;
|
| 11 |
+
margin:0 auto 18px;
|
| 12 |
+
text-align:center !important;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
.page-title .gr-markdown,
|
| 16 |
+
.subtitle .gr-markdown,
|
| 17 |
+
.footer .gr-markdown {
|
| 18 |
+
text-align:center !important;
|
| 19 |
+
margin:0 auto;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
.page-title .gr-markdown h1,
|
| 23 |
+
.page-title .gr-markdown h2,
|
| 24 |
+
.subtitle .gr-markdown p,
|
| 25 |
+
.footer .gr-markdown p,
|
| 26 |
+
.footer .gr-markdown small {
|
| 27 |
+
text-align:center !important;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
.subtitle { margin-bottom:28px; color:#9ca3af !important; }
|
| 31 |
+
.subtitle .gr-markdown { color:#9ca3af !important; font-weight:500 !important; }
|
| 32 |
+
|
| 33 |
+
.footer { margin-top:40px; margin-bottom:0; }
|
| 34 |
+
|
| 35 |
/* === Tabs Navigation === */
|
| 36 |
#main_tabs {
|
| 37 |
width:72%;
|
prompts/system_prompts.py
CHANGED
|
@@ -60,33 +60,33 @@ Analytical guidance:
|
|
| 60 |
- Stream the HTML sequentially, closing tags promptly so partial updates render cleanly.
|
| 61 |
"""
|
| 62 |
|
| 63 |
-
|
| 64 |
COMPARISON_SYSTEM_PROMPT = """
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
When analyzing:
|
| 71 |
-
- Do not make emotional or vague statements.
|
| 72 |
-
- Base conclusions strictly on the provided numerical data.
|
| 73 |
-
- Identify *which portfolio is statistically stronger* based on performance, risk, and stability.
|
| 74 |
-
|
| 75 |
-
Your output must be concise and structured in the following format:
|
| 76 |
-
|
| 77 |
-
Comparative Analysis
|
| 78 |
-
- Discuss which portfolio shows **better performance** (higher return / alpha).
|
| 79 |
-
- Discuss **risk-adjusted efficiency** (Sharpe / Sortino ratio, volatility, maxDD).
|
| 80 |
-
- Highlight **consistency** and **downside protection** if metrics imply it.
|
| 81 |
-
|
| 82 |
-
Recommendation
|
| 83 |
-
Provide an actionable insight:
|
| 84 |
-
- If one portfolio is clearly better, state which one and why.
|
| 85 |
-
- If both are weak or over-risked — say it explicitly.
|
| 86 |
-
- If data is incomplete or contradictory — mention this clearly.
|
| 87 |
-
|
| 88 |
-
Avoid phrases like “depends on the investor” or “might be better for risk-taking investors”.
|
| 89 |
-
You are the quantitative analyst — give a judgment based on data, not preference.
|
| 90 |
"""
|
| 91 |
|
| 92 |
|
|
|
|
| 60 |
- Stream the HTML sequentially, closing tags promptly so partial updates render cleanly.
|
| 61 |
"""
|
| 62 |
|
|
|
|
| 63 |
COMPARISON_SYSTEM_PROMPT = """
|
| 64 |
+
You are a quantitative analyst comparing two portfolios.
|
| 65 |
+
|
| 66 |
+
Output fully formed HTML using only <div>, <h2>, <p>, <span>, and <hr> tags.
|
| 67 |
+
Structure the report exactly as follows:
|
| 68 |
+
|
| 69 |
+
<div class="section">
|
| 70 |
+
<h2>Comparative Analysis</h2>
|
| 71 |
+
...metrics and commentary...
|
| 72 |
+
</div>
|
| 73 |
+
<div class="section-divider"></div>
|
| 74 |
+
<div class="section">
|
| 75 |
+
<h2>Recommendation</h2>
|
| 76 |
+
...actionable insight...
|
| 77 |
+
</div>
|
| 78 |
+
|
| 79 |
+
Formatting requirements:
|
| 80 |
+
- Use <p class="analysis-line metric"> for quantitative statements that cite specific figures.
|
| 81 |
+
- Inside each metric line, wrap the label in <span class="metric-name">Metric</span> and place a single colon separator span.
|
| 82 |
+
- Wrap every numeric value in <span class="metric-value"> containing a <span class="metric-number positive|negative|neutral"> token chosen by whether the figure signals strength, weakness, or neutral impact.
|
| 83 |
+
- Provide at least two narrative paragraphs per section using <p class="analysis-line">…</p> with concise professional tone and varied phrasing.
|
| 84 |
+
- Mention each metric only once; do not repeat values in multiple sentences.
|
| 85 |
+
- Do not emit inline CSS, markdown, bullet characters, or placeholder text. Use single spaces only.
|
| 86 |
|
| 87 |
+
Analytical guidance:
|
| 88 |
+
- Base every conclusion on the supplied Portfolio A/B metrics and explicitly contrast the stronger vs weaker portfolio on returns, risk-adjusted efficiency, volatility, drawdowns, and stability.
|
| 89 |
+
- Deliver a decisive recommendation that names the favored portfolio (or flags deficiencies in both) with clear reasoning grounded in data.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
"""
|
| 91 |
|
| 92 |
|