iozxv commited on
Commit
53b1b6b
·
verified ·
1 Parent(s): a3fc361

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +522 -0
app.py ADDED
@@ -0,0 +1,522 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import duckdb
3
+ import time
4
+ import psutil
5
+ import platform
6
+ import logging
7
+ import sys
8
+ from flask import Flask, request, jsonify, render_template_string
9
+ from huggingface_hub import hf_hub_download
10
+
11
+ # --------------------------------------------------
12
+ # LOGGING (FORCE SHOW)
13
+ # --------------------------------------------------
14
+
15
+ sys.stdout.reconfigure(line_buffering=True)
16
+
17
+ logging.basicConfig(
18
+ level=logging.INFO,
19
+ format="%(asctime)s | %(message)s",
20
+ force=True
21
+ )
22
+
23
+ log = logging.info
24
+ log("🔥 APP STARTED")
25
+
26
+ # --------------------------------------------------
27
+ # CONFIG
28
+ # --------------------------------------------------
29
+
30
+ FILES = [
31
+ ("Oiasx/Datx", "chunk_026.parquet"),
32
+ ("Oiasx/Datx", "chunk_027.parquet")
33
+ ]
34
+
35
+ app = Flask(__name__)
36
+
37
+ # --------------------------------------------------
38
+ # TOKEN
39
+ # --------------------------------------------------
40
+
41
+ HF_TOKEN = os.environ.get("Oiasx_TOKEN")
42
+
43
+ if not HF_TOKEN:
44
+ raise RuntimeError("Oiasx_TOKEN missing")
45
+
46
+ log("🔐 Token loaded")
47
+
48
+ # --------------------------------------------------
49
+ # SYSTEM INFO
50
+ # --------------------------------------------------
51
+
52
+ log("🚀 Starting system")
53
+ log(f"CPU: {os.cpu_count()}")
54
+
55
+ ram = psutil.virtual_memory()
56
+ log(f"RAM: {round(ram.total/1e9,2)} GB")
57
+
58
+ # --------------------------------------------------
59
+ # DOWNLOAD FILES
60
+ # --------------------------------------------------
61
+
62
+ parquet_files = []
63
+
64
+ log("📥 Downloading parquet files...")
65
+
66
+ for repo, fname in FILES:
67
+
68
+ try:
69
+ log(f"⬇️ {fname}")
70
+
71
+ path = hf_hub_download(
72
+ repo_id=repo,
73
+ filename=fname,
74
+ repo_type="dataset",
75
+ token=HF_TOKEN
76
+ )
77
+
78
+ parquet_files.append(path)
79
+
80
+ log(f"✅ Done: {fname}")
81
+
82
+ except Exception as e:
83
+ log(f"❌ Failed: {fname} | {e}")
84
+
85
+ log(f"📊 Total files: {len(parquet_files)}")
86
+
87
+ # --------------------------------------------------
88
+ # CONNECT DUCKDB (NO TABLE)
89
+ # --------------------------------------------------
90
+
91
+ con = duckdb.connect()
92
+
93
+ con.execute(f"PRAGMA threads={os.cpu_count()}")
94
+ con.execute("PRAGMA enable_object_cache")
95
+
96
+ log("⚡ DuckDB ready")
97
+
98
+ # --------------------------------------------------
99
+ # GET COLUMNS
100
+ # --------------------------------------------------
101
+
102
+ parquet_list_sql = ",".join([f"'{p}'" for p in parquet_files])
103
+
104
+ log("🔍 Reading schema...")
105
+
106
+ cols = con.execute(f"""
107
+ DESCRIBE SELECT * FROM read_parquet([{parquet_list_sql}])
108
+ """).fetchall()
109
+
110
+ columns = [c[0] for c in cols]
111
+
112
+ log(f"Columns: {columns}")
113
+
114
+ # --------------------------------------------------
115
+ # SEARCH (DIRECT PARQUET)
116
+ # --------------------------------------------------
117
+
118
+ def run_search(column, value):
119
+
120
+ log(f"🔎 Search: {column} = {value}")
121
+
122
+ if column not in columns:
123
+ return {"error": "column not found"}
124
+
125
+ value = str(value).strip()
126
+
127
+ base = f"SELECT * FROM read_parquet([{parquet_list_sql}])"
128
+
129
+ if column == "mobile":
130
+
131
+ query = base + f" WHERE mobile = ? LIMIT 100"
132
+ param = value
133
+ log("⚡ Mode: EXACT mobile")
134
+
135
+ elif column in ["circle","email","id","alt"]:
136
+
137
+ query = base + f" WHERE {column} = ? LIMIT 100"
138
+ param = value
139
+ log("⚡ Mode: EXACT")
140
+
141
+ else:
142
+
143
+ query = base + f" WHERE {column} LIKE ? LIMIT 100"
144
+ param = value + "%"
145
+ log("🔍 Mode: PREFIX")
146
+
147
+ start = time.time()
148
+
149
+ rows = con.execute(query, [param]).fetchall()
150
+
151
+ query_time = (time.time() - start) * 1000
152
+
153
+ log(f"⏱ {round(query_time,3)} ms | Rows: {len(rows)}")
154
+
155
+ result = [dict(zip(columns, r)) for r in rows]
156
+
157
+ return {
158
+ "rows": result,
159
+ "returned": len(result),
160
+ "query_ms": round(query_time, 3)
161
+ }
162
+
163
+ # --------------------------------------------------
164
+ # UI
165
+ # --------------------------------------------------
166
+
167
+ HTML = """<!DOCTYPE html>
168
+ <html lang="en">
169
+ <head>
170
+ <meta charset="UTF-8">
171
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
172
+ <title>Telecom Search</title>
173
+ <style>
174
+ *{margin:0;padding:0;box-sizing:border-box}
175
+ :root{
176
+ --bg:#07070f;
177
+ --bg2:#0d0d1c;
178
+ --bg3:#12122a;
179
+ --surface:#161630;
180
+ --s2:#1c1c3a;
181
+ --pu:#7c3aed;
182
+ --pu2:#a855f7;
183
+ --pu3:#c084fc;
184
+ --cy:#06b6d4;
185
+ --cy2:#22d3ee;
186
+ --pk:#ec4899;
187
+ --gn:#10b981;
188
+ --am:#f59e0b;
189
+ --tx:#e2e8f0;
190
+ --tx2:#94a3b8;
191
+ --tx3:#475569;
192
+ --bd:rgba(124,58,237,0.18);
193
+ --bd2:rgba(124,58,237,0.35);
194
+ }
195
+ body{
196
+ font-family:'Segoe UI',system-ui,sans-serif;
197
+ background:var(--bg);
198
+ color:var(--tx);
199
+ min-height:100vh;
200
+ overflow-x:hidden;
201
+ }
202
+ /* ─── LOADING ─── */
203
+ #ld{
204
+ position:fixed;inset:0;
205
+ background:var(--bg);
206
+ display:flex;flex-direction:column;
207
+ align-items:center;justify-content:center;
208
+ gap:44px;z-index:999;
209
+ transition:opacity .6s ease;
210
+ }
211
+ #ld.out{opacity:0;pointer-events:none}
212
+ /* Orb */
213
+ .orb{position:relative;width:148px;height:148px}
214
+ .ring{
215
+ position:absolute;inset:0;border-radius:50%;
216
+ border:1.5px solid transparent;
217
+ animation:spin linear infinite;
218
+ }
219
+ .ring:nth-child(1){border-top-color:var(--pu);border-right-color:var(--pu);animation-duration:2.2s;inset:0}
220
+ .ring:nth-child(2){border-top-color:var(--cy);animation-duration:3.4s;animation-direction:reverse;inset:16px}
221
+ .ring:nth-child(3){border-top-color:var(--pk);border-left-color:var(--pk);animation-duration:1.8s;inset:30px}
222
+ .ring:nth-child(4){border-bottom-color:var(--am);animation-duration:4.1s;animation-direction:reverse;inset:44px}
223
+ .core{
224
+ position:absolute;inset:58px;border-radius:50%;
225
+ background:radial-gradient(circle at 33% 33%,var(--pu2),var(--pu) 55%,#2e1065);
226
+ display:flex;align-items:center;justify-content:center;
227
+ }
228
+ .core::before{
229
+ content:'';position:absolute;inset:0;border-radius:50%;
230
+ background:radial-gradient(circle at 30% 25%,rgba(255,255,255,.3) 0%,transparent 55%);
231
+ }
232
+ .pulse{
233
+ width:7px;height:7px;border-radius:50%;
234
+ background:#fff;opacity:.85;
235
+ animation:pulse 1.5s ease-in-out infinite;
236
+ }
237
+ /* Progress */
238
+ .prog-wrap{width:300px;display:flex;flex-direction:column;align-items:center;gap:10px}
239
+ .bar-track{
240
+ width:100%;height:3px;
241
+ background:rgba(124,58,237,.1);border-radius:99px;overflow:hidden;
242
+ }
243
+ .bar-fill{
244
+ height:100%;border-radius:99px;width:0%;
245
+ background:linear-gradient(90deg,var(--pu),var(--cy2));
246
+ transition:width .7s cubic-bezier(.4,0,.2,1);
247
+ position:relative;
248
+ }
249
+ .bar-fill::after{
250
+ content:'';position:absolute;right:-1px;top:-2px;
251
+ width:7px;height:7px;border-radius:50%;
252
+ background:var(--cy2);
253
+ box-shadow:0 0 10px var(--cy),0 0 20px var(--cy);
254
+ }
255
+ .segs{display:flex;gap:5px}
256
+ .seg{
257
+ width:30px;height:3px;border-radius:99px;
258
+ background:rgba(124,58,237,.1);
259
+ transition:background .4s,box-shadow .4s;
260
+ }
261
+ .seg.on{background:var(--pu);box-shadow:0 0 8px rgba(124,58,237,.7)}
262
+ .seg.done{background:var(--pu2)}
263
+ .prog-txt{
264
+ font-size:11px;letter-spacing:.1em;text-transform:uppercase;
265
+ color:var(--tx3);font-weight:500;min-height:15px;
266
+ transition:color .3s;
267
+ }
268
+ /* Steps */
269
+ .steps{display:flex;flex-direction:column;gap:9px;width:290px}
270
+ .step{
271
+ display:flex;align-items:center;gap:10px;
272
+ font-size:12px;letter-spacing:.03em;color:var(--tx3);
273
+ opacity:0;transform:translateY(8px);
274
+ transition:opacity .4s,transform .4s,color .3s;
275
+ }
276
+ .step.show{opacity:1;transform:translateY(0)}
277
+ .step.done{color:var(--tx2)}
278
+ .dot{
279
+ width:7px;height:7px;border-radius:50%;
280
+ background:var(--tx3);flex-shrink:0;
281
+ transition:background .3s,box-shadow .3s;
282
+ }
283
+ .step.active .dot{
284
+ background:var(--cy2);
285
+ box-shadow:0 0 6px var(--cy),0 0 12px var(--cy);
286
+ animation:blink .9s ease-in-out infinite;
287
+ }
288
+ .step.done .dot{background:var(--gn);box-shadow:0 0 6px var(--gn);animation:none}
289
+ /* Clock */
290
+ .clk-wrap{display:flex;flex-direction:column;align-items:center;gap:5px}
291
+ .clk-lbl{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--tx3);font-weight:500}
292
+ .clk{
293
+ font-size:30px;font-weight:300;letter-spacing:.06em;
294
+ color:var(--tx);font-variant-numeric:tabular-nums;line-height:1;
295
+ }
296
+ .clk .sep{color:var(--pu2);animation:blink2 1s step-end infinite}
297
+ /* ─── APP ─── */
298
+ #app{display:none;min-height:100vh}
299
+ .hdr{
300
+ background:var(--bg2);
301
+ border-bottom:1px solid var(--bd);
302
+ padding:16px 28px;
303
+ display:flex;align-items:center;gap:14px;
304
+ }
305
+ .hdr-ico{
306
+ width:38px;height:38px;border-radius:10px;
307
+ background:linear-gradient(135deg,var(--pu),#2e1065);
308
+ display:flex;align-items:center;justify-content:center;font-size:17px;
309
+ }
310
+ .hdr-t{font-size:15px;font-weight:600;letter-spacing:.02em}
311
+ .hdr-s{font-size:11px;color:var(--tx3);margin-top:2px;letter-spacing:.04em}
312
+ .body{max-width:860px;margin:0 auto;padding:32px 20px}
313
+ .card{
314
+ background:var(--surface);
315
+ border:1px solid var(--bd);border-radius:14px;
316
+ padding:22px;margin-bottom:20px;
317
+ }
318
+ .row{display:flex;gap:12px;align-items:flex-end;flex-wrap:wrap}
319
+ .fg{display:flex;flex-direction:column;gap:6px}
320
+ .fl{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--tx3);font-weight:500}
321
+ select,input[type=text]{
322
+ background:var(--bg3);border:1px solid var(--bd);border-radius:8px;
323
+ color:var(--tx);font-size:13.5px;padding:9px 13px;outline:none;
324
+ transition:border-color .2s;font-family:inherit;
325
+ }
326
+ select{min-width:130px;cursor:pointer}
327
+ input[type=text]{min-width:220px}
328
+ select:focus,input[type=text]:focus{border-color:var(--pu2)}
329
+ .btn{
330
+ padding:9px 22px;background:var(--pu);color:#fff;border:none;
331
+ border-radius:8px;font-size:13.5px;font-weight:500;cursor:pointer;
332
+ letter-spacing:.03em;transition:background .2s,transform .1s;
333
+ font-family:inherit;
334
+ }
335
+ .btn:hover{background:var(--pu2)}
336
+ .btn:active{transform:scale(.97)}
337
+ .stats{
338
+ display:flex;gap:18px;margin-top:12px;
339
+ font-size:12px;color:var(--tx3);letter-spacing:.03em;
340
+ }
341
+ .sc{display:flex;align-items:center;gap:5px}
342
+ .sc .v{color:var(--cy2);font-weight:500;font-variant-numeric:tabular-nums}
343
+ .res{background:var(--surface);border:1px solid var(--bd);border-radius:14px;overflow:hidden}
344
+ .res-h{
345
+ padding:13px 18px;border-bottom:1px solid var(--bd);
346
+ font-size:11px;color:var(--tx3);letter-spacing:.07em;text-transform:uppercase;font-weight:500;
347
+ }
348
+ pre#out{
349
+ padding:18px;font-size:12px;line-height:1.75;color:var(--tx2);
350
+ font-family:'Cascadia Code','Fira Code',Consolas,monospace;
351
+ overflow-x:auto;max-height:500px;overflow-y:auto;
352
+ white-space:pre-wrap;word-break:break-all;
353
+ }
354
+ pre#out::-webkit-scrollbar{width:5px;height:5px}
355
+ pre#out::-webkit-scrollbar-track{background:transparent}
356
+ pre#out::-webkit-scrollbar-thumb{background:var(--bd2);border-radius:99px}
357
+ @keyframes spin{to{transform:rotate(360deg)}}
358
+ @keyframes pulse{0%,100%{opacity:.4;transform:scale(.7)}50%{opacity:1;transform:scale(1.3)}}
359
+ @keyframes blink{0%,100%{opacity:1}50%{opacity:.2}}
360
+ @keyframes blink2{0%,100%{opacity:1}50%{opacity:0}}
361
+ </style>
362
+ </head>
363
+ <body>
364
+ <div id="ld">
365
+ <div class="orb">
366
+ <div class="ring"></div>
367
+ <div class="ring"></div>
368
+ <div class="ring"></div>
369
+ <div class="ring"></div>
370
+ <div class="core"><div class="pulse"></div></div>
371
+ </div>
372
+ <div class="prog-wrap">
373
+ <div class="bar-track"><div class="bar-fill" id="bf"></div></div>
374
+ <div class="segs" id="segs">
375
+ <div class="seg"></div><div class="seg"></div><div class="seg"></div>
376
+ <div class="seg"></div><div class="seg"></div><div class="seg"></div>
377
+ </div>
378
+ <div class="prog-txt" id="ptxt">Initializing…</div>
379
+ </div>
380
+ <div class="steps" id="steps">
381
+ <div class="step" id="st0"><div class="dot"></div>Loading token</div>
382
+ <div class="step" id="st1"><div class="dot"></div>Downloading parquet files</div>
383
+ <div class="step" id="st2"><div class="dot"></div>Connecting DuckDB</div>
384
+ <div class="step" id="st3"><div class="dot"></div>Reading schema</div>
385
+ <div class="step" id="st4"><div class="dot"></div>Ready</div>
386
+ </div>
387
+ <div class="clk-wrap">
388
+ <div class="clk-lbl">Clock</div>
389
+ <div class="clk" id="clk">00<span class="sep">:</span>00<span class="sep">:</span>00</div>
390
+ </div>
391
+ </div>
392
+ <div id="app">
393
+ <div class="hdr">
394
+ <div class="hdr-ico">&#9889;</div>
395
+ <div>
396
+ <div class="hdr-t">Telecom Search</div>
397
+ <div class="hdr-s">DuckDB · Parquet · Real-time query</div>
398
+ </div>
399
+ </div>
400
+ <div class="body">
401
+ <div class="card">
402
+ <div class="row">
403
+ <div class="fg">
404
+ <div class="fl">Column</div>
405
+ <select id="column">
406
+ {% for c in columns %}
407
+ <option value="{{c}}">{{c}}</option>
408
+ {% endfor %}
409
+ </select>
410
+ </div>
411
+ <div class="fg">
412
+ <div class="fl">Value</div>
413
+ <input type="text" id="value" placeholder="Search value…">
414
+ </div>
415
+ <button class="btn" onclick="go()">Search</button>
416
+ </div>
417
+ <div class="stats" id="stats" style="display:none">
418
+ <div class="sc">Rows <span class="v" id="sr">—</span></div>
419
+ <div class="sc">Query <span class="v" id="sq">—</span> ms</div>
420
+ </div>
421
+ </div>
422
+ <div class="res">
423
+ <div class="res-h">Results</div>
424
+ <pre id="out">Run a search to see results.</pre>
425
+ </div>
426
+ </div>
427
+ </div>
428
+ <script>
429
+ /* Clock */
430
+ function tick(){
431
+ var n=new Date();
432
+ var p=function(v){return String(v).padStart(2,'0');};
433
+ document.getElementById('clk').innerHTML=
434
+ p(n.getHours())+'<span class="sep">:</span>'+
435
+ p(n.getMinutes())+'<span class="sep">:</span>'+
436
+ p(n.getSeconds());
437
+ }
438
+ tick();setInterval(tick,1000);
439
+ /* Loading sequence */
440
+ var STEPS=[
441
+ {id:'st0',txt:'Token verified',pct:16},
442
+ {id:'st1',txt:'Downloading parquet…',pct:42},
443
+ {id:'st2',txt:'DuckDB connected',pct:64},
444
+ {id:'st3',txt:'Schema ready',pct:83},
445
+ {id:'st4',txt:'All systems go',pct:100},
446
+ ];
447
+ var DELAYS=[350,1000,1900,2700,3400];
448
+ var segs=document.querySelectorAll('.seg');
449
+ var bf=document.getElementById('bf');
450
+ var ptxt=document.getElementById('ptxt');
451
+ function setProg(pct){
452
+ bf.style.width=pct+'%';
453
+ var n=Math.round(pct/100*segs.length);
454
+ segs.forEach(function(s,i){
455
+ s.classList.remove('on','done');
456
+ if(i<n-1)s.classList.add('done');
457
+ else if(i===n-1)s.classList.add('on');
458
+ });
459
+ }
460
+ function setStep(idx){
461
+ STEPS.forEach(function(s,i){
462
+ var el=document.getElementById(s.id);
463
+ el.classList.remove('active','done','show');
464
+ if(i<idx){el.classList.add('show','done');}
465
+ else if(i===idx){el.classList.add('show','active');}
466
+ });
467
+ }
468
+ STEPS.forEach(function(s,i){
469
+ setTimeout(function(){
470
+ setStep(i);setProg(s.pct);ptxt.textContent=s.txt;
471
+ },DELAYS[i]);
472
+ });
473
+ setTimeout(function(){
474
+ var ld=document.getElementById('ld');
475
+ ld.classList.add('out');
476
+ setTimeout(function(){
477
+ ld.style.display='none';
478
+ document.getElementById('app').style.display='block';
479
+ },650);
480
+ },4200);
481
+ /* Search */
482
+ async function go(){
483
+ var column=document.getElementById('column').value;
484
+ var value=document.getElementById('value').value;
485
+ var out=document.getElementById('out');
486
+ out.textContent='Searching…';
487
+ try{
488
+ var r=await fetch('/search?column='+encodeURIComponent(column)+'&value='+encodeURIComponent(value));
489
+ var d=await r.json();
490
+ document.getElementById('stats').style.display='flex';
491
+ document.getElementById('sr').textContent=d.returned;
492
+ document.getElementById('sq').textContent=d.query_ms;
493
+ out.textContent=JSON.stringify(d.rows,null,2);
494
+ }catch(e){out.textContent='Error: '+e.message;}
495
+ }
496
+ document.getElementById('value').addEventListener('keydown',function(e){if(e.key==='Enter')go();});
497
+ </script>
498
+ </body>
499
+ </html>
500
+ """
501
+
502
+ # --------------------------------------------------
503
+ # ROUTES
504
+ # --------------------------------------------------
505
+
506
+ @app.route("/")
507
+ def home():
508
+ return render_template_string(HTML, columns=columns)
509
+
510
+ @app.route("/search")
511
+ def search():
512
+ column = request.args.get("column")
513
+ value = request.args.get("value")
514
+ return jsonify(run_search(column, value))
515
+
516
+ # --------------------------------------------------
517
+ # START
518
+ # --------------------------------------------------
519
+
520
+ if __name__ == "__main__":
521
+ log("🌐 Server starting...")
522
+ app.run(host="0.0.0.0", port=7860)