DocDoeAI / scripts /clear_beta_demo.py
asnannp's picture
Deploy backend cd4237ff: support routes + rate limit + exam_date nullable + upload 413 fix
7c6ffa6
Raw
History Blame Contribute Delete
6.95 kB
#!/usr/bin/env python3
"""Beta Demo Clear Script β€” DocDoe AI.
Removes ONLY the data created by seed_beta_demo.py:
- Video render jobs owned by demo@docdoe.in
- Document chunks owned by demo@docdoe.in
- Documents owned by demo@docdoe.in (+ stub files on disk)
- UserPlan owned by demo@docdoe.in
- StudyProfile owned by demo@docdoe.in
- All normalized learning-state rows owned by demo@docdoe.in
- The demo user account itself
Safe to run when demo data is absent (no-ops on missing rows).
Does NOT touch any other user data.
Usage:
cd backend
python scripts/clear_beta_demo.py
python scripts/clear_beta_demo.py --dry-run
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
# ── Path setup ────────────────────────────────────────────────────────────────
BACKEND_DIR = Path(__file__).resolve().parents[1]
if str(BACKEND_DIR) not in sys.path:
sys.path.insert(0, str(BACKEND_DIR))
DEMO_EMAIL = "demo@docdoe.in"
def clear(dry_run: bool = False) -> None:
from app.core.database import SessionLocal, init_db
from app.models.document import Document
from app.models.document_chunk import DocumentChunk
from app.models.learning_state import (
Chapter,
DailyTask,
GeneratedResource,
LessonProgress,
QuizAttempt,
StudentProfileState,
StudyPlan,
StudySession,
Subject,
Subscription,
TopicMastery,
UsageEvent,
)
from app.models.study_profile import StudyProfile
from app.models.user import User
from app.models.user_plan import UserPlan
from app.models.video_render_job import VideoRenderJob
init_db()
prefix = "[DRY RUN] " if dry_run else ""
with SessionLocal() as db:
user = db.query(User).filter(User.email == DEMO_EMAIL).first()
if user is None:
print(f"Demo user '{DEMO_EMAIL}' not found β€” nothing to clear.")
return
uid = user.id
print(f"{prefix}Clearing demo data for user {uid} ({DEMO_EMAIL})")
# ── 1. Video render jobs ──────────────────────────────────────────────
jobs = db.query(VideoRenderJob).filter(VideoRenderJob.user_id == uid).all()
print(f"{prefix} Video jobs : {len(jobs)}")
if not dry_run:
for job in jobs:
db.delete(job)
db.flush()
# ── 2. Document chunks + documents + stub files ───────────────────────
docs = db.query(Document).filter(Document.user_id == uid).all()
chunk_total = 0
file_total = 0
for doc in docs:
chunks = (
db.query(DocumentChunk)
.filter(DocumentChunk.document_id == doc.id)
.all()
)
chunk_total += len(chunks)
if not dry_run:
for chunk in chunks:
db.delete(chunk)
if not dry_run:
db.flush() # flush chunks before deleting parent docs
for doc in docs:
# Remove stub file from disk if it is a seeded placeholder
if doc.file_path:
stub = Path(doc.file_path)
if stub.exists() and stub.name.startswith("demo_"):
file_total += 1
if not dry_run:
stub.unlink(missing_ok=True)
if not dry_run:
db.delete(doc)
if not dry_run:
db.flush() # flush docs before deleting user
print(f"{prefix} Documents : {len(docs)} (chunks: {chunk_total}, stub files: {file_total})")
# ── 3. User plan ──────────────────────────────────────────────────────
plans = db.query(UserPlan).filter(UserPlan.user_id == uid).all()
print(f"{prefix} User plans : {len(plans)}")
if not dry_run:
for p in plans:
db.delete(p)
db.flush()
# ── 4. Study profile ──────────────────────────────────────────────────
profiles = db.query(StudyProfile).filter(StudyProfile.user_id == uid).all()
print(f"{prefix} Study profiles : {len(profiles)}")
if not dry_run:
for sp in profiles:
db.delete(sp)
db.flush()
# ── 5. Normalized learning state ──────────────────────────────────────
learning_models = [
(UsageEvent, "Usage events"),
(GeneratedResource, "Generated resources"),
(TopicMastery, "Topic mastery"),
(QuizAttempt, "Quiz attempts"),
(LessonProgress, "Lesson progress"),
(StudySession, "Study sessions"),
(DailyTask, "Daily tasks"),
(StudyPlan, "Study plans"),
(Chapter, "Chapters"),
(Subject, "Subjects"),
(Subscription, "Subscriptions"),
(StudentProfileState, "Student profile state"),
]
for model, label in learning_models:
rows = db.query(model).filter(model.user_id == uid).all()
print(f"{prefix} {label:<22}: {len(rows)}")
if not dry_run:
for row in rows:
db.delete(row)
db.flush()
# ── 6. User ───────────────────────────────────────────────────────────
print(f"{prefix} User account : 1 ({DEMO_EMAIL})")
if not dry_run:
db.delete(user)
if not dry_run:
db.commit()
print()
print("[OK] Demo data cleared.")
else:
print()
print("Dry run complete β€” no changes made.")
print("Run without --dry-run to delete.")
# ── Entry point ───────────────────────────────────────────────────────────────
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Remove beta demo seed data from DocDoe AI.")
parser.add_argument(
"--dry-run",
action="store_true",
help="Show what would be deleted without making changes.",
)
args = parser.parse_args()
clear(dry_run=args.dry_run)