Spaces:
Sleeping
Sleeping
File size: 12,631 Bytes
a5e47b1 7406fe4 a5e47b1 077a1b7 4df293d 5ad7e33 a5e47b1 f459461 e512f47 f459461 a3598c4 a3db757 a3598c4 a3db757 f459461 a3598c4 e512f47 7406fe4 e512f47 7406fe4 e512f47 4df293d a3db757 077a1b7 5ad7e33 7406fe4 4df293d e512f47 f459461 5ad7e33 7406fe4 e512f47 a3db757 7406fe4 077a1b7 f459461 e512f47 4df293d 077a1b7 e512f47 7406fe4 e512f47 077a1b7 a497b92 e512f47 077a1b7 e512f47 077a1b7 e512f47 077a1b7 59d7584 f459461 077a1b7 f6d737b 077a1b7 f459461 9406d39 f459461 9406d39 f459461 077a1b7 a3db757 f459461 f6d737b f459461 9406d39 f459461 077a1b7 f459461 e512f47 f459461 077a1b7 f459461 a3db757 f459461 a3db757 f459461 e512f47 a3db757 f459461 a3db757 f459461 a3db757 f459461 a3db757 f459461 d2d797d f459461 9406d39 f459461 a497b92 f459461 e512f47 f459461 9406d39 f459461 e512f47 9406d39 e512f47 f459461 a5e47b1 59d7584 f459461 4df293d f459461 4df293d 9fb37a7 e512f47 9fb37a7 e512f47 f459461 e512f47 f459461 e512f47 f459461 e512f47 f459461 4df293d 077a1b7 e512f47 077a1b7 | 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 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 | import os
import sys
import time
import requests
import json
import shutil
from huggingface_hub import HfApi, hf_hub_download
from datetime import datetime
# --- CONFIGURATION ---
REPO_ID = "ravi2814/grant-data-storage"
STATUS_FILE = "status.json"
def get_token():
try:
from kaggle_secrets import UserSecretsClient
token = UserSecretsClient().get_secret("HF_TOKEN")
if token: return token
except: pass
token = os.getenv("HF_TOKEN")
if token and "PLACEHOLDER" not in token: return token
return "HF_TOKEN_PLACEHOLDER"
# --- STATUS MANAGER (Heartbeat) ---
class StatusManager:
def __init__(self, repo_id, token):
self.repo_id = repo_id
self.api = HfApi(token=token)
self.last_upload = 0
def update(self, state, phase, logs, progress):
"""Uploads status.json to HF so the UI can read it remotely."""
try:
# Rate limit: Update at most every 2 seconds
if time.time() - self.last_upload < 2 and state == "running":
return
data = {
"kaggle_state": state,
"phase": phase,
"logs": logs,
"progress": progress,
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
# Write locally
with open(STATUS_FILE, "w") as f: json.dump(data, f)
# Upload to Dataset
self.api.upload_file(
path_or_fileobj=STATUS_FILE,
path_in_repo=STATUS_FILE,
repo_id=self.repo_id,
repo_type="dataset"
)
self.last_upload = time.time()
except: pass
# --- LOGGER ---
class Logger:
def __init__(self, repo_id, token, status_mgr):
self.repo_id = repo_id
self.api = HfApi(token=token)
self.status_mgr = status_mgr
self.log_file = "live_log.txt"
self.buffer = []
self.last_upload = 0
# Init file
with open(self.log_file, "w") as f:
f.write(f"Worker Started at {datetime.now().strftime('%H:%M:%S')}\n")
self.flush()
def log(self, message):
print(message, flush=True)
timestamp = datetime.now().strftime('%H:%M:%S')
log_entry = f"[{timestamp}] {message}"
self.buffer.append(log_entry)
# Update Status JSON immediately with log snippet
self.status_mgr.update("running", "Connected to Kaggle (Safe to close)", log_entry, 95)
if time.time() - self.last_upload > 3:
self.flush()
def flush(self):
try:
with open(self.log_file, "w") as f: f.write("\n".join(self.buffer))
self.api.upload_file(path_or_fileobj=self.log_file, path_in_repo=self.log_file, repo_id=self.repo_id, repo_type="dataset")
self.last_upload = time.time()
except: pass
if __name__ == "__main__":
HF_TOKEN = get_token()
if "PLACEHOLDER" in HF_TOKEN: sys.exit(1)
# 1. Start Status Manager
status_mgr = StatusManager(REPO_ID, HF_TOKEN)
status_mgr.update("running", "Initializing Environment...", "Starting...", 70)
try:
logger = Logger(REPO_ID, HF_TOKEN, status_mgr)
logger.log("Environment Ready.")
except: sys.exit(1)
# 2. Install Libraries
try:
import sentence_transformers
import pyarrow
except ImportError:
logger.log("Installing libraries...")
os.system("pip install sentence-transformers pyarrow numpy pandas huggingface_hub torch")
# 3. Imports (Fixed Torch Error)
import pandas as pd
import hashlib
import re
import numpy as np
import pyarrow.parquet as pq
import pyarrow as pa
# Explicitly import torch with error handling
try:
import torch
except ImportError:
logger.log("Re-installing torch...")
os.system("pip install torch")
import torch
from sentence_transformers import SentenceTransformer
class Updater:
def __init__(self, logger_instance):
self.logger = logger_instance
self.data_url = "https://open.canada.ca/data/dataset/432527ab-7aac-45b5-81d6-7597107a7013/resource/1d15a62f-5656-49ad-8c88-f40ce689d831/download/grants.csv"
self.api_url = "https://open.canada.ca/data/api/3/action/package_show?id=432527ab-7aac-45b5-81d6-7597107a7013"
self.local_parquet = "grant data.parquet"
self.temp_csv = "temp_data.csv"
self.api_metadata = {}
device = 'cuda' if torch.cuda.is_available() else 'cpu'
self.logger.log(f"Loading AI Model on {device.upper()}...")
self.model = SentenceTransformer('intfloat/multilingual-e5-large', device=device)
def create_hash(self, text):
if not isinstance(text, str): text = ""
clean = re.sub(r'\s+', '', text).lower()
return hashlib.md5(clean.encode('utf-8')).hexdigest()
def format_record(self, record):
def val(k): return str(record.get(k, "") or "").strip()
mapping = [
("Reference number", "ref_number"), ("amendment number", "amendment_number"),
("agreement type", "agreement_type"), ("recipient legal name", "recipient_legal_name"),
("recipient country", "recipient_country"), ("recipient province", "recipient_province"),
("recipient city", "recipient_city"), ("recipient postal code", "recipient_postal_code"),
("program name english", "prog_name_en"), ("program name french", "prog_name_fr"),
("program purpose english", "prog_purpose_en"), ("program purpose french", "prog_purpose_fr"),
("agreement title english", "agreement_title_en"), ("agreement title french", "agreement_title_fr"),
("agreement number", "agreement_number"), ("agreement value", "agreement_value"),
("agreement start date", "agreement_start_date"), ("agreement end date", "agreement_end_date"),
("description english", "description_en"), ("description french", "description_fr"),
("expected results english", "expected_results_en"), ("expected results french", "expected_results_fr"),
("additional information english", "additional_information_en"), ("additional information french", "additional_information_fr"),
("owner organization", "owner_org"), ("owner organization title", "owner_org_title"),
]
return ", ".join([f"{d} : {val(k)}" for d, k in mapping])
def download_current_database(self):
self.logger.log("Downloading current Database...")
try:
hf_hub_download(repo_id=REPO_ID, filename="grant data.parquet", repo_type="dataset", token=HF_TOKEN, local_dir=".", force_download=True)
hf_hub_download(repo_id=REPO_ID, filename="embeddings.npy", repo_type="dataset", token=HF_TOKEN, local_dir=".", force_download=True)
except:
self.logger.log("No existing database found. Starting fresh.")
def download_government_data(self):
self.logger.log("Downloading fresh data from Canada.ca...")
try:
r = requests.get(self.data_url, stream=True)
with open(self.temp_csv, 'wb') as f:
for chunk in r.iter_content(chunk_size=1048576): f.write(chunk)
r_api = requests.get(self.api_url).json()['result']
res = next((r for r in r_api['resources'] if r['id'] == '1d15a62f-5656-49ad-8c88-f40ce689d831'), {})
self.api_metadata = {
"metadata_modified": r_api.get('metadata_modified'),
"resource_modified": res.get('last_modified'),
"checked_at": datetime.now().isoformat()
}
return True
except Exception as e:
self.logger.log(f"Download Error: {e}")
return False
def synchronize_data(self):
if not os.path.exists(self.temp_csv): return False
self.logger.log("Scanning Government CSV...")
status_mgr.update("running", "Scanning CSV...", "Reading...", 60)
valid_hashes = set()
new_rows_buffer = []
for chunk in pd.read_csv(self.temp_csv, chunksize=50000, low_memory=False):
for _, row in chunk.iterrows():
fmt = self.format_record(row.to_dict())
h = self.create_hash(fmt)
valid_hashes.add(h)
new_rows_buffer.append({'data': fmt, 'hash': h})
self.logger.log(f"Total Official Records: {len(valid_hashes)}")
if os.path.exists(self.local_parquet) and os.path.exists("embeddings.npy"):
df = pd.read_parquet(self.local_parquet)
emb = np.load("embeddings.npy")
keep_indices = []
local_hashes = set()
for idx, row in df.iterrows():
h = self.create_hash(row['data'])
if h in valid_hashes:
keep_indices.append(idx)
local_hashes.add(h)
if len(keep_indices) < len(df):
self.logger.log(f"Removing {len(df) - len(keep_indices)} obsolete records...")
df = df.loc[keep_indices].reset_index(drop=True)
emb = emb[keep_indices]
else:
df = pd.DataFrame(columns=['data'])
emb = np.empty((0, 1024))
local_hashes = set()
rows_to_add = []
for item in new_rows_buffer:
if item['hash'] not in local_hashes:
rows_to_add.append(item['data'])
local_hashes.add(item['hash'])
if rows_to_add:
self.logger.log(f"Found {len(rows_to_add)} NEW records. Processing...")
status_mgr.update("running", "Embedding New Data...", f"Processing {len(rows_to_add)} rows", 80)
new_vectors = []
for i in range(0, len(rows_to_add), 2000):
batch = rows_to_add[i : i + 2000]
batch_fmt = ["passage: " + t for t in batch]
v = self.model.encode(batch_fmt, convert_to_numpy=True, normalize_embeddings=True)
new_vectors.append(v)
new_emb_stack = np.vstack(new_vectors)
new_df = pd.DataFrame({'data': rows_to_add})
df = pd.concat([df, new_df], ignore_index=True)
if len(emb) > 0: emb = np.vstack([emb, new_emb_stack])
else: emb = new_emb_stack
self.logger.log(f"Database Updated. Total: {len(df)}")
else:
self.logger.log("No new records to add.")
self.logger.log("Saving files...")
table = pa.Table.from_pandas(df)
pq.write_table(table, self.local_parquet)
np.save("embeddings.npy", emb)
with open("last_metadata.json", "w") as f:
json.dump(self.api_metadata, f)
return True
try:
updater = Updater(logger)
updater.download_current_database()
if updater.download_government_data():
updater.synchronize_data()
logger.log("Uploading results...")
status_mgr.update("running", "Uploading Final Data...", "Finalizing...", 95)
api = HfApi(token=HF_TOKEN)
os.makedirs("staging", exist_ok=True)
files = ["grant data.parquet", "embeddings.npy", "last_metadata.json", "live_log.txt", STATUS_FILE]
for f in files:
if os.path.exists(f): shutil.copy(f, f"staging/{f}")
api.upload_folder(folder_path="staging", repo_id=REPO_ID, repo_type="dataset", commit_message="Batch Update")
# FINAL SUCCESS
status_mgr.update("finished", "Sync Complete", "All data uploaded successfully.", 100)
logger.log("SUCCESS: Process Complete.")
except Exception as e:
logger.log(f"CRITICAL ERROR: {e}")
status_mgr.update("error", "Worker Failed", str(e), 0)
sys.exit(1) |