QAway-to commited on
Commit
ba99459
·
1 Parent(s): e53a7f3

New style APP (Tab Changes v1.3)

Browse files
Files changed (1) hide show
  1. core/formatters.py +69 -91
core/formatters.py CHANGED
@@ -1,117 +1,95 @@
1
  """
2
  🇬🇧 Module: formatters.py
3
- Purpose: Cleanly format LLM Markdown portfolio comparison output into styled HTML.
4
 
5
  🇷🇺 Модуль: formatters.py
6
- Назначение: форматирует Markdown-ответ LLM в аккуратную HTML-таблицу без дубликатов и пустых столбцов.
7
  """
8
-
9
  import re
10
  from html import escape
11
 
12
 
13
- def highlight_metrics(text: str) -> str:
14
- """Color numeric values depending on sign."""
15
- def colorize(match):
16
- val = match.group(0)
 
 
 
17
  try:
18
- num = float(val.replace("%", ""))
19
- if num > 0:
20
- color = "#10b981" # green
21
- elif num < 0:
22
- color = "#ef4444" # red
23
- else:
24
- color = "#e6edf3"
25
- return f"<span style='color:{color};font-weight:600;'>{val}</span>"
26
- except ValueError:
27
- return val
28
-
29
- return re.sub(r"-?\d+\.?\d*%", colorize, text)
30
-
31
-
32
- def clean_table_lines(lines):
33
- """Remove invalid / empty Markdown table lines."""
34
- result = []
35
- for ln in lines:
36
- if not ln.strip():
37
- continue
38
- if ln.strip().startswith("|") and len(ln.split("|")) > 3:
39
- result.append(ln)
40
- return result
41
-
42
-
43
- def md_table_to_html(md_table: str) -> str:
44
- """Convert one Markdown table to HTML safely."""
45
- lines = clean_table_lines(md_table.strip().splitlines())
46
- if len(lines) < 2:
47
- return md_table
48
-
49
- header_line = lines[0]
50
- data_lines = lines[2:] # skip the ---|--- line
51
- headers = [escape(c.strip()) for c in header_line.split("|")[1:-1] if c.strip()]
52
 
53
- html = [
54
- "<table style='width:100%;border-collapse:collapse;"
55
- "margin:10px 0;font-size:14px;font-family:Inter,sans-serif;'>",
56
  "<thead><tr>"
57
- + "".join(
58
- f"<th style='border-bottom:1px solid #30363d;padding:6px;color:#c9d1d9;"
59
- f"font-weight:600;text-align:center;'>{h}</th>"
60
- for h in headers
61
- )
62
- + "</tr></thead><tbody>"
63
- ]
64
-
65
- for ln in data_lines:
66
- cols = [c.strip() for c in ln.split("|")[1:-1] if c.strip()]
67
- if len(cols) != len(headers):
68
- continue # skip malformed rows
69
- html.append(
70
- "<tr>"
71
- + "".join(
72
- f"<td style='padding:6px 8px;border-bottom:1px solid #30363d;"
73
- f"text-align:center;color:#e6edf3;'>"
74
- f"{highlight_metrics(escape(c))}</td>"
75
- for c in cols
76
- )
77
- + "</tr>"
78
- )
79
 
80
- html.append("</tbody></table>")
81
- return "".join(html)
 
 
 
 
 
 
 
 
82
 
83
 
84
  def format_comparison_output(raw: str) -> str:
85
- """Transform Markdown to HTML with correct table parsing."""
86
  if not raw:
87
  return "<i>No comparison data available.</i>"
88
 
89
- text = raw.strip()
90
-
91
- # Section headers
92
- text = re.sub(r"###\s*1️⃣.*", "<h3 style='color:#93c5fd;margin-top:12px;'>1. Summary Table</h3>", text)
93
- text = re.sub(r"###\s*2️⃣.*", "<h3 style='color:#a78bfa;margin-top:12px;'>2. Comparative Analysis</h3>", text)
94
- text = re.sub(r"###\s*3️⃣.*", "<h3 style='color:#fbbf24;margin-top:12px;'>3. Recommendation</h3>", text)
95
 
96
- # Extract Markdown tables only once
97
- table_blocks = re.findall(r"((?:\|[^\n]+\n)+)", text)
98
- used_blocks = set()
99
 
100
- for tbl in table_blocks:
101
- tbl_clean = tbl.strip()
102
- if tbl_clean in used_blocks:
103
- continue
104
- used_blocks.add(tbl_clean)
105
- html_table = md_table_to_html(tbl_clean)
106
- text = text.replace(tbl_clean, html_table)
107
 
108
- text = highlight_metrics(text)
109
- text = text.replace("**", "")
110
- text = text.replace("\n", "<br>")
111
 
112
  return (
113
- "<div style='font-family:Inter,sans-serif;font-size:15px;line-height:1.6;"
114
- "color:#e6edf3;'>"
115
- + text +
 
116
  "</div>"
117
  )
 
