import logging import requests import config from db.db import mark_signal_alerted logger = logging.getLogger(__name__) def format_signal(signal: dict) -> str: filing = signal.get("filing", {}) ticker = signal["ticker"] insider = filing.get("insider_name", "Unknown") role = filing.get("role", "Unknown") tx_date = filing.get("transaction_date", "") shares = filing.get("shares") price = filing.get("price") total_value = filing.get("total_value") or signal.get("total_cluster_value", 0) cluster_size = signal["cluster_size"] score = signal["score"] is_10b51 = "Yes" if filing.get("is_10b51") else "No" accession = filing.get("accession_number", "") shares_str = f"{shares:,.0f}" if shares else "N/A" price_str = f"${price:,.2f}" if price else "N/A" value_str = f"${total_value:,.0f}" if total_value else "N/A" edgar_url = f"https://www.sec.gov/cgi-bin/browse-edgar?action=getcompany&type=4&dateb=&owner=include&count=10&search_text=&ticker={ticker}" return ( f"*INSIDER BUY SIGNAL*\n" f"Ticker: ${ticker}\n" f"Insider: {insider} ({role})\n" f"Date: {tx_date}\n" f"Shares: {shares_str} @ {price_str} = {value_str}\n" f"Cluster: {cluster_size} insider(s) in last {config.CLUSTER_WINDOW_DAYS} days\n" f"Score: {score}\n" f"10b5-1: {is_10b51}\n" f"EDGAR: {edgar_url}" ) def send_slack_alert(signal: dict) -> bool: if not config.SLACK_WEBHOOK_URL: logger.warning("SLACK_WEBHOOK_URL not configured") return False if signal.get("score", 0) < config.SCORE_ALERT_THRESHOLD: logger.debug(f"Signal score {signal['score']} below threshold {config.SCORE_ALERT_THRESHOLD}") return False text = format_signal(signal) try: resp = requests.post( config.SLACK_WEBHOOK_URL, json={"text": text}, timeout=10, ) resp.raise_for_status() mark_signal_alerted(signal["id"]) logger.info(f"Slack alert sent for {signal['ticker']}") return True except Exception as e: logger.error(f"Failed to send Slack alert: {e}") return False