Jitendra12421 commited on
Commit
bc2ae54
·
verified ·
1 Parent(s): f32b8de

Force sync entire directory including feature_pipeline.py

Browse files
__pycache__/feature_pipeline.cpython-311.pyc CHANGED
Binary files a/__pycache__/feature_pipeline.cpython-311.pyc and b/__pycache__/feature_pipeline.cpython-311.pyc differ
 
__pycache__/forecaster_cli.cpython-311.pyc ADDED
Binary file (19.5 kB). View file
 
feature_pipeline.py CHANGED
@@ -1,5 +1,6 @@
1
  from __future__ import annotations
2
 
 
3
  import re
4
  import threading
5
  from datetime import datetime
@@ -14,6 +15,8 @@ from bs4 import BeautifulSoup
14
  from requests.adapters import HTTPAdapter
15
  from urllib3.util.retry import Retry
16
 
 
 
17
  def _browser_headers() -> dict:
18
  return {
19
  "User-Agent": (
@@ -28,12 +31,24 @@ def _browser_headers() -> dict:
28
  "Accept-Language": "en-US,en;q=0.9",
29
  "Accept-Encoding": "gzip, deflate, br",
30
  "Connection": "keep-alive",
31
- "Upgrade-Insecure-Requests": "1",
32
  "Cache-Control": "no-cache",
33
  "Pragma": "no-cache",
34
  "Referer": "https://www.screener.in/",
 
 
 
 
 
35
  }
36
 
 
 
 
 
 
 
 
 
37
  class _ThreadLocalSessionFactory:
38
  def __init__(
39
  self,
@@ -76,7 +91,6 @@ class _ThreadLocalSessionFactory:
76
  if not hasattr(self._local, "session"):
77
  self._local.session = self._build_session()
78
 
79
- # Warm up cookies / anti-bot checks by visiting the homepage first.
80
  if self.prime_homepage:
81
  try:
82
  self._local.session.get(
@@ -93,6 +107,7 @@ class ScreenerScraper:
93
  self,
94
  timeout: Tuple[float, float] = (5.0, 15.0),
95
  pool_maxsize: int = 32,
 
96
  ):
97
  self._session_factory = _ThreadLocalSessionFactory(
98
  timeout=timeout,
@@ -100,23 +115,16 @@ class ScreenerScraper:
100
  prime_homepage=True,
101
  )
102
  self.timeout = timeout
 
103
 
104
  def _session(self) -> requests.Session:
105
  return self._session_factory.get()
106
 
107
  @staticmethod
108
- def _make_soup(html: str):
109
- try:
110
- return BeautifulSoup(html, "lxml")
111
- except Exception:
112
- return BeautifulSoup(html, "html.parser")
113
 
114
- @staticmethod
115
- def _normalize_ticker(ticker: str) -> str:
116
- t = ticker.strip().upper()
117
- return quote(t, safe="-._~")
118
-
119
- def _fetch_url(self, url: str) -> str:
120
  session = self._session()
121
  response = session.get(url, timeout=self.timeout)
122
 
@@ -125,48 +133,117 @@ class ScreenerScraper:
125
 
126
  preview = ""
127
  try:
128
- preview = response.text[:300].replace("\n", " ").replace("\r", " ")
129
  except Exception:
130
- preview = "<no response text>"
131
 
132
  raise RuntimeError(
133
- f"Request failed.\n"
134
  f"URL: {url}\n"
135
  f"Status: {response.status_code}\n"
136
  f"Reason: {response.reason}\n"
137
- f"Response preview: {preview}"
138
  )
139
 
140
- def _fetch_html(self, ticker: str, consolidated: bool = True) -> str:
141
- t = self._normalize_ticker(ticker)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
- if consolidated:
144
- urls = [
145
- f"https://www.screener.in/company/{t}/consolidated/",
146
- f"https://www.screener.in/company/{t}/",
 
 
147
  ]
148
- else:
149
- urls = [
150
- f"https://www.screener.in/company/{t}/",
151
- f"https://www.screener.in/company/{t}/consolidated/",
152
  ]
 
153
 
154
  last_error: Optional[Exception] = None
155
-
156
  for url in urls:
157
  try:
158
- html = self._fetch_url(url)
159
  return html
160
  except Exception as e:
161
  last_error = e
162
- continue
163
 
164
- raise RuntimeError(
165
- f"Failed to fetch data for '{ticker}'. "
166
- f"Last error: {last_error}"
167
- ) from last_error
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
  @staticmethod
 
 
 
 
 
 
170
  def _clean_text(value: str) -> str:
171
  return " ".join(value.split()).replace("₹", "Rs.")
172
 
 
1
  from __future__ import annotations
2
 
3
+ import logging
4
  import re
5
  import threading
6
  from datetime import datetime
 
15
  from requests.adapters import HTTPAdapter
16
  from urllib3.util.retry import Retry
17
 
18
+ logger = logging.getLogger(__name__)
19
+
20
  def _browser_headers() -> dict:
21
  return {
22
  "User-Agent": (
 
31
  "Accept-Language": "en-US,en;q=0.9",
32
  "Accept-Encoding": "gzip, deflate, br",
33
  "Connection": "keep-alive",
 
34
  "Cache-Control": "no-cache",
35
  "Pragma": "no-cache",
36
  "Referer": "https://www.screener.in/",
37
+ "Upgrade-Insecure-Requests": "1",
38
+ "Sec-Fetch-Dest": "document",
39
+ "Sec-Fetch-Mode": "navigate",
40
+ "Sec-Fetch-Site": "none",
41
+ "Sec-Fetch-User": "?1",
42
  }
43
 
44
+ def _clean_number(text: str) -> float:
45
+ return float(text.replace(",", "").strip())
46
+
47
+ @dataclass(frozen=True)
48
+ class CapThresholds:
49
+ large: float = 20000.0
50
+ mid: float = 5000.0
51
+
52
  class _ThreadLocalSessionFactory:
53
  def __init__(
54
  self,
 
91
  if not hasattr(self._local, "session"):
92
  self._local.session = self._build_session()
93
 
 
94
  if self.prime_homepage:
95
  try:
96
  self._local.session.get(
 
107
  self,
108
  timeout: Tuple[float, float] = (5.0, 15.0),
109
  pool_maxsize: int = 32,
110
+ thresholds: CapThresholds = CapThresholds(),
111
  ):
112
  self._session_factory = _ThreadLocalSessionFactory(
113
  timeout=timeout,
 
115
  prime_homepage=True,
116
  )
117
  self.timeout = timeout
118
+ self.thresholds = thresholds
119
 
120
  def _session(self) -> requests.Session:
121
  return self._session_factory.get()
122
 
123
  @staticmethod
124
+ def _normalize_symbol(symbol: str) -> str:
125
+ return quote(symbol.strip().upper(), safe="-._~")
 
 
 
126
 
127
+ def _fetch(self, url: str) -> str:
 
 
 
 
 
128
  session = self._session()
129
  response = session.get(url, timeout=self.timeout)
130
 
 
133
 
134
  preview = ""
135
  try:
136
+ preview = response.text[:250].replace("\n", " ").replace("\r", " ")
137
  except Exception:
138
+ preview = "<no preview>"
139
 
140
  raise RuntimeError(
141
+ f"Request failed\n"
142
  f"URL: {url}\n"
143
  f"Status: {response.status_code}\n"
144
  f"Reason: {response.reason}\n"
145
+ f"Preview: {preview}"
146
  )
147
 
148
+ @staticmethod
149
+ def _parse_market_cap_crore(html: str) -> Optional[float]:
150
+ patterns = [
151
+ r"Mkt Cap:\s*([0-9][0-9,]*(?:\.[0-9]+)?)\s*Crore",
152
+ r"Market Cap\s*₹\s*([0-9][0-9,]*(?:\.[0-9]+)?)\s*Cr\.?",
153
+ r"Market Cap:\s*₹\s*([0-9][0-9,]*(?:\.[0-9]+)?)\s*Cr\.?",
154
+ r"Market Capitalization.*?₹\s*([0-9][0-9,]*(?:\.[0-9]+)?)\s*Cr\.?",
155
+ ]
156
+
157
+ for pattern in patterns:
158
+ match = re.search(pattern, html, flags=re.IGNORECASE | re.DOTALL)
159
+ if match:
160
+ try:
161
+ return _clean_number(match.group(1))
162
+ except Exception:
163
+ continue
164
+
165
+ return None
166
 
167
+ def _fetch_screener_html(self, symbol: str, consolidated: bool = True) -> str:
168
+ s = self._normalize_symbol(symbol)
169
+ urls = (
170
+ [
171
+ f"https://www.screener.in/company/{s}/consolidated/",
172
+ f"https://www.screener.in/company/{s}/",
173
  ]
174
+ if consolidated
175
+ else [
176
+ f"https://www.screener.in/company/{s}/",
177
+ f"https://www.screener.in/company/{s}/consolidated/",
178
  ]
179
+ )
180
 
181
  last_error: Optional[Exception] = None
 
182
  for url in urls:
183
  try:
184
+ html = self._fetch(url)
185
  return html
186
  except Exception as e:
187
  last_error = e
 
188
 
189
+ raise RuntimeError(f"Failed to fetch Screener page for {symbol}. Last error: {last_error}") from last_error
190
+
191
+ def _fetch_nse_quote_html(self, symbol: str) -> str:
192
+ s = self._normalize_symbol(symbol)
193
+ url = f"https://www.nseindia.com/get-quotes/equity?symbol={s}"
194
+ session = self._session()
195
+ try:
196
+ session.get("https://www.nseindia.com/", timeout=self.timeout)
197
+ except requests.RequestException:
198
+ pass
199
+ return self._fetch(url)
200
+
201
+ def get_market_cap_crore(self, symbol: str, consolidated: bool = True) -> Optional[float]:
202
+ try:
203
+ html = self._fetch_screener_html(symbol, consolidated=consolidated)
204
+ cap = self._parse_market_cap_crore(html)
205
+ if cap is not None:
206
+ return cap
207
+ except Exception as e:
208
+ logger.warning("Screener lookup failed for %s: %s", symbol, e)
209
+
210
+ try:
211
+ html = self._fetch_nse_quote_html(symbol)
212
+ cap = self._parse_market_cap_crore(html)
213
+ if cap is not None:
214
+ return cap
215
+ except Exception as e:
216
+ logger.warning("NSE lookup failed for %s: %s", symbol, e)
217
+ return None
218
+
219
+ def classify_market_cap(self, market_cap_crore: Optional[float]) -> str:
220
+ if market_cap_crore is None:
221
+ return "Unknown"
222
+ if market_cap_crore >= self.thresholds.large:
223
+ return "Large Cap"
224
+ if market_cap_crore >= self.thresholds.mid:
225
+ return "Mid Cap"
226
+ return "Small Cap"
227
+
228
+ def get_cap_info(self, symbol: str, consolidated: bool = True) -> dict:
229
+ cap = self.get_market_cap_crore(symbol, consolidated=consolidated)
230
+ return {
231
+ "symbol": symbol.upper().strip(),
232
+ "market_cap_crore": cap,
233
+ "market_cap_class": self.classify_market_cap(cap),
234
+ }
235
+
236
+ # -- Keep original helper methods for get_stock_info --
237
+ def _fetch_html(self, ticker: str, consolidated: bool = True) -> str:
238
+ return self._fetch_screener_html(ticker, consolidated)
239
 
240
  @staticmethod
241
+ def _make_soup(html: str):
242
+ try:
243
+ return BeautifulSoup(html, "lxml")
244
+ except Exception:
245
+ return BeautifulSoup(html, "html.parser")
246
+ @staticmethod
247
  def _clean_text(value: str) -> str:
248
  return " ".join(value.split()).replace("₹", "Rs.")
249