File size: 3,480 Bytes
c496840
1638b2e
c496840
1638b2e
2753128
f927995
 
2753128
 
 
1638b2e
 
2753128
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f927995
1638b2e
 
 
 
 
 
2753128
1638b2e
 
 
 
f59a149
2753128
1638b2e
 
 
 
 
f927995
1638b2e
2753128
1638b2e
2753128
1638b2e
f927995
 
 
 
 
2753128
1638b2e
 
f927995
1638b2e
f927995
1638b2e
2753128
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1638b2e
f927995
 
 
 
1638b2e
2753128
1638b2e
 
 
 
 
2753128
1638b2e
 
2753128
 
 
 
 
f927995
2753128
 
1638b2e
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
"""
Configuration management for the PharmaShield backend using Pydantic Settings.
"""

import json
import logging
from pathlib import Path
from typing import List

from pydantic import field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict


def _discover_base_dir() -> Path:
    """
    Find the project base directory by looking for bundled seed data.

    Local development usually has ``repo_root/data``.
    Backend-only serverless deployments use ``backend/data``.
    """
    config_path = Path(__file__).resolve()

    for candidate in reversed(config_path.parents):
        if (candidate / "data" / "seed" / "drugs.json").exists():
            return candidate

    # Fallback to the backend root if no data bundle is found yet.
    return config_path.parent.parent


BASE_DIR = _discover_base_dir()


class Settings(BaseSettings):
    """
    Application settings and environment variables.
    Defaults are provided where safe; sensitive keys must be provided in .env or environment.
    """

    # API Keys & Endpoints
    GEMINI_API_KEY: str = ""
    QDRANT_URL: str = ""
    QDRANT_API_KEY: str = ""
    GOOGLE_MAPS_API_KEY: str = ""

    # Project Info
    FIREBASE_PROJECT_ID: str = ""
    BACKEND_URL: str = "http://localhost:8080"
    ALLOWED_ORIGINS: List[str] = [
        "http://localhost:3000",
        "http://localhost:5173",
        "http://localhost:8080",
        "https://pharmashield.web.app",
    ]

    # Paths
    SEED_DATA_DIR: str = str(BASE_DIR / "data" / "seed")
    GNN_WEIGHTS_PATH: str = str(BASE_DIR / "ml" / "weights" / "gnn_v1.pt")
    ENABLE_GNN: bool = False
    DEMO_MODE: bool = True
    DEMO_SCENARIOS_PATH: str = str(BASE_DIR / "data" / "seed" / "demo_scenarios.json")

    # Model & Database Configurations
    QDRANT_COLLECTION: str = "pharmashield_kb"
    GEMINI_FLASH_MODEL: str = "gemini-2.0-flash"
    GEMINI_PRO_MODEL: str = "gemini-2.5-pro"
    EMBEDDING_MODEL: str = "models/gemini-embedding-001"

    @field_validator("ALLOWED_ORIGINS", mode="before")
    @classmethod
    def parse_allowed_origins(cls, value):
        if isinstance(value, list):
            return value
        if isinstance(value, str):
            text = value.strip()
            if not text:
                return []
            if text.startswith("["):
                try:
                    parsed = json.loads(text)
                except json.JSONDecodeError:
                    parsed = None
                if isinstance(parsed, list):
                    return [str(item).strip() for item in parsed if str(item).strip()]
            return [item.strip() for item in text.split(",") if item.strip()]
        return value

    model_config = SettingsConfigDict(
        env_file=(
            BASE_DIR / "backend" / ".env",
            BASE_DIR / ".env",
        ),
        env_file_encoding="utf-8",
        extra="ignore",
    )


try:
    settings = Settings()

    if not settings.GEMINI_API_KEY or not settings.QDRANT_URL:
        missing = []
        if not settings.GEMINI_API_KEY:
            missing.append("GEMINI_API_KEY")
        if not settings.QDRANT_URL:
            missing.append("QDRANT_URL")

        logging.warning(
            "Missing non-critical configuration: %s. Some features may be disabled.",
            ", ".join(missing),
        )
except Exception as e:
    if isinstance(e, RuntimeError):
        raise e
    raise RuntimeError(f"Failed to load application settings: {str(e)}")