C11 / app.py
iozxv's picture
Create app.py
7c0a519 verified
Raw
History Blame Contribute Delete
15.9 kB
import os
import duckdb
import time
import psutil
import platform
import logging
import sys
from flask import Flask, request, jsonify, render_template_string
from huggingface_hub import hf_hub_download
# --------------------------------------------------
# LOGGING (FORCE SHOW)
# --------------------------------------------------
sys.stdout.reconfigure(line_buffering=True)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(message)s",
force=True
)
log = logging.info
log("🔥 APP STARTED")
# --------------------------------------------------
# CONFIG
# --------------------------------------------------
FILES = [
("Oiasx/Datx", "chunk_020.parquet"),
("Oiasx/Datx", "chunk_021.parquet")
]
app = Flask(__name__)
# --------------------------------------------------
# TOKEN
# --------------------------------------------------
HF_TOKEN = os.environ.get("Oiasx_TOKEN")
if not HF_TOKEN:
raise RuntimeError("Oiasx_TOKEN missing")
log("🔐 Token loaded")
# --------------------------------------------------
# SYSTEM INFO
# --------------------------------------------------
log("🚀 Starting system")
log(f"CPU: {os.cpu_count()}")
ram = psutil.virtual_memory()
log(f"RAM: {round(ram.total/1e9,2)} GB")
# --------------------------------------------------
# DOWNLOAD FILES
# --------------------------------------------------
parquet_files = []
log("📥 Downloading parquet files...")
for repo, fname in FILES:
try:
log(f"⬇️ {fname}")
path = hf_hub_download(
repo_id=repo,
filename=fname,
repo_type="dataset",
token=HF_TOKEN
)
parquet_files.append(path)
log(f"✅ Done: {fname}")
except Exception as e:
log(f"❌ Failed: {fname} | {e}")
log(f"📊 Total files: {len(parquet_files)}")
# --------------------------------------------------
# CONNECT DUCKDB (NO TABLE)
# --------------------------------------------------
con = duckdb.connect()
con.execute(f"PRAGMA threads={os.cpu_count()}")
con.execute("PRAGMA enable_object_cache")
log("⚡ DuckDB ready")
# --------------------------------------------------
# GET COLUMNS
# --------------------------------------------------
parquet_list_sql = ",".join([f"'{p}'" for p in parquet_files])
log("🔍 Reading schema...")
cols = con.execute(f"""
DESCRIBE SELECT * FROM read_parquet([{parquet_list_sql}])
""").fetchall()
columns = [c[0] for c in cols]
log(f"Columns: {columns}")
# --------------------------------------------------
# SEARCH (DIRECT PARQUET)
# --------------------------------------------------
def run_search(column, value):
log(f"🔎 Search: {column} = {value}")
if column not in columns:
return {"error": "column not found"}
value = str(value).strip()
base = f"SELECT * FROM read_parquet([{parquet_list_sql}])"
if column == "mobile":
query = base + f" WHERE mobile = ? LIMIT 100"
param = value
log("⚡ Mode: EXACT mobile")
elif column in ["circle","email","id","alt"]:
query = base + f" WHERE {column} = ? LIMIT 100"
param = value
log("⚡ Mode: EXACT")
else:
query = base + f" WHERE {column} LIKE ? LIMIT 100"
param = value + "%"
log("🔍 Mode: PREFIX")
start = time.time()
rows = con.execute(query, [param]).fetchall()
query_time = (time.time() - start) * 1000
log(f"⏱ {round(query_time,3)} ms | Rows: {len(rows)}")
result = [dict(zip(columns, r)) for r in rows]
return {
"rows": result,
"returned": len(result),
"query_ms": round(query_time, 3)
}
# --------------------------------------------------
# UI
# --------------------------------------------------
HTML = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Telecom Search</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
:root{
--bg:#07070f;
--bg2:#0d0d1c;
--bg3:#12122a;
--surface:#161630;
--s2:#1c1c3a;
--pu:#7c3aed;
--pu2:#a855f7;
--pu3:#c084fc;
--cy:#06b6d4;
--cy2:#22d3ee;
--pk:#ec4899;
--gn:#10b981;
--am:#f59e0b;
--tx:#e2e8f0;
--tx2:#94a3b8;
--tx3:#475569;
--bd:rgba(124,58,237,0.18);
--bd2:rgba(124,58,237,0.35);
}
body{
font-family:'Segoe UI',system-ui,sans-serif;
background:var(--bg);
color:var(--tx);
min-height:100vh;
overflow-x:hidden;
}
/* ─── LOADING ─── */
#ld{
position:fixed;inset:0;
background:var(--bg);
display:flex;flex-direction:column;
align-items:center;justify-content:center;
gap:44px;z-index:999;
transition:opacity .6s ease;
}
#ld.out{opacity:0;pointer-events:none}
/* Orb */
.orb{position:relative;width:148px;height:148px}
.ring{
position:absolute;inset:0;border-radius:50%;
border:1.5px solid transparent;
animation:spin linear infinite;
}
.ring:nth-child(1){border-top-color:var(--pu);border-right-color:var(--pu);animation-duration:2.2s;inset:0}
.ring:nth-child(2){border-top-color:var(--cy);animation-duration:3.4s;animation-direction:reverse;inset:16px}
.ring:nth-child(3){border-top-color:var(--pk);border-left-color:var(--pk);animation-duration:1.8s;inset:30px}
.ring:nth-child(4){border-bottom-color:var(--am);animation-duration:4.1s;animation-direction:reverse;inset:44px}
.core{
position:absolute;inset:58px;border-radius:50%;
background:radial-gradient(circle at 33% 33%,var(--pu2),var(--pu) 55%,#2e1065);
display:flex;align-items:center;justify-content:center;
}
.core::before{
content:'';position:absolute;inset:0;border-radius:50%;
background:radial-gradient(circle at 30% 25%,rgba(255,255,255,.3) 0%,transparent 55%);
}
.pulse{
width:7px;height:7px;border-radius:50%;
background:#fff;opacity:.85;
animation:pulse 1.5s ease-in-out infinite;
}
/* Progress */
.prog-wrap{width:300px;display:flex;flex-direction:column;align-items:center;gap:10px}
.bar-track{
width:100%;height:3px;
background:rgba(124,58,237,.1);border-radius:99px;overflow:hidden;
}
.bar-fill{
height:100%;border-radius:99px;width:0%;
background:linear-gradient(90deg,var(--pu),var(--cy2));
transition:width .7s cubic-bezier(.4,0,.2,1);
position:relative;
}
.bar-fill::after{
content:'';position:absolute;right:-1px;top:-2px;
width:7px;height:7px;border-radius:50%;
background:var(--cy2);
box-shadow:0 0 10px var(--cy),0 0 20px var(--cy);
}
.segs{display:flex;gap:5px}
.seg{
width:30px;height:3px;border-radius:99px;
background:rgba(124,58,237,.1);
transition:background .4s,box-shadow .4s;
}
.seg.on{background:var(--pu);box-shadow:0 0 8px rgba(124,58,237,.7)}
.seg.done{background:var(--pu2)}
.prog-txt{
font-size:11px;letter-spacing:.1em;text-transform:uppercase;
color:var(--tx3);font-weight:500;min-height:15px;
transition:color .3s;
}
/* Steps */
.steps{display:flex;flex-direction:column;gap:9px;width:290px}
.step{
display:flex;align-items:center;gap:10px;
font-size:12px;letter-spacing:.03em;color:var(--tx3);
opacity:0;transform:translateY(8px);
transition:opacity .4s,transform .4s,color .3s;
}
.step.show{opacity:1;transform:translateY(0)}
.step.done{color:var(--tx2)}
.dot{
width:7px;height:7px;border-radius:50%;
background:var(--tx3);flex-shrink:0;
transition:background .3s,box-shadow .3s;
}
.step.active .dot{
background:var(--cy2);
box-shadow:0 0 6px var(--cy),0 0 12px var(--cy);
animation:blink .9s ease-in-out infinite;
}
.step.done .dot{background:var(--gn);box-shadow:0 0 6px var(--gn);animation:none}
/* Clock */
.clk-wrap{display:flex;flex-direction:column;align-items:center;gap:5px}
.clk-lbl{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--tx3);font-weight:500}
.clk{
font-size:30px;font-weight:300;letter-spacing:.06em;
color:var(--tx);font-variant-numeric:tabular-nums;line-height:1;
}
.clk .sep{color:var(--pu2);animation:blink2 1s step-end infinite}
/* ─── APP ─── */
#app{display:none;min-height:100vh}
.hdr{
background:var(--bg2);
border-bottom:1px solid var(--bd);
padding:16px 28px;
display:flex;align-items:center;gap:14px;
}
.hdr-ico{
width:38px;height:38px;border-radius:10px;
background:linear-gradient(135deg,var(--pu),#2e1065);
display:flex;align-items:center;justify-content:center;font-size:17px;
}
.hdr-t{font-size:15px;font-weight:600;letter-spacing:.02em}
.hdr-s{font-size:11px;color:var(--tx3);margin-top:2px;letter-spacing:.04em}
.body{max-width:860px;margin:0 auto;padding:32px 20px}
.card{
background:var(--surface);
border:1px solid var(--bd);border-radius:14px;
padding:22px;margin-bottom:20px;
}
.row{display:flex;gap:12px;align-items:flex-end;flex-wrap:wrap}
.fg{display:flex;flex-direction:column;gap:6px}
.fl{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--tx3);font-weight:500}
select,input[type=text]{
background:var(--bg3);border:1px solid var(--bd);border-radius:8px;
color:var(--tx);font-size:13.5px;padding:9px 13px;outline:none;
transition:border-color .2s;font-family:inherit;
}
select{min-width:130px;cursor:pointer}
input[type=text]{min-width:220px}
select:focus,input[type=text]:focus{border-color:var(--pu2)}
.btn{
padding:9px 22px;background:var(--pu);color:#fff;border:none;
border-radius:8px;font-size:13.5px;font-weight:500;cursor:pointer;
letter-spacing:.03em;transition:background .2s,transform .1s;
font-family:inherit;
}
.btn:hover{background:var(--pu2)}
.btn:active{transform:scale(.97)}
.stats{
display:flex;gap:18px;margin-top:12px;
font-size:12px;color:var(--tx3);letter-spacing:.03em;
}
.sc{display:flex;align-items:center;gap:5px}
.sc .v{color:var(--cy2);font-weight:500;font-variant-numeric:tabular-nums}
.res{background:var(--surface);border:1px solid var(--bd);border-radius:14px;overflow:hidden}
.res-h{
padding:13px 18px;border-bottom:1px solid var(--bd);
font-size:11px;color:var(--tx3);letter-spacing:.07em;text-transform:uppercase;font-weight:500;
}
pre#out{
padding:18px;font-size:12px;line-height:1.75;color:var(--tx2);
font-family:'Cascadia Code','Fira Code',Consolas,monospace;
overflow-x:auto;max-height:500px;overflow-y:auto;
white-space:pre-wrap;word-break:break-all;
}
pre#out::-webkit-scrollbar{width:5px;height:5px}
pre#out::-webkit-scrollbar-track{background:transparent}
pre#out::-webkit-scrollbar-thumb{background:var(--bd2);border-radius:99px}
@keyframes spin{to{transform:rotate(360deg)}}
@keyframes pulse{0%,100%{opacity:.4;transform:scale(.7)}50%{opacity:1;transform:scale(1.3)}}
@keyframes blink{0%,100%{opacity:1}50%{opacity:.2}}
@keyframes blink2{0%,100%{opacity:1}50%{opacity:0}}
</style>
</head>
<body>
<div id="ld">
<div class="orb">
<div class="ring"></div>
<div class="ring"></div>
<div class="ring"></div>
<div class="ring"></div>
<div class="core"><div class="pulse"></div></div>
</div>
<div class="prog-wrap">
<div class="bar-track"><div class="bar-fill" id="bf"></div></div>
<div class="segs" id="segs">
<div class="seg"></div><div class="seg"></div><div class="seg"></div>
<div class="seg"></div><div class="seg"></div><div class="seg"></div>
</div>
<div class="prog-txt" id="ptxt">Initializing…</div>
</div>
<div class="steps" id="steps">
<div class="step" id="st0"><div class="dot"></div>Loading token</div>
<div class="step" id="st1"><div class="dot"></div>Downloading parquet files</div>
<div class="step" id="st2"><div class="dot"></div>Connecting DuckDB</div>
<div class="step" id="st3"><div class="dot"></div>Reading schema</div>
<div class="step" id="st4"><div class="dot"></div>Ready</div>
</div>
<div class="clk-wrap">
<div class="clk-lbl">Clock</div>
<div class="clk" id="clk">00<span class="sep">:</span>00<span class="sep">:</span>00</div>
</div>
</div>
<div id="app">
<div class="hdr">
<div class="hdr-ico">&#9889;</div>
<div>
<div class="hdr-t">Telecom Search</div>
<div class="hdr-s">DuckDB · Parquet · Real-time query</div>
</div>
</div>
<div class="body">
<div class="card">
<div class="row">
<div class="fg">
<div class="fl">Column</div>
<select id="column">
{% for c in columns %}
<option value="{{c}}">{{c}}</option>
{% endfor %}
</select>
</div>
<div class="fg">
<div class="fl">Value</div>
<input type="text" id="value" placeholder="Search value…">
</div>
<button class="btn" onclick="go()">Search</button>
</div>
<div class="stats" id="stats" style="display:none">
<div class="sc">Rows <span class="v" id="sr">—</span></div>
<div class="sc">Query <span class="v" id="sq">—</span> ms</div>
</div>
</div>
<div class="res">
<div class="res-h">Results</div>
<pre id="out">Run a search to see results.</pre>
</div>
</div>
</div>
<script>
/* Clock */
function tick(){
var n=new Date();
var p=function(v){return String(v).padStart(2,'0');};
document.getElementById('clk').innerHTML=
p(n.getHours())+'<span class="sep">:</span>'+
p(n.getMinutes())+'<span class="sep">:</span>'+
p(n.getSeconds());
}
tick();setInterval(tick,1000);
/* Loading sequence */
var STEPS=[
{id:'st0',txt:'Token verified',pct:16},
{id:'st1',txt:'Downloading parquet…',pct:42},
{id:'st2',txt:'DuckDB connected',pct:64},
{id:'st3',txt:'Schema ready',pct:83},
{id:'st4',txt:'All systems go',pct:100},
];
var DELAYS=[350,1000,1900,2700,3400];
var segs=document.querySelectorAll('.seg');
var bf=document.getElementById('bf');
var ptxt=document.getElementById('ptxt');
function setProg(pct){
bf.style.width=pct+'%';
var n=Math.round(pct/100*segs.length);
segs.forEach(function(s,i){
s.classList.remove('on','done');
if(i<n-1)s.classList.add('done');
else if(i===n-1)s.classList.add('on');
});
}
function setStep(idx){
STEPS.forEach(function(s,i){
var el=document.getElementById(s.id);
el.classList.remove('active','done','show');
if(i<idx){el.classList.add('show','done');}
else if(i===idx){el.classList.add('show','active');}
});
}
STEPS.forEach(function(s,i){
setTimeout(function(){
setStep(i);setProg(s.pct);ptxt.textContent=s.txt;
},DELAYS[i]);
});
setTimeout(function(){
var ld=document.getElementById('ld');
ld.classList.add('out');
setTimeout(function(){
ld.style.display='none';
document.getElementById('app').style.display='block';
},650);
},4200);
/* Search */
async function go(){
var column=document.getElementById('column').value;
var value=document.getElementById('value').value;
var out=document.getElementById('out');
out.textContent='Searching…';
try{
var r=await fetch('/search?column='+encodeURIComponent(column)+'&value='+encodeURIComponent(value));
var d=await r.json();
document.getElementById('stats').style.display='flex';
document.getElementById('sr').textContent=d.returned;
document.getElementById('sq').textContent=d.query_ms;
out.textContent=JSON.stringify(d.rows,null,2);
}catch(e){out.textContent='Error: '+e.message;}
}
document.getElementById('value').addEventListener('keydown',function(e){if(e.key==='Enter')go();});
</script>
</body>
</html>
"""
# --------------------------------------------------
# ROUTES
# --------------------------------------------------
@app.route("/")
def home():
return render_template_string(HTML, columns=columns)
@app.route("/search")
def search():
column = request.args.get("column")
value = request.args.get("value")
return jsonify(run_search(column, value))
# --------------------------------------------------
# START
# --------------------------------------------------
if __name__ == "__main__":
log("🌐 Server starting...")
app.run(host="0.0.0.0", port=7860)