backendfastapi / app /screener.py
eshan6704's picture
Update app/screener.py
8ee03c3 verified
import requests
import pandas as pd
from bs4 import BeautifulSoup
from typing import List, Tuple
from . import persist
# ===============================
# SCREENER MAP (OWNER)
# ===============================
SCREENER_MAP = {
"from_high": "https://www.screener.in/screens/3355081/from-high/",
"sales_wise": "https://www.screener.in/screens/880780/sales_wise/",
"fii_buying": "https://www.screener.in/screens/343087/fii-buying/",
"debt_reduction": "https://www.screener.in/screens/126864/debt-reduction/",
"magic_formula": "https://www.screener.in/screens/59/magic-formula/",
}
# ===============================
# Public API
# ===============================
def fetch_screener(screen_name: str) -> str:
"""
Returns a fully styled HTML table for a given screener name.
Uses disk persistence (HTML primary, CSV secondary).
"""
url = SCREENER_MAP.get(screen_name)
if not url:
return _error_html(f"Invalid screener: {screen_name}")
cache_name = f"SCREENER_{screen_name.upper()}"
# 1️⃣ Cache hit
if persist.exists(cache_name, "html"):
return persist.load(cache_name, "html")
# 2️⃣ Fetch live
headers, rows = _fetch_table(url)
if not headers or not rows:
return _error_html("No data available")
# 3️⃣ Build outputs
html = _build_html(headers, rows)
csv_df = pd.DataFrame(rows, columns=headers)
# 4️⃣ Persist
persist.save(cache_name, html, "html")
persist.save(cache_name, csv_df, "csv")
return html
# ===============================
# Internal helpers
# ===============================
def _fetch_table(url: str) -> Tuple[List[str], List[List[str]]]:
r = requests.get(
url,
headers={"User-Agent": "Mozilla/5.0"},
timeout=15
)
r.raise_for_status()
soup = BeautifulSoup(r.text, "html.parser")
table = soup.find("table")
if not table:
return [], []
thead = table.find("thead")
header_row = thead.find("tr") if thead else table.find("tr")
headers = [th.get_text(strip=True) for th in header_row.find_all("th")]
rows = []
for tr in table.find_all("tr")[1:]:
cells = tr.find_all("td")
if cells:
rows.append([td.get_text(strip=True) for td in cells])
return headers, rows
def _build_html(headers: List[str], rows: List[List[str]]) -> str:
style = """
<style>
.screener-wrap {
width: 100%;
overflow-x: auto;
font-family: Arial, sans-serif;
}
table.screener {
border-collapse: collapse;
width: 100%;
min-width: 900px;
font-size: 13px;
}
table.screener th {
position: sticky;
top: 0;
background: #1e293b;
color: #ffffff;
padding: 8px;
border: 1px solid #334155;
white-space: nowrap;
}
table.screener td {
padding: 6px 8px;
border: 1px solid #e5e7eb;
white-space: nowrap;
}
table.screener tr:nth-child(even) {
background: #f8fafc;
}
table.screener tr:hover {
background: #e0f2fe;
}
</style>
"""
html = [style, "<div class='screener-wrap'>", "<table class='screener'>"]
html.append("<tr>")
for h in headers:
html.append(f"<th>{h}</th>")
html.append("</tr>")
for row in rows:
html.append("<tr>")
for cell in row:
html.append(f"<td>{cell}</td>")
html.append("</tr>")
html.append("</table></div>")
return "".join(html)
def _error_html(msg: str) -> str:
return f"""
<div style="
color:#b91c1c;
background:#fee2e2;
padding:12px;
border-radius:6px;
font-family:Arial;
">
{msg}
</div>
"""