atif-sql 0.2.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.
Files changed (164) hide show
  1. atif_analytics/__init__.py +9 -0
  2. atif_analytics/application/__init__.py +3 -0
  3. atif_analytics/application/analyze.py +158 -0
  4. atif_analytics/application/prompts.py +750 -0
  5. atif_analytics/application/use_cases/__init__.py +3 -0
  6. atif_analytics/application/use_cases/_shared.py +264 -0
  7. atif_analytics/application/use_cases/classify.py +430 -0
  8. atif_analytics/application/use_cases/conflicts.py +448 -0
  9. atif_analytics/application/use_cases/friction.py +634 -0
  10. atif_analytics/application/use_cases/perceived.py +451 -0
  11. atif_analytics/domain/__init__.py +3 -0
  12. atif_analytics/domain/authorship.py +166 -0
  13. atif_analytics/domain/config.py +30 -0
  14. atif_analytics/domain/costs.py +50 -0
  15. atif_analytics/domain/friction.py +117 -0
  16. atif_analytics/domain/layout.py +94 -0
  17. atif_analytics/domain/models.py +385 -0
  18. atif_analytics/domain/ports.py +100 -0
  19. atif_analytics/domain/transcript.py +359 -0
  20. atif_analytics/infrastructure/__init__.py +3 -0
  21. atif_analytics/infrastructure/corpus_reader.py +546 -0
  22. atif_analytics/infrastructure/parquet_cache.py +186 -0
  23. atif_analytics/infrastructure/settings.py +114 -0
  24. atif_analytics/infrastructure/sqlite_state/__init__.py +3 -0
  25. atif_analytics/infrastructure/sqlite_state/checkpointer.py +326 -0
  26. atif_analytics/infrastructure/sqlite_state/retry_queue.py +296 -0
  27. atif_analytics/py.typed +0 -0
  28. atif_cli/__init__.py +3 -0
  29. atif_cli/__main__.py +8 -0
  30. atif_cli/app.py +2459 -0
  31. atif_cli/converter_adapter.py +151 -0
  32. atif_cli/corpus.py +409 -0
  33. atif_cli/cron.py +311 -0
  34. atif_cli/duck_errors.py +97 -0
  35. atif_cli/embed_lake.py +101 -0
  36. atif_cli/errors.py +89 -0
  37. atif_cli/lake.py +373 -0
  38. atif_cli/lake_sessions.py +270 -0
  39. atif_cli/output.py +297 -0
  40. atif_cli/py.typed +0 -0
  41. atif_converter/__init__.py +3 -0
  42. atif_converter/application/__init__.py +3 -0
  43. atif_converter/application/convert_and_audit.py +294 -0
  44. atif_converter/application/convert_codex.py +210 -0
  45. atif_converter/domain/__init__.py +9 -0
  46. atif_converter/domain/agents.py +40 -0
  47. atif_converter/domain/atif/__init__.py +55 -0
  48. atif_converter/domain/atif/agent.py +37 -0
  49. atif_converter/domain/atif/content.py +161 -0
  50. atif_converter/domain/atif/final_metrics.py +45 -0
  51. atif_converter/domain/atif/metrics.py +49 -0
  52. atif_converter/domain/atif/observation.py +21 -0
  53. atif_converter/domain/atif/observation_result.py +47 -0
  54. atif_converter/domain/atif/step.py +144 -0
  55. atif_converter/domain/atif/subagent_trajectory_ref.py +90 -0
  56. atif_converter/domain/atif/tool_call.py +36 -0
  57. atif_converter/domain/atif/trajectory.py +211 -0
  58. atif_converter/domain/atif/trajectory_validator.py +299 -0
  59. atif_converter/domain/blobs.py +510 -0
  60. atif_converter/domain/claude_code_conversion.py +1077 -0
  61. atif_converter/domain/codex_conversion.py +1036 -0
  62. atif_converter/domain/codex_edges.py +192 -0
  63. atif_converter/domain/codex_enrichment.py +419 -0
  64. atif_converter/domain/codex_fidelity.py +124 -0
  65. atif_converter/domain/codex_nested_calls.py +165 -0
  66. atif_converter/domain/edges.py +113 -0
  67. atif_converter/domain/enrichment.py +412 -0
  68. atif_converter/domain/errors.py +60 -0
  69. atif_converter/domain/fidelity.py +171 -0
  70. atif_converter/domain/model_prices.json +8538 -0
  71. atif_converter/domain/pricing.py +790 -0
  72. atif_converter/domain/result_signals.py +774 -0
  73. atif_converter/domain/schema_version.py +90 -0
  74. atif_converter/domain/session_events.py +408 -0
  75. atif_converter/infrastructure/__init__.py +3 -0
  76. atif_converter/infrastructure/census.py +89 -0
  77. atif_converter/infrastructure/claude_code_converter.py +127 -0
  78. atif_converter/infrastructure/codex_adapter.py +132 -0
  79. atif_converter/infrastructure/codex_census.py +100 -0
  80. atif_converter/infrastructure/codex_converter.py +82 -0
  81. atif_converter/infrastructure/harbor_adapter.py +187 -0
  82. atif_converter/infrastructure/raw_records.py +426 -0
  83. atif_converter/infrastructure/source_archive.py +154 -0
  84. atif_converter/py.typed +0 -0
  85. atif_corpus/__init__.py +13 -0
  86. atif_corpus/application/__init__.py +7 -0
  87. atif_corpus/application/materialize.py +1826 -0
  88. atif_corpus/domain/__init__.py +9 -0
  89. atif_corpus/domain/agents.py +33 -0
  90. atif_corpus/domain/generation.py +34 -0
  91. atif_corpus/domain/layout.py +168 -0
  92. atif_corpus/domain/ports.py +175 -0
  93. atif_corpus/domain/session_id.py +68 -0
  94. atif_corpus/domain/sessions.py +228 -0
  95. atif_corpus/domain/slug.py +72 -0
  96. atif_corpus/domain/source_layout.py +143 -0
  97. atif_corpus/domain/watermark.py +76 -0
  98. atif_corpus/infrastructure/__init__.py +7 -0
  99. atif_corpus/infrastructure/atomic.py +266 -0
  100. atif_corpus/infrastructure/compress_artifacts.py +161 -0
  101. atif_corpus/infrastructure/fake_converter.py +174 -0
  102. atif_corpus/infrastructure/scanner.py +316 -0
  103. atif_corpus/infrastructure/settings.py +130 -0
  104. atif_corpus/infrastructure/source_archive.py +202 -0
  105. atif_corpus/py.typed +0 -0
  106. atif_duck/__init__.py +43 -0
  107. atif_duck/domain/__init__.py +32 -0
  108. atif_duck/domain/artifacts.py +66 -0
  109. atif_duck/domain/authorship.py +165 -0
  110. atif_duck/domain/catalog.py +626 -0
  111. atif_duck/domain/columnar.py +149 -0
  112. atif_duck/domain/embedding_guard.py +88 -0
  113. atif_duck/domain/examples.py +265 -0
  114. atif_duck/domain/lake.py +427 -0
  115. atif_duck/domain/lake_sessions.py +97 -0
  116. atif_duck/domain/raw_readers.py +140 -0
  117. atif_duck/domain/session_id.py +69 -0
  118. atif_duck/domain/sql_literal.py +50 -0
  119. atif_duck/infrastructure/__init__.py +34 -0
  120. atif_duck/infrastructure/analytics.py +469 -0
  121. atif_duck/infrastructure/authorship.py +139 -0
  122. atif_duck/infrastructure/columnar.py +641 -0
  123. atif_duck/infrastructure/lake.py +1383 -0
  124. atif_duck/infrastructure/lake_sessions.py +194 -0
  125. atif_duck/infrastructure/lake_settings.py +63 -0
  126. atif_duck/infrastructure/lake_steps.py +225 -0
  127. atif_duck/infrastructure/projections.py +221 -0
  128. atif_duck/infrastructure/registry.py +2194 -0
  129. atif_duck/infrastructure/stored_artifacts.py +115 -0
  130. atif_duck/py.typed +0 -0
  131. atif_embed/__init__.py +12 -0
  132. atif_embed/application/__init__.py +3 -0
  133. atif_embed/application/embed.py +293 -0
  134. atif_embed/application/prune.py +80 -0
  135. atif_embed/domain/__init__.py +3 -0
  136. atif_embed/domain/discovery.py +70 -0
  137. atif_embed/domain/embedding_guard.py +83 -0
  138. atif_embed/domain/errors.py +94 -0
  139. atif_embed/domain/ports.py +223 -0
  140. atif_embed/domain/sql_literal.py +49 -0
  141. atif_embed/domain/text_stamp.py +71 -0
  142. atif_embed/infrastructure/__init__.py +3 -0
  143. atif_embed/infrastructure/cohere_bedrock.py +433 -0
  144. atif_embed/infrastructure/corpus_text_rows.py +273 -0
  145. atif_embed/infrastructure/gemma_local.py +355 -0
  146. atif_embed/infrastructure/lake_text_rows.py +203 -0
  147. atif_embed/infrastructure/lance_store.py +440 -0
  148. atif_embed/infrastructure/providers.py +30 -0
  149. atif_embed/infrastructure/settings.py +154 -0
  150. atif_embed/py.typed +0 -0
  151. atif_models/__init__.py +14 -0
  152. atif_models/domain/__init__.py +9 -0
  153. atif_models/domain/ports.py +143 -0
  154. atif_models/domain/registry.py +151 -0
  155. atif_models/domain/schema.py +109 -0
  156. atif_models/infrastructure/__init__.py +8 -0
  157. atif_models/infrastructure/openai_bedrock.py +314 -0
  158. atif_models/infrastructure/settings.py +85 -0
  159. atif_models/py.typed +0 -0
  160. atif_sql-0.2.0.dist-info/METADATA +272 -0
  161. atif_sql-0.2.0.dist-info/RECORD +164 -0
  162. atif_sql-0.2.0.dist-info/WHEEL +4 -0
  163. atif_sql-0.2.0.dist-info/entry_points.txt +2 -0
  164. atif_sql-0.2.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,626 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ """Static view/macro catalog for the atif-duck DuckDB surface.
4
+
5
+ Why a static catalog, not runtime introspection?
6
+ ------------------------------------------------
7
+ ``DESCRIBE`` over every view re-binds the raw trajectory reader and re-runs
8
+ JSON work per call, which puts the schema dump on the same cost curve as the
9
+ corpus size. Answering from this module instead keeps the agent-facing
10
+ ``atif-sql schema`` command under 50 ms with zero DuckDB connection cost. The
11
+ price is that this file can lie, so drift against the actual DDL is caught by
12
+ two CI tests in ``tests/test_duck_views.py``:
13
+
14
+ * ``test_view_schema_matches_describe`` — registers the views over a
15
+ synthetic contract-shaped fixture corpus and asserts ``DESCRIBE`` output
16
+ equals :data:`VIEW_SCHEMA` column-for-column.
17
+ * ``test_macro_signatures_match_ddl`` — regex-parses ``CREATE OR REPLACE
18
+ MACRO <name>(<args>)`` out of the registry source and asserts equality
19
+ with :data:`MACRO_SIGNATURES`.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ # Business-level views emitted by ``atif_duck.infrastructure.registry``.
25
+ # The raw readers (``v_raw_trajectories``, ``v_raw_edges``,
26
+ # ``v_raw_loss_reports``, ``v_raw_meta``) are deliberately absent: they
27
+ # describe the corpus files rather than being queryable business surface. A
28
+ # ``v_raw_*`` name added here would appear in ``atif-sql schema`` and in the
29
+ # derived examples, promising a stable surface the registry does not offer.
30
+ VIEW_NAMES: tuple[str, ...] = (
31
+ "sessions",
32
+ "steps",
33
+ "messages",
34
+ "tool_calls",
35
+ "tool_results",
36
+ "todo_events",
37
+ "todo_state_current",
38
+ "subagent_spawns",
39
+ "task_creations",
40
+ "task_updates",
41
+ "tasks_state_current",
42
+ "skill_invocations",
43
+ "skill_usage",
44
+ "subagent_steps",
45
+ "subagents",
46
+ "images",
47
+ "loss_reports",
48
+ "session_events",
49
+ "message_embeddings",
50
+ "user_steps",
51
+ "human_turns",
52
+ "session_outcomes",
53
+ )
54
+
55
+ # Hand-maintained column schema for every view in :data:`VIEW_NAMES`.
56
+ # Column ORDER matters — the drift test asserts tuple equality with
57
+ # ``DESCRIBE`` output, so a contributor who edits view DDL without updating
58
+ # this dict gets a hard CI failure rather than a runtime mystery.
59
+ VIEW_SCHEMA: dict[str, tuple[tuple[str, str], ...]] = {
60
+ "sessions": (
61
+ ("session_id", "VARCHAR"),
62
+ ("agent", "VARCHAR"),
63
+ ("agent_version", "VARCHAR"),
64
+ ("cwd", "VARCHAR"),
65
+ ("git_branch", "VARCHAR"),
66
+ ("started_at", "TIMESTAMP"),
67
+ ("ended_at", "TIMESTAMP"),
68
+ ("agent_steps", "BIGINT"),
69
+ ("step_count", "BIGINT"),
70
+ ("model_name", "VARCHAR"),
71
+ ("total_cost_usd", "DOUBLE"),
72
+ ("reported_cost_usd", "DOUBLE"),
73
+ ("trajectory_path", "VARCHAR"),
74
+ ("corpus", "VARCHAR"),
75
+ ),
76
+ "steps": (
77
+ ("session_id", "VARCHAR"),
78
+ ("step_id", "BIGINT"),
79
+ ("ts", "TIMESTAMP"),
80
+ ("source", "VARCHAR"),
81
+ ("model_name", "VARCHAR"),
82
+ ("message", "VARCHAR"),
83
+ ("is_sidechain", "BOOLEAN"),
84
+ ("is_compact_summary", "BOOLEAN"),
85
+ ("prompt_tokens", "BIGINT"),
86
+ ("completion_tokens", "BIGINT"),
87
+ ("cached_tokens", "BIGINT"),
88
+ ("cache_creation", "BIGINT"),
89
+ ("llm_call_count", "BIGINT"),
90
+ ("source_uuids", "JSON"),
91
+ ("agent_id", "VARCHAR"),
92
+ ("images", "JSON"),
93
+ ),
94
+ "messages": (
95
+ ("uuid", "VARCHAR"),
96
+ ("parent_uuid", "VARCHAR"),
97
+ ("session_id", "VARCHAR"),
98
+ ("ts", "TIMESTAMP"),
99
+ ("type", "VARCHAR"),
100
+ ("is_sidechain", "BOOLEAN"),
101
+ ("is_compact_summary", "BOOLEAN"),
102
+ ("message_id", "VARCHAR"),
103
+ ("source_file", "VARCHAR"),
104
+ ),
105
+ "tool_calls": (
106
+ ("session_id", "VARCHAR"),
107
+ ("step_id", "BIGINT"),
108
+ ("ts", "TIMESTAMP"),
109
+ ("tool_name", "VARCHAR"),
110
+ ("tool_use_id", "VARCHAR"),
111
+ ("tool_input", "JSON"),
112
+ ),
113
+ "tool_results": (
114
+ ("session_id", "VARCHAR"),
115
+ ("step_id", "BIGINT"),
116
+ ("ts", "TIMESTAMP"),
117
+ ("tool_use_id", "VARCHAR"),
118
+ ("content", "JSON"),
119
+ ("is_error", "BOOLEAN"),
120
+ ("exit_code", "BIGINT"),
121
+ ("interrupted", "BOOLEAN"),
122
+ ("images", "JSON"),
123
+ ),
124
+ "todo_events": (
125
+ ("session_id", "VARCHAR"),
126
+ ("written_at", "TIMESTAMP"),
127
+ ("step_id", "BIGINT"),
128
+ ("subject", "VARCHAR"),
129
+ ("status", "VARCHAR"),
130
+ ("active_form", "VARCHAR"),
131
+ ("snapshot_ix", "BIGINT"),
132
+ ),
133
+ "todo_state_current": (
134
+ ("session_id", "VARCHAR"),
135
+ ("subject", "VARCHAR"),
136
+ ("status", "VARCHAR"),
137
+ ("active_form", "VARCHAR"),
138
+ ("written_at", "TIMESTAMP"),
139
+ ),
140
+ "subagent_spawns": (
141
+ ("session_id", "VARCHAR"),
142
+ ("spawned_at", "TIMESTAMP"),
143
+ ("step_id", "BIGINT"),
144
+ ("tool_use_id", "VARCHAR"),
145
+ ("spawn_tool", "VARCHAR"),
146
+ ("subagent_type", "VARCHAR"),
147
+ ("description", "VARCHAR"),
148
+ ("prompt", "VARCHAR"),
149
+ ("run_in_background", "VARCHAR"),
150
+ ),
151
+ "task_creations": (
152
+ ("session_id", "VARCHAR"),
153
+ ("created_at", "TIMESTAMP"),
154
+ ("step_id", "BIGINT"),
155
+ ("tool_use_id", "VARCHAR"),
156
+ ("create_tool", "VARCHAR"),
157
+ ("subject", "VARCHAR"),
158
+ ("description", "VARCHAR"),
159
+ ("active_form", "VARCHAR"),
160
+ ("metadata", "JSON"),
161
+ ),
162
+ "task_updates": (
163
+ ("session_id", "VARCHAR"),
164
+ ("updated_at", "TIMESTAMP"),
165
+ ("step_id", "BIGINT"),
166
+ ("tool_use_id", "VARCHAR"),
167
+ ("update_tool", "VARCHAR"),
168
+ ("task_id", "VARCHAR"),
169
+ ("status", "VARCHAR"),
170
+ ("add_blocked_by", "JSON"),
171
+ ("owner", "VARCHAR"),
172
+ ),
173
+ "tasks_state_current": (
174
+ ("session_id", "VARCHAR"),
175
+ ("task_id", "VARCHAR"),
176
+ ("subject", "VARCHAR"),
177
+ ("active_form", "VARCHAR"),
178
+ ("status", "VARCHAR"),
179
+ ("created_at", "TIMESTAMP"),
180
+ ("last_updated_at", "TIMESTAMP"),
181
+ ),
182
+ "skill_invocations": (
183
+ ("session_id", "VARCHAR"),
184
+ ("ts", "TIMESTAMP"),
185
+ ("step_id", "BIGINT"),
186
+ ("source", "VARCHAR"),
187
+ ("skill_id", "VARCHAR"),
188
+ ("args", "VARCHAR"),
189
+ ("tool_use_id", "VARCHAR"),
190
+ ),
191
+ "skill_usage": (
192
+ ("session_id", "VARCHAR"),
193
+ ("ts", "TIMESTAMP"),
194
+ ("step_id", "BIGINT"),
195
+ ("source", "VARCHAR"),
196
+ ("skill_id", "VARCHAR"),
197
+ ("args", "VARCHAR"),
198
+ ("tool_use_id", "VARCHAR"),
199
+ ("skill_name", "VARCHAR"),
200
+ ("plugin", "VARCHAR"),
201
+ ("is_builtin", "BOOLEAN"),
202
+ ),
203
+ "subagent_steps": (
204
+ ("session_id", "VARCHAR"),
205
+ ("step_id", "BIGINT"),
206
+ ("ts", "TIMESTAMP"),
207
+ ("source", "VARCHAR"),
208
+ ("model_name", "VARCHAR"),
209
+ ("message", "VARCHAR"),
210
+ ("is_sidechain", "BOOLEAN"),
211
+ ("is_compact_summary", "BOOLEAN"),
212
+ ("prompt_tokens", "BIGINT"),
213
+ ("completion_tokens", "BIGINT"),
214
+ ("cached_tokens", "BIGINT"),
215
+ ("cache_creation", "BIGINT"),
216
+ ("llm_call_count", "BIGINT"),
217
+ ("source_uuids", "JSON"),
218
+ ("agent_id", "VARCHAR"),
219
+ ("images", "JSON"),
220
+ ),
221
+ "subagents": (
222
+ ("session_id", "VARCHAR"),
223
+ ("agent_id", "VARCHAR"),
224
+ ("agent_type", "VARCHAR"),
225
+ ("description", "VARCHAR"),
226
+ ("parent_tool_call_id", "VARCHAR"),
227
+ ("parent_step_id", "BIGINT"),
228
+ ("link_source", "VARCHAR"),
229
+ ("spawn_depth", "BIGINT"),
230
+ ("parent_agent_id", "VARCHAR"),
231
+ ("first_ts", "TIMESTAMP"),
232
+ ("last_ts", "TIMESTAMP"),
233
+ ("step_count", "BIGINT"),
234
+ ),
235
+ "images": (
236
+ ("session_id", "VARCHAR"),
237
+ ("step_id", "BIGINT"),
238
+ ("ts", "TIMESTAMP"),
239
+ ("origin", "VARCHAR"),
240
+ ("tool_use_id", "VARCHAR"),
241
+ ("sha256", "VARCHAR"),
242
+ ("media_type", "VARCHAR"),
243
+ ("size_bytes", "BIGINT"),
244
+ ("width", "BIGINT"),
245
+ ("height", "BIGINT"),
246
+ ("blob_path", "VARCHAR"),
247
+ ),
248
+ "loss_reports": (
249
+ ("session_id", "VARCHAR"),
250
+ ("records_total", "BIGINT"),
251
+ ("records_converted", "BIGINT"),
252
+ ("records_captured", "BIGINT"),
253
+ ("records_dropped", "BIGINT"),
254
+ ("gaps_observed", "JSON"),
255
+ ("record_counts", "JSON"),
256
+ ("subagent_files_found", "BIGINT"),
257
+ ("subagent_files_convertible", "BIGINT"),
258
+ ("workflow_subagent_files_found", "BIGINT"),
259
+ ("report_path", "VARCHAR"),
260
+ ),
261
+ "session_events": (
262
+ ("session_id", "VARCHAR"),
263
+ ("seq", "BIGINT"),
264
+ ("ts", "TIMESTAMP"),
265
+ ("event_type", "VARCHAR"),
266
+ ("subtype", "VARCHAR"),
267
+ ("uuid", "VARCHAR"),
268
+ ("parent_uuid", "VARCHAR"),
269
+ ("tool_use_id", "VARCHAR"),
270
+ ("is_sidechain", "BOOLEAN"),
271
+ ("source_file", "VARCHAR"),
272
+ ("payload", "JSON"),
273
+ ("payload_bytes", "BIGINT"),
274
+ ("payload_truncated", "BOOLEAN"),
275
+ ),
276
+ # VSS surface bound by ``register_vss``: a view over the Lance-attached
277
+ # embeddings table, or an empty fallback table with the same shape when
278
+ # no store exists yet. ``embedding`` is FLOAT[dim] — the catalog pins
279
+ # the 1024 default; a store stamped at another Matryoshka width binds at
280
+ # that width instead (the drift test runs against the default).
281
+ "message_embeddings": (
282
+ ("uuid", "VARCHAR"),
283
+ ("model", "VARCHAR"),
284
+ ("dim", "INTEGER"),
285
+ ("embedding", "FLOAT[1024]"),
286
+ ("embedded_at", "TIMESTAMP WITH TIME ZONE"),
287
+ ),
288
+ # Authorship surface bound by ``register_authorship`` over ``steps``: no
289
+ # analytics run needed. ``author`` values and rules live in
290
+ # :mod:`atif_duck.domain.authorship`.
291
+ "user_steps": (
292
+ ("session_id", "VARCHAR"),
293
+ ("step_id", "BIGINT"),
294
+ ("ts", "TIMESTAMP"),
295
+ ("uuid", "VARCHAR"),
296
+ ("is_sidechain", "BOOLEAN"),
297
+ ("is_compact_summary", "BOOLEAN"),
298
+ ("author", "VARCHAR"),
299
+ ("message", "VARCHAR"),
300
+ ),
301
+ "human_turns": (
302
+ ("session_id", "VARCHAR"),
303
+ ("step_id", "BIGINT"),
304
+ ("ts", "TIMESTAMP"),
305
+ ("uuid", "VARCHAR"),
306
+ ("message", "VARCHAR"),
307
+ ),
308
+ "session_outcomes": (
309
+ ("session_id", "VARCHAR"),
310
+ ("kind", "VARCHAR"),
311
+ ("outcome", "VARCHAR"),
312
+ ("human_turns", "BIGINT"),
313
+ ("interrupts", "BIGINT"),
314
+ ("reviewer_blocks", "BIGINT"),
315
+ ),
316
+ }
317
+
318
+ # The core macros: the ones ``register_macros`` creates over the
319
+ # always-present transcript-derived views, ``semantic_search``, which needs the
320
+ # embedding store and is skipped when ``skip_vss=True``, and ``step_author``,
321
+ # which ``register_authorship`` creates beside its views. The analytics macros
322
+ # are NOT here — they live in :data:`ANALYTICS_MACRO_SIGNATURES` and register
323
+ # separately, because their backing views exist only once the pipelines run.
324
+ MACRO_NAMES: tuple[str, ...] = (
325
+ "ago",
326
+ "model_used",
327
+ "cost_estimate",
328
+ "tool_rank",
329
+ "todo_velocity",
330
+ "subagent_fanout",
331
+ "semantic_search",
332
+ "skill_rank",
333
+ "skill_source_mix",
334
+ "step_author",
335
+ )
336
+
337
+ # Hand-maintained signatures for every macro in :data:`MACRO_NAMES`. They are
338
+ # hand-maintained because DuckDB's ``duckdb_functions()`` returns NULL
339
+ # ``parameters`` for a table macro, so runtime introspection cannot recover
340
+ # them; the regex drift test over the registry's DDL text guards this dict
341
+ # instead. Parameter NAMES are part of the surface: they are what
342
+ # ``atif-sql schema`` prints and what ARG_EXEMPLARS keys the examples on.
343
+ MACRO_SIGNATURES: dict[str, tuple[str, ...]] = {
344
+ "ago": ("interval_text",),
345
+ "model_used": ("sid",),
346
+ "cost_estimate": ("sid",),
347
+ "tool_rank": ("last_n_days",),
348
+ "todo_velocity": ("sid",),
349
+ "subagent_fanout": ("sid",),
350
+ "semantic_search": ("query_vec", "k"),
351
+ "skill_rank": ("last_n_days",),
352
+ "skill_source_mix": ("last_n_days",),
353
+ "step_author": ("src", "msg"),
354
+ }
355
+
356
+ # ---------------------------------------------------------------------------
357
+ # v2 analytics surface (views + macros over the atif-analytics parquets).
358
+ # These bind ONLY when the backing parquet exists (a fresh corpus has none),
359
+ # so they live in their own catalogs rather than VIEW_NAMES/VIEW_SCHEMA —
360
+ # the DESCRIBE drift test binds them over a fixture with parquets present.
361
+ # ---------------------------------------------------------------------------
362
+
363
+ # Views registered by ``atif_duck.infrastructure.analytics.register_analytics``
364
+ # (parquet-gated; ``session_goals`` / ``conflicts_summary`` /
365
+ # ``perceived_summary`` derive from their upstream view).
366
+ #
367
+ # Removed 2026-09-27 with the trajectory and structural pipelines:
368
+ # ``message_trajectory``, ``message_clusters``, ``cluster_terms``,
369
+ # ``session_communities``, ``community_profile``.
370
+ ANALYTICS_VIEW_NAMES: tuple[str, ...] = (
371
+ "session_classifications",
372
+ "session_goals",
373
+ "session_conflicts",
374
+ "conflicts_summary",
375
+ "user_friction",
376
+ "perceived_errors",
377
+ "perceived_summary",
378
+ )
379
+
380
+ # Column schema for every analytics view, in DESCRIBE order. Drift-tested by
381
+ # ``test_analytics_view_schema_matches_describe`` over a fixture corpus with
382
+ # every parquet present, and printed by ``atif-sql schema`` under
383
+ # ``requires: analytics``.
384
+ ANALYTICS_VIEW_SCHEMA: dict[str, tuple[tuple[str, str], ...]] = {
385
+ "session_classifications": (
386
+ ("session_id", "VARCHAR"),
387
+ ("work_category", "VARCHAR"),
388
+ ("goal", "VARCHAR"),
389
+ ("confidence", "FLOAT"),
390
+ ("classified_at", "TIMESTAMP WITH TIME ZONE"),
391
+ ("autonomy_tier", "VARCHAR"),
392
+ ("success", "VARCHAR"),
393
+ ("category", "VARCHAR"),
394
+ ),
395
+ "session_goals": (
396
+ ("session_id", "VARCHAR"),
397
+ ("goal", "VARCHAR"),
398
+ ("confidence", "FLOAT"),
399
+ ("classified_at", "TIMESTAMP WITH TIME ZONE"),
400
+ ),
401
+ "session_conflicts": (
402
+ ("session_id", "VARCHAR"),
403
+ ("turn_a_uuid", "VARCHAR"),
404
+ ("turn_b_uuid", "VARCHAR"),
405
+ ("conflict_kind", "VARCHAR"),
406
+ ("severity", "VARCHAR"),
407
+ ("agent_position", "VARCHAR"),
408
+ ("user_position", "VARCHAR"),
409
+ ("confidence", "DOUBLE"),
410
+ ("detected_at", "TIMESTAMP WITH TIME ZONE"),
411
+ ),
412
+ "conflicts_summary": (
413
+ ("session_id", "VARCHAR"),
414
+ ("conflict_count", "BIGINT"),
415
+ ),
416
+ "user_friction": (
417
+ ("uuid", "VARCHAR"),
418
+ ("session_id", "VARCHAR"),
419
+ ("ts", "TIMESTAMP WITH TIME ZONE"),
420
+ ("text_snippet", "VARCHAR"),
421
+ ("label", "VARCHAR"),
422
+ ("rationale", "VARCHAR"),
423
+ ("source", "VARCHAR"),
424
+ ("confidence", "FLOAT"),
425
+ ("classified_at", "TIMESTAMP WITH TIME ZONE"),
426
+ ),
427
+ "perceived_errors": (
428
+ ("session_id", "VARCHAR"),
429
+ ("turn_uuid", "VARCHAR"),
430
+ ("signal", "VARCHAR"),
431
+ ("severity", "VARCHAR"),
432
+ ("evidence", "VARCHAR"),
433
+ ("agent_error_summary", "VARCHAR"),
434
+ ("confidence", "DOUBLE"),
435
+ ("detected_at", "TIMESTAMP WITH TIME ZONE"),
436
+ ),
437
+ "perceived_summary": (
438
+ ("session_id", "VARCHAR"),
439
+ ("n_errors", "BIGINT"),
440
+ ("max_severity", "VARCHAR"),
441
+ ("signals", "VARCHAR[]"),
442
+ ),
443
+ }
444
+
445
+ # The macros ``register_analytics_macros`` creates. Each one registers only
446
+ # when every view it binds against exists, so a corpus with no analytics run
447
+ # has these names in the catalog and not on the connection — which is what
448
+ # ``requires: analytics`` on an example means. The regex drift test parses the
449
+ # signatures out of the DDL source.
450
+ #
451
+ # Removed 2026-09-27: ``autonomy_trend``, ``success_rate_by_work``,
452
+ # ``sentiment_arc``, ``cluster_top_terms``, ``community_top_topics``.
453
+ ANALYTICS_MACRO_SIGNATURES: dict[str, tuple[str, ...]] = {
454
+ "work_mix": ("since_days",),
455
+ "friction_counts": ("since_days",),
456
+ "friction_rate": ("since_days",),
457
+ "friction_examples": ("label_name", "n"),
458
+ "conflicts_over_time": ("since_days",),
459
+ "perceived_counts": ("since_days",),
460
+ "perceived_rate": ("since_days",),
461
+ "perceived_examples": ("signal_name", "n"),
462
+ }
463
+
464
+ # Macros whose DDL is ``CREATE OR REPLACE MACRO ... AS TABLE`` — callers
465
+ # write ``SELECT * FROM name(args)``; every other macro is scalar and is
466
+ # called as ``SELECT name(args)``. The examples generator derives its SQL
467
+ # shape from this set; drift against the actual DDL is caught by the
468
+ # ``AS TABLE`` regex test in ``tests/test_examples.py``.
469
+ TABLE_MACRO_NAMES: frozenset[str] = frozenset(
470
+ {
471
+ "cost_estimate",
472
+ "tool_rank",
473
+ "semantic_search",
474
+ "skill_rank",
475
+ "skill_source_mix",
476
+ *ANALYTICS_MACRO_SIGNATURES,
477
+ }
478
+ )
479
+
480
+ # ---------------------------------------------------------------------------
481
+ # Agent-facing descriptions — the ONLY hand-maintained text on the examples
482
+ # surface. One entry per view and macro across every catalog above; the
483
+ # coverage drift test in ``tests/test_examples.py`` fails when a catalog
484
+ # object is added without a description (or a description goes stale-keyed).
485
+ # ---------------------------------------------------------------------------
486
+
487
+ DESCRIPTIONS: dict[str, str] = {
488
+ # -- core views ---------------------------------------------------------
489
+ "sessions": (
490
+ "One row per materialized session: which agent wrote it, timing, step "
491
+ "counts, model, cost. total_cost_usd is our estimate from token counts "
492
+ "(NULL when any step's model has no price); reported_cost_usd is what "
493
+ "Claude Code itself recorded (its last cost-state record), NULL for Codex "
494
+ "and for sessions without one. corpus names the corpus the session belongs "
495
+ "to; `query --all-corpora` spans every corpus in the lake."
496
+ ),
497
+ "steps": "One row per ATIF step (turn): flattened message text plus token metrics.",
498
+ "messages": "Raw-record identity from edges.jsonl: uuid, parent_uuid, type, timestamp.",
499
+ "tool_calls": "One row per tool call: tool_name plus JSON tool_input.",
500
+ "tool_results": (
501
+ "One row per tool result; join tool_calls USING (tool_use_id). is_error is "
502
+ "the flag the agent recorded, exit_code the process exit code where the "
503
+ "transcript states one (Claude Code states it only for a failed Bash call), "
504
+ "interrupted is Bash's interrupted flag; NULL means not stated. Inline images "
505
+ "are replaced in content by [image sha256:<hash> <media> <n> bytes] and "
506
+ "listed in images."
507
+ ),
508
+ "todo_events": "Every TodoWrite snapshot item with status and snapshot index.",
509
+ "todo_state_current": "Latest todo status per (session_id, subject).",
510
+ "subagent_spawns": "Task/Agent launches: subagent_type, description, prompt.",
511
+ "task_creations": "TaskCreate calls: subject, description, metadata.",
512
+ "task_updates": "TaskUpdate calls: task_id, status, owner.",
513
+ "tasks_state_current": "Latest status per persistent task, recovered from tool results.",
514
+ "skill_invocations": "Skill tool calls and /slash-command invocations, unioned.",
515
+ "skill_usage": "skill_invocations plus derived skill_name, plugin, is_builtin.",
516
+ "subagent_steps": "Only the inlined subagent (sidechain) steps; agent_id says whose.",
517
+ "subagents": (
518
+ "One row per subagent a session spawned: its type, description, and the "
519
+ "Task/Agent call that spawned it (parent_tool_call_id joins tool_calls."
520
+ "tool_use_id; link_source says whether the agent-*.meta.json sidecar or the "
521
+ "call's own result supplied it), plus the span and count of its steps."
522
+ ),
523
+ "images": (
524
+ "One row per image lifted out of a tool result or a user message into the "
525
+ "corpus blob store: sha256, media type, size, pixel dimensions, and "
526
+ "blob_path relative to the corpus root."
527
+ ),
528
+ "loss_reports": (
529
+ "Per-session conversion loss accounting from atif-converter: records "
530
+ "converted to steps, captured as session_events rows, or dropped."
531
+ ),
532
+ "session_events": (
533
+ "Non-message transcript records, one row each: hooks (event_type "
534
+ "'attachment', subtype 'hook_success' / 'hook_blocking_error' / ...; "
535
+ "'system' / 'stop_hook_summary'), injected context ('queued_command', "
536
+ "'hook_additional_context'), 'api_error', 'compact_boundary', "
537
+ "'model_refusal_fallback', 'cost-state', 'mode', 'permission-mode'; for "
538
+ "Codex 'compacted' and event_msg 'turn_aborted' / 'error'. payload is the "
539
+ "record body, cut to 8 KB (payload_bytes = original size, "
540
+ "payload_truncated says whether it was cut). ts is NULL for cost-state and "
541
+ "mode rows; order by seq. parent_uuid joins steps.source_uuids."
542
+ ),
543
+ "message_embeddings": "Step embeddings from the Lance store: uuid, model, dim, vector.",
544
+ "user_steps": (
545
+ "Every user-role step with its author: human, stop_hook, task_notification, "
546
+ "harness, or audit_prompt. Machine-written text arrives in the user role too."
547
+ ),
548
+ "human_turns": "Main-chain user steps a human wrote; the rate macros count these.",
549
+ "session_outcomes": (
550
+ "Deterministic per-session outcome: kind (interactive | one_shot_job | "
551
+ "turn_audit), outcome (pass | block | reviewer_blocked | interrupted | "
552
+ "clean_end), and the human_turns / interrupts / reviewer_blocks counts behind them."
553
+ ),
554
+ # -- core macros --------------------------------------------------------
555
+ "ago": "Timestamp N ago; use in filters like WHERE ts >= ago('7 days').",
556
+ "model_used": "Model name one session used.",
557
+ "cost_estimate": (
558
+ "Estimated USD cost of one session from token counts and pricing, as "
559
+ "(est_cost_usd, priced_steps, unpriced_steps). est_cost_usd covers the "
560
+ "PRICED steps only — trust it only when unpriced_steps = 0. Both counts "
561
+ "cover agent steps; user turns carry no model and are excluded. Cache "
562
+ "reads are charged at zero, so the estimate is a lower bound."
563
+ ),
564
+ "tool_rank": "Tool call counts over the last N days, most used first.",
565
+ "todo_velocity": "Completed-to-distinct-subject todo ratio for one session.",
566
+ "subagent_fanout": "Number of Task/Agent subagent launches in one session.",
567
+ "semantic_search": (
568
+ "Top-k nearest steps by cosine over message_embeddings; sim and distance are "
569
+ "the two halves of one metric (distance = 1 - sim). Two-step for text queries: "
570
+ "embed the query text first (atif-sql search does both steps), then pass the "
571
+ "dim-wide vector. Probes must be UNIT-NORM — stored vectors are un-normalized, "
572
+ "so a raw-magnitude probe ranks by length instead of meaning."
573
+ ),
574
+ "skill_rank": "Skill/slash-command leaderboard over the last N days.",
575
+ "skill_source_mix": "Per skill: tool vs slash-command invocation counts (non-builtin).",
576
+ "step_author": (
577
+ "Who wrote a step: NULL for non-user sources, else human, stop_hook, "
578
+ "task_notification, harness, or audit_prompt (prefix rules)."
579
+ ),
580
+ # -- analytics views ----------------------------------------------------
581
+ "session_classifications": (
582
+ "LLM session labels: work category and goal. autonomy_tier and success are "
583
+ "always-NULL compatibility columns; use session_outcomes instead."
584
+ ),
585
+ "session_goals": "Session goal text plus confidence (session_classifications projection).",
586
+ "session_conflicts": "LLM-detected agent/user conflicts per session.",
587
+ "conflicts_summary": "Conflict counts per session.",
588
+ "user_friction": "Labeled user friction moments (correction, confusion, ...).",
589
+ # -- analytics macros ---------------------------------------------------
590
+ "work_mix": "Work-category counts over the last N days.",
591
+ "friction_counts": "Counts per friction label over the last N days (NULL = full corpus).",
592
+ "friction_rate": "Per-session friction hits vs human turn count.",
593
+ "friction_examples": "Top-N example user messages for one friction label.",
594
+ "perceived_errors": "User-perceived agent errors (7 signals), uuid-anchored with evidence.",
595
+ "perceived_summary": "Per-session perceived-error rollup: count, max severity, signals.",
596
+ "perceived_counts": "Counts per perceived-error signal over the last N days.",
597
+ "perceived_rate": "Per-session perceived-error pressure vs human turn count.",
598
+ "perceived_examples": "Top-N evidence quotes for one perceived-error signal.",
599
+ "conflicts_over_time": "Conflicts on the conversation-time axis over the last N days.",
600
+ }
601
+
602
+ # Model pricing per 1M tokens (in_rate, out_rate) at public list rates from
603
+ # Anthropic's published pricing page. The ``cost_estimate`` macro LEFT JOINs
604
+ # step models against this table with a dated-suffix-stripping prefix match; a
605
+ # model absent here is counted in that macro's ``unpriced_steps`` rather than
606
+ # silently dropped, so omitting an unpriceable model understates nothing — it
607
+ # just declares itself.
608
+ #
609
+ # Base input/output rates only: prompt-cache write and read multipliers (1.25x
610
+ # / 2x / 0.1x of base input) are NOT modeled, matching the macro's formula,
611
+ # which charges uncached input at the base rate and leaves cache reads free.
612
+ DEFAULT_PRICING: dict[str, tuple[float, float]] = {
613
+ "claude-fable-5": (10.0, 50.0),
614
+ "claude-mythos-5": (10.0, 50.0),
615
+ "claude-fable-5-1": (10.0, 50.0),
616
+ "claude-opus-5-5": (4.0, 20.0),
617
+ "claude-opus-5": (5.0, 25.0),
618
+ "claude-opus-4-8": (5.0, 25.0),
619
+ "claude-opus-4-7": (5.0, 25.0),
620
+ "claude-opus-4-6": (5.0, 25.0),
621
+ "claude-opus-4-5": (5.0, 25.0),
622
+ "claude-sonnet-5": (2.0, 10.0),
623
+ "claude-sonnet-4-6": (3.0, 15.0),
624
+ "claude-sonnet-4-5": (3.0, 15.0),
625
+ "claude-haiku-4-5": (1.0, 5.0),
626
+ }