"""
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,
)