From eaa9726a5f818050aba4d1b754ec47aa38eaf1e8 Mon Sep 17 00:00:00 2001 From: Claudiu Farcas Date: Sun, 21 Jun 2026 13:35:47 +0300 Subject: [PATCH] Add tooltip notes functionality to HTML report generation --- skills/xtb-portfolio-review/scripts/main.py | 52 +++++++++++++++++++++ test_portfolio.py | 29 ++++++++++++ 2 files changed, 81 insertions(+) diff --git a/skills/xtb-portfolio-review/scripts/main.py b/skills/xtb-portfolio-review/scripts/main.py index ba4699a..6d08a49 100644 --- a/skills/xtb-portfolio-review/scripts/main.py +++ b/skills/xtb-portfolio-review/scripts/main.py @@ -1546,6 +1546,40 @@ TERM_TOOLTIPS = { } _TERM_TOOLTIP_SEQ = 0 +_TERM_TOOLTIP_NOTES: list[tuple[int, str, str]] = [] +_TERM_TOOLTIP_NOTE_INDEX: dict[str, int] = {} + + +def _reset_term_tooltips() -> None: + global _TERM_TOOLTIP_SEQ + _TERM_TOOLTIP_SEQ = 0 + _TERM_TOOLTIP_NOTES.clear() + _TERM_TOOLTIP_NOTE_INDEX.clear() + + +def _term_note_number(text: str, help_text: str) -> int: + key = text.strip() + note_num = _TERM_TOOLTIP_NOTE_INDEX.get(key) + if note_num is None: + note_num = len(_TERM_TOOLTIP_NOTES) + 1 + _TERM_TOOLTIP_NOTE_INDEX[key] = note_num + _TERM_TOOLTIP_NOTES.append((note_num, text, help_text)) + return note_num + + +def _tooltip_notes_html() -> str: + if not _TERM_TOOLTIP_NOTES: + return "" + items = "".join( + f"
  • {escape(text)}: {escape(help_text)}
  • " + for _, text, help_text in _TERM_TOOLTIP_NOTES + ) + return ( + "
    " + "

    Tooltip Notes

    " + f"
      {items}
    " + "
    " + ) def _label_html(label: str) -> str: @@ -1557,11 +1591,13 @@ def _label_html(label: str) -> str: slug = re.sub(r"[^a-z0-9]+", "-", text.strip().lower()).strip("-") or "term" _TERM_TOOLTIP_SEQ += 1 tip_id = f"term-tip-{slug}-{_TERM_TOOLTIP_SEQ}" + note_num = _term_note_number(text, help_text) return ( f"" f"{escape(text)}" f"" + f"" f"" f"{escape(help_text)}" ) @@ -1714,6 +1750,7 @@ def build_html_report( as_of: date | None = None, cost_fallback_tickers: list[str] | None = None, ) -> str: + _reset_term_tooltips() cost_fallback_tickers = cost_fallback_tickers or [] diff = perf["reconciliation_diff"] recon_status = "OK" if (diff is None or abs(diff) < 0.01) else "CHECK" @@ -1910,6 +1947,7 @@ def build_html_report( border:6px solid transparent; border-top-color:#111827; }} .term-help:hover .term-tip, .term-help:focus .term-tip, .term-help:focus-within .term-tip {{ opacity:1; transform:translateY(0); }} + .term-note-ref, .tooltip-notes {{ display:none; }} .section-nav {{ position:sticky; top:0; z-index:10; display:flex; gap:8px; flex-wrap:wrap; align-items:center; background:rgba(245,246,248,.96); border:1px solid var(--line); border-radius:10px; padding:10px; margin:-8px 0 18px; backdrop-filter:blur(8px); }} @@ -1931,6 +1969,18 @@ def build_html_report( table {{ page-break-inside:auto; }} tr {{ break-inside:avoid; page-break-inside:avoid; }} a {{ color:inherit; text-decoration:none; }} + .term-help {{ display:inline; border-bottom:0; cursor:default; }} + .term-icon {{ display:none; }} + .term-note-ref {{ display:inline; color:#4b5563; font-size:.78em; + vertical-align:super; margin-left:1px; }} + .term-tip {{ display:none; }} + .term-tip::after {{ content:none; }} + .tooltip-notes {{ display:block; margin-top:18px; padding-top:12px; + border-top:1px solid #d1d5db; color:#374151; + break-inside:avoid; page-break-inside:avoid; }} + .tooltip-notes h2 {{ color:#374151; }} + .tooltip-notes ol {{ margin:0; padding-left:20px; }} + .tooltip-notes li {{ margin:0 0 5px; font-size:12px; line-height:1.35; }} }} @@ -2026,6 +2076,8 @@ def build_html_report( + {_tooltip_notes_html()} +