datamatters24 commited on
Commit
e5454bb
·
verified ·
1 Parent(s): 883836c

Upload notebooks/00_setup/02_schema_migration.ipynb with huggingface_hub

Browse files
notebooks/00_setup/02_schema_migration.ipynb ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "# Schema Migration — Analysis Tables\n",
8
+ "\n",
9
+ "Creates all database tables required by the analysis pipelines.\n",
10
+ "This notebook is idempotent (`CREATE TABLE IF NOT EXISTS`) and safe to re-run.\n",
11
+ "\n",
12
+ "Supports Papermill parameterization: set `dry_run = True` to print DDL without executing."
13
+ ]
14
+ },
15
+ {
16
+ "cell_type": "code",
17
+ "metadata": {
18
+ "tags": [
19
+ "parameters"
20
+ ]
21
+ },
22
+ "source": [
23
+ "# Parameters\n",
24
+ "dry_run = False"
25
+ ],
26
+ "execution_count": null,
27
+ "outputs": []
28
+ },
29
+ {
30
+ "cell_type": "code",
31
+ "metadata": {},
32
+ "source": [
33
+ "from research_lib.db import fetch_df\n",
34
+ "from research_lib.config import get_engine\n",
35
+ "\n",
36
+ "engine = get_engine()\n",
37
+ "\n",
38
+ "DDL_STATEMENTS = [\n",
39
+ " # --- analysis_runs ---\n",
40
+ " \"\"\"\n",
41
+ " CREATE TABLE IF NOT EXISTS analysis_runs (\n",
42
+ " id SERIAL PRIMARY KEY,\n",
43
+ " pipeline_name TEXT NOT NULL,\n",
44
+ " source_section TEXT,\n",
45
+ " started_at TIMESTAMP NOT NULL DEFAULT NOW(),\n",
46
+ " finished_at TIMESTAMP,\n",
47
+ " status TEXT NOT NULL DEFAULT 'running',\n",
48
+ " documents_processed INTEGER DEFAULT 0,\n",
49
+ " parameters JSONB,\n",
50
+ " error_message TEXT\n",
51
+ " );\n",
52
+ " \"\"\",\n",
53
+ " # --- entity_aliases ---\n",
54
+ " \"\"\"\n",
55
+ " CREATE TABLE IF NOT EXISTS entity_aliases (\n",
56
+ " id SERIAL PRIMARY KEY,\n",
57
+ " canonical_name TEXT NOT NULL,\n",
58
+ " alias_name TEXT NOT NULL,\n",
59
+ " entity_type TEXT NOT NULL,\n",
60
+ " confidence REAL DEFAULT 1.0,\n",
61
+ " created_at TIMESTAMP DEFAULT NOW(),\n",
62
+ " UNIQUE(alias_name, entity_type)\n",
63
+ " );\n",
64
+ " \"\"\",\n",
65
+ " # --- entity_relationships ---\n",
66
+ " \"\"\"\n",
67
+ " CREATE TABLE IF NOT EXISTS entity_relationships (\n",
68
+ " id SERIAL PRIMARY KEY,\n",
69
+ " entity_a TEXT NOT NULL,\n",
70
+ " entity_a_type TEXT NOT NULL,\n",
71
+ " entity_b TEXT NOT NULL,\n",
72
+ " entity_b_type TEXT NOT NULL,\n",
73
+ " co_occurrence_count INTEGER NOT NULL DEFAULT 1,\n",
74
+ " document_count INTEGER NOT NULL DEFAULT 1,\n",
75
+ " source_section TEXT,\n",
76
+ " avg_distance REAL,\n",
77
+ " sample_doc_ids INTEGER[],\n",
78
+ " created_at TIMESTAMP DEFAULT NOW(),\n",
79
+ " UNIQUE(entity_a, entity_a_type, entity_b, entity_b_type, source_section)\n",
80
+ " );\n",
81
+ " \"\"\",\n",
82
+ " # --- topics ---\n",
83
+ " \"\"\"\n",
84
+ " CREATE TABLE IF NOT EXISTS topics (\n",
85
+ " id SERIAL PRIMARY KEY,\n",
86
+ " model_name TEXT NOT NULL,\n",
87
+ " source_section TEXT,\n",
88
+ " topic_id INTEGER NOT NULL,\n",
89
+ " topic_label TEXT,\n",
90
+ " top_words TEXT[] NOT NULL,\n",
91
+ " top_word_scores REAL[],\n",
92
+ " document_count INTEGER,\n",
93
+ " representative_doc_ids INTEGER[],\n",
94
+ " embedding vector(384),\n",
95
+ " created_at TIMESTAMP DEFAULT NOW(),\n",
96
+ " UNIQUE(model_name, source_section, topic_id)\n",
97
+ " );\n",
98
+ " \"\"\",\n",
99
+ " # --- document_topics ---\n",
100
+ " \"\"\"\n",
101
+ " CREATE TABLE IF NOT EXISTS document_topics (\n",
102
+ " id SERIAL PRIMARY KEY,\n",
103
+ " document_id INTEGER NOT NULL REFERENCES documents(id) ON DELETE CASCADE,\n",
104
+ " model_name TEXT NOT NULL,\n",
105
+ " topic_id INTEGER NOT NULL,\n",
106
+ " probability REAL,\n",
107
+ " created_at TIMESTAMP DEFAULT NOW(),\n",
108
+ " UNIQUE(document_id, model_name)\n",
109
+ " );\n",
110
+ " \"\"\",\n",
111
+ " # --- document_features ---\n",
112
+ " \"\"\"\n",
113
+ " CREATE TABLE IF NOT EXISTS document_features (\n",
114
+ " id SERIAL PRIMARY KEY,\n",
115
+ " document_id INTEGER NOT NULL REFERENCES documents(id) ON DELETE CASCADE,\n",
116
+ " feature_name TEXT NOT NULL,\n",
117
+ " feature_value REAL,\n",
118
+ " feature_json JSONB,\n",
119
+ " created_at TIMESTAMP DEFAULT NOW(),\n",
120
+ " UNIQUE(document_id, feature_name)\n",
121
+ " );\n",
122
+ " \"\"\",\n",
123
+ " # --- page_features ---\n",
124
+ " \"\"\"\n",
125
+ " CREATE TABLE IF NOT EXISTS page_features (\n",
126
+ " id SERIAL PRIMARY KEY,\n",
127
+ " page_id INTEGER NOT NULL REFERENCES pages(id) ON DELETE CASCADE,\n",
128
+ " feature_name TEXT NOT NULL,\n",
129
+ " feature_value REAL,\n",
130
+ " feature_json JSONB,\n",
131
+ " created_at TIMESTAMP DEFAULT NOW(),\n",
132
+ " UNIQUE(page_id, feature_name)\n",
133
+ " );\n",
134
+ " \"\"\",\n",
135
+ " # --- duplicate_pairs ---\n",
136
+ " \"\"\"\n",
137
+ " CREATE TABLE IF NOT EXISTS duplicate_pairs (\n",
138
+ " id SERIAL PRIMARY KEY,\n",
139
+ " page_id_a INTEGER NOT NULL REFERENCES pages(id) ON DELETE CASCADE,\n",
140
+ " page_id_b INTEGER NOT NULL REFERENCES pages(id) ON DELETE CASCADE,\n",
141
+ " similarity REAL NOT NULL,\n",
142
+ " method TEXT NOT NULL DEFAULT 'embedding',\n",
143
+ " created_at TIMESTAMP DEFAULT NOW(),\n",
144
+ " UNIQUE(page_id_a, page_id_b, method)\n",
145
+ " );\n",
146
+ " \"\"\",\n",
147
+ " # --- document_keywords ---\n",
148
+ " \"\"\"\n",
149
+ " CREATE TABLE IF NOT EXISTS document_keywords (\n",
150
+ " id SERIAL PRIMARY KEY,\n",
151
+ " document_id INTEGER NOT NULL REFERENCES documents(id) ON DELETE CASCADE,\n",
152
+ " keyword TEXT NOT NULL,\n",
153
+ " score REAL NOT NULL,\n",
154
+ " method TEXT NOT NULL DEFAULT 'tfidf',\n",
155
+ " created_at TIMESTAMP DEFAULT NOW()\n",
156
+ " );\n",
157
+ " \"\"\",\n",
158
+ "]\n",
159
+ "\n",
160
+ "if dry_run:\n",
161
+ " print(\"=== DRY RUN — DDL statements that would be executed ===\")\n",
162
+ " for i, stmt in enumerate(DDL_STATEMENTS, 1):\n",
163
+ " print(f\"\\n--- Statement {i} ---\")\n",
164
+ " print(stmt.strip())\n",
165
+ "else:\n",
166
+ " with engine.begin() as conn:\n",
167
+ " for i, stmt in enumerate(DDL_STATEMENTS, 1):\n",
168
+ " print(f\"Executing DDL statement {i}/{len(DDL_STATEMENTS)}...\")\n",
169
+ " conn.execute(stmt)\n",
170
+ " print(f\"\\nAll {len(DDL_STATEMENTS)} DDL statements executed successfully.\")"
171
+ ],
172
+ "execution_count": null,
173
+ "outputs": []
174
+ },
175
+ {
176
+ "cell_type": "code",
177
+ "metadata": {},
178
+ "source": [
179
+ "# Create indexes for query performance\n",
180
+ "\n",
181
+ "INDEX_STATEMENTS = [\n",
182
+ " # analysis_runs\n",
183
+ " \"CREATE INDEX IF NOT EXISTS idx_analysis_runs_pipeline ON analysis_runs (pipeline_name);\",\n",
184
+ " \"CREATE INDEX IF NOT EXISTS idx_analysis_runs_status ON analysis_runs (status);\",\n",
185
+ " \"CREATE INDEX IF NOT EXISTS idx_analysis_runs_section ON analysis_runs (source_section);\",\n",
186
+ " # entity_aliases\n",
187
+ " \"CREATE INDEX IF NOT EXISTS idx_entity_aliases_canonical ON entity_aliases (canonical_name);\",\n",
188
+ " \"CREATE INDEX IF NOT EXISTS idx_entity_aliases_type ON entity_aliases (entity_type);\",\n",
189
+ " # entity_relationships\n",
190
+ " \"CREATE INDEX IF NOT EXISTS idx_entity_rel_a ON entity_relationships (entity_a, entity_a_type);\",\n",
191
+ " \"CREATE INDEX IF NOT EXISTS idx_entity_rel_b ON entity_relationships (entity_b, entity_b_type);\",\n",
192
+ " \"CREATE INDEX IF NOT EXISTS idx_entity_rel_section ON entity_relationships (source_section);\",\n",
193
+ " \"CREATE INDEX IF NOT EXISTS idx_entity_rel_cooccurrence ON entity_relationships (co_occurrence_count DESC);\",\n",
194
+ " # topics\n",
195
+ " \"CREATE INDEX IF NOT EXISTS idx_topics_model ON topics (model_name);\",\n",
196
+ " \"CREATE INDEX IF NOT EXISTS idx_topics_section ON topics (source_section);\",\n",
197
+ " # document_topics\n",
198
+ " \"CREATE INDEX IF NOT EXISTS idx_doc_topics_doc ON document_topics (document_id);\",\n",
199
+ " \"CREATE INDEX IF NOT EXISTS idx_doc_topics_model ON document_topics (model_name);\",\n",
200
+ " \"CREATE INDEX IF NOT EXISTS idx_doc_topics_topic ON document_topics (topic_id);\",\n",
201
+ " # document_features\n",
202
+ " \"CREATE INDEX IF NOT EXISTS idx_doc_features_doc ON document_features (document_id);\",\n",
203
+ " \"CREATE INDEX IF NOT EXISTS idx_doc_features_name ON document_features (feature_name);\",\n",
204
+ " # page_features\n",
205
+ " \"CREATE INDEX IF NOT EXISTS idx_page_features_page ON page_features (page_id);\",\n",
206
+ " \"CREATE INDEX IF NOT EXISTS idx_page_features_name ON page_features (feature_name);\",\n",
207
+ " # duplicate_pairs\n",
208
+ " \"CREATE INDEX IF NOT EXISTS idx_dup_pairs_a ON duplicate_pairs (page_id_a);\",\n",
209
+ " \"CREATE INDEX IF NOT EXISTS idx_dup_pairs_b ON duplicate_pairs (page_id_b);\",\n",
210
+ " \"CREATE INDEX IF NOT EXISTS idx_dup_pairs_sim ON duplicate_pairs (similarity DESC);\",\n",
211
+ " # document_keywords\n",
212
+ " \"CREATE INDEX IF NOT EXISTS idx_doc_kw_doc ON document_keywords (document_id);\",\n",
213
+ " \"CREATE INDEX IF NOT EXISTS idx_doc_kw_keyword ON document_keywords (keyword);\",\n",
214
+ " \"CREATE INDEX IF NOT EXISTS idx_doc_kw_method ON document_keywords (method);\",\n",
215
+ "]\n",
216
+ "\n",
217
+ "if dry_run:\n",
218
+ " print(\"=== DRY RUN — Index statements ===\")\n",
219
+ " for stmt in INDEX_STATEMENTS:\n",
220
+ " print(f\" {stmt}\")\n",
221
+ "else:\n",
222
+ " with engine.begin() as conn:\n",
223
+ " for i, stmt in enumerate(INDEX_STATEMENTS, 1):\n",
224
+ " conn.execute(stmt)\n",
225
+ " print(f\"Created {len(INDEX_STATEMENTS)} indexes successfully.\")"
226
+ ],
227
+ "execution_count": null,
228
+ "outputs": []
229
+ },
230
+ {
231
+ "cell_type": "code",
232
+ "metadata": {},
233
+ "source": [
234
+ "# Grant SELECT on all new tables to web_reader\n",
235
+ "\n",
236
+ "NEW_TABLES = [\n",
237
+ " \"analysis_runs\",\n",
238
+ " \"entity_aliases\",\n",
239
+ " \"entity_relationships\",\n",
240
+ " \"topics\",\n",
241
+ " \"document_topics\",\n",
242
+ " \"document_features\",\n",
243
+ " \"page_features\",\n",
244
+ " \"duplicate_pairs\",\n",
245
+ " \"document_keywords\",\n",
246
+ "]\n",
247
+ "\n",
248
+ "if dry_run:\n",
249
+ " print(\"=== DRY RUN — GRANT statements ===\")\n",
250
+ " for table in NEW_TABLES:\n",
251
+ " print(f\" GRANT SELECT ON {table} TO web_reader;\")\n",
252
+ "else:\n",
253
+ " with engine.begin() as conn:\n",
254
+ " for table in NEW_TABLES:\n",
255
+ " conn.execute(f\"GRANT SELECT ON {table} TO web_reader;\")\n",
256
+ " print(f\" GRANT SELECT ON {table} TO web_reader -- OK\")\n",
257
+ " print(f\"\\nGranted SELECT to web_reader on {len(NEW_TABLES)} tables.\")"
258
+ ],
259
+ "execution_count": null,
260
+ "outputs": []
261
+ },
262
+ {
263
+ "cell_type": "code",
264
+ "metadata": {},
265
+ "source": [
266
+ "# Confirm: list all tables in the database\n",
267
+ "from research_lib.db import fetch_df\n",
268
+ "\n",
269
+ "df_tables = fetch_df(\"\"\"\n",
270
+ " SELECT table_name, \n",
271
+ " pg_size_pretty(pg_total_relation_size(quote_ident(table_name))) AS total_size\n",
272
+ " FROM information_schema.tables\n",
273
+ " WHERE table_schema = 'public'\n",
274
+ " ORDER BY table_name\n",
275
+ "\"\"\")\n",
276
+ "\n",
277
+ "print(f\"Total tables in public schema: {len(df_tables)}\")\n",
278
+ "print()\n",
279
+ "print(df_tables.to_string(index=False))"
280
+ ],
281
+ "execution_count": null,
282
+ "outputs": []
283
+ }
284
+ ],
285
+ "metadata": {
286
+ "kernelspec": {
287
+ "display_name": "Python 3",
288
+ "language": "python",
289
+ "name": "python3"
290
+ },
291
+ "language_info": {
292
+ "name": "python",
293
+ "version": "3.10.0"
294
+ }
295
+ },
296
+ "nbformat": 4,
297
+ "nbformat_minor": 5
298
+ }