Spaces:
Sleeping
Sleeping
File size: 8,015 Bytes
9b159c2 | 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 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | """
scripts/setup.py
ββββββββββββββββ
One-command pipeline: download β preprocess β validate β ingest β index
Usage
-----
python scripts/setup.py # auto-detect Kaggle or use seed
python scripts/setup.py --source kaggle # force Kaggle download
python scripts/setup.py --source seed # use bundled seed data
python scripts/setup.py --skip-index # skip Qdrant indexing (ingest only)
python scripts/setup.py --rebuild # drop DB + Qdrant and start fresh
python scripts/setup.py --stats-only # print stats on existing DB
"""
from __future__ import annotations
import argparse
import asyncio
import sys
import time
from pathlib import Path
# Make root importable
sys.path.insert(0, str(Path(__file__).parent.parent))
from rich.console import Console
from rich.panel import Panel
from rich.rule import Rule
from rich import box
console = Console()
def banner() -> None:
console.print(
Panel.fit(
"[bold cyan]PharmaAI[/bold cyan] β Data Setup Pipeline\n"
"[dim]Medicine Alternative Recommendation System[/dim]",
box=box.DOUBLE_EDGE,
border_style="cyan",
padding=(1, 4),
)
)
def step(n: int, total: int, label: str) -> None:
console.print(f"\n[bold cyan][{n}/{total}][/bold cyan] {label}")
def run(args: argparse.Namespace) -> None:
start = time.perf_counter()
banner()
from core.config import settings
from core.database import init_db, get_connection
from data.pipeline.download import DataDownloader
from data.pipeline.preprocess import Preprocessor
from data.pipeline.validate import Validator
from data.pipeline.stats import DataStats
TOTAL_STEPS = 4 if args.skip_index else 5
# ββ Stats-only mode ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
if args.stats_only:
import pandas as pd
init_db()
with get_connection() as conn:
df = pd.read_sql("SELECT * FROM medicines", conn)
if df.empty:
console.print("[red]No medicines in DB. Run setup first.[/red]")
return
DataStats.summary(df, "Current Database")
DataStats.sample(df)
return
# ββ Rebuild: drop existing data ββββββββββββββββββββββββββββββββββββββββββββ
if args.rebuild:
console.print(Rule("[yellow]Rebuild mode β dropping existing data[/yellow]"))
db_path = Path(settings.DB_PATH)
if db_path.exists():
db_path.unlink()
console.print(f" [dim]Removed {db_path}[/dim]")
qdrant_path = Path(settings.QDRANT_PATH)
if qdrant_path.exists():
import shutil
shutil.rmtree(qdrant_path)
console.print(f" [dim]Removed {qdrant_path}[/dim]")
# ββ Step 1: Download βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
step(1, TOTAL_STEPS, "Downloading / loading raw data")
downloader = DataDownloader()
if args.source == "kaggle":
from data.pipeline.download import RAW_DIR, KAGGLE_FILENAME
raw_path = downloader._download_kaggle(RAW_DIR / KAGGLE_FILENAME)
elif args.source == "seed":
from data.pipeline.seed_generator import generate_csv
raw_path = Path(settings.DB_PATH).parent / "raw" / "seed_medicines.csv"
generate_csv(raw_path)
console.print(f"[green]β Seed CSV generated: {raw_path.name}[/green]")
else:
raw_path = downloader.get(force=args.rebuild)
# ββ Step 2: Preprocess ββββββββββββββββββββββββββββββββββββββββββββββββββββ
step(2, TOTAL_STEPS, "Preprocessing β parse compositions, infer categories, clean text")
preprocessor = Preprocessor()
clean_path = preprocessor.run(raw_path)
# ββ Step 3: Validate ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
step(3, TOTAL_STEPS, "Validating data quality")
import pandas as pd
df = pd.read_csv(clean_path)
rejected_path = clean_path.parent / "rejected_rows.csv"
result = Validator().run(df, rejected_path=rejected_path)
DataStats.summary(result.valid, "Validated Dataset")
# ββ Step 4: Ingest to SQLite ββββββββββββββββββββββββββββββββββββββββββββββ
step(4, TOTAL_STEPS, "Loading medicines into SQLite")
init_db()
with get_connection() as conn:
conn.execute("DELETE FROM medicines")
conn.execute("DELETE FROM sqlite_sequence WHERE name = 'medicines'")
ingest_df = result.valid.copy()
text_columns = [
"brand_name", "composition", "salt_name", "manufacturer",
"strength", "category", "description", "uses",
"side_effects", "image_url",
]
ingest_df[text_columns] = ingest_df[text_columns].fillna("")
rows = ingest_df.to_dict(orient="records")
conn.executemany(
"""
INSERT INTO medicines
(brand_name, composition, salt_name, manufacturer, strength, category,
description, uses, side_effects, image_url,
excellent_review_pct, average_review_pct, poor_review_pct)
VALUES
(:brand_name, :composition, :salt_name, :manufacturer, :strength, :category,
:description, :uses, :side_effects, :image_url,
:excellent_review_pct, :average_review_pct, :poor_review_pct)
""",
rows,
)
conn.commit()
count = conn.execute("SELECT COUNT(*) FROM medicines").fetchone()[0]
console.print(f"[green]β {count:,} medicines inserted into SQLite[/green]")
# ββ Step 5: Build vector index ββββββββββββββββββββββββββββββββββββββββββββ
if not args.skip_index:
step(5, TOTAL_STEPS, f"Building Qdrant vector index [{settings.QDRANT_MODE} mode]")
asyncio.run(_build_index())
elapsed = time.perf_counter() - start
result_label = "loaded" if args.skip_index else "indexed"
console.print(
Panel.fit(
f"[bold green]Setup complete![/bold green] "
f"{count:,} medicines {result_label} β’ {elapsed:.1f}s",
border_style="green",
)
)
async def _build_index() -> None:
from services.vector_store import VectorStoreService
await VectorStoreService.connect()
if VectorStoreService.is_ready():
from core.config import settings as s
if s.QDRANT_MODE == "local":
console.print("[dim]Existing local index found β use --rebuild to force[/dim]")
return
await VectorStoreService.build_from_db()
def parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(description="PharmaAI data setup pipeline")
p.add_argument(
"--source",
choices=["auto", "kaggle", "seed"],
default="auto",
help="Data source (default: auto β Kaggle if creds available, else seed)",
)
p.add_argument("--skip-index", action="store_true", help="Skip Qdrant indexing")
p.add_argument("--rebuild", action="store_true", help="Drop and rebuild everything")
p.add_argument("--stats-only", action="store_true", help="Print DB stats and exit")
return p.parse_args()
if __name__ == "__main__":
run(parse_args())
|