Source code for src.classes.vulnerability

"""
This module defines the Vulnerability class and related data classes for representing healthcare vulnerabilities across various subsectors.
Each vulnerability includes required fields such as id, title, source_name, direct_link, subsector, date_accessed, date_published, and content.
Optional fields include exec_summary, confidence_level, risk_level, geography_scope, start_date, end_date, resilience_or_mitigation_observed, and subsector_data.

Functions:
- Vulnerability.to_dict(): Convert a Vulnerability instance to a dictionary.
- Vulnerability.from_dict(data: dict): Create a Vulnerability instance from a dictionary.
"""

from __future__ import annotations
from dataclasses import dataclass, field


[docs] @dataclass class SubsectorData:
[docs] def to_dict(self) -> dict: raise NotImplementedError
[docs] @classmethod def from_dict(cls, data: dict) -> SubsectorData: raise NotImplementedError
[docs] @dataclass class DrugShortageData(SubsectorData): """Class for representing drug shortage-related healthcare vulnerabilities.""" drug_name: str | None = None generic_name: str | None = None manufacturer: str | None = None dosage_form: str | None = None shortage_reason: str | None = None estimated_resolution_date: str | None = None affected_regions: list[str] | None = None domestic_vs_foreign_dependency: str | None = None # domestic | foreign | both
[docs] def to_dict(self) -> dict: """Convert the DrugShortageData instance to a dictionary.""" return { "drug_name": self.drug_name, "generic_name": self.generic_name, "manufacturer": self.manufacturer, "dosage_form": self.dosage_form, "shortage_reason": self.shortage_reason, "estimated_resolution_date": self.estimated_resolution_date, "affected_regions": self.affected_regions, "domestic_vs_foreign_dependency": self.domestic_vs_foreign_dependency, }
[docs] @classmethod def from_dict(cls, data: dict) -> DrugShortageData: """Create a DrugShortageData instance from a dictionary.""" return cls( drug_name=data.get("drug_name"), generic_name=data.get("generic_name"), manufacturer=data.get("manufacturer"), dosage_form=data.get("dosage_form"), shortage_reason=data.get("shortage_reason"), estimated_resolution_date=data.get("estimated_resolution_date"), affected_regions=data.get("affected_regions"), domestic_vs_foreign_dependency=data.get("domestic_vs_foreign_dependency"), )
[docs] @dataclass class MedicalDeviceShortageData(SubsectorData): """Class for representing medical device shortage-related healthcare vulnerabilities.""" device_name: str | None = None device_category: str | None = None manufacturer: str | None = None manufacturer_country: str | None = None shortage_reason: str | None = None fda_recall_number: str | None = None recall_class: str | None = None affected_specialties: list[str] = field(default_factory=list) alternatives_available: bool | None = None estimated_resolution_date: str | None = None domestic_vs_foreign_dependency: str | None = None # domestic | foreign | both
[docs] def to_dict(self) -> dict: """Convert the MedicalDeviceShortageData instance to a dictionary.""" return { "device_name": self.device_name, "device_category": self.device_category, "manufacturer": self.manufacturer, "manufacturer_country": self.manufacturer_country, "shortage_reason": self.shortage_reason, "fda_recall_number": self.fda_recall_number, "recall_class": self.recall_class, "affected_specialties": self.affected_specialties, "alternatives_available": self.alternatives_available, "estimated_resolution_date": self.estimated_resolution_date, "domestic_vs_foreign_dependency": self.domestic_vs_foreign_dependency, }
[docs] @classmethod def from_dict(cls, data: dict) -> MedicalDeviceShortageData: """Create a MedicalDeviceShortageData instance from a dictionary.""" return cls( device_name=data.get("device_name"), device_category=data.get("device_category"), manufacturer=data.get("manufacturer"), manufacturer_country=data.get("manufacturer_country"), shortage_reason=data.get("shortage_reason"), fda_recall_number=data.get("fda_recall_number"), recall_class=data.get("recall_class"), affected_specialties=data.get("affected_specialties") or [], alternatives_available=data.get("alternatives_available"), estimated_resolution_date=data.get("estimated_resolution_date"), domestic_vs_foreign_dependency=data.get("domestic_vs_foreign_dependency"), )
[docs] @dataclass class CyberAttackData(SubsectorData): """Class for representing cyber attack-related healthcare vulnerabilities.""" attack_type: str | None = None threat_actor: str | None = None individuals_affected: int | None = None data_types_exposed: list[str] = field(default_factory=list) systems_affected: list[str] = field(default_factory=list) ransom_demanded_usd: float | None = None ransom_paid: bool | None = None downtime_days: int | None = None services_disrupted: list[str] = field(default_factory=list) law_enforcement_involved: bool | None = None hhs_breach_portal_listed: bool | None = None
[docs] def to_dict(self) -> dict: """Convert the CyberAttackData instance to a dictionary.""" return { "attack_type": self.attack_type, "threat_actor": self.threat_actor, "individuals_affected": self.individuals_affected, "data_types_exposed": self.data_types_exposed, "systems_affected": self.systems_affected, "ransom_demanded_usd": self.ransom_demanded_usd, "ransom_paid": self.ransom_paid, "downtime_days": self.downtime_days, "services_disrupted": self.services_disrupted, "law_enforcement_involved": self.law_enforcement_involved, "hhs_breach_portal_listed": self.hhs_breach_portal_listed, }
[docs] @classmethod def from_dict(cls, data: dict) -> CyberAttackData: """Create a CyberAttackData instance from a dictionary.""" return cls( attack_type=data.get("attack_type"), threat_actor=data.get("threat_actor"), individuals_affected=data.get("individuals_affected"), data_types_exposed=data.get("data_types_exposed") or [], systems_affected=data.get("systems_affected") or [], ransom_demanded_usd=data.get("ransom_demanded_usd"), ransom_paid=data.get("ransom_paid"), downtime_days=data.get("downtime_days"), services_disrupted=data.get("services_disrupted") or [], law_enforcement_involved=data.get("law_enforcement_involved"), hhs_breach_portal_listed=data.get("hhs_breach_portal_listed"), )
[docs] @dataclass class NaturalDisasterData(SubsectorData): """Class for representing natural disaster-related healthcare vulnerabilities.""" disaster_type: str | None = None disaster_name: str | None = None fema_declaration_id: str | None = None category_magnitude: str | None = None affected_facilities_count: int | None = None evacuation_ordered: bool | None = None field_hospitals: int | None = None beds_offline: int | None = None facility_status: str | None = None estimated_damage_usd: float | None = None infrastructure_damage: list[str] = field(default_factory=list) services_disrupted: list[str] = field(default_factory=list)
[docs] def to_dict(self) -> dict: """Convert the NaturalDisasterData instance to a dictionary.""" return { "disaster_type": self.disaster_type, "disaster_name": self.disaster_name, "fema_declaration_id": self.fema_declaration_id, "category_magnitude": self.category_magnitude, "affected_facilities_count": self.affected_facilities_count, "evacuation_ordered": self.evacuation_ordered, "field_hospitals": self.field_hospitals, "beds_offline": self.beds_offline, "facility_status": self.facility_status, "estimated_damage_usd": self.estimated_damage_usd, "infrastructure_damage": self.infrastructure_damage, "services_disrupted": self.services_disrupted, }
[docs] @classmethod def from_dict(cls, data: dict) -> NaturalDisasterData: """Create a NaturalDisasterData instance from a dictionary.""" return cls( disaster_type=data.get("disaster_type"), disaster_name=data.get("disaster_name"), fema_declaration_id=data.get("fema_declaration_id"), category_magnitude=data.get("category_magnitude"), affected_facilities_count=data.get("affected_facilities_count"), evacuation_ordered=data.get("evacuation_ordered"), field_hospitals=data.get("field_hospitals"), beds_offline=data.get("beds_offline"), facility_status=data.get("facility_status"), estimated_damage_usd=data.get("estimated_damage_usd"), infrastructure_damage=data.get("infrastructure_damage") or [], services_disrupted=data.get("services_disrupted") or [], )
[docs] @dataclass class OtherData(SubsectorData): """Class for representing other types of healthcare vulnerabilities that don't fit into predefined categories.""" event_type: str | None = None event_description: str | None = None severity: str | None = None departments_affected: list[str] = field(default_factory=list) staff_type_affected: str | None = None beds_offline: int | None = None services_disrupted: list[str] = field(default_factory=list) regulatory_response: str | None = None
[docs] def to_dict(self) -> dict: """Convert the OtherData instance to a dictionary.""" return { "event_type": self.event_type, "event_description": self.event_description, "severity": self.severity, "departments_affected": self.departments_affected, "staff_type_affected": self.staff_type_affected, "beds_offline": self.beds_offline, "services_disrupted": self.services_disrupted, "regulatory_response": self.regulatory_response, }
[docs] @classmethod def from_dict(cls, data: dict) -> OtherData: """Create an OtherData instance from a dictionary.""" return cls( event_type=data.get("event_type"), event_description=data.get("event_description"), severity=data.get("severity"), departments_affected=data.get("departments_affected") or [], staff_type_affected=data.get("staff_type_affected"), beds_offline=data.get("beds_offline"), services_disrupted=data.get("services_disrupted") or [], regulatory_response=data.get("regulatory_response"), )
SUBSECTOR_DATA_CLASSES: dict[str, type[SubsectorData]] = { "drug_shortage": DrugShortageData, "medical_device_shortage": MedicalDeviceShortageData, "cyber_attack": CyberAttackData, "natural_disaster": NaturalDisasterData, "other": OtherData, }
[docs] @dataclass class Vulnerability: """Class representing a healthcare vulnerability with both required and optional fields.""" # Required fields id: str title: str source_name: str direct_link: str subsector: str date_accessed: str date_published: str content: str # Optional fields exec_summary: str = "" confidence_level: str | None = None # high | medium | low risk_level: str | None = None geography_scope: str | None = None start_date: str | None = None # YYYY-MM-DD end_date: str | None = None # YYYY-MM-DD resilience_or_mitigation_observed: str | None = None subsector_data: SubsectorData | None = None
[docs] def to_dict(self) -> dict: """Convert the Vulnerability instance to a dictionary.""" return { "id": self.id, "title": self.title, "source_name": self.source_name, "direct_link": self.direct_link, "subsector": self.subsector, "date_accessed": self.date_accessed, "date_published": self.date_published, "content": self.content, "exec_summary": self.exec_summary, "confidence_level": self.confidence_level, "risk_level": self.risk_level, "geography_scope": self.geography_scope, "start_date": self.start_date, "end_date": self.end_date, "resilience_or_mitigation_observed": self.resilience_or_mitigation_observed, "subsector_data": self.subsector_data.to_dict() if self.subsector_data else {}, }
[docs] @classmethod def from_dict(cls, data: dict) -> Vulnerability: """ Create a Vulnerability instance from a dictionary. Parameters: - data (dict): A dictionary containing the vulnerability data. Returns: - Vulnerability: An instance of the Vulnerability class populated with the provided data. """ subsector = data.get("subsector", "") subsector_cls = SUBSECTOR_DATA_CLASSES.get(subsector) subsector_data = None if subsector_cls and isinstance(data.get("subsector_data"), dict): subsector_data = subsector_cls.from_dict(data["subsector_data"]) return cls( id=str(data.get("id", "")), title=data.get("title", ""), source_name=data.get("source_name", ""), direct_link=data.get("direct_link", ""), subsector=subsector, date_accessed=data.get("date_accessed", ""), date_published=data.get("date_published", ""), content=data.get("content", ""), exec_summary=data.get("exec_summary", ""), confidence_level=data.get("confidence_level"), risk_level=data.get("risk_level"), geography_scope=data.get("geography_scope"), start_date=data.get("start_date"), end_date=data.get("end_date"), resilience_or_mitigation_observed=data.get( "resilience_or_mitigation_observed" ), subsector_data=subsector_data, )