Scrapes EC Medical Devices Latest Updates page for regulatory news. This page covers a much wider scope than the MDCG guidance list: - Delegated Regulations (e.g. WET expansions under Art. 52(4), 61(6)(b)) - Implementing Regulations (e.g. conformity assessment, notified bodies) - MD
| 1238 | # --------------------------------------------------------------------------- |
| 1239 | |
| 1240 | class ECLatestUpdatesChecker: |
| 1241 | """Scrapes EC Medical Devices Latest Updates page for regulatory news. |
| 1242 | |
| 1243 | This page covers a much wider scope than the MDCG guidance list: |
| 1244 | - Delegated Regulations (e.g. WET expansions under Art. 52(4), 61(6)(b)) |
| 1245 | - Implementing Regulations (e.g. conformity assessment, notified bodies) |
| 1246 | - MDCG position papers and new guidance |
| 1247 | - EUDAMED updates, MIR form updates |
| 1248 | - Policy announcements (breakthrough device programmes, etc.) |
| 1249 | """ |
| 1250 | |
| 1251 | URL = "https://ec.europa.eu/health/medical-devices-sector/latest-updates_en" |
| 1252 | |
| 1253 | _PRIORITY_KEYWORDS = re.compile( |
| 1254 | r"(?i)(delegated\s+(?:act|regulation)|implementing\s+regulation|" |
| 1255 | r"well.established|harmonised\s+standard|common\s+specification|" |
| 1256 | r"clinical\s+investigation|classification|notified\s+bod|" |
| 1257 | r"unique\s+device\s+identif|UDI|EUDAMED|conformity\s+assessment|" |
| 1258 | r"borderline|breakthrough|Article\s+\d+)", |
| 1259 | ) |
| 1260 | |
| 1261 | def __init__(self, session: requests.Session, state: dict): |
| 1262 | self.session = session |
| 1263 | self.state = state |
| 1264 | |
| 1265 | def check(self, source_id: str, source: dict) -> Optional[dict]: |
| 1266 | prev = self.state.get(source_id, {}) |
| 1267 | prev_titles = set(prev.get("seen_titles", [])) |
| 1268 | |
| 1269 | try: |
| 1270 | resp = self.session.get(self.URL, timeout=30) |
| 1271 | resp.raise_for_status() |
| 1272 | except Exception as e: |
| 1273 | print(f" ERROR fetching EC Latest Updates: {e}") |
| 1274 | return None |
| 1275 | |
| 1276 | entries = self._parse_entries(resp.text) |
| 1277 | if not entries: |
| 1278 | print(f" WARNING: No entries parsed from EC Latest Updates page") |
| 1279 | return None |
| 1280 | |
| 1281 | print(f" INFO: Parsed {len(entries)} entries from EC Latest Updates page") |
| 1282 | |
| 1283 | new_items = [] |
| 1284 | all_titles = list(prev_titles) |
| 1285 | |
| 1286 | for entry in entries: |
| 1287 | title = entry.get("title", "") |
| 1288 | if not title or title in prev_titles: |
| 1289 | continue |
| 1290 | all_titles.append(title) |
| 1291 | new_items.append({ |
| 1292 | "title": title, |
| 1293 | "link": entry.get("link", ""), |
| 1294 | "pub_date": entry.get("date", ""), |
| 1295 | "description": entry.get("type", ""), |
| 1296 | }) |
| 1297 |