File size: 6,946 Bytes
7c6ffa6 | 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 | #!/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)
|