wiizm commited on
Commit
9554911
ยท
verified ยท
1 Parent(s): ad410ef

Upload app\database.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app//database.py +368 -0
app//database.py ADDED
@@ -0,0 +1,368 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask_sqlalchemy import SQLAlchemy
2
+ from flask_login import UserMixin
3
+ from datetime import datetime
4
+ from werkzeug.security import generate_password_hash, check_password_hash
5
+
6
+ db = SQLAlchemy()
7
+
8
+ # ์‚ฌ์šฉ์ž ๋ชจ๋ธ
9
+ class User(UserMixin, db.Model):
10
+ id = db.Column(db.Integer, primary_key=True)
11
+ username = db.Column(db.String(80), unique=True, nullable=False)
12
+ nickname = db.Column(db.String(80), nullable=True) # ๋‹‰๋„ค์ž„ ํ•„๋“œ ์ถ”๊ฐ€
13
+ password_hash = db.Column(db.String(255), nullable=False)
14
+ is_admin = db.Column(db.Boolean, default=False, nullable=False)
15
+ is_active = db.Column(db.Boolean, default=True, nullable=False)
16
+ created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
17
+ last_login = db.Column(db.DateTime, nullable=True)
18
+
19
+ def set_password(self, password):
20
+ self.password_hash = generate_password_hash(password)
21
+
22
+ def check_password(self, password):
23
+ return check_password_hash(self.password_hash, password)
24
+
25
+ def to_dict(self):
26
+ return {
27
+ 'id': self.id,
28
+ 'username': self.username,
29
+ 'nickname': self.nickname,
30
+ 'is_admin': self.is_admin,
31
+ 'is_active': self.is_active,
32
+ 'created_at': self.created_at.isoformat() if self.created_at else None,
33
+ 'last_login': self.last_login.isoformat() if self.last_login else None
34
+ }
35
+
36
+ # ์—…๋กœ๋“œ๋œ ํŒŒ์ผ ์ •๋ณด ๋ชจ๋ธ
37
+ class UploadedFile(db.Model):
38
+ id = db.Column(db.Integer, primary_key=True)
39
+ filename = db.Column(db.String(255), nullable=False)
40
+ original_filename = db.Column(db.String(255), nullable=False)
41
+ file_path = db.Column(db.String(500), nullable=False)
42
+ file_size = db.Column(db.Integer, nullable=False)
43
+ model_name = db.Column(db.String(100), nullable=True) # ์—ฐ๊ฒฐ๋œ ๋ชจ๋ธ ์ด๋ฆ„
44
+ is_public = db.Column(db.Boolean, default=False, nullable=False) # ๊ณต๊ฐœ ์—ฌ๋ถ€ (๊ธฐ๋ณธ๊ฐ’: ๋ฏธ๊ณต๊ฐœ)
45
+ uploaded_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
46
+ uploaded_by = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True)
47
+ parent_file_id = db.Column(db.Integer, db.ForeignKey('uploaded_file.id'), nullable=True) # ์ด์–ด์„œ ์—…๋กœ๋“œํ•œ ๊ฒฝ์šฐ ์›๋ณธ ํŒŒ์ผ ID
48
+
49
+ # ๊ด€๊ณ„
50
+ parent_file = db.relationship('UploadedFile', remote_side=[id], backref='child_files')
51
+
52
+ def to_dict(self):
53
+ # ์ฒญํฌ ๊ฐœ์ˆ˜ ๊ณ„์‚ฐ
54
+ chunk_count = len(self.chunks) if hasattr(self, 'chunks') else 0
55
+
56
+ return {
57
+ 'id': self.id,
58
+ 'filename': self.filename,
59
+ 'original_filename': self.original_filename,
60
+ 'file_size': self.file_size,
61
+ 'model_name': self.model_name,
62
+ 'is_public': self.is_public,
63
+ 'uploaded_at': self.uploaded_at.isoformat() if self.uploaded_at else None,
64
+ 'uploaded_by': self.uploaded_by,
65
+ 'parent_file_id': self.parent_file_id,
66
+ 'chunk_count': chunk_count,
67
+ 'child_count': len(self.child_files) if self.child_files else 0
68
+ }
69
+
70
+ # ๋Œ€ํ™” ์„ธ์…˜ ๋ชจ๋ธ
71
+ class ChatSession(db.Model):
72
+ id = db.Column(db.Integer, primary_key=True)
73
+ user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
74
+ title = db.Column(db.String(255), nullable=True)
75
+ model_name = db.Column(db.String(100), nullable=True) # ํ•˜์œ„ ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•ด ์œ ์ง€
76
+ analysis_model = db.Column(db.String(100), nullable=True) # ์งˆ๋ฌธ ๋ถ„์„์šฉ ๋ชจ๋ธ
77
+ answer_model = db.Column(db.String(100), nullable=True) # ์ตœ์ข… ๋‹ต๋ณ€์šฉ ๋ชจ๋ธ
78
+ created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
79
+ updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
80
+
81
+ # ๊ด€๊ณ„
82
+ user = db.relationship('User', backref='chat_sessions')
83
+ messages = db.relationship('ChatMessage', backref='session', lazy=True, cascade='all, delete-orphan', order_by='ChatMessage.created_at')
84
+
85
+ def to_dict(self):
86
+ return {
87
+ 'id': self.id,
88
+ 'user_id': self.user_id,
89
+ 'title': self.title,
90
+ 'model_name': self.model_name, # ํ•˜์œ„ ํ˜ธํ™˜์„ฑ
91
+ 'analysis_model': self.analysis_model,
92
+ 'answer_model': self.answer_model,
93
+ 'created_at': self.created_at.isoformat() if self.created_at else None,
94
+ 'updated_at': self.updated_at.isoformat() if self.updated_at else None,
95
+ 'message_count': len(self.messages)
96
+ }
97
+
98
+ # ๋Œ€ํ™” ๋ฉ”์‹œ์ง€ ๋ชจ๋ธ
99
+ class ChatMessage(db.Model):
100
+ id = db.Column(db.Integer, primary_key=True)
101
+ session_id = db.Column(db.Integer, db.ForeignKey('chat_session.id'), nullable=True) # ์‹œ์Šคํ…œ ์‚ฌ์šฉ์€ NULL ํ—ˆ์šฉ
102
+ role = db.Column(db.String(20), nullable=False) # 'user' or 'ai'
103
+ content = db.Column(db.Text, nullable=False)
104
+ created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
105
+ # ํ† ํฐ ์‚ฌ์šฉ๋Ÿ‰ ์ •๋ณด (AI ์‘๋‹ต ๋ฉ”์‹œ๏ฟฝ๏ฟฝ๏ฟฝ์—๋งŒ ์ €์žฅ)
106
+ input_tokens = db.Column(db.Integer, nullable=True) # ์ž…๋ ฅ ํ† ํฐ ์ˆ˜
107
+ output_tokens = db.Column(db.Integer, nullable=True) # ์ถœ๋ ฅ ํ† ํฐ ์ˆ˜
108
+ model_name = db.Column(db.String(100), nullable=True) # ์‚ฌ์šฉ๋œ AI ๋ชจ๋ธ๋ช…
109
+ usage_type = db.Column(db.String(20), nullable=True, default='user') # 'user' or 'system' (์‹œ์Šคํ…œ ์‚ฌ์šฉ ๊ตฌ๋ถ„)
110
+
111
+ def to_dict(self):
112
+ return {
113
+ 'id': self.id,
114
+ 'session_id': self.session_id,
115
+ 'role': self.role,
116
+ 'content': self.content,
117
+ 'created_at': self.created_at.isoformat() if self.created_at else None,
118
+ 'input_tokens': self.input_tokens,
119
+ 'output_tokens': self.output_tokens,
120
+ 'model_name': self.model_name,
121
+ 'usage_type': self.usage_type
122
+ }
123
+
124
+ # ๋ฌธ์„œ ์ฒญํฌ ๋ชจ๋ธ (RAG์šฉ)
125
+ class DocumentChunk(db.Model):
126
+ id = db.Column(db.Integer, primary_key=True)
127
+ file_id = db.Column(db.Integer, db.ForeignKey('uploaded_file.id'), nullable=False)
128
+ chunk_index = db.Column(db.Integer, nullable=False) # ์ฒญํฌ ์ˆœ์„œ
129
+ content = db.Column(db.Text, nullable=False) # ์ฒญํฌ ๋‚ด์šฉ
130
+ embedding = db.Column(db.Text, nullable=True) # ์ž„๋ฒ ๋”ฉ ๋ฒกํ„ฐ (JSON ๋ฌธ์ž์—ด๋กœ ์ €์žฅ)
131
+ chunk_metadata = db.Column(db.Text, nullable=True) # ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ (JSON ๋ฌธ์ž์—ด๋กœ ์ €์žฅ: chapter, pov, characters, time_background)
132
+ created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
133
+
134
+ # ๊ด€๊ณ„
135
+ file = db.relationship('UploadedFile', backref='chunks')
136
+
137
+ def to_dict(self):
138
+ import json
139
+ metadata_dict = None
140
+ if self.chunk_metadata:
141
+ try:
142
+ metadata_dict = json.loads(self.chunk_metadata)
143
+ except:
144
+ metadata_dict = None
145
+
146
+ return {
147
+ 'id': self.id,
148
+ 'file_id': self.file_id,
149
+ 'chunk_index': self.chunk_index,
150
+ 'content': self.content,
151
+ 'metadata': metadata_dict,
152
+ 'created_at': self.created_at.isoformat() if self.created_at else None
153
+ }
154
+
155
+ # Parent Chunk ๋ชจ๋ธ (AI ๋ถ„์„ ๊ฒฐ๊ณผ ์ €์žฅ)
156
+ class ParentChunk(db.Model):
157
+ id = db.Column(db.Integer, primary_key=True)
158
+ file_id = db.Column(db.Integer, db.ForeignKey('uploaded_file.id'), nullable=False, unique=True)
159
+ world_view = db.Column(db.Text, nullable=True) # ์„ธ๊ณ„๊ด€ ์„ค๋ช…
160
+ characters = db.Column(db.Text, nullable=True) # ์ฃผ์š” ์บ๋ฆญํ„ฐ ๋ถ„์„
161
+ story = db.Column(db.Text, nullable=True) # ์ฃผ์š” ์Šคํ† ๋ฆฌ ๋ถ„์„
162
+ episodes = db.Column(db.Text, nullable=True) # ์ฃผ์š” ์—ํ”ผ์†Œ๋“œ ๋ถ„์„
163
+ others = db.Column(db.Text, nullable=True) # ๊ธฐํƒ€
164
+ created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
165
+ updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
166
+
167
+ # ๊ด€๊ณ„
168
+ file = db.relationship('UploadedFile', backref='parent_chunk')
169
+
170
+ def to_dict(self):
171
+ return {
172
+ 'id': self.id,
173
+ 'file_id': self.file_id,
174
+ 'world_view': self.world_view,
175
+ 'characters': self.characters,
176
+ 'story': self.story,
177
+ 'episodes': self.episodes,
178
+ 'others': self.others,
179
+ 'created_at': self.created_at.isoformat() if self.created_at else None,
180
+ 'updated_at': self.updated_at.isoformat() if self.updated_at else None
181
+ }
182
+
183
+ # ์‹œ์Šคํ…œ ์„ค์ • ๋ชจ๋ธ (API ํ‚ค ๋“ฑ)
184
+ class SystemConfig(db.Model):
185
+ id = db.Column(db.Integer, primary_key=True)
186
+ key = db.Column(db.String(100), unique=True, nullable=False) # ์„ค์ • ํ‚ค (์˜ˆ: 'gemini_api_key')
187
+ value = db.Column(db.Text, nullable=True) # ์„ค์ • ๊ฐ’ (์•”ํ˜ธํ™”๋œ API ํ‚ค)
188
+ description = db.Column(db.String(255), nullable=True) # ์„ค์ • ์„ค๋ช…
189
+ created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
190
+ updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
191
+
192
+ def to_dict(self):
193
+ return {
194
+ 'id': self.id,
195
+ 'key': self.key,
196
+ 'value': self.value, # ์‹ค์ œ ๊ฐ’ ๋ฐ˜ํ™˜ (๋ณด์•ˆ์„ ์œ„ํ•ด ๋งˆ์Šคํ‚น ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Œ)
197
+ 'description': self.description,
198
+ 'created_at': self.created_at.isoformat() if self.created_at else None,
199
+ 'updated_at': self.updated_at.isoformat() if self.updated_at else None
200
+ }
201
+
202
+ @staticmethod
203
+ def get_config(key, default=None):
204
+ """์„ค์ • ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ"""
205
+ try:
206
+ config = SystemConfig.query.filter_by(key=key).first()
207
+ return config.value if config else default
208
+ except Exception as e:
209
+ # ํ…Œ์ด๋ธ”์ด ์—†๊ฑฐ๋‚˜ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ ๊ธฐ๋ณธ๊ฐ’ ๋ฐ˜ํ™˜
210
+ print(f"[SystemConfig.get_config] ์˜ค๋ฅ˜: {e}")
211
+ return default
212
+
213
+ @staticmethod
214
+ def set_config(key, value, description=None):
215
+ """์„ค์ • ๊ฐ’ ์ €์žฅ/์—…๋ฐ์ดํŠธ"""
216
+ try:
217
+ # ํ…Œ์ด๋ธ”์ด ์—†์œผ๋ฉด ์ƒ์„ฑ ์‹œ๋„
218
+ from sqlalchemy import inspect
219
+ inspector = inspect(db.engine)
220
+ if 'system_config' not in inspector.get_table_names():
221
+ print("[SystemConfig.set_config] SystemConfig ํ…Œ์ด๋ธ” ์ƒ์„ฑ ์ค‘...")
222
+ db.create_all()
223
+
224
+ config = SystemConfig.query.filter_by(key=key).first()
225
+ if config:
226
+ print(f"[SystemConfig.set_config] ๊ธฐ์กด ์„ค์ • ์—…๋ฐ์ดํŠธ: {key}")
227
+ config.value = value
228
+ if description:
229
+ config.description = description
230
+ config.updated_at = datetime.utcnow()
231
+ else:
232
+ print(f"[SystemConfig.set_config] ์ƒˆ ์„ค์ • ์ƒ์„ฑ: {key}")
233
+ config = SystemConfig(key=key, value=value, description=description)
234
+ db.session.add(config)
235
+
236
+ db.session.commit()
237
+
238
+ # ์ €์žฅ ํ™•์ธ
239
+ verify_config = SystemConfig.query.filter_by(key=key).first()
240
+ if verify_config and verify_config.value == value:
241
+ print(f"[SystemConfig.set_config] ์„ค์ • ์ €์žฅ ํ™•์ธ๋จ: {key} (๊ธธ์ด: {len(str(value))}์ž)")
242
+ else:
243
+ print(f"[SystemConfig.set_config] ๊ฒฝ๊ณ : ์„ค์ • ์ €์žฅ ํ›„ ํ™•์ธ ์‹คํŒจ: {key}")
244
+
245
+ return config
246
+ except Exception as e:
247
+ db.session.rollback()
248
+ print(f"[SystemConfig.set_config] ์˜ค๋ฅ˜: {e}")
249
+ import traceback
250
+ traceback.print_exc()
251
+ raise
252
+
253
+ # ํšŒ์ฐจ๋ณ„ ๋ถ„์„ ๋ชจ๋ธ
254
+ class EpisodeAnalysis(db.Model):
255
+ id = db.Column(db.Integer, primary_key=True)
256
+ file_id = db.Column(db.Integer, db.ForeignKey('uploaded_file.id'), nullable=False)
257
+ episode_title = db.Column(db.String(100), nullable=False) # ํšŒ์ฐจ ์ œ๋ชฉ (์˜ˆ: '1ํ™”', '2ํ™”')
258
+ analysis_content = db.Column(db.Text, nullable=False) # ๋ถ„์„ ๊ฒฐ๊ณผ (ํ•˜๋‚˜์˜ ํ…์ŠคํŠธ๋กœ ์ด์–ด์„œ ์ €์žฅ)
259
+ created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
260
+ updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
261
+
262
+ # ๊ด€๊ณ„
263
+ file = db.relationship('UploadedFile', backref='episode_analyses')
264
+
265
+ def to_dict(self):
266
+ return {
267
+ 'id': self.id,
268
+ 'file_id': self.file_id,
269
+ 'episode_title': self.episode_title,
270
+ 'analysis_content': self.analysis_content,
271
+ 'created_at': self.created_at.isoformat() if self.created_at else None,
272
+ 'updated_at': self.updated_at.isoformat() if self.updated_at else None
273
+ }
274
+
275
+ # Graph Extraction ๋ชจ๋ธ (GraphRAG์šฉ)
276
+ class GraphEntity(db.Model):
277
+ """Graph Extraction์—์„œ ์ถ”์ถœ๋œ ์—”ํ‹ฐํ‹ฐ (์ธ๋ฌผ/์žฅ์†Œ)"""
278
+ id = db.Column(db.Integer, primary_key=True)
279
+ file_id = db.Column(db.Integer, db.ForeignKey('uploaded_file.id'), nullable=False)
280
+ episode_title = db.Column(db.String(100), nullable=False) # ํšŒ์ฐจ ์ œ๋ชฉ
281
+ entity_name = db.Column(db.String(200), nullable=False) # ์—”ํ‹ฐํ‹ฐ ์ด๋ฆ„
282
+ entity_type = db.Column(db.String(50), nullable=False) # 'character' ๋˜๋Š” 'location'
283
+ description = db.Column(db.Text, nullable=True) # ์—”ํ‹ฐํ‹ฐ ์„ค๋ช…
284
+ role = db.Column(db.String(200), nullable=True) # ์ธ๋ฌผ์˜ ๊ฒฝ์šฐ ์—ญํ• 
285
+ category = db.Column(db.String(200), nullable=True) # ์žฅ์†Œ์˜ ๊ฒฝ์šฐ ์œ ํ˜•
286
+ created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
287
+
288
+ # ๊ด€๊ณ„
289
+ file = db.relationship('UploadedFile', backref='graph_entities')
290
+
291
+ def to_dict(self):
292
+ return {
293
+ 'id': self.id,
294
+ 'file_id': self.file_id,
295
+ 'episode_title': self.episode_title,
296
+ 'entity_name': self.entity_name,
297
+ 'entity_type': self.entity_type,
298
+ 'description': self.description,
299
+ 'role': self.role,
300
+ 'category': self.category,
301
+ 'created_at': self.created_at.isoformat() if self.created_at else None
302
+ }
303
+
304
+ class GraphRelationship(db.Model):
305
+ """Graph Extraction์—์„œ ์ถ”์ถœ๋œ ๊ด€๊ณ„"""
306
+ id = db.Column(db.Integer, primary_key=True)
307
+ file_id = db.Column(db.Integer, db.ForeignKey('uploaded_file.id'), nullable=False)
308
+ episode_title = db.Column(db.String(100), nullable=False) # ํšŒ์ฐจ ์ œ๋ชฉ
309
+ source = db.Column(db.String(200), nullable=False) # ๊ด€๊ณ„์˜ ์ฃผ์ฒด
310
+ target = db.Column(db.String(200), nullable=False) # ๊ด€๊ณ„์˜ ๋Œ€์ƒ
311
+ relationship_type = db.Column(db.String(200), nullable=False) # ๊ด€๊ณ„ ์œ ํ˜•
312
+ description = db.Column(db.Text, nullable=True) # ๊ด€๊ณ„ ์„ค๋ช…
313
+ event = db.Column(db.String(500), nullable=True) # ๊ด€๊ณ„๋ฅผ ํ˜•์„ฑ/๋ณ€ํ™”์‹œํ‚จ ์‚ฌ๊ฑด
314
+ created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
315
+
316
+ # ๊ด€๊ณ„
317
+ file = db.relationship('UploadedFile', backref='graph_relationships')
318
+
319
+ def to_dict(self):
320
+ return {
321
+ 'id': self.id,
322
+ 'file_id': self.file_id,
323
+ 'episode_title': self.episode_title,
324
+ 'source': self.source,
325
+ 'target': self.target,
326
+ 'relationship_type': self.relationship_type,
327
+ 'description': self.description,
328
+ 'event': self.event,
329
+ 'created_at': self.created_at.isoformat() if self.created_at else None
330
+ }
331
+
332
+ class GraphEvent(db.Model):
333
+ """Graph Extraction์—์„œ ์ถ”์ถœ๋œ ์‚ฌ๊ฑด"""
334
+ id = db.Column(db.Integer, primary_key=True)
335
+ file_id = db.Column(db.Integer, db.ForeignKey('uploaded_file.id'), nullable=False)
336
+ episode_title = db.Column(db.String(100), nullable=False) # ํšŒ์ฐจ ์ œ๋ชฉ
337
+ event_name = db.Column(db.String(500), nullable=False) # ์‚ฌ๊ฑด ์ด๋ฆ„
338
+ description = db.Column(db.Text, nullable=False) # ์‚ฌ๊ฑด ์„ค๋ช…
339
+ participants = db.Column(db.Text, nullable=True) # ๊ด€๋ จ ์ธ๋ฌผ๋“ค (JSON ๋ฌธ์ž์—ด๋กœ ์ €์žฅ)
340
+ location = db.Column(db.String(500), nullable=True) # ์‚ฌ๊ฑด ๋ฐœ์ƒ ์žฅ์†Œ
341
+ significance = db.Column(db.String(200), nullable=True) # ์‚ฌ๊ฑด์˜ ์ค‘์š”๋„
342
+ created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
343
+
344
+ # ๊ด€๊ณ„
345
+ file = db.relationship('UploadedFile', backref='graph_events')
346
+
347
+ def to_dict(self):
348
+ import json
349
+ participants_list = []
350
+ if self.participants:
351
+ try:
352
+ participants_list = json.loads(self.participants)
353
+ except:
354
+ participants_list = []
355
+
356
+ return {
357
+ 'id': self.id,
358
+ 'file_id': self.file_id,
359
+ 'episode_title': self.episode_title,
360
+ 'event_name': self.event_name,
361
+ 'description': self.description,
362
+ 'participants': participants_list,
363
+ 'location': self.location,
364
+ 'significance': self.significance,
365
+ 'created_at': self.created_at.isoformat() if self.created_at else None
366
+ }
367
+
368
+