dakheel commited on
Commit
fde7362
·
verified ·
1 Parent(s): 9511ff3

Upload 4 files

Browse files
Files changed (4) hide show
  1. README.md +114 -25
  2. build_prebuilt_assets.py +324 -0
  3. hudanet_prebuilt_guard.py +325 -0
  4. patch_app_v41_0_2.py +106 -0
README.md CHANGED
@@ -1,25 +1,114 @@
1
- ---
2
- title: HUDA-Net
3
- emoji: 🕋
4
- colorFrom: green
5
- colorTo: blue
6
- sdk: gradio
7
- sdk_version: 6.20.0
8
- hardware: cpu-basic
9
- app_file: app.py
10
- python_version: 3.10
11
- fullWidth: true
12
- header: mini
13
- pinned: false
14
- license: cc-by-sa-4.0
15
- datasets:
16
- - dakheel/hudanet-knowledge-base-v1
17
- - dakheel/hudanet-runtime
18
- ---
19
-
20
- # HUDA-Net
21
-
22
- Bilingual Hajj and Umrah evidence-based research assistant. v41.0.1 is Knowledge-Base native: 3,067 KB records are searched, 2,799 answer records may support answers, and 268 index-only records are exploration/routing aids only.
23
-
24
- ## v41.0.1
25
- Fixes Hugging Face startup when the legacy Runtime model assets are stored in the repository flat layout. Only E5/BGE model files are selected and reconstructed; legacy records/indexes are not downloaded.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # HUDA-Net v41.0.2 Prebuilt Retrieval Fix
2
+
3
+ This package fixes the Hugging Face Space startup timeout caused by rebuilding
4
+ the full bilingual hybrid retrieval stack on every restart.
5
+
6
+ ## Files
7
+
8
+ - `hudanet_prebuilt_guard.py`
9
+ - Production runtime guard.
10
+ - Downloads a prebuilt retrieval bundle.
11
+ - Verifies HUDA-Net's own KB fingerprint and every SHA-256.
12
+ - Restores the final runtime files.
13
+ - Refuses to rebuild in production if assets are missing or stale.
14
+
15
+ - `build_prebuilt_assets.py`
16
+ - One-time builder/publisher.
17
+ - Imports the CURRENT `app.py` with `HUDANET_SKIP_APP_INIT=1`.
18
+ - Runs `_prepare_kb_native_runtime_ui()` once.
19
+ - Packages `HYBRID_FILES` and Arabic/English embedding indexes.
20
+ - Creates/updates a private Hugging Face Dataset.
21
+
22
+ - `patch_app_v41_0_2.py`
23
+ - Safely patches the CURRENT `app.py`.
24
+ - Makes a backup.
25
+ - Inserts the guard immediately before `HUDA_DEMO`.
26
+ - Changes 41.0.1 strings to 41.0.2.
27
+ - Compile-checks and restores backup if patching fails.
28
+
29
+ ## Recommended repository
30
+
31
+ Default:
32
+ `dakheel/hudanet-v41-prebuilt-retrieval`
33
+
34
+ Override everywhere with:
35
+ `HUDANET_PREBUILT_REPO_ID`
36
+
37
+ ## One-time setup
38
+
39
+ Place these three `.py` files beside the CURRENT `app.py`.
40
+
41
+ Patch the current app:
42
+
43
+ ```bash
44
+ python patch_app_v41_0_2.py
45
+ ```
46
+
47
+ Build and publish the retrieval bundle in Kaggle/local/build environment:
48
+
49
+ Linux/macOS:
50
+
51
+ ```bash
52
+ export HF_TOKEN="READ_TOKEN"
53
+ export HF_WRITE_TOKEN="WRITE_TOKEN"
54
+ export HUDANET_PREBUILT_REPO_ID="dakheel/hudanet-v41-prebuilt-retrieval"
55
+ python build_prebuilt_assets.py
56
+ ```
57
+
58
+ PowerShell:
59
+
60
+ ```powershell
61
+ $env:HF_TOKEN="READ_TOKEN"
62
+ $env:HF_WRITE_TOKEN="WRITE_TOKEN"
63
+ $env:HUDANET_PREBUILT_REPO_ID="dakheel/hudanet-v41-prebuilt-retrieval"
64
+ python build_prebuilt_assets.py
65
+ ```
66
+
67
+ If the same token has both read and write permission, `HF_WRITE_TOKEN` can be
68
+ omitted and the builder will use `HF_TOKEN`.
69
+
70
+ ## Production Space
71
+
72
+ Commit these files to the Space:
73
+
74
+ - patched `app.py`
75
+ - `hudanet_prebuilt_guard.py`
76
+
77
+ `build_prebuilt_assets.py` and `patch_app_v41_0_2.py` may remain in the repo,
78
+ but they are not run automatically.
79
+
80
+ Space secrets/variables:
81
+
82
+ - `HF_TOKEN`: read access to all private HUDA-Net datasets, including the new
83
+ prebuilt retrieval dataset.
84
+ - optional `HUDANET_PREBUILT_REPO_ID` if you used a different repo id.
85
+
86
+ Do NOT put a write token in the production Space unless you genuinely need it.
87
+
88
+ ## Expected startup
89
+
90
+ You should see something similar to:
91
+
92
+ ```text
93
+ 🔒 HUDA-Net v41.0.2 prebuilt retrieval guard active | production rebuild fallback disabled
94
+ ⬇️ Loading HUDA-Net prebuilt retrieval assets from dakheel/hudanet-v41-prebuilt-retrieval...
95
+ ⚡ HUDA-Net prebuilt retrieval restored | ... files | fingerprint=...
96
+ ✅ HUDA-Net v41 KB-native Runtime ready
97
+ ```
98
+
99
+ You should NOT see:
100
+
101
+ ```text
102
+ 💾 ar: completed batch 1/384
103
+ ...
104
+ 💾 en: completed batch 1/384
105
+ ...
106
+ ```
107
+
108
+ ## When the Knowledge Base changes
109
+
110
+ Run `build_prebuilt_assets.py` again.
111
+
112
+ The production guard compares the bundle fingerprint with the current KB. A
113
+ mismatch fails fast with a clear "STALE" message instead of silently using old
114
+ embeddings or spending ~30 minutes rebuilding during Space startup.
build_prebuilt_assets.py ADDED
@@ -0,0 +1,324 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ HUDA-Net v41.0.2 One-Time Prebuilt Retrieval Builder
4
+ ====================================================
5
+
6
+ Run this file OUTSIDE the production Space, for example in Kaggle, a local
7
+ machine, or another build environment.
8
+
9
+ Requirements
10
+ ------------
11
+ 1. Put this file beside the CURRENT v41 app.py.
12
+ 2. app.py must support HUDANET_SKIP_APP_INIT=1 (v41.0.1 already does).
13
+ 3. Set:
14
+ HF_TOKEN=<token that can read the private HUDA-Net source datasets>
15
+ HF_WRITE_TOKEN=<token that can create/write the prebuilt dataset>
16
+ If one token has both permissions, HF_WRITE_TOKEN may be omitted.
17
+ 4. Optional:
18
+ HUDANET_PREBUILT_REPO_ID=dakheel/hudanet-v41-prebuilt-retrieval
19
+
20
+ What it does
21
+ ------------
22
+ - Imports the CURRENT app.py without starting Gradio.
23
+ - Runs HUDA-Net's own KB-native runtime preparation ONCE.
24
+ - Lets the original hybrid builder calculate BM25, E5 dense vectors and
25
+ calibration exactly as HUDA-Net already does.
26
+ - Packages the final runtime retrieval files plus Arabic/English embedding
27
+ indexes.
28
+ - Writes a fingerprinted SHA-256 manifest.
29
+ - Creates/updates a private Hugging Face Dataset and uploads the bundle.
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import hashlib
35
+ import importlib
36
+ import json
37
+ import os
38
+ import shutil
39
+ import sys
40
+ import tempfile
41
+ from datetime import datetime, timezone
42
+ from pathlib import Path
43
+ from typing import Any, Dict, Iterable, List, Mapping, Set
44
+
45
+ from huggingface_hub import HfApi
46
+
47
+
48
+ BUNDLE_MANIFEST = "hudanet_prebuilt_bundle_v41.json"
49
+ DEFAULT_REPO_ID = "dakheel/hudanet-v41-prebuilt-retrieval"
50
+
51
+
52
+ def _sha256_file(path: Path, chunk_size: int = 8 * 1024 * 1024) -> str:
53
+ digest = hashlib.sha256()
54
+ with Path(path).open("rb") as handle:
55
+ while True:
56
+ chunk = handle.read(chunk_size)
57
+ if not chunk:
58
+ break
59
+ digest.update(chunk)
60
+ return digest.hexdigest()
61
+
62
+
63
+ def _utc_now() -> str:
64
+ return datetime.now(timezone.utc).isoformat()
65
+
66
+
67
+ def _copy_file_preserve_relative(root: Path, source: Path, stage: Path) -> str:
68
+ root = Path(root).resolve()
69
+ source = Path(source).resolve()
70
+ stage = Path(stage)
71
+
72
+ try:
73
+ rel = source.relative_to(root)
74
+ except Exception as exc:
75
+ raise RuntimeError(
76
+ f"Refusing to package file outside runtime root: {source}"
77
+ ) from exc
78
+
79
+ dst = stage / rel
80
+ dst.parent.mkdir(parents=True, exist_ok=True)
81
+ shutil.copy2(source, dst)
82
+ return rel.as_posix()
83
+
84
+
85
+ def _collect_runtime_files(hudanet: Any, runtime_root: Path) -> List[Path]:
86
+ runtime_root = Path(runtime_root)
87
+ files: Set[Path] = set()
88
+
89
+ hybrid_files = getattr(hudanet, "HYBRID_FILES", None)
90
+ if not isinstance(hybrid_files, Mapping):
91
+ raise RuntimeError("Current app.py has no HYBRID_FILES mapping.")
92
+
93
+ for filename in hybrid_files.values():
94
+ path = runtime_root / str(filename)
95
+ if not path.is_file():
96
+ raise RuntimeError(f"Required hybrid runtime file is missing: {path}")
97
+ files.add(path)
98
+
99
+ embedding_index_fn = getattr(hudanet, "_embedding_index_path", None)
100
+ if callable(embedding_index_fn):
101
+ for lang in ("ar", "en"):
102
+ path = Path(embedding_index_fn(runtime_root, lang))
103
+ if path.is_file():
104
+ files.add(path)
105
+ else:
106
+ raise RuntimeError(
107
+ f"Final {lang} embedding index is missing: {path}"
108
+ )
109
+
110
+ return sorted(files, key=lambda p: str(p))
111
+
112
+
113
+ def main() -> None:
114
+ # Critical: allow the original expensive builder only in this one-time build.
115
+ os.environ["HUDANET_SKIP_APP_INIT"] = "1"
116
+ os.environ["HUDANET_PREBUILT_BUILD_MODE"] = "1"
117
+
118
+ repo_id = os.getenv(
119
+ "HUDANET_PREBUILT_REPO_ID",
120
+ DEFAULT_REPO_ID,
121
+ ).strip()
122
+
123
+ read_token = os.getenv("HF_TOKEN", "").strip()
124
+ write_token = os.getenv("HF_WRITE_TOKEN", "").strip() or read_token
125
+
126
+ if not read_token:
127
+ raise RuntimeError(
128
+ "HF_TOKEN is missing. It must be able to read HUDA-Net private datasets."
129
+ )
130
+ if not write_token:
131
+ raise RuntimeError(
132
+ "HF_WRITE_TOKEN is missing. It must be able to create/write the "
133
+ "prebuilt retrieval dataset."
134
+ )
135
+
136
+ print("=" * 88)
137
+ print("🕋 HUDA-Net v41.0.2 ONE-TIME PREBUILT RETRIEVAL BUILD")
138
+ print("=" * 88)
139
+ print(f"Target dataset: {repo_id}")
140
+
141
+ # Import CURRENT app.py, but do not create the Gradio app.
142
+ hudanet = importlib.import_module("app")
143
+
144
+ prepare_fn = getattr(hudanet, "_prepare_kb_native_runtime_ui", None)
145
+ if not callable(prepare_fn):
146
+ raise RuntimeError(
147
+ "Current app.py does not expose _prepare_kb_native_runtime_ui()."
148
+ )
149
+
150
+ print("🧠 Running the original KB-native builder once...")
151
+ runtime_root, runtime_manifest = prepare_fn()
152
+ runtime_root = Path(runtime_root)
153
+
154
+ if not runtime_root.exists():
155
+ raise RuntimeError(f"Builder returned missing runtime root: {runtime_root}")
156
+
157
+ fingerprint_fn = getattr(hudanet, "_hybrid_fingerprint", None)
158
+ if not callable(fingerprint_fn):
159
+ raise RuntimeError("Current app.py has no _hybrid_fingerprint(df).")
160
+
161
+ # Resolve the dataframe from the current runtime without guessing schema.
162
+ # First try well-known module-level dataframe holders, then load the runtime records.
163
+ df = None
164
+ for candidate_name in (
165
+ "RUNTIME_DF",
166
+ "KB_DF",
167
+ "KNOWLEDGE_BASE_DF",
168
+ "DF",
169
+ "df",
170
+ ):
171
+ candidate = getattr(hudanet, candidate_name, None)
172
+ if candidate is not None and hasattr(candidate, "__len__") and hasattr(candidate, "columns"):
173
+ try:
174
+ if len(candidate) > 0:
175
+ df = candidate
176
+ break
177
+ except Exception:
178
+ pass
179
+
180
+ if df is None:
181
+ import pandas as pd
182
+
183
+ candidates = [
184
+ runtime_root / "records.parquet",
185
+ runtime_root / "records.csv.gz",
186
+ runtime_root / "records.csv",
187
+ ]
188
+ for path in candidates:
189
+ if not path.exists():
190
+ continue
191
+ if path.suffix == ".parquet":
192
+ df = pd.read_parquet(path)
193
+ else:
194
+ df = pd.read_csv(path, dtype=str, keep_default_na=False)
195
+ break
196
+
197
+ if df is None:
198
+ # v41 KB-native runtime may expose a dedicated loader.
199
+ loader_candidates = [
200
+ "_load_kb_native_dataframe",
201
+ "_load_knowledge_base_dataframe",
202
+ "load_knowledge_base",
203
+ ]
204
+ for name in loader_candidates:
205
+ fn = getattr(hudanet, name, None)
206
+ if callable(fn):
207
+ try:
208
+ loaded = fn()
209
+ if hasattr(loaded, "columns"):
210
+ df = loaded
211
+ break
212
+ if isinstance(loaded, tuple):
213
+ for item in loaded:
214
+ if hasattr(item, "columns"):
215
+ df = item
216
+ break
217
+ except TypeError:
218
+ continue
219
+
220
+ if df is None:
221
+ # Final reliable source: hybrid_manifest.json already contains the exact
222
+ # fingerprint produced by HUDA-Net's own build. We don't need to invent
223
+ # a second fingerprint implementation.
224
+ hybrid_manifest_name = str(
225
+ hudanet.HYBRID_FILES.get("hybrid_manifest", "hybrid_manifest.json")
226
+ )
227
+ hm_path = runtime_root / hybrid_manifest_name
228
+ if not hm_path.exists():
229
+ raise RuntimeError(
230
+ "Could not resolve the KB dataframe and hybrid_manifest.json is missing."
231
+ )
232
+ hybrid_meta = json.loads(hm_path.read_text(encoding="utf-8"))
233
+ fingerprint = str(hybrid_meta.get("fingerprint", ""))
234
+ records = int(hybrid_meta.get("records", 0))
235
+ else:
236
+ fingerprint = str(fingerprint_fn(df))
237
+ records = int(len(df))
238
+
239
+ if not fingerprint:
240
+ raise RuntimeError("Could not resolve HUDA-Net hybrid fingerprint.")
241
+
242
+ files = _collect_runtime_files(hudanet, runtime_root)
243
+
244
+ stage = Path(tempfile.mkdtemp(prefix="hudanet_v41_prebuilt_stage_"))
245
+ print(f"📦 Staging bundle at: {stage}")
246
+
247
+ try:
248
+ packaged_files: Dict[str, Dict[str, Any]] = {}
249
+
250
+ for source in files:
251
+ rel = _copy_file_preserve_relative(runtime_root, source, stage)
252
+ staged = stage / rel
253
+ packaged_files[rel] = {
254
+ "size": int(staged.stat().st_size),
255
+ "sha256": _sha256_file(staged),
256
+ }
257
+
258
+ hybrid_manifest_name = str(
259
+ hudanet.HYBRID_FILES.get("hybrid_manifest", "hybrid_manifest.json")
260
+ )
261
+
262
+ bundle_manifest = {
263
+ "schema_version": 1,
264
+ "created_at": _utc_now(),
265
+ "hudanet_version": str(getattr(hudanet, "VERSION", "unknown")),
266
+ "hybrid_asset_version": str(
267
+ getattr(hudanet, "HYBRID_ASSET_VERSION", "unknown")
268
+ ),
269
+ "fingerprint": fingerprint,
270
+ "records": records,
271
+ "hybrid_manifest_file": hybrid_manifest_name,
272
+ "source_runtime_root_name": runtime_root.name,
273
+ "files": packaged_files,
274
+ }
275
+
276
+ (stage / BUNDLE_MANIFEST).write_text(
277
+ json.dumps(bundle_manifest, ensure_ascii=False, indent=2),
278
+ encoding="utf-8",
279
+ )
280
+
281
+ print(
282
+ f"✅ Bundle ready | {len(packaged_files)} runtime files | "
283
+ f"records={records} | fingerprint={fingerprint[:16]}..."
284
+ )
285
+
286
+ api = HfApi(token=write_token)
287
+ api.create_repo(
288
+ repo_id=repo_id,
289
+ repo_type="dataset",
290
+ private=True,
291
+ exist_ok=True,
292
+ token=write_token,
293
+ )
294
+
295
+ print("⬆️ Uploading prebuilt retrieval bundle to Hugging Face...")
296
+ api.upload_folder(
297
+ repo_id=repo_id,
298
+ repo_type="dataset",
299
+ folder_path=str(stage),
300
+ path_in_repo=".",
301
+ token=write_token,
302
+ commit_message=(
303
+ f"HUDA-Net prebuilt retrieval "
304
+ f"{getattr(hudanet, 'VERSION', 'v41')} "
305
+ f"{fingerprint[:12]}"
306
+ ),
307
+ )
308
+
309
+ print("=" * 88)
310
+ print("✅ PREBUILT RETRIEVAL DATASET PUBLISHED SUCCESSFULLY")
311
+ print(f"Repo : {repo_id}")
312
+ print(f"Fingerprint: {fingerprint}")
313
+ print(f"Files : {len(packaged_files)}")
314
+ print("=" * 88)
315
+
316
+ finally:
317
+ if os.getenv("HUDANET_KEEP_PREBUILT_STAGE", "0").strip().casefold() not in {
318
+ "1", "true", "yes", "on"
319
+ }:
320
+ shutil.rmtree(stage, ignore_errors=True)
321
+
322
+
323
+ if __name__ == "__main__":
324
+ main()
hudanet_prebuilt_guard.py ADDED
@@ -0,0 +1,325 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ HUDA-Net v41.0.2 Prebuilt Retrieval Guard
4
+ =========================================
5
+
6
+ Purpose
7
+ -------
8
+ Prevent Hugging Face Space startup from rebuilding BM25, dense E5 embeddings,
9
+ and calibration assets for the full KB on every restart.
10
+
11
+ Production behavior
12
+ -------------------
13
+ - Download one prebuilt retrieval bundle from a private Hugging Face Dataset.
14
+ - Verify the bundle fingerprint against the CURRENT KB using HUDA-Net's own
15
+ _hybrid_fingerprint(df) function.
16
+ - Verify every file SHA-256.
17
+ - Restore the packaged hybrid files into the runtime target directory.
18
+ - NEVER fall back to expensive production-time rebuilding.
19
+
20
+ Build behavior
21
+ --------------
22
+ When HUDANET_PREBUILT_BUILD_MODE=1, this module does nothing so the original
23
+ HUDA-Net builder can run once in an offline/build environment.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import hashlib
29
+ import json
30
+ import os
31
+ import shutil
32
+ from pathlib import Path
33
+ from typing import Any, Dict, Mapping, MutableMapping, Optional, Tuple
34
+
35
+ from huggingface_hub import snapshot_download
36
+
37
+
38
+ BUNDLE_MANIFEST = "hudanet_prebuilt_bundle_v41.json"
39
+ DEFAULT_REPO_ID = "dakheel/hudanet-v41-prebuilt-retrieval"
40
+ DEFAULT_LOCAL_ROOT = Path("/tmp/hudanet_v41_prebuilt_retrieval")
41
+
42
+ _BUNDLE_ROOT: Optional[Path] = None
43
+ _INSTALLED = False
44
+
45
+
46
+ def _truthy(value: Any) -> bool:
47
+ return str(value or "").strip().casefold() in {"1", "true", "yes", "on"}
48
+
49
+
50
+ def _sha256_file(path: Path, chunk_size: int = 8 * 1024 * 1024) -> str:
51
+ digest = hashlib.sha256()
52
+ with Path(path).open("rb") as handle:
53
+ while True:
54
+ chunk = handle.read(chunk_size)
55
+ if not chunk:
56
+ break
57
+ digest.update(chunk)
58
+ return digest.hexdigest()
59
+
60
+
61
+ def _safe_relative_path(value: str) -> Path:
62
+ rel = Path(str(value).replace("\\", "/"))
63
+ if rel.is_absolute() or ".." in rel.parts:
64
+ raise RuntimeError(f"Unsafe path in prebuilt bundle: {value!r}")
65
+ return rel
66
+
67
+
68
+ def _link_or_copy(src: Path, dst: Path) -> None:
69
+ src = Path(src)
70
+ dst = Path(dst)
71
+ dst.parent.mkdir(parents=True, exist_ok=True)
72
+
73
+ if dst.exists() or dst.is_symlink():
74
+ if dst.is_dir() and not dst.is_symlink():
75
+ shutil.rmtree(dst)
76
+ else:
77
+ dst.unlink()
78
+
79
+ try:
80
+ os.link(src, dst)
81
+ except Exception:
82
+ shutil.copy2(src, dst)
83
+
84
+
85
+ def _download_bundle() -> Path:
86
+ global _BUNDLE_ROOT
87
+
88
+ if _BUNDLE_ROOT is not None:
89
+ manifest = _BUNDLE_ROOT / BUNDLE_MANIFEST
90
+ if manifest.exists():
91
+ return _BUNDLE_ROOT
92
+
93
+ repo_id = os.getenv("HUDANET_PREBUILT_REPO_ID", DEFAULT_REPO_ID).strip()
94
+ token = os.getenv("HF_TOKEN", "").strip() or None
95
+ local_root = Path(
96
+ os.getenv("HUDANET_PREBUILT_LOCAL_ROOT", str(DEFAULT_LOCAL_ROOT))
97
+ )
98
+
99
+ local_root.mkdir(parents=True, exist_ok=True)
100
+
101
+ print(f"⬇️ Loading HUDA-Net prebuilt retrieval assets from {repo_id}...")
102
+
103
+ snapshot_download(
104
+ repo_id=repo_id,
105
+ repo_type="dataset",
106
+ token=token,
107
+ local_dir=str(local_root),
108
+ max_workers=8,
109
+ )
110
+
111
+ manifest = local_root / BUNDLE_MANIFEST
112
+ if not manifest.exists():
113
+ raise RuntimeError(
114
+ f"Prebuilt retrieval bundle is missing {BUNDLE_MANIFEST} in {repo_id}. "
115
+ "Run build_prebuilt_assets.py once before starting the Space."
116
+ )
117
+
118
+ _BUNDLE_ROOT = local_root
119
+ return local_root
120
+
121
+
122
+ def _read_bundle_manifest(bundle_root: Path) -> Dict[str, Any]:
123
+ path = Path(bundle_root) / BUNDLE_MANIFEST
124
+ try:
125
+ data = json.loads(path.read_text(encoding="utf-8"))
126
+ except Exception as exc:
127
+ raise RuntimeError(f"Could not read prebuilt bundle manifest: {path}") from exc
128
+
129
+ if int(data.get("schema_version", 0)) != 1:
130
+ raise RuntimeError(
131
+ f"Unsupported HUDA-Net prebuilt schema: {data.get('schema_version')!r}"
132
+ )
133
+
134
+ files = data.get("files")
135
+ if not isinstance(files, dict) or not files:
136
+ raise RuntimeError("Prebuilt retrieval manifest has no files.")
137
+
138
+ return data
139
+
140
+
141
+ def _validate_bundle(
142
+ ns: Mapping[str, Any],
143
+ df: Any,
144
+ bundle_root: Path,
145
+ bundle_meta: Mapping[str, Any],
146
+ ) -> str:
147
+ fingerprint_fn = ns.get("_hybrid_fingerprint")
148
+ if not callable(fingerprint_fn):
149
+ raise RuntimeError(
150
+ "HUDA-Net _hybrid_fingerprint(df) is unavailable. "
151
+ "The prebuilt guard must be installed after hybrid functions are defined."
152
+ )
153
+
154
+ current_fingerprint = str(fingerprint_fn(df))
155
+ packaged_fingerprint = str(bundle_meta.get("fingerprint", ""))
156
+
157
+ if not packaged_fingerprint:
158
+ raise RuntimeError("Prebuilt retrieval bundle does not contain a fingerprint.")
159
+
160
+ if packaged_fingerprint != current_fingerprint:
161
+ raise RuntimeError(
162
+ "HUDA-Net prebuilt retrieval assets are STALE for the current Knowledge Base.\n"
163
+ f"Current KB fingerprint : {current_fingerprint}\n"
164
+ f"Packaged fingerprint : {packaged_fingerprint}\n"
165
+ "Run build_prebuilt_assets.py again and publish the new bundle. "
166
+ "Production-time rebuilding is intentionally disabled."
167
+ )
168
+
169
+ files = bundle_meta["files"]
170
+ for rel_text, file_meta in files.items():
171
+ rel = _safe_relative_path(rel_text)
172
+ src = Path(bundle_root) / rel
173
+
174
+ if not src.is_file():
175
+ raise RuntimeError(f"Missing prebuilt retrieval file: {rel}")
176
+
177
+ expected_size = int(file_meta.get("size", -1))
178
+ actual_size = src.stat().st_size
179
+ if expected_size >= 0 and actual_size != expected_size:
180
+ raise RuntimeError(
181
+ f"Prebuilt file size mismatch for {rel}: "
182
+ f"expected={expected_size}, actual={actual_size}"
183
+ )
184
+
185
+ expected_sha = str(file_meta.get("sha256", "")).strip().lower()
186
+ if expected_sha:
187
+ actual_sha = _sha256_file(src)
188
+ if actual_sha.lower() != expected_sha:
189
+ raise RuntimeError(
190
+ f"Prebuilt file SHA-256 mismatch for {rel}: "
191
+ f"expected={expected_sha}, actual={actual_sha}"
192
+ )
193
+
194
+ return current_fingerprint
195
+
196
+
197
+ def _restore_bundle_to_target(
198
+ ns: Mapping[str, Any],
199
+ df: Any,
200
+ target_root: Path,
201
+ ) -> Tuple[Path, Dict[str, Any]]:
202
+ target_root = Path(target_root)
203
+ target_root.mkdir(parents=True, exist_ok=True)
204
+
205
+ bundle_root = _download_bundle()
206
+ bundle_meta = _read_bundle_manifest(bundle_root)
207
+ fingerprint = _validate_bundle(ns, df, bundle_root, bundle_meta)
208
+
209
+ copied = 0
210
+ for rel_text in bundle_meta["files"].keys():
211
+ rel = _safe_relative_path(rel_text)
212
+ src = bundle_root / rel
213
+ dst = target_root / rel
214
+ _link_or_copy(src, dst)
215
+ copied += 1
216
+
217
+ hybrid_files = ns.get("HYBRID_FILES")
218
+ if not isinstance(hybrid_files, Mapping):
219
+ raise RuntimeError("HUDA-Net HYBRID_FILES mapping is unavailable.")
220
+
221
+ hybrid_manifest_name = str(
222
+ bundle_meta.get(
223
+ "hybrid_manifest_file",
224
+ hybrid_files.get("hybrid_manifest", "hybrid_manifest.json"),
225
+ )
226
+ )
227
+ hybrid_manifest_path = target_root / _safe_relative_path(hybrid_manifest_name)
228
+
229
+ if not hybrid_manifest_path.exists():
230
+ raise RuntimeError(
231
+ f"Restored prebuilt bundle but hybrid manifest is missing: "
232
+ f"{hybrid_manifest_path}"
233
+ )
234
+
235
+ try:
236
+ hybrid_meta = json.loads(hybrid_manifest_path.read_text(encoding="utf-8"))
237
+ except Exception as exc:
238
+ raise RuntimeError(
239
+ f"Could not parse restored hybrid manifest: {hybrid_manifest_path}"
240
+ ) from exc
241
+
242
+ if str(hybrid_meta.get("fingerprint", "")) != fingerprint:
243
+ raise RuntimeError(
244
+ "Restored hybrid_manifest.json fingerprint does not match the current KB."
245
+ )
246
+
247
+ required_names = list(hybrid_files.values())
248
+ missing = [
249
+ name for name in required_names
250
+ if not (target_root / str(name)).exists()
251
+ ]
252
+ if missing:
253
+ raise RuntimeError(
254
+ "Prebuilt retrieval bundle is incomplete. Missing runtime files: "
255
+ + ", ".join(map(str, missing))
256
+ )
257
+
258
+ print(
259
+ f"⚡ HUDA-Net prebuilt retrieval restored | "
260
+ f"{copied} files | fingerprint={fingerprint[:16]}..."
261
+ )
262
+ return target_root, hybrid_meta
263
+
264
+
265
+ def install_prebuilt_guard(ns: MutableMapping[str, Any]) -> None:
266
+ """
267
+ Install production-only wrappers into app.py globals().
268
+
269
+ Call this ONCE near the bottom of app.py, immediately before HUDA_DEMO is
270
+ created. In build mode the wrappers are intentionally not installed.
271
+ """
272
+ global _INSTALLED
273
+
274
+ if _INSTALLED:
275
+ return
276
+
277
+ if _truthy(os.getenv("HUDANET_PREBUILT_BUILD_MODE", "0")):
278
+ print("🛠️ HUDA-Net prebuilt BUILD MODE: original hybrid builder remains enabled.")
279
+ _INSTALLED = True
280
+ return
281
+
282
+ original_build = ns.get("build_hybrid_runtime_assets")
283
+ if not callable(original_build):
284
+ raise RuntimeError(
285
+ "build_hybrid_runtime_assets is not defined yet. "
286
+ "Install the prebuilt guard near the bottom of app.py."
287
+ )
288
+
289
+ def build_hybrid_runtime_assets_prebuilt(
290
+ df: Any,
291
+ sparse_assets: Mapping[str, Any],
292
+ target_root: Path,
293
+ *,
294
+ force: bool = False,
295
+ previous_root: Optional[Path] = None,
296
+ **kwargs: Any,
297
+ ) -> Dict[str, Any]:
298
+ del sparse_assets, force, previous_root, kwargs
299
+ _, meta = _restore_bundle_to_target(ns, df, Path(target_root))
300
+ return meta
301
+
302
+ ns["_build_hybrid_runtime_assets_original_v41_0_1"] = original_build
303
+ ns["build_hybrid_runtime_assets"] = build_hybrid_runtime_assets_prebuilt
304
+
305
+ original_ensure = ns.get("ensure_hybrid_runtime_assets")
306
+ if callable(original_ensure):
307
+ def ensure_hybrid_runtime_assets_prebuilt(
308
+ df: Any,
309
+ sparse_assets: Mapping[str, Any],
310
+ runtime_root: Path,
311
+ *args: Any,
312
+ **kwargs: Any,
313
+ ) -> Tuple[Path, Dict[str, Any]]:
314
+ del sparse_assets, args, kwargs
315
+ root, meta = _restore_bundle_to_target(ns, df, Path(runtime_root))
316
+ return root, meta
317
+
318
+ ns["_ensure_hybrid_runtime_assets_original_v41_0_1"] = original_ensure
319
+ ns["ensure_hybrid_runtime_assets"] = ensure_hybrid_runtime_assets_prebuilt
320
+
321
+ _INSTALLED = True
322
+ print(
323
+ "🔒 HUDA-Net v41.0.2 prebuilt retrieval guard active | "
324
+ "production rebuild fallback disabled"
325
+ )
patch_app_v41_0_2.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ HUDA-Net v41.0.2 app.py Patcher
4
+ ===============================
5
+
6
+ Run once in the SAME folder as the CURRENT v41.0.1 app.py:
7
+
8
+ python patch_app_v41_0_2.py
9
+
10
+ It will:
11
+ - create a backup of app.py,
12
+ - change visible 41.0.1 version strings to 41.0.2,
13
+ - install hudanet_prebuilt_guard immediately before HUDA_DEMO creation,
14
+ - compile-check the modified app.py,
15
+ - restore the backup automatically if syntax validation fails.
16
+
17
+ This patcher does NOT replace your v41 logic and does NOT overwrite the
18
+ 10,000+ line application with an older copy.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import py_compile
24
+ import re
25
+ import shutil
26
+ from pathlib import Path
27
+
28
+
29
+ APP = Path("app.py")
30
+ GUARD = Path("hudanet_prebuilt_guard.py")
31
+ BACKUP = Path("app.py.v41_0_1.backup")
32
+
33
+ MARKER = "# HUDA-Net v41.0.2 prebuilt retrieval startup guard"
34
+ INJECTION = f"""
35
+ {MARKER}
36
+ from hudanet_prebuilt_guard import install_prebuilt_guard as _install_hudanet_prebuilt_guard
37
+ _install_hudanet_prebuilt_guard(globals())
38
+
39
+ """
40
+
41
+
42
+ def main() -> None:
43
+ if not APP.is_file():
44
+ raise RuntimeError("app.py was not found in the current folder.")
45
+
46
+ if not GUARD.is_file():
47
+ raise RuntimeError(
48
+ "hudanet_prebuilt_guard.py was not found beside app.py."
49
+ )
50
+
51
+ original = APP.read_text(encoding="utf-8")
52
+
53
+ if MARKER in original:
54
+ print("ℹ️ v41.0.2 prebuilt guard is already installed.")
55
+ py_compile.compile(str(APP), doraise=True)
56
+ print("✅ app.py syntax check passed.")
57
+ return
58
+
59
+ # The v41.0.1 logs prove this startup assignment exists.
60
+ match = re.search(
61
+ r"(?m)^(?P<indent>[ \t]*)HUDA_DEMO\s*=\s*None\s+if\s+os\.getenv\("
62
+ r"[\"']HUDANET_SKIP_APP_INIT[\"']",
63
+ original,
64
+ )
65
+
66
+ if not match:
67
+ # Fallback for formatting changes while keeping the same variable.
68
+ match = re.search(r"(?m)^(?P<indent>[ \t]*)HUDA_DEMO\s*=", original)
69
+
70
+ if not match:
71
+ raise RuntimeError(
72
+ "Could not find the HUDA_DEMO startup assignment. "
73
+ "No changes were made."
74
+ )
75
+
76
+ if BACKUP.exists():
77
+ raise RuntimeError(
78
+ f"Backup already exists: {BACKUP}. "
79
+ "Move/delete it only if you intentionally want a new backup."
80
+ )
81
+
82
+ shutil.copy2(APP, BACKUP)
83
+
84
+ try:
85
+ updated = original.replace("41.0.1", "41.0.2")
86
+ insertion_at = match.start()
87
+
88
+ # Because version replacement does not change text length for 41.0.1 -> 41.0.2,
89
+ # the original match offset remains correct.
90
+ updated = updated[:insertion_at] + INJECTION + updated[insertion_at:]
91
+
92
+ APP.write_text(updated, encoding="utf-8")
93
+ py_compile.compile(str(APP), doraise=True)
94
+
95
+ except Exception:
96
+ shutil.copy2(BACKUP, APP)
97
+ raise
98
+
99
+ print("✅ app.py patched to HUDA-Net v41.0.2.")
100
+ print(f"✅ Backup created: {BACKUP}")
101
+ print("✅ Prebuilt retrieval guard inserted before HUDA_DEMO creation.")
102
+ print("✅ Python syntax validation passed.")
103
+
104
+
105
+ if __name__ == "__main__":
106
+ main()