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)