Kasualdad commited on
Commit
0e9c140
·
1 Parent(s): 9bae1e9

perf: load seed data from Parquet instead of generating on every query

Browse files
app.py CHANGED
@@ -14,10 +14,24 @@ from model_inference import load_model, generate_sql
14
  from data_engine import create_session, execute_safe, QueryTimeoutError
15
 
16
  # ── Startup ───────────────────────────────────────────────────────────
17
- # Model loads into RAM at startup; Zero GPU Spaces run CPU-only so the
18
- # model stays resident between @spaces.GPU calls.
19
 
20
  print("🚀 Starting Kasualdad LFED...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  llm = load_model()
22
  print("✅ Ready.")
23
 
 
14
  from data_engine import create_session, execute_safe, QueryTimeoutError
15
 
16
  # ── Startup ───────────────────────────────────────────────────────────
 
 
17
 
18
  print("🚀 Starting Kasualdad LFED...")
19
+
20
+ # Ensure Parquet seed files exist (generate on first boot, persist in /data/)
21
+ from pathlib import Path
22
+ _parquet_dirs = [Path("/data"), Path(__file__).parent / "data"]
23
+ _pq_files = ["enrollment.parquet", "attendance.parquet"]
24
+ _pq_found = any(
25
+ all((base / f).exists() for f in _pq_files)
26
+ for base in _parquet_dirs
27
+ )
28
+ if not _pq_found:
29
+ print("📦 Generating seed Parquet files (first boot)...")
30
+ from data.export_parquet import export_parquet
31
+ _pq_out = _parquet_dirs[0] if _parquet_dirs[0].exists() else _parquet_dirs[1]
32
+ export_parquet(_pq_out)
33
+
34
+ print("🦙 Loading model...")
35
  llm = load_model()
36
  print("✅ Ready.")
37
 
data/attendance.parquet ADDED
Binary file (31.5 kB). View file
 
data/enrollment.parquet ADDED
Binary file (1.53 kB). View file
 
data/export_parquet.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ export_parquet.py — Generate seed Parquet files for Kasualdad LFED.
3
+
4
+ Run once to produce enrollment.parquet and attendance.parquet.
5
+ Place them in /data/ (HF Space persistent storage) for fast loading.
6
+
7
+ Usage:
8
+ python data/export_parquet.py # → data/
9
+ python data/export_parquet.py --out /data # → /data/ (HF Space)
10
+ """
11
+
12
+ import argparse
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ # Allow running from repo root
17
+ sys.path.insert(0, str(Path(__file__).parent.parent))
18
+
19
+ import duckdb
20
+ from data.generate_seed import generate_seed_data
21
+
22
+
23
+ def export_parquet(out_dir: Path) -> None:
24
+ """Generate seed data and export both tables as Parquet."""
25
+ out_dir.mkdir(parents=True, exist_ok=True)
26
+
27
+ conn = duckdb.connect(":memory:")
28
+ conn.execute("SET enable_progress_bar = false;")
29
+ generate_seed_data(conn)
30
+
31
+ enrollment_path = out_dir / "enrollment.parquet"
32
+ attendance_path = out_dir / "attendance.parquet"
33
+
34
+ conn.execute(f"COPY enrollment TO '{enrollment_path}' (FORMAT PARQUET)")
35
+ conn.execute(f"COPY attendance TO '{attendance_path}' (FORMAT PARQUET)")
36
+
37
+ size_enroll = enrollment_path.stat().st_size
38
+ size_attend = attendance_path.stat().st_size
39
+
40
+ print(f"✅ Exported:")
41
+ print(f" {enrollment_path} ({size_enroll:,} bytes)")
42
+ print(f" {attendance_path} ({size_attend:,} bytes)")
43
+ print(f" Total: {(size_enroll + size_attend):,} bytes")
44
+
45
+ # Verify round-trip
46
+ verify = duckdb.connect(":memory:")
47
+ verify.execute(f"CREATE TABLE enroll AS SELECT * FROM read_parquet('{enrollment_path}')")
48
+ verify.execute(f"CREATE TABLE attend AS SELECT * FROM read_parquet('{attendance_path}')")
49
+ n_enroll = verify.execute("SELECT COUNT(*) FROM enroll").fetchone()[0]
50
+ n_attend = verify.execute("SELECT COUNT(*) FROM attend").fetchone()[0]
51
+ print(f" ✓ Round-trip OK: {n_enroll} enrollment rows, {n_attend} attendance rows")
52
+ verify.close()
53
+ conn.close()
54
+
55
+
56
+ if __name__ == "__main__":
57
+ parser = argparse.ArgumentParser(description="Export seed data to Parquet")
58
+ parser.add_argument(
59
+ "--out",
60
+ type=Path,
61
+ default=Path(__file__).parent,
62
+ help="Output directory (default: data/)",
63
+ )
64
+ args = parser.parse_args()
65
+ export_parquet(args.out)
data_engine.py CHANGED
@@ -34,6 +34,15 @@ QUERY_TIMEOUT_SEC = 10
34
  DATA_DIR = Path(__file__).parent / "data"
35
  SEED_SQL_PATH = DATA_DIR / "seed.sql"
36
 
 
 
 
 
 
 
 
 
 
37
 
38
  # ── Connection factory ─────────────────────────────────────────────────
39
 
@@ -52,28 +61,50 @@ def get_connection(read_only: bool = False) -> duckdb.DuckDBPyConnection:
52
 
53
  # ── Database seeding ───────────────────────────────────────────────────
54
 
55
- def seed_database(conn: duckdb.DuckDBPyConnection, seed_sql_path: Path | None = None) -> None:
 
 
 
56
  """
57
- Create tables and load seed data into the given connection.
58
 
59
- Tries data/seed.sql first (for custom overrides), otherwise uses the
60
- Python seed data generator (5 schools × 4 years, ~500 students).
 
 
 
61
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  if seed_sql_path is None:
63
  seed_sql_path = SEED_SQL_PATH
64
 
65
  if seed_sql_path.exists():
66
- # User-provided seed.sql — execute it
67
  with open(seed_sql_path) as f:
68
  sql = f.read()
69
  for statement in sql.split(";"):
70
  statement = statement.strip()
71
  if statement and not statement.startswith("--"):
72
  conn.execute(statement)
73
- else:
74
- # Default: use Python generator with realistic distributions
75
- from data.generate_seed import generate_seed_data
76
- generate_seed_data(conn)
 
77
 
78
 
79
  # ── Schema introspection ───────────────────────────────────────────────
 
34
  DATA_DIR = Path(__file__).parent / "data"
35
  SEED_SQL_PATH = DATA_DIR / "seed.sql"
36
 
37
+ # Parquet files (pre-generated once via data/export_parquet.py).
38
+ # On HF Spaces, place them in the persistent /data/ directory.
39
+ _PARQUET_DIRS = [
40
+ Path("/data"), # HF Space persistent storage
41
+ DATA_DIR, # local dev (data/)
42
+ ]
43
+ _ENROLLMENT_PQ = "enrollment.parquet"
44
+ _ATTENDANCE_PQ = "attendance.parquet"
45
+
46
 
47
  # ── Connection factory ─────────────────────────────────────────────────
48
 
 
61
 
62
  # ── Database seeding ───────────────────────────────────────────────────
63
 
64
+ def seed_database(
65
+ conn: duckdb.DuckDBPyConnection,
66
+ seed_sql_path: Path | None = None,
67
+ ) -> None:
68
  """
69
+ Create tables and load seed data. Tries, in order:
70
 
71
+ 1. Parquet files (fastest pre-generated, ~33 KB total)
72
+ /data/enrollment.parquet + /data/attendance.parquet (Space)
73
+ → data/enrollment.parquet + data/attendance.parquet (local)
74
+ 2. data/seed.sql (custom overrides)
75
+ 3. Python generator (slow fallback)
76
  """
77
+ # ── 1. Parquet files ─────────────────────────────────────────
78
+ for base in _PARQUET_DIRS:
79
+ enroll_pq = base / _ENROLLMENT_PQ
80
+ attend_pq = base / _ATTENDANCE_PQ
81
+ if enroll_pq.exists() and attend_pq.exists():
82
+ conn.execute(
83
+ "CREATE TABLE enrollment AS "
84
+ f"SELECT * FROM read_parquet('{enroll_pq}')"
85
+ )
86
+ conn.execute(
87
+ "CREATE TABLE attendance AS "
88
+ f"SELECT * FROM read_parquet('{attend_pq}')"
89
+ )
90
+ return
91
+
92
+ # ── 2. seed.sql ──────────────────────────────────────────────
93
  if seed_sql_path is None:
94
  seed_sql_path = SEED_SQL_PATH
95
 
96
  if seed_sql_path.exists():
 
97
  with open(seed_sql_path) as f:
98
  sql = f.read()
99
  for statement in sql.split(";"):
100
  statement = statement.strip()
101
  if statement and not statement.startswith("--"):
102
  conn.execute(statement)
103
+ return
104
+
105
+ # ── 3. Python generator (slow) ───────────────────────────────
106
+ from data.generate_seed import generate_seed_data
107
+ generate_seed_data(conn)
108
 
109
 
110
  # ── Schema introspection ───────────────────────────────────────────────