Attendance / database.py
Aruplayz's picture
Upload 3 files
bb238a1 verified
Raw
History Blame Contribute Delete
5.77 kB
"""database.py β€” Persistent storage for groups, students, attendance"""
import json, uuid, os
from pathlib import Path
from datetime import datetime
DATA_DIR = Path("data")
EMBED_FILE = DATA_DIR / "embeddings.json"
GROUPS_FILE = DATA_DIR / "groups.json"
ATT_DIR = DATA_DIR / "attendance"
def _init():
DATA_DIR.mkdir(exist_ok=True)
ATT_DIR.mkdir(exist_ok=True)
# ── Groups ────────────────────────────────────────────────────
def load_groups():
_init()
if not GROUPS_FILE.exists(): return {}
try: return json.loads(GROUPS_FILE.read_text())
except: return {}
def save_groups(g):
_init(); GROUPS_FILE.write_text(json.dumps(g, indent=2))
def create_group(name, color="#a855f7"):
g = load_groups()
if any(v["name"].lower()==name.lower() for v in g.values()):
return None, "Group already exists"
gid = str(uuid.uuid4())[:8]
g[gid] = {"name": name, "color": color, "created": datetime.now().isoformat()}
save_groups(g); return gid, "OK"
def delete_group(gid):
g = load_groups()
if gid not in g: return False
del g[gid]; save_groups(g)
# remove students
e = load_embeddings()
for sid in [k for k,v in e.items() if v.get("group_id")==gid]:
del e[sid]
save_embeddings(e); return True
# ── Students / Embeddings ─────────────────────────────────────
def load_embeddings():
_init()
if not EMBED_FILE.exists(): return {}
try: return json.loads(EMBED_FILE.read_text())
except: return {}
def save_embeddings(e):
_init(); EMBED_FILE.write_text(json.dumps(e, indent=2))
def add_student(name, group_id, fingerprint):
"""
fingerprint = list of vectors (one per captured pose/angle) OR
a single plain vector (legacy) OR
{ 'insight': list-of-vectors|vector|None, 'facenet':..., 'geo':... }
Each pose is stored separately so matching can pick the best-fitting angle
instead of averaging poses into one blurry vector.
One registration per name per group.
"""
e = load_embeddings()
if any(v["group_id"]==group_id and v["name"].lower()==name.lower() for v in e.values()):
return None, f"'{name}' already registered in this group"
sid = str(uuid.uuid4())[:12]
def _as_pose_list(v):
if v is None:
return None
# list of vectors (each vector is itself a list of floats)
if isinstance(v, list) and v and isinstance(v[0], list):
return v
# single flat vector -> wrap as a 1-pose list
if isinstance(v, list):
return [v]
return None
if isinstance(fingerprint, dict):
fp_dict = fingerprint
insight = _as_pose_list(fp_dict.get("insight"))
facenet = _as_pose_list(fp_dict.get("facenet"))
geo = _as_pose_list(fp_dict.get("geo"))
else:
insight = _as_pose_list(fingerprint)
facenet = None
geo = None
e[sid] = {
"name": name,
"group_id": group_id,
"insight": insight, # list of pose vectors, e.g. [front, left, right]
"facenet": facenet,
"geo": geo,
"registered": datetime.now().isoformat(),
}
save_embeddings(e); return sid, "OK"
def delete_student(sid):
e = load_embeddings()
if sid not in e: return False
del e[sid]; save_embeddings(e); return True
def get_students_in_group(gid):
e = load_embeddings()
return [{"id":k,"name":v["name"],"registered":v.get("registered","")}
for k,v in e.items() if v.get("group_id")==gid]
def get_group_embeddings(gid):
e = load_embeddings()
return {k:v for k,v in e.items() if v.get("group_id")==gid}
def get_all_embeddings():
return load_embeddings()
# ── Attendance ────────────────────────────────────────────────
def today():
return datetime.now().strftime("%Y-%m-%d")
def _att_file(d=None):
_init(); return ATT_DIR / f"{d or today()}.json"
def load_att():
f = _att_file()
if not f.exists(): return {}
try: return json.loads(f.read_text())
except: return {}
def save_att(a):
_att_file().write_text(json.dumps(a, indent=2))
def mark_present(sid, name, gid):
a = load_att()
now = datetime.now()
if sid in a:
last = datetime.fromisoformat(a[sid]["time"])
if (now-last).total_seconds() < 600: return False
a[sid] = {"name":name,"group_id":gid,"time":now.isoformat(),"status":"present"}
save_att(a); return True
def get_summary(gid=None):
a = load_att()
e = load_embeddings()
all_s = {k:v for k,v in e.items() if gid is None or v.get("group_id")==gid}
present = [{"id":k,"name":v["name"],"group_id":v.get("group_id",""),"time":a[k]["time"]}
for k,v in all_s.items() if k in a]
absent = [{"id":k,"name":v["name"],"group_id":v.get("group_id","")}
for k,v in all_s.items() if k not in a]
return {"present":present,"absent":absent}
def reset_att():
f = _att_file()
if f.exists(): f.unlink()
reset_today = reset_att # alias used in app.py
def export_csv(gid=None):
s = get_summary(gid); g = load_groups()
gn = lambda id: g.get(id,{}).get("name",id)
rows = ["Name,Group,Status,Time"]
for x in s["present"]:
rows.append(f"{x['name']},{gn(x['group_id'])},Present,{x['time'][:19].replace('T',' ')}")
for x in s["absent"]:
rows.append(f"{x['name']},{gn(x['group_id'])},Absent,")
return "\n".join(rows)