Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for empower API #16

Merged
merged 16 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions health_rec/api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ class Config:

Attributes
----------
TEST_MODE : bool
Flag to indicate if the application is running in test mode.
OPENAI_API_KEY : str
API key for OpenAI services.
OPENAI_MODEL : str
Expand All @@ -54,14 +52,13 @@ class Config:
The weight of the relevancy score in the ranking strategy.
"""

TEST_MODE: bool = getenv("TEST_MODE", "False").lower() == "true"
OPENAI_API_KEY: str = getenv("OPENAI_API_KEY", "")
OPENAI_MODEL: str = getenv("OPENAI_MODEL", "gpt-4o-mini")
OPENAI_EMBEDDING: Optional[str] = (
getenv("OPENAI_EMBEDDING", "text-embedding-3-small") if not TEST_MODE else None
OPENAI_EMBEDDING: Optional[str] = getenv(
"OPENAI_EMBEDDING", "text-embedding-3-small"
)
COHERE_API_KEY: str = getenv("COHERE_API_KEY", "")
CHROMA_HOST: str = getenv("CHROMA_HOST", "chromadb-dev")
CHROMA_PORT: int = 8000
COLLECTION_NAME: str = getenv("COLLECTION_NAME", "test")
COLLECTION_NAME: str = getenv("COLLECTION_NAME", "empower")
RELEVANCY_WEIGHT: float = float(getenv("RELEVANCY_WEIGHT", "0.5"))
337 changes: 164 additions & 173 deletions health_rec/api/data.py
Original file line number Diff line number Diff line change
@@ -1,192 +1,183 @@
"""Data models."""

from datetime import datetime
from enum import Enum
from typing import Any, Dict, List, Optional

from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, validator


class PhoneNumber(BaseModel):
"""
Represents a phone number with various attributes.
class ServiceType(str, Enum):
"""Standardized service types across different APIs."""

Attributes
----------
phone : str
The phone number.
name : Optional[str]
The name of the phone number.
description : Optional[str]
The description of the phone number.
type : Optional[str]
The type of the phone number.
"""
EMERGENCY_ROOM = "emergency_room"
URGENT_CARE = "urgent_care"
WALK_IN_CLINIC = "walk_in_clinic"
PHARMACY = "pharmacy"
MEDICAL_LAB = "medical_lab"
FAMILY_DOCTOR = "family_doctor"
COMMUNITY_SERVICE = "community_service"

phone: Optional[str] = Field(default=None)
name: Optional[str] = Field(default=None)
description: Optional[str] = Field(default=None)
type: Optional[str] = Field(default=None)

class AccessibilityLevel(str, Enum):
"""Wheelchair accessibility levels."""

class Service(BaseModel):
"""
Represents a service with various attributes.
FULL = "full"
PARTIAL = "partial"
NONE = "none"
UNKNOWN = "unknown"