1
  """
2
  🇬🇧 Module: formatters.py
3
+ Purpose: Cleanly format comparison output into a Bloomberg-style HTML table + narrative.
4
 
5
  🇷🇺 Модуль: formatters.py
6
+ Назначение: отображает сравнение портфелей в виде таблицы и аналитического блока в едином стиле.
7
  """
 
8
  import re
9
  from html import escape
10
 
11
 
12
+ def build_comparison_table(metrics: list[dict]) -> str:
13
+ """Builds a styled HTML table from list of metrics."""
14
+ rows = []
15
+ for m in metrics:
16
+ name = escape(m["metric"])
17
+ a = escape(m["a"])
18
+ b = escape(m["b"])
19
  try:
20
+ fa = float(a.replace("%", ""))
21
+ fb = float(b.replace("%", ""))
22
+ except Exception:
23
+ fa, fb = None, None
24
+
25
+ def color(v):
26
+ if v is None:
27
+ return "#e6edf3"
28
+ return "#10b981" if v > 0 else "#ef4444" if v < 0 else "#e6edf3"
29
+
30
+ def fmt(val, c):
31
+ arrow = "" if c == "#10b981" else "▼" if c == "#ef4444" else "▪"
32
+ return f"<span style='color:{c};font-weight:600'>{val} {arrow}</span>"
33
+
34
+ ca, cb = color(fa), color(fb)
35
+ rows.append(
36
+ f"<tr>"
37
+ f"<td style='padding:6px 8px;border-bottom:1px solid #30363d;text-align:left;color:#c9d1d9'>{name}</td>"
38
+ f"<td style='padding:6px 8px;border-bottom:1px solid #30363d;text-align:center'>{fmt(a, ca)}</td>"
39
+ f"<td style='padding:6px 8px;border-bottom:1px solid #30363d;text-align:center'>{fmt(b, cb)}</td>"
40
+ f"</tr>"
41
+ )
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
+ html = (
44
+ "<table style='width:100%;border-collapse:collapse;margin:12px 0;"
45
+ "font-family:Inter,sans-serif;font-size:14px;'>"
46
  "<thead><tr>"
47
+ "<th style='color:#9da5b4;text-align:left;padding:6px 8px;'>Metric</th>"
48
+ "<th style='color:#9da5b4;text-align:center;'>Portfolio A</th>"
49
+ "<th style='color:#9da5b4;text-align:center;'>Portfolio B</th>"
50
+ "</tr></thead><tbody>"
51
+ + "".join(rows)
52
+ + "</tbody></table>"
53
+ )
54
+ return html
55
+
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
+ def extract_metrics_from_text(raw: str) -> list[dict]:
58
+ """Extracts metric rows like '| Annualized Return | 97.73% | -10.46% |'."""
59
+ metrics = []
60
+ for line in raw.splitlines():
61
+ if "|" not in line:
62
+ continue
63
+ cols = [c.strip() for c in line.split("|") if c.strip()]
64
+ if len(cols) == 3 and re.search(r"\d", cols[1]) and re.search(r"\d", cols[2]):
65
+ metrics.append({"metric": cols[0], "a": cols[1], "b": cols[2]})
66
+ return metrics
67
 
68
 
69
  def format_comparison_output(raw: str) -> str:
70
+ """Transforms LLM comparison text into styled HTML."""
71
  if not raw:
72
  return "<i>No comparison data available.</i>"
73
 
74
+ # Extract metric rows
75
+ metrics = extract_metrics_from_text(raw)
 
 
 
 
76
 
77
+ # Remove the original table to avoid duplication
78
+ cleaned = re.sub(r"(\|.*\|\n)+", "", raw).strip()
 
79
 
80
+ # Section styling
81
+ cleaned = re.sub(r"###\s*1️⃣.*", "<h3 style='color:#93c5fd;'>1. Summary Table</h3>", cleaned)
82
+ cleaned = re.sub(r"###\s*2️⃣.*", "<h3 style='color:#a78bfa;'>2. Comparative Analysis</h3>", cleaned)
83
+ cleaned = re.sub(r"###\s*3️⃣.*", "<h3 style='color:#fbbf24;'>3. Recommendation</h3>", cleaned)
 
 
 
84
 
85
+ # Build HTML
86
+ html_table = build_comparison_table(metrics)
87
+ cleaned = cleaned.replace("**", "").replace("\n", "<br>")
88
 
89
  return (
90
+ "<div style='font-family:Inter,sans-serif;font-size:15px;line-height:1.6;color:#e6edf3;'>"
91
+ "<h3 style='color:#93c5fd;margin-bottom:4px;'>1. Summary Table</h3>"
92
+ f"{html_table}"
93
+ f"<div style='margin-top:12px;'>{cleaned}</div>"
94
  "</div>"
95
  )