---
title: "Deal Screener"
description: "AI adoption signals for investor due diligence — sector benchmarks, momentum leaders, and value opportunities."
format:
  html:
    page-layout: full
    toc: false
execute:
  echo: false
  warning: false
---

```{python}
import pandas as pd
import numpy as np
from pathlib import Path
from itables import show
import itables.options as opt

opt.maxBytes = 0

REPO = Path(".")
signals_path = REPO / "data" / "research" / "signals.csv"
panel_path = REPO / "data" / "research" / "ai_adoption_panel.csv"
universe_path = REPO / "data" / "universes" / "comparables_universe.csv"

# ── Build signals if not pre-computed ─────────────────────────────
if signals_path.exists():
    df = pd.read_csv(signals_path)
else:
    # Fallback: compute on the fly
    import sys
    sys.path.insert(0, str(REPO))
    from research.alarms import build_signals_table
    df = build_signals_table()

from IPython.display import display, HTML

# Signal badge colors
BADGE_COLORS = {
    "AI_LEADER":     ("#1a6b3c", "#d4edda", "AI Leader"),
    "AI_MOMENTUM":   ("#155a8a", "#d1ecf1", "Momentum"),
    "AI_LAGGARD":    ("#856404", "#fff3cd", "Laggard"),
    "VALUE_PLUS_AI": ("#4a235a", "#e8daef", "Value + AI"),
    "RISING_STAR":   ("#7d3c00", "#fde8d8", "Rising Star"),
}

def render_badges(signal_str):
    if not signal_str or pd.isna(signal_str):
        return ""
    badges = ""
    for s in str(signal_str).split("|"):
        s = s.strip()
        if s in BADGE_COLORS:
            fg, bg, label = BADGE_COLORS[s]
            badges += (
                f'<span style="background:{bg};color:{fg};border:1px solid {fg};'
                f'border-radius:12px;padding:2px 8px;font-size:.72rem;'
                f'font-weight:600;margin-right:4px;white-space:nowrap;">{label}</span>'
            )
    return badges

# Summary counts
all_sigs = "|".join(df["signals"].fillna("")).split("|")
n_leader   = all_sigs.count("AI_LEADER")
n_momentum = all_sigs.count("AI_MOMENTUM")
n_laggard  = all_sigs.count("AI_LAGGARD")
n_value    = all_sigs.count("VALUE_PLUS_AI")
n_rising   = all_sigs.count("RISING_STAR")
n_coverage = int((df.get("tier", pd.Series(dtype=str)) == "coverage").sum())
```

::: {.callout-note appearance="minimal"}
**Signals are derived from SEC 10-K NLP analysis.** They reflect AI adoption intensity, not stock price performance. See [Research Methodology](assumptions.html) for full signal definitions. Not investment advice.
:::

## Signal Overview

```{python}
cards_html = f"""
<div style="display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin:20px 0 28px;">
  <div style="background:#d4edda;border:1px solid #1a6b3c;border-radius:8px;padding:16px;text-align:center;">
    <div style="font-size:1.8rem;font-weight:700;color:#1a6b3c;">{n_leader}</div>
    <div style="font-size:.8rem;color:#1a6b3c;font-weight:600;">AI Leaders</div>
    <div style="font-size:.72rem;color:#555;margin-top:4px;">Top 20% of sector</div>
  </div>
  <div style="background:#d1ecf1;border:1px solid #155a8a;border-radius:8px;padding:16px;text-align:center;">
    <div style="font-size:1.8rem;font-weight:700;color:#155a8a;">{n_momentum}</div>
    <div style="font-size:.8rem;color:#155a8a;font-weight:600;">Momentum</div>
    <div style="font-size:.72rem;color:#555;margin-top:4px;">Score rising fast</div>
  </div>
  <div style="background:#fff3cd;border:1px solid #856404;border-radius:8px;padding:16px;text-align:center;">
    <div style="font-size:1.8rem;font-weight:700;color:#856404;">{n_laggard}</div>
    <div style="font-size:.8rem;color:#856404;font-weight:600;">Laggards</div>
    <div style="font-size:.72rem;color:#555;margin-top:4px;">Bottom 25% of sector</div>
  </div>
  <div style="background:#e8daef;border:1px solid #4a235a;border-radius:8px;padding:16px;text-align:center;">
    <div style="font-size:1.8rem;font-weight:700;color:#4a235a;">{n_value}</div>
    <div style="font-size:.8rem;color:#4a235a;font-weight:600;">Value + AI</div>
    <div style="font-size:.72rem;color:#555;margin-top:4px;">Momentum + cheap valuation</div>
  </div>
  <div style="background:#fde8d8;border:1px solid #7d3c00;border-radius:8px;padding:16px;text-align:center;">
    <div style="font-size:1.8rem;font-weight:700;color:#7d3c00;">{n_rising}</div>
    <div style="font-size:.8rem;color:#7d3c00;font-weight:600;">Rising Stars</div>
    <div style="font-size:.72rem;color:#555;margin-top:4px;">Coverage cos. +10pts YoY</div>
  </div>
</div>
"""
display(HTML(cards_html))
```

## Signal Definitions

| Signal | Criteria | Investor Read |
|--------|----------|---------------|
| **AI Leader** | Top 20% of sector by AI Adoption Score | Established AI moat; benchmark for sector |
| **AI Momentum** | YoY score change > sector 75th percentile | AI investment accelerating; may signal emerging moat |
| **AI Laggard** | Bottom 25% of sector by AI Adoption Score | Transformation risk **or** acquisition opportunity |
| **Value + AI** | AI Momentum + EV/EBITDA below sector median | Under-appreciated AI adopter — core deal screen signal |
| **Rising Star** | Coverage company with ≥10pt YoY gain | Mid-market companies catching up; watch list candidates |

## Deal Screen

Use the search box to filter by company, sector, or signal. Sort any column.

```{python}
# Build display table
display_cols = {
    "ticker": "Ticker",
    "name": "Company",
    "industry": "Sector",
    "tier": "Tier",
    "ai_adoption_score": "AI Score",
    "ai_score_yoy_change": "YoY Δ",
    "ai_adoption_score_sector_pctile": "Sector %ile",
    "ev_to_ebitda": "EV/EBITDA",
    "operating_margin_calc": "Op Margin",
    "revenue_growth": "Rev Growth",
    "signals": "Signals",
}
available = {k: v for k, v in display_cols.items() if k in df.columns}
screen = df[list(available.keys())].copy()
screen.columns = list(available.values())

# Format numerics
for col, fmt in [("AI Score", 1), ("YoY Δ", 1)]:
    if col in screen.columns:
        screen[col] = screen[col].round(fmt)
if "Sector %ile" in screen.columns:
    screen["Sector %ile"] = (screen["Sector %ile"] * 100).round(0).astype("Int64")
if "EV/EBITDA" in screen.columns:
    screen["EV/EBITDA"] = screen["EV/EBITDA"].round(1)
if "Op Margin" in screen.columns:
    screen["Op Margin"] = (screen["Op Margin"] * 100).round(1)
if "Rev Growth" in screen.columns:
    screen["Rev Growth"] = (screen["Rev Growth"] * 100).round(1)

# Capitalise Tier
if "Tier" in screen.columns:
    screen["Tier"] = screen["Tier"].str.capitalize()

# Sort: signal count desc, AI score desc
screen = screen.sort_values(
    ["AI Score"], ascending=False
).reset_index(drop=True)

show(
    screen,
    caption="AI Deal Screen — latest 10-K scoring",
    scrollX=True,
    pageLength=20,
    classes="display compact stripe",
    columnDefs=[
        {"width": "75px",  "targets": [0]},
        {"width": "170px", "targets": [1]},
        {"width": "110px", "targets": [2]},
        {"width": "75px",  "targets": [3]},
    ],
)
```

## AI Leaders by Sector

```{python}
if not df.empty and "ai_adoption_score" in df.columns and "industry" in df.columns:
    top3 = (
        df.sort_values("ai_adoption_score", ascending=False)
        .groupby("industry")
        .head(3)[["industry", "ticker", "name", "ai_adoption_score", "ai_score_yoy_change", "signals"]]
        .sort_values(["industry", "ai_adoption_score"], ascending=[True, False])
    )

    COLORS = {
        "Technology": "#2980b9", "Financials": "#16a085",
        "Industrials": "#8e44ad", "Healthcare": "#c0392b",
        "Consumer": "#d35400", "Real Estate": "#7f8c8d", "Energy": "#f39c12",
    }

    def sector_card(sector, grp):
        color = COLORS.get(sector, "#555")
        rows = ""
        for _, r in grp.iterrows():
            yoy = r.get("ai_score_yoy_change", np.nan)
            yoy_str = f"+{yoy:.1f}" if pd.notna(yoy) and yoy > 0 else (f"{yoy:.1f}" if pd.notna(yoy) else "—")
            yoy_color = "#1a6b3c" if yoy_str.startswith("+") else "#c0392b"
            badges = render_badges(r.get("signals", ""))
            rows += f"""
            <tr style="border-bottom:1px solid #f0f0f0;">
              <td style="padding:7px 8px;font-weight:600;width:60px;">{r['ticker']}</td>
              <td style="padding:7px 8px;">{r['name']}</td>
              <td style="padding:7px 8px;text-align:right;font-weight:600;width:60px;">{r['ai_adoption_score']:.1f}</td>
              <td style="padding:7px 8px;text-align:right;color:{yoy_color};width:60px;">{yoy_str}</td>
              <td style="padding:7px 8px;white-space:nowrap;">{badges}</td>
            </tr>"""
        return f"""
        <div style="background:#fff;border:1px solid #e8e8e8;border-radius:8px;
                    padding:16px 20px;margin-bottom:20px;overflow:hidden;">
          <h4 style="color:{color};border-left:4px solid {color};padding-left:10px;
                     margin:0 0 12px 0;font-size:1rem;">{sector}</h4>
          <table style="width:100%;border-collapse:collapse;font-size:.84rem;">
            <thead>
              <tr style="border-bottom:2px solid #eee;background:#fafafa;">
                <th style="text-align:left;padding:5px 8px;color:#888;font-weight:600;">Ticker</th>
                <th style="text-align:left;padding:5px 8px;color:#888;font-weight:600;">Company</th>
                <th style="text-align:right;padding:5px 8px;color:#888;font-weight:600;">AI Score</th>
                <th style="text-align:right;padding:5px 8px;color:#888;font-weight:600;">YoY Δ</th>
                <th style="text-align:left;padding:5px 8px;color:#888;font-weight:600;">Signals</th>
              </tr>
            </thead>
            <tbody>{rows}</tbody>
          </table>
        </div>"""

    sectors = sorted(top3["industry"].unique())
    left_sectors  = sectors[::2]   # even indices → left column
    right_sectors = sectors[1::2]  # odd indices → right column

    left_html  = "".join(sector_card(s, top3[top3["industry"] == s]) for s in left_sectors)
    right_html = "".join(sector_card(s, top3[top3["industry"] == s]) for s in right_sectors)

    display(HTML(f"""
    <div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:8px;">
      <div>{left_html}</div>
      <div>{right_html}</div>
    </div>
    """))
```

---

*AI Adoption Scores extracted from SEC EDGAR 10-K filings via NLP. Updated monthly via automated pipeline. Not investment advice.*