Attributes
----------
id : int
The unique identifier of the service.
parent_id : Optional[int]
The ID of the parent service.
public_name : str
The public name of the service.
score : Optional[int]
The score of the service.
service_area : Optional[List[str]]
The areas where the service is available.
distance : Optional[str]
The distance to the service.
description : Optional[str]
The description of the service.
latitude : Optional[float]
The latitude coordinate of the service location.
longitude : Optional[float]
The longitude coordinate of the service location.
physical_address_street1 : Optional[str]
The first line of the physical address.
physical_address_street2 : Optional[str]
The second line of the physical address.
physical_address_city : Optional[str]
The city of the physical address.
physical_address_province : Optional[str]
The province of the physical address.
physical_address_postal_code : Optional[str]
The postal code of the physical address.
physical_address_country : Optional[str]
The country of the physical address.
mailing_attention_name : Optional[str]
The attention name for mailing.
mailing_address_street1 : Optional[str]
The first line of the mailing address.
mailing_address_street2 : Optional[str]
The second line of the mailing address.
mailing_address_city : Optional[str]
The city of the mailing address.
mailing_address_province : Optional[str]
The province of the mailing address.
mailing_address_postal_code : Optional[str]
The postal code of the mailing address.
mailing_address_country : Optional[str]
The country of the mailing address.
phone_numbers : List[PhoneNumber]
The phone numbers associated with the service.
website : Optional[str]
The website of the service.
email : Optional[str]
The email address of the service.
hours : Optional[str]
The hours of operation.
hours2 : Optional[str]
Additional hours of operation.
min_age : Optional[str]
The minimum age for the service.
max_age : Optional[str]
The maximum age for the service.
updated_on : Optional[str]
The date and time the service was last updated.
taxonomy_term : Optional[str]
The taxonomy terms associated with the service.
taxonomy_terms : Optional[str]
Additional taxonomy terms.
taxonomy_codes : Optional[str]
The taxonomy codes associated with the service.
eligibility : Optional[str]
The eligibility criteria for the service.
fee_structure_source : Optional[str]
The source of the fee structure.
official_name : Optional[str]
The official name of the service.
physical_city : Optional[str]
The physical city of the service.
unique_id_prior_system : Optional[str]
The unique ID from a prior system.
record_owner : Optional[str]
The owner of the record.
"""

class DayOfWeek(str, Enum):
"""Days of the week."""

SUNDAY = "sunday"
MONDAY = "monday"
TUESDAY = "tuesday"
WEDNESDAY = "wednesday"
THURSDAY = "thursday"
FRIDAY = "friday"
SATURDAY = "saturday"


class OperatingHours(BaseModel):
"""Operating hours for a specific day."""

day: DayOfWeek
is_open: bool
is_24hour: bool = False
open_time: Optional[str] = None
close_time: Optional[str] = None


class HoursException(BaseModel):
"""Special hours or holiday schedules."""

name: Optional[str] = None
start_date: datetime
end_date: datetime
is_open: bool
is_24hour: bool = False
open_time: Optional[str] = None
close_time: Optional[str] = None


class Address(BaseModel):
"""Physical address information."""

street1: Optional[str] = None
street2: Optional[str] = None
city: Optional[str] = None
province: Optional[str] = None
postal_code: Optional[str] = None
country: Optional[str] = None
attention_name: Optional[str] = None


class PhoneNumber(BaseModel):
"""Phone number with additional metadata."""

number: str
type: Optional[str] = None
name: Optional[str] = None
description: Optional[str] = None
extension: Optional[str] = None


class Service(BaseModel):
"""Standardized service model that can accommodate data from multiple APIs."""

# Core identification
id: int
parent_id: Optional[int] = Field(default=None, alias="ParentId")
public_name: str = Field(alias="PublicName")
score: Optional[int] = Field(default=None, alias="Score")
service_area: Optional[List[str]] = Field(default=None, alias="ServiceArea")
distance: Optional[str] = Field(default=None, alias="Distance")
description: Optional[str] = Field(default=None, alias="Description")
latitude: Optional[float] = Field(default=None, alias="Latitude")
longitude: Optional[float] = Field(default=None, alias="Longitude")
physical_address_street1: Optional[str] = Field(
default=None, alias="PhysicalAddressStreet1"
)
physical_address_street2: Optional[str] = Field(
default=None, alias="PhysicalAddressStreet2"
)
physical_address_city: Optional[str] = Field(
default=None, alias="PhysicalAddressCity"
)
physical_address_province: Optional[str] = Field(
default=None, alias="PhysicalAddressProvince"
)
physical_address_postal_code: Optional[str] = Field(
default=None, alias="PhysicalAddressPostalCode"
)
physical_address_country: Optional[str] = Field(
default=None, alias="PhysicalAddressCountry"
)
mailing_attention_name: Optional[str] = Field(
default=None, alias="MailingAttentionName"
)
mailing_address_street1: Optional[str] = Field(
default=None, alias="MailingAddressStreet1"
)
mailing_address_street2: Optional[str] = Field(
default=None, alias="MailingAddressStreet2"
)
mailing_address_city: Optional[str] = Field(
default=None, alias="MailingAddressCity"
)
mailing_address_province: Optional[str] = Field(
default=None, alias="MailingAddressProvince"
)
mailing_address_postal_code: Optional[str] = Field(
default=None, alias="MailingAddressPostalCode"
)
mailing_address_country: Optional[str] = Field(
default=None, alias="MailingAddressCountry"
)
phone_numbers: List[PhoneNumber] = Field(default_factory=list, alias="PhoneNumbers")
website: Optional[str] = Field(default=None, alias="Website")
email: Optional[str] = Field(default=None, alias="Email")
hours: Optional[str] = Field(default=None, alias="Hours")
hours2: Optional[str] = Field(default=None, alias="Hours2")
min_age: Optional[str] = Field(default=None, alias="MinAge")
max_age: Optional[str] = Field(default=None, alias="MaxAge")
updated_on: Optional[str] = Field(default=None, alias="UpdatedOn")
taxonomy_term: Optional[str] = Field(default=None, alias="TaxonomyTerm")
taxonomy_terms: Optional[str] = Field(default=None, alias="TaxonomyTerms")
taxonomy_codes: Optional[str] = Field(default=None, alias="TaxonomyCodes")
eligibility: Optional[str] = Field(default=None, alias="Eligibility")
fee_structure_source: Optional[str] = Field(
default=None, alias="FeeStructureSource"
)
official_name: Optional[str] = Field(default=None, alias="OfficialName")
physical_city: Optional[str] = Field(default=None, alias="PhysicalCity")
unique_id_prior_system: Optional[str] = Field(
default=None, alias="UniqueIDPriorSystem"
)
record_owner: Optional[str] = Field(default=None, alias="RecordOwner")
name: str
service_type: ServiceType
source_id: Optional[str] = None
official_name: Optional[str] = None

# Location
latitude: float
longitude: float
distance: Optional[float] = None
physical_address: Optional[Address] = None
mailing_address: Optional[Address] = None

# Contact information
phone_numbers: List[PhoneNumber] = Field(default_factory=list)
fax: Optional[str] = None
email: Optional[str] = None
website: Optional[str] = None
social_media: Dict[str, str] = Field(default_factory=dict)

# Service details
description: Optional[str] = None
services: List[str] = Field(default_factory=list)
languages: List[str] = Field(default_factory=list)
taxonomy_terms: List[str] = Field(default_factory=list)
taxonomy_codes: List[str] = Field(default_factory=list)

# Operating information
status: Optional[str] = None
regular_hours: List[OperatingHours] = Field(default_factory=list)
hours_exceptions: List[HoursException] = Field(default_factory=list)
timezone_offset: Optional[str] = None

# Accessibility and special features
wheelchair_accessible: AccessibilityLevel = AccessibilityLevel.UNKNOWN
parking_type: Optional[str] = None
accepts_new_patients: Optional[bool] = None
wait_time: Optional[int] = None

# Booking capabilities
has_online_booking: bool = False
has_queue_system: bool = False
accepts_walk_ins: bool = False
can_book: bool = False

# Eligibility and fees
eligibility_criteria: Optional[str] = None
fee_structure: Optional[str] = None
min_age: Optional[int] = None
max_age: Optional[int] = None

# Metadata
last_updated: Optional[datetime] = None
record_owner: Optional[str] = None
data_source: Optional[str] = None # e.g., "211", "Empower"

class Config:
"""Override Pydantic configuration."""
"""Pydantic configuration."""

use_enum_values = True

@validator("wheelchair_accessible", pre=True)
def normalize_wheelchair_access(cls, v: str) -> AccessibilityLevel: # noqa: N805
"""Normalize wheelchair accessibility values from different sources."""
if isinstance(v, str):
mapping = {
"t": AccessibilityLevel.FULL,
"true": AccessibilityLevel.FULL,
"p": AccessibilityLevel.PARTIAL,
"partial": AccessibilityLevel.PARTIAL,
"f": AccessibilityLevel.NONE,
"false": AccessibilityLevel.NONE,
}
return mapping.get(v.lower(), AccessibilityLevel.UNKNOWN)
return AccessibilityLevel.UNKNOWN

populate_by_name = True
@validator("service_type", pre=True)
def normalize_service_type(cls, v: str) -> ServiceType: # noqa: N805
"""Normalize service type values from different sources."""
if isinstance(v, str):
mapping = {
"Retail Pharmacy": ServiceType.PHARMACY,
"Emergency Rooms": ServiceType.EMERGENCY_ROOM,
"Urgent Care Centre": ServiceType.URGENT_CARE,
"Primary Care Walk-In Clinic": ServiceType.WALK_IN_CLINIC,
"Family Doctor's Office": ServiceType.FAMILY_DOCTOR,
"Medical Labs & Diagnostic Imaging Centres": ServiceType.MEDICAL_LAB,
}
return mapping.get(v, ServiceType.COMMUNITY_SERVICE)
return ServiceType.COMMUNITY_SERVICE


class ServiceDocument(BaseModel):
Expand Down
1 change: 1 addition & 0 deletions health_rec/api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ async def get_additional_questions(
A dictionary with the generated questions.
"""
try:
logger.info(f"Received query for additional questions: {query}")
questions = refine_service.generate_questions(query, recommendation)
return {"questions": questions}
except Exception as e:
Expand Down
Loading