cattykit 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cattykit/__init__.py +10 -0
- cattykit/experiments.py +99 -0
- cattykit/logging/__init__.py +9 -0
- cattykit/logging/event.py +32 -0
- cattykit/logging/model_logger.py +18 -0
- cattykit/logging/null_logger.py +13 -0
- cattykit/logging/print_logger.py +24 -0
- cattykit/logging/sqlite_logger.py +761 -0
- cattykit/models/__init__.py +37 -0
- cattykit/models/loading.py +186 -0
- cattykit/models/protocol.py +59 -0
- cattykit/models/registry.py +157 -0
- cattykit/py.typed +1 -0
- cattykit-0.1.0.dist-info/METADATA +729 -0
- cattykit-0.1.0.dist-info/RECORD +18 -0
- cattykit-0.1.0.dist-info/WHEEL +5 -0
- cattykit-0.1.0.dist-info/licenses/LICENSE +661 -0
- cattykit-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sqlite3
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from .event import ModelEvent
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SQLiteLogger:
|
|
13
|
+
"""Persist model events in a SQLite database that can be read by Cattycam."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, path: str | Path) -> None:
|
|
16
|
+
self._connection = sqlite3.connect(path)
|
|
17
|
+
self._connection.execute("PRAGMA foreign_keys = ON")
|
|
18
|
+
self._run_ids: dict[str, int] = {}
|
|
19
|
+
self._create_schema()
|
|
20
|
+
self._connection.commit()
|
|
21
|
+
self._codelets_run = 0
|
|
22
|
+
self._active_codelet_id: str | None = None
|
|
23
|
+
|
|
24
|
+
def log(self, event: ModelEvent) -> None:
|
|
25
|
+
"""Record an event using the logger's current codelet-time cursor."""
|
|
26
|
+
data = dict(event.data)
|
|
27
|
+
self._update_codelet_time(event.kind, data)
|
|
28
|
+
if data.get("time") is None:
|
|
29
|
+
data["time"] = self._codelets_run
|
|
30
|
+
if event.kind == "run_started":
|
|
31
|
+
self._active_codelet_id = None
|
|
32
|
+
elif event.kind != "codelet_selected" and self._active_codelet_id is not None:
|
|
33
|
+
data.setdefault("parent_codelet_id", self._active_codelet_id)
|
|
34
|
+
self._record_event(event, data)
|
|
35
|
+
if event.kind == "codelet_selected":
|
|
36
|
+
self._active_codelet_id = data.get("codelet_id")
|
|
37
|
+
elif event.kind in {"codelet_finished", "run_finished"}:
|
|
38
|
+
self._active_codelet_id = None
|
|
39
|
+
self._connection.commit()
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def codelets_run(self) -> int:
|
|
43
|
+
"""Return the current codelet-time cursor for the active run."""
|
|
44
|
+
return self._codelets_run
|
|
45
|
+
|
|
46
|
+
def close(self) -> None:
|
|
47
|
+
"""Close the SQLite connection."""
|
|
48
|
+
self._connection.close()
|
|
49
|
+
|
|
50
|
+
def _create_schema(self) -> None:
|
|
51
|
+
self._connection.executescript(
|
|
52
|
+
"""
|
|
53
|
+
CREATE TABLE IF NOT EXISTS runs (
|
|
54
|
+
id INTEGER PRIMARY KEY,
|
|
55
|
+
model TEXT NOT NULL,
|
|
56
|
+
run_time TEXT NOT NULL,
|
|
57
|
+
problem TEXT,
|
|
58
|
+
solution TEXT,
|
|
59
|
+
final_temperature REAL,
|
|
60
|
+
number_of_codelets_run INTEGER,
|
|
61
|
+
number_of_snags INTEGER NOT NULL DEFAULT 0
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
CREATE TABLE IF NOT EXISTS snags (
|
|
65
|
+
id INTEGER PRIMARY KEY,
|
|
66
|
+
run_id INTEGER NOT NULL REFERENCES runs(id),
|
|
67
|
+
snag_start INTEGER NOT NULL,
|
|
68
|
+
snag_end INTEGER
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
CREATE TABLE IF NOT EXISTS codelets (
|
|
72
|
+
id INTEGER PRIMARY KEY,
|
|
73
|
+
run_id INTEGER NOT NULL REFERENCES runs(id),
|
|
74
|
+
codelet_id TEXT,
|
|
75
|
+
parent_codelet_id TEXT,
|
|
76
|
+
codelet_type TEXT NOT NULL,
|
|
77
|
+
urgency_bin INTEGER,
|
|
78
|
+
arguments_json TEXT,
|
|
79
|
+
birth_time INTEGER,
|
|
80
|
+
run_time INTEGER,
|
|
81
|
+
removal_time INTEGER,
|
|
82
|
+
result TEXT,
|
|
83
|
+
fizzle_reason TEXT
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
CREATE TABLE IF NOT EXISTS strings (
|
|
87
|
+
id INTEGER PRIMARY KEY,
|
|
88
|
+
run_id INTEGER NOT NULL REFERENCES runs(id),
|
|
89
|
+
string_id TEXT NOT NULL,
|
|
90
|
+
role TEXT NOT NULL,
|
|
91
|
+
value TEXT,
|
|
92
|
+
UNIQUE(run_id, string_id)
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
CREATE TABLE IF NOT EXISTS letters (
|
|
96
|
+
id INTEGER PRIMARY KEY,
|
|
97
|
+
run_id INTEGER NOT NULL REFERENCES runs(id),
|
|
98
|
+
letter_id TEXT NOT NULL,
|
|
99
|
+
parent_codelet_id TEXT,
|
|
100
|
+
string_id TEXT,
|
|
101
|
+
position INTEGER,
|
|
102
|
+
letter_category TEXT,
|
|
103
|
+
proposal_time INTEGER,
|
|
104
|
+
creation_time INTEGER,
|
|
105
|
+
destruction_time INTEGER,
|
|
106
|
+
UNIQUE(run_id, letter_id)
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
CREATE TABLE IF NOT EXISTS descriptions (
|
|
110
|
+
id INTEGER PRIMARY KEY,
|
|
111
|
+
run_id INTEGER NOT NULL REFERENCES runs(id),
|
|
112
|
+
description_id TEXT NOT NULL,
|
|
113
|
+
parent_codelet_id TEXT,
|
|
114
|
+
object_id TEXT,
|
|
115
|
+
facet TEXT,
|
|
116
|
+
descriptor TEXT,
|
|
117
|
+
proposal_time INTEGER,
|
|
118
|
+
creation_time INTEGER,
|
|
119
|
+
destruction_time INTEGER,
|
|
120
|
+
UNIQUE(run_id, description_id)
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
CREATE TABLE IF NOT EXISTS bonds (
|
|
124
|
+
id INTEGER PRIMARY KEY,
|
|
125
|
+
run_id INTEGER NOT NULL REFERENCES runs(id),
|
|
126
|
+
bond_id TEXT NOT NULL,
|
|
127
|
+
parent_codelet_id TEXT,
|
|
128
|
+
string_id TEXT,
|
|
129
|
+
source_id TEXT,
|
|
130
|
+
target_id TEXT,
|
|
131
|
+
bond_category TEXT,
|
|
132
|
+
direction_category TEXT,
|
|
133
|
+
bond_facet TEXT,
|
|
134
|
+
source_descriptor TEXT,
|
|
135
|
+
target_descriptor TEXT,
|
|
136
|
+
proposal_time INTEGER,
|
|
137
|
+
creation_time INTEGER,
|
|
138
|
+
destruction_time INTEGER,
|
|
139
|
+
UNIQUE(run_id, bond_id)
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
CREATE TABLE IF NOT EXISTS groups (
|
|
143
|
+
id INTEGER PRIMARY KEY,
|
|
144
|
+
run_id INTEGER NOT NULL REFERENCES runs(id),
|
|
145
|
+
group_id TEXT NOT NULL,
|
|
146
|
+
parent_codelet_id TEXT,
|
|
147
|
+
string_id TEXT,
|
|
148
|
+
group_category TEXT,
|
|
149
|
+
direction_category TEXT,
|
|
150
|
+
bond_category TEXT,
|
|
151
|
+
bond_facet TEXT,
|
|
152
|
+
left_position INTEGER,
|
|
153
|
+
right_position INTEGER,
|
|
154
|
+
proposal_time INTEGER,
|
|
155
|
+
creation_time INTEGER,
|
|
156
|
+
destruction_time INTEGER,
|
|
157
|
+
UNIQUE(run_id, group_id)
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
CREATE TABLE IF NOT EXISTS group_members (
|
|
161
|
+
group_id INTEGER NOT NULL REFERENCES groups(id),
|
|
162
|
+
object_id TEXT NOT NULL,
|
|
163
|
+
member_order INTEGER NOT NULL,
|
|
164
|
+
PRIMARY KEY (group_id, object_id)
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
CREATE TABLE IF NOT EXISTS group_bonds (
|
|
168
|
+
group_id INTEGER NOT NULL REFERENCES groups(id),
|
|
169
|
+
bond_id TEXT NOT NULL,
|
|
170
|
+
bond_order INTEGER NOT NULL,
|
|
171
|
+
PRIMARY KEY (group_id, bond_id)
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
CREATE TABLE IF NOT EXISTS correspondences (
|
|
175
|
+
id INTEGER PRIMARY KEY,
|
|
176
|
+
run_id INTEGER NOT NULL REFERENCES runs(id),
|
|
177
|
+
correspondence_id TEXT NOT NULL,
|
|
178
|
+
parent_codelet_id TEXT,
|
|
179
|
+
source_id TEXT,
|
|
180
|
+
target_id TEXT,
|
|
181
|
+
proposal_time INTEGER,
|
|
182
|
+
creation_time INTEGER,
|
|
183
|
+
destruction_time INTEGER,
|
|
184
|
+
UNIQUE(run_id, correspondence_id)
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
CREATE TABLE IF NOT EXISTS concept_mappings (
|
|
188
|
+
id INTEGER PRIMARY KEY,
|
|
189
|
+
run_id INTEGER NOT NULL REFERENCES runs(id),
|
|
190
|
+
concept_mapping_id TEXT NOT NULL,
|
|
191
|
+
correspondence_id TEXT,
|
|
192
|
+
description_type_1 TEXT,
|
|
193
|
+
description_type_2 TEXT,
|
|
194
|
+
initial_descriptor TEXT,
|
|
195
|
+
target_descriptor TEXT,
|
|
196
|
+
label TEXT,
|
|
197
|
+
UNIQUE(run_id, concept_mapping_id)
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
CREATE TABLE IF NOT EXISTS replacements (
|
|
201
|
+
id INTEGER PRIMARY KEY,
|
|
202
|
+
run_id INTEGER NOT NULL REFERENCES runs(id),
|
|
203
|
+
replacement_id TEXT NOT NULL,
|
|
204
|
+
parent_codelet_id TEXT,
|
|
205
|
+
source_id TEXT,
|
|
206
|
+
target_id TEXT,
|
|
207
|
+
proposal_time INTEGER,
|
|
208
|
+
creation_time INTEGER,
|
|
209
|
+
destruction_time INTEGER,
|
|
210
|
+
UNIQUE(run_id, replacement_id)
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
CREATE TABLE IF NOT EXISTS rules (
|
|
214
|
+
id INTEGER PRIMARY KEY,
|
|
215
|
+
run_id INTEGER NOT NULL REFERENCES runs(id),
|
|
216
|
+
rule_id TEXT NOT NULL,
|
|
217
|
+
parent_codelet_id TEXT,
|
|
218
|
+
object_category_1 TEXT,
|
|
219
|
+
descriptor_1_facet TEXT,
|
|
220
|
+
descriptor_1 TEXT,
|
|
221
|
+
object_category_2 TEXT,
|
|
222
|
+
descriptor_2 TEXT,
|
|
223
|
+
replaced_description_type TEXT,
|
|
224
|
+
relation TEXT,
|
|
225
|
+
proposal_time INTEGER,
|
|
226
|
+
creation_time INTEGER,
|
|
227
|
+
destruction_time INTEGER,
|
|
228
|
+
UNIQUE(run_id, rule_id)
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
CREATE TABLE IF NOT EXISTS translated_rules (
|
|
232
|
+
id INTEGER PRIMARY KEY,
|
|
233
|
+
run_id INTEGER NOT NULL REFERENCES runs(id),
|
|
234
|
+
rule_id TEXT NOT NULL,
|
|
235
|
+
parent_codelet_id TEXT,
|
|
236
|
+
object_category_1 TEXT,
|
|
237
|
+
descriptor_1_facet TEXT,
|
|
238
|
+
descriptor_1 TEXT,
|
|
239
|
+
object_category_2 TEXT,
|
|
240
|
+
descriptor_2 TEXT,
|
|
241
|
+
replaced_description_type TEXT,
|
|
242
|
+
relation TEXT,
|
|
243
|
+
proposal_time INTEGER,
|
|
244
|
+
creation_time INTEGER,
|
|
245
|
+
destruction_time INTEGER,
|
|
246
|
+
UNIQUE(run_id, rule_id)
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
CREATE TABLE IF NOT EXISTS slipnodes (
|
|
250
|
+
id INTEGER PRIMARY KEY,
|
|
251
|
+
run_id INTEGER NOT NULL REFERENCES runs(id),
|
|
252
|
+
name TEXT NOT NULL,
|
|
253
|
+
conceptual_depth REAL,
|
|
254
|
+
depth_factor REAL,
|
|
255
|
+
intrinsic_link_length REAL,
|
|
256
|
+
shrunk_link_length REAL,
|
|
257
|
+
category_links_json TEXT NOT NULL DEFAULT '[]',
|
|
258
|
+
instance_links_json TEXT NOT NULL DEFAULT '[]',
|
|
259
|
+
has_property_links_json TEXT NOT NULL DEFAULT '[]',
|
|
260
|
+
lateral_sliplinks_json TEXT NOT NULL DEFAULT '[]',
|
|
261
|
+
lateral_non_sliplinks_json TEXT NOT NULL DEFAULT '[]',
|
|
262
|
+
incoming_links_json TEXT NOT NULL DEFAULT '[]',
|
|
263
|
+
UNIQUE(run_id, name)
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
CREATE TABLE IF NOT EXISTS sliplinks (
|
|
267
|
+
id INTEGER PRIMARY KEY,
|
|
268
|
+
run_id INTEGER NOT NULL REFERENCES runs(id),
|
|
269
|
+
source TEXT NOT NULL,
|
|
270
|
+
target TEXT NOT NULL,
|
|
271
|
+
label TEXT,
|
|
272
|
+
fixed_length REAL,
|
|
273
|
+
is_category_link INTEGER NOT NULL DEFAULT 0,
|
|
274
|
+
is_instance_link INTEGER NOT NULL DEFAULT 0,
|
|
275
|
+
is_has_property_link INTEGER NOT NULL DEFAULT 0,
|
|
276
|
+
is_lateral_sliplink INTEGER NOT NULL DEFAULT 0,
|
|
277
|
+
is_lateral_non_sliplink INTEGER NOT NULL DEFAULT 0
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
CREATE TABLE IF NOT EXISTS attribute_values (
|
|
281
|
+
id INTEGER PRIMARY KEY,
|
|
282
|
+
run_id INTEGER NOT NULL REFERENCES runs(id),
|
|
283
|
+
time INTEGER NOT NULL,
|
|
284
|
+
object_id TEXT NOT NULL,
|
|
285
|
+
attribute TEXT NOT NULL,
|
|
286
|
+
value_json TEXT NOT NULL
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
CREATE INDEX IF NOT EXISTS codelets_by_run_time
|
|
290
|
+
ON codelets(run_id, run_time);
|
|
291
|
+
CREATE INDEX IF NOT EXISTS attributes_by_run_time
|
|
292
|
+
ON attribute_values(run_id, time);
|
|
293
|
+
CREATE INDEX IF NOT EXISTS bonds_by_run_id ON bonds(run_id, bond_id);
|
|
294
|
+
CREATE INDEX IF NOT EXISTS groups_by_run_id ON groups(run_id, group_id);
|
|
295
|
+
CREATE INDEX IF NOT EXISTS correspondences_by_run_id
|
|
296
|
+
ON correspondences(run_id, correspondence_id);
|
|
297
|
+
"""
|
|
298
|
+
)
|
|
299
|
+
self._ensure_column("codelets", "removal_time", "INTEGER")
|
|
300
|
+
|
|
301
|
+
def _ensure_column(self, table: str, column: str, definition: str) -> None:
|
|
302
|
+
"""Add a schema column when opening a history database from an older logger."""
|
|
303
|
+
columns = {
|
|
304
|
+
row[1] for row in self._connection.execute(f"PRAGMA table_info({table})")
|
|
305
|
+
}
|
|
306
|
+
if column not in columns:
|
|
307
|
+
self._connection.execute(
|
|
308
|
+
f"ALTER TABLE {table} ADD COLUMN {column} {definition}"
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
@staticmethod
|
|
312
|
+
def _json(value: Any) -> str:
|
|
313
|
+
"""Encode arbitrary model values without losing the logging event."""
|
|
314
|
+
return json.dumps(value, sort_keys=True, default=str)
|
|
315
|
+
|
|
316
|
+
def _run_id(self, event: ModelEvent) -> int | None:
|
|
317
|
+
return self._run_ids.get(event.model)
|
|
318
|
+
|
|
319
|
+
def _update_codelet_time(self, kind: str, data: Mapping[str, Any]) -> None:
|
|
320
|
+
"""Synchronize the logger's codelet-time cursor with an incoming event."""
|
|
321
|
+
if kind == "run_started":
|
|
322
|
+
self._codelets_run = int(data.get("time", 0))
|
|
323
|
+
elif data.get("time") is not None:
|
|
324
|
+
self._codelets_run = int(data["time"])
|
|
325
|
+
elif kind == "codelet_selected":
|
|
326
|
+
self._codelets_run += 1
|
|
327
|
+
|
|
328
|
+
def _record_event(self, event: ModelEvent, data: Mapping[str, Any]) -> None:
|
|
329
|
+
if event.kind == "run_started":
|
|
330
|
+
cursor = self._connection.execute(
|
|
331
|
+
"INSERT INTO runs (model, run_time, problem) VALUES (?, ?, ?)",
|
|
332
|
+
(event.model, event.timestamp.isoformat(), data.get("problem")),
|
|
333
|
+
)
|
|
334
|
+
if cursor.lastrowid is None:
|
|
335
|
+
raise RuntimeError("SQLite did not return the new run identifier")
|
|
336
|
+
self._run_ids[event.model] = cursor.lastrowid
|
|
337
|
+
return
|
|
338
|
+
|
|
339
|
+
run_id = self._run_id(event)
|
|
340
|
+
if run_id is None:
|
|
341
|
+
return
|
|
342
|
+
if event.kind == "run_finished":
|
|
343
|
+
self._connection.execute(
|
|
344
|
+
"""UPDATE runs SET number_of_codelets_run = ?,
|
|
345
|
+
final_temperature = COALESCE(?, final_temperature)
|
|
346
|
+
WHERE id = ?""",
|
|
347
|
+
(
|
|
348
|
+
data.get("codelets_run", data.get("time")),
|
|
349
|
+
data.get("temperature"),
|
|
350
|
+
run_id,
|
|
351
|
+
),
|
|
352
|
+
)
|
|
353
|
+
self._run_ids.pop(event.model, None)
|
|
354
|
+
elif event.kind == "answer_found":
|
|
355
|
+
self._connection.execute(
|
|
356
|
+
"UPDATE runs SET solution = ? WHERE id = ?",
|
|
357
|
+
(data.get("answer"), run_id),
|
|
358
|
+
)
|
|
359
|
+
elif event.kind == "snag_encountered":
|
|
360
|
+
self._connection.execute(
|
|
361
|
+
"UPDATE runs SET number_of_snags = number_of_snags + 1 WHERE id = ?",
|
|
362
|
+
(run_id,),
|
|
363
|
+
)
|
|
364
|
+
self._connection.execute(
|
|
365
|
+
"INSERT INTO snags (run_id, snag_start) VALUES (?, ?)",
|
|
366
|
+
(run_id, data.get("time")),
|
|
367
|
+
)
|
|
368
|
+
elif event.kind == "snag_ended":
|
|
369
|
+
self._connection.execute(
|
|
370
|
+
"UPDATE snags SET snag_end = ? WHERE run_id = ? AND snag_end IS NULL",
|
|
371
|
+
(data.get("time"), run_id),
|
|
372
|
+
)
|
|
373
|
+
elif event.kind in {"codelet_selected", "codelet_started"}:
|
|
374
|
+
cursor = self._connection.execute(
|
|
375
|
+
"""UPDATE codelets SET
|
|
376
|
+
parent_codelet_id = COALESCE(?, parent_codelet_id),
|
|
377
|
+
codelet_type = COALESCE(?, codelet_type),
|
|
378
|
+
urgency_bin = COALESCE(?, urgency_bin),
|
|
379
|
+
birth_time = COALESCE(?, birth_time), run_time = ?, removal_time = ?
|
|
380
|
+
WHERE run_id = ? AND codelet_id = ?""",
|
|
381
|
+
(
|
|
382
|
+
data.get("parent_codelet_id"),
|
|
383
|
+
data.get("codelet_type", data.get("codelet", "unknown")),
|
|
384
|
+
data.get("urgency_bin"),
|
|
385
|
+
data.get("birth_time"),
|
|
386
|
+
data.get("time"),
|
|
387
|
+
data.get("time"),
|
|
388
|
+
run_id,
|
|
389
|
+
data.get("codelet_id"),
|
|
390
|
+
),
|
|
391
|
+
)
|
|
392
|
+
if cursor.rowcount == 0:
|
|
393
|
+
self._connection.execute(
|
|
394
|
+
"""INSERT INTO codelets
|
|
395
|
+
(run_id, codelet_id, parent_codelet_id, codelet_type, urgency_bin,
|
|
396
|
+
arguments_json, birth_time, run_time, removal_time)
|
|
397
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
|
398
|
+
(
|
|
399
|
+
run_id,
|
|
400
|
+
data.get("codelet_id"),
|
|
401
|
+
data.get("parent_codelet_id"),
|
|
402
|
+
data.get("codelet_type", data.get("codelet", "unknown")),
|
|
403
|
+
data.get("urgency_bin"),
|
|
404
|
+
self._json(data.get("arguments", {})),
|
|
405
|
+
data.get("birth_time"),
|
|
406
|
+
data.get("time"),
|
|
407
|
+
data.get("time"),
|
|
408
|
+
),
|
|
409
|
+
)
|
|
410
|
+
elif event.kind == "codelet_posted":
|
|
411
|
+
self._post_codelet(run_id, data)
|
|
412
|
+
elif event.kind == "codelet_removed":
|
|
413
|
+
self._connection.execute(
|
|
414
|
+
"""UPDATE codelets SET removal_time = ?
|
|
415
|
+
WHERE run_id = ? AND codelet_id = ? AND removal_time IS NULL""",
|
|
416
|
+
(data["time"], run_id, data.get("codelet_id")),
|
|
417
|
+
)
|
|
418
|
+
elif event.kind == "codelet_finished":
|
|
419
|
+
self._finish_codelet(run_id, data)
|
|
420
|
+
elif event.kind in {"string_created", "string_initialized"}:
|
|
421
|
+
self._upsert_string(run_id, data)
|
|
422
|
+
elif event.kind in {
|
|
423
|
+
"letter_proposed",
|
|
424
|
+
"letter_created",
|
|
425
|
+
"letter_destroyed",
|
|
426
|
+
}:
|
|
427
|
+
self._upsert_lifecycle_entity("letters", "letter", run_id, event.kind, data)
|
|
428
|
+
elif event.kind in {
|
|
429
|
+
"description_proposed",
|
|
430
|
+
"description_created",
|
|
431
|
+
"description_destroyed",
|
|
432
|
+
}:
|
|
433
|
+
self._upsert_lifecycle_entity(
|
|
434
|
+
"descriptions", "description", run_id, event.kind, data
|
|
435
|
+
)
|
|
436
|
+
elif event.kind in {"bond_proposed", "bond_created", "bond_destroyed"}:
|
|
437
|
+
self._upsert_lifecycle_entity("bonds", "bond", run_id, event.kind, data)
|
|
438
|
+
elif event.kind in {"group_proposed", "group_created", "group_destroyed"}:
|
|
439
|
+
group_row_id = self._upsert_lifecycle_entity(
|
|
440
|
+
"groups", "group", run_id, event.kind, data
|
|
441
|
+
)
|
|
442
|
+
if group_row_id is not None and "members" in data:
|
|
443
|
+
self._replace_group_members(group_row_id, data["members"])
|
|
444
|
+
if group_row_id is not None and "bonds" in data:
|
|
445
|
+
self._replace_group_bonds(group_row_id, data["bonds"])
|
|
446
|
+
elif event.kind in {
|
|
447
|
+
"correspondence_proposed",
|
|
448
|
+
"correspondence_created",
|
|
449
|
+
"correspondence_destroyed",
|
|
450
|
+
}:
|
|
451
|
+
self._upsert_lifecycle_entity(
|
|
452
|
+
"correspondences", "correspondence", run_id, event.kind, data
|
|
453
|
+
)
|
|
454
|
+
elif event.kind in {
|
|
455
|
+
"replacement_proposed",
|
|
456
|
+
"replacement_created",
|
|
457
|
+
"replacement_destroyed",
|
|
458
|
+
}:
|
|
459
|
+
self._upsert_lifecycle_entity(
|
|
460
|
+
"replacements", "replacement", run_id, event.kind, data
|
|
461
|
+
)
|
|
462
|
+
elif event.kind in {"rule_proposed", "rule_created", "rule_destroyed"}:
|
|
463
|
+
self._upsert_lifecycle_entity("rules", "rule", run_id, event.kind, data)
|
|
464
|
+
elif event.kind in {
|
|
465
|
+
"translated_rule_proposed",
|
|
466
|
+
"translated_rule_created",
|
|
467
|
+
"translated_rule_destroyed",
|
|
468
|
+
}:
|
|
469
|
+
self._upsert_lifecycle_entity(
|
|
470
|
+
"translated_rules", "rule", run_id, event.kind, data
|
|
471
|
+
)
|
|
472
|
+
elif event.kind in {"concept_mapping_created", "concept_mapping_updated"}:
|
|
473
|
+
self._upsert_concept_mapping(run_id, data)
|
|
474
|
+
elif event.kind in {"slipnode_created", "slipnode_initialized"}:
|
|
475
|
+
self._upsert_slipnode(run_id, data)
|
|
476
|
+
elif event.kind in {"sliplink_created", "sliplink_initialized"}:
|
|
477
|
+
self._insert_sliplink(run_id, data)
|
|
478
|
+
elif event.kind in {"attribute_value", "attribute_updated"}:
|
|
479
|
+
self._connection.execute(
|
|
480
|
+
"""INSERT INTO attribute_values
|
|
481
|
+
(run_id, time, object_id, attribute, value_json)
|
|
482
|
+
VALUES (?, ?, ?, ?, ?)""",
|
|
483
|
+
(
|
|
484
|
+
run_id,
|
|
485
|
+
data.get("time", 0),
|
|
486
|
+
data["object_id"],
|
|
487
|
+
data["attribute"],
|
|
488
|
+
self._json(data.get("value")),
|
|
489
|
+
),
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
def _post_codelet(self, run_id: int, data: Mapping[str, Any]) -> None:
|
|
493
|
+
"""Record a codelet when it enters the coderack.
|
|
494
|
+
|
|
495
|
+
Selection later updates this row with the codelet's run time, preserving
|
|
496
|
+
the interval between posting and execution.
|
|
497
|
+
"""
|
|
498
|
+
self._connection.execute(
|
|
499
|
+
"""INSERT INTO codelets
|
|
500
|
+
(run_id, codelet_id, parent_codelet_id, codelet_type, urgency_bin,
|
|
501
|
+
arguments_json, birth_time)
|
|
502
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)""",
|
|
503
|
+
(
|
|
504
|
+
run_id,
|
|
505
|
+
data.get("codelet_id"),
|
|
506
|
+
self._active_codelet_id,
|
|
507
|
+
data.get("codelet_type", data.get("codelet", "unknown")),
|
|
508
|
+
data.get("urgency_bin"),
|
|
509
|
+
self._json(data.get("arguments", data.get("argument"))),
|
|
510
|
+
data.get("birth_time", data.get("time")),
|
|
511
|
+
),
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
def _finish_codelet(self, run_id: int, data: Mapping[str, Any]) -> None:
|
|
515
|
+
cursor = self._connection.execute(
|
|
516
|
+
"""UPDATE codelets SET result = ?, fizzle_reason = ?, run_time = ?
|
|
517
|
+
WHERE id = (SELECT id FROM codelets WHERE run_id = ?
|
|
518
|
+
AND codelet_id = ? AND result IS NULL ORDER BY id DESC LIMIT 1)""",
|
|
519
|
+
(
|
|
520
|
+
data.get("outcome", data.get("result")),
|
|
521
|
+
data.get("reason", data.get("fizzle_reason")),
|
|
522
|
+
data.get("time"),
|
|
523
|
+
run_id,
|
|
524
|
+
data.get("codelet_id"),
|
|
525
|
+
),
|
|
526
|
+
)
|
|
527
|
+
if cursor.rowcount == 0:
|
|
528
|
+
self._connection.execute(
|
|
529
|
+
"""INSERT INTO codelets (run_id, codelet_id, codelet_type, urgency_bin,
|
|
530
|
+
arguments_json, run_time, result, fizzle_reason)
|
|
531
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
|
|
532
|
+
(
|
|
533
|
+
run_id,
|
|
534
|
+
data.get("codelet_id"),
|
|
535
|
+
data.get("codelet_type", data.get("codelet", "unknown")),
|
|
536
|
+
data.get("urgency_bin"),
|
|
537
|
+
self._json(data.get("arguments", {})),
|
|
538
|
+
data.get("time"),
|
|
539
|
+
data.get("outcome", data.get("result")),
|
|
540
|
+
data.get("reason", data.get("fizzle_reason")),
|
|
541
|
+
),
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
def _upsert_string(self, run_id: int, data: Mapping[str, Any]) -> None:
|
|
545
|
+
string_id = str(data.get("string_id", data.get("role")))
|
|
546
|
+
self._connection.execute(
|
|
547
|
+
"""INSERT INTO strings (run_id, string_id, role, value) VALUES (?, ?, ?, ?)
|
|
548
|
+
ON CONFLICT(run_id, string_id) DO UPDATE SET value = excluded.value""",
|
|
549
|
+
(
|
|
550
|
+
run_id,
|
|
551
|
+
string_id,
|
|
552
|
+
data.get("role", string_id),
|
|
553
|
+
data.get("value", data.get("string")),
|
|
554
|
+
),
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
def _upsert_lifecycle_entity(
|
|
558
|
+
self,
|
|
559
|
+
table: str,
|
|
560
|
+
entity: str,
|
|
561
|
+
run_id: int,
|
|
562
|
+
kind: str,
|
|
563
|
+
data: Mapping[str, Any],
|
|
564
|
+
) -> int | None:
|
|
565
|
+
"""Upsert one typed workspace entity and its lifecycle timestamp."""
|
|
566
|
+
entity_id = str(data[f"{entity}_id"])
|
|
567
|
+
time_column = {
|
|
568
|
+
"proposed": "proposal_time",
|
|
569
|
+
"created": "creation_time",
|
|
570
|
+
"destroyed": "destruction_time",
|
|
571
|
+
}[kind.rsplit("_", maxsplit=1)[1]]
|
|
572
|
+
columns_by_table = {
|
|
573
|
+
"letters": (
|
|
574
|
+
"parent_codelet_id",
|
|
575
|
+
"string_id",
|
|
576
|
+
"position",
|
|
577
|
+
"letter_category",
|
|
578
|
+
),
|
|
579
|
+
"descriptions": (
|
|
580
|
+
"parent_codelet_id",
|
|
581
|
+
"object_id",
|
|
582
|
+
"facet",
|
|
583
|
+
"descriptor",
|
|
584
|
+
),
|
|
585
|
+
"bonds": (
|
|
586
|
+
"parent_codelet_id",
|
|
587
|
+
"string_id",
|
|
588
|
+
"source_id",
|
|
589
|
+
"target_id",
|
|
590
|
+
"bond_category",
|
|
591
|
+
"direction_category",
|
|
592
|
+
"bond_facet",
|
|
593
|
+
"source_descriptor",
|
|
594
|
+
"target_descriptor",
|
|
595
|
+
),
|
|
596
|
+
"groups": (
|
|
597
|
+
"parent_codelet_id",
|
|
598
|
+
"string_id",
|
|
599
|
+
"group_category",
|
|
600
|
+
"direction_category",
|
|
601
|
+
"bond_category",
|
|
602
|
+
"bond_facet",
|
|
603
|
+
"left_position",
|
|
604
|
+
"right_position",
|
|
605
|
+
),
|
|
606
|
+
"correspondences": ("parent_codelet_id", "source_id", "target_id"),
|
|
607
|
+
"replacements": (
|
|
608
|
+
"parent_codelet_id",
|
|
609
|
+
"source_id",
|
|
610
|
+
"target_id",
|
|
611
|
+
),
|
|
612
|
+
"rules": (
|
|
613
|
+
"parent_codelet_id",
|
|
614
|
+
"object_category_1",
|
|
615
|
+
"descriptor_1_facet",
|
|
616
|
+
"descriptor_1",
|
|
617
|
+
"object_category_2",
|
|
618
|
+
"descriptor_2",
|
|
619
|
+
"replaced_description_type",
|
|
620
|
+
"relation",
|
|
621
|
+
),
|
|
622
|
+
"translated_rules": (
|
|
623
|
+
"parent_codelet_id",
|
|
624
|
+
"object_category_1",
|
|
625
|
+
"descriptor_1_facet",
|
|
626
|
+
"descriptor_1",
|
|
627
|
+
"object_category_2",
|
|
628
|
+
"descriptor_2",
|
|
629
|
+
"replaced_description_type",
|
|
630
|
+
"relation",
|
|
631
|
+
),
|
|
632
|
+
}
|
|
633
|
+
detail_columns = columns_by_table[table]
|
|
634
|
+
id_column = f"{entity}_id"
|
|
635
|
+
columns = ("run_id", id_column, *detail_columns, time_column)
|
|
636
|
+
values = [
|
|
637
|
+
run_id,
|
|
638
|
+
entity_id,
|
|
639
|
+
*(data.get(column) for column in detail_columns),
|
|
640
|
+
data.get("time"),
|
|
641
|
+
]
|
|
642
|
+
assignments = ", ".join(
|
|
643
|
+
f"{column} = excluded.{column}" for column in (*detail_columns, time_column)
|
|
644
|
+
)
|
|
645
|
+
placeholders = ", ".join("?" for _ in values)
|
|
646
|
+
self._connection.execute(
|
|
647
|
+
f"INSERT INTO {table} ({', '.join(columns)}) VALUES ({placeholders}) "
|
|
648
|
+
f"ON CONFLICT(run_id, {id_column}) DO UPDATE SET {assignments}",
|
|
649
|
+
values,
|
|
650
|
+
)
|
|
651
|
+
row = self._connection.execute(
|
|
652
|
+
f"SELECT id FROM {table} WHERE run_id = ? AND {id_column} = ?",
|
|
653
|
+
(run_id, entity_id),
|
|
654
|
+
).fetchone()
|
|
655
|
+
return None if row is None else int(row[0])
|
|
656
|
+
|
|
657
|
+
def _replace_group_members(self, group_id: int, members: Any) -> None:
|
|
658
|
+
self._connection.execute(
|
|
659
|
+
"DELETE FROM group_members WHERE group_id = ?", (group_id,)
|
|
660
|
+
)
|
|
661
|
+
self._connection.executemany(
|
|
662
|
+
"INSERT INTO group_members "
|
|
663
|
+
"(group_id, object_id, member_order) VALUES (?, ?, ?)",
|
|
664
|
+
((group_id, str(member), index) for index, member in enumerate(members)),
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
def _replace_group_bonds(self, group_id: int, bonds: Any) -> None:
|
|
668
|
+
self._connection.execute(
|
|
669
|
+
"DELETE FROM group_bonds WHERE group_id = ?", (group_id,)
|
|
670
|
+
)
|
|
671
|
+
self._connection.executemany(
|
|
672
|
+
"INSERT INTO group_bonds (group_id, bond_id, bond_order) VALUES (?, ?, ?)",
|
|
673
|
+
((group_id, str(bond), index) for index, bond in enumerate(bonds)),
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
def _upsert_concept_mapping(self, run_id: int, data: Mapping[str, Any]) -> None:
|
|
677
|
+
self._connection.execute(
|
|
678
|
+
"""INSERT INTO concept_mappings
|
|
679
|
+
(run_id, concept_mapping_id, correspondence_id,
|
|
680
|
+
description_type_1, description_type_2,
|
|
681
|
+
initial_descriptor, target_descriptor, label)
|
|
682
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
683
|
+
ON CONFLICT(run_id, concept_mapping_id) DO UPDATE SET
|
|
684
|
+
correspondence_id = excluded.correspondence_id,
|
|
685
|
+
description_type_1 = excluded.description_type_1,
|
|
686
|
+
description_type_2 = excluded.description_type_2,
|
|
687
|
+
initial_descriptor = excluded.initial_descriptor,
|
|
688
|
+
target_descriptor = excluded.target_descriptor,
|
|
689
|
+
label = excluded.label""",
|
|
690
|
+
(
|
|
691
|
+
run_id,
|
|
692
|
+
str(data["concept_mapping_id"]),
|
|
693
|
+
data.get("correspondence_id"),
|
|
694
|
+
data.get("description_type_1"),
|
|
695
|
+
data.get("description_type_2"),
|
|
696
|
+
data.get("initial_descriptor"),
|
|
697
|
+
data.get("target_descriptor"),
|
|
698
|
+
data.get("label"),
|
|
699
|
+
),
|
|
700
|
+
)
|
|
701
|
+
|
|
702
|
+
def _upsert_slipnode(self, run_id: int, data: Mapping[str, Any]) -> None:
|
|
703
|
+
column_names = (
|
|
704
|
+
"name",
|
|
705
|
+
"conceptual_depth",
|
|
706
|
+
"depth_factor",
|
|
707
|
+
"intrinsic_link_length",
|
|
708
|
+
"shrunk_link_length",
|
|
709
|
+
"category_links_json",
|
|
710
|
+
"instance_links_json",
|
|
711
|
+
"has_property_links_json",
|
|
712
|
+
"lateral_sliplinks_json",
|
|
713
|
+
"lateral_non_sliplinks_json",
|
|
714
|
+
"incoming_links_json",
|
|
715
|
+
)
|
|
716
|
+
columns = ", ".join(column_names)
|
|
717
|
+
values = [
|
|
718
|
+
data["name"],
|
|
719
|
+
data.get("conceptual_depth"),
|
|
720
|
+
data.get("depth_factor"),
|
|
721
|
+
data.get("intrinsic_link_length"),
|
|
722
|
+
data.get("shrunk_link_length"),
|
|
723
|
+
]
|
|
724
|
+
values.extend(
|
|
725
|
+
self._json(data.get(key, []))
|
|
726
|
+
for key in (
|
|
727
|
+
"category_links",
|
|
728
|
+
"instance_links",
|
|
729
|
+
"has_property_links",
|
|
730
|
+
"lateral_sliplinks",
|
|
731
|
+
"lateral_non_sliplinks",
|
|
732
|
+
"incoming_links",
|
|
733
|
+
)
|
|
734
|
+
)
|
|
735
|
+
placeholders = ", ".join("?" for _ in range(len(values) + 1))
|
|
736
|
+
self._connection.execute(
|
|
737
|
+
f"INSERT INTO slipnodes (run_id, {columns}) VALUES ({placeholders}) "
|
|
738
|
+
"ON CONFLICT(run_id, name) DO UPDATE SET "
|
|
739
|
+
+ ", ".join(f"{column} = excluded.{column}" for column in column_names[1:]),
|
|
740
|
+
[run_id, *values],
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
def _insert_sliplink(self, run_id: int, data: Mapping[str, Any]) -> None:
|
|
744
|
+
self._connection.execute(
|
|
745
|
+
"""INSERT INTO sliplinks (run_id, source, target, label, fixed_length,
|
|
746
|
+
is_category_link, is_instance_link, is_has_property_link,
|
|
747
|
+
is_lateral_sliplink, is_lateral_non_sliplink)
|
|
748
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
|
749
|
+
(
|
|
750
|
+
run_id,
|
|
751
|
+
data["source"],
|
|
752
|
+
data["target"],
|
|
753
|
+
data.get("label"),
|
|
754
|
+
data.get("fixed_length"),
|
|
755
|
+
int(bool(data.get("is_category_link"))),
|
|
756
|
+
int(bool(data.get("is_instance_link"))),
|
|
757
|
+
int(bool(data.get("is_has_property_link"))),
|
|
758
|
+
int(bool(data.get("is_lateral_sliplink"))),
|
|
759
|
+
int(bool(data.get("is_lateral_non_sliplink"))),
|
|
760
|
+
),
|
|
761
|
+
)
|