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,221 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ """The step-level column expressions, written once and used from two places.
4
+
5
+ The ``steps`` / ``tool_calls`` / ``tool_results`` views turn ATIF step JSON
6
+ into typed columns. The same columns are written to the columnar artifacts at
7
+ materialize time. Both readers must agree byte for byte, so the expressions
8
+ live here and each caller supplies only how it reaches a step's members:
9
+
10
+ * The registry's JSON path has one ``step`` JSON column holding the whole
11
+ step, so a member is ``json_extract(step, '$.<member>')``.
12
+ * The columnar producer receives one JSON column per top-level step member
13
+ (``step_id``, ``timestamp``, ``message``, ...), so the same member is
14
+ ``json_extract(<member>, '$')``.
15
+
16
+ Everything below the member boundary is identical text: the message
17
+ flattener, the coalesced booleans, the token casts, and the per-call and
18
+ per-result projections (a ``call`` or ``res`` column always holds one whole
19
+ tool call or observation result as JSON on both paths).
20
+
21
+ Pure string building. No duckdb import; the callers own the connection.
22
+ Every expression comes back typed :data:`~atif_duck.domain.sql_literal.SqlFragment`:
23
+ this module is one of the three producers of SQL text (with the catalog and
24
+ ``sql_literal``), and nothing here reads a value from outside the process.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ from dataclasses import dataclass
30
+
31
+ from atif_duck.domain.sql_literal import SqlFragment
32
+
33
+
34
+ @dataclass(frozen=True, slots=True)
35
+ class StepMemberAccess:
36
+ """How to spell "the JSON value of top-level step member ``name``" in SQL.
37
+
38
+ ``column`` names the SQL column that holds the JSON; ``path_prefix`` is
39
+ the JSONPath that reaches the member inside it (``'$.'`` when the column
40
+ holds a whole step, ``'$'`` when the column already is the member, in
41
+ which case ``name`` is the column itself).
42
+ """
43
+
44
+ #: Column holding the whole step, or ``None`` when each member is its own column.
45
+ whole_step_column: str | None
46
+
47
+ def json(self, member: str, path: str = "") -> SqlFragment:
48
+ """SQL for ``json_extract`` of ``member`` (optionally a sub-path below it)."""
49
+ column, root = self._locate(member)
50
+ return SqlFragment(f"json_extract({column}, '{root}{path}')")
51
+
52
+ def string(self, member: str, path: str = "") -> SqlFragment:
53
+ """SQL for ``json_extract_string`` of ``member`` (optionally a sub-path)."""
54
+ column, root = self._locate(member)
55
+ return SqlFragment(f"json_extract_string({column}, '{root}{path}')")
56
+
57
+ def type_of(self, member: str) -> SqlFragment:
58
+ """SQL for ``json_type`` of ``member``."""
59
+ column, root = self._locate(member)
60
+ return SqlFragment(f"json_type({column}, '{root}')")
61
+
62
+ def _locate(self, member: str) -> tuple[str, str]:
63
+ if self.whole_step_column is not None:
64
+ return self.whole_step_column, f"$.{member}"
65
+ return member, "$"
66
+
67
+
68
+ #: The registry's JSON path: ``UNNEST(t.steps) AS s(step)`` yields ``step``.
69
+ WHOLE_STEP: StepMemberAccess = StepMemberAccess(whole_step_column="step")
70
+
71
+ #: The producer's path: one JSON column per member, named after the member.
72
+ MEMBER_COLUMNS: StepMemberAccess = StepMemberAccess(whole_step_column=None)
73
+
74
+ #: Top-level step members the ``steps`` view reads. ``tool_calls`` and
75
+ #: ``observation`` are deliberately absent: they feed the other two views.
76
+ STEP_MEMBERS: tuple[str, ...] = (
77
+ "step_id",
78
+ "timestamp",
79
+ "source",
80
+ "model_name",
81
+ "message",
82
+ "metrics",
83
+ "llm_call_count",
84
+ "extra",
85
+ )
86
+
87
+
88
+ def step_columns(access: StepMemberAccess) -> tuple[tuple[str, str], ...]:
89
+ """``(expression, alias)`` pairs for every ``steps`` column after ``session_id``.
90
+
91
+ Column semantics a transcript-shaped reader would misread:
92
+
93
+ * ``message`` is flattened text: ATIF ``Step.message`` is
94
+ ``str | ContentPart[]``; the ARRAY branch joins the parts' ``text``
95
+ fields with blank lines (mirrors harbor's own text bundling).
96
+ * ``prompt_tokens`` is the ATIF TOTAL (input + cache_read +
97
+ cache_creation); ``cached_tokens`` is the cache-read subset;
98
+ ``cache_creation`` is dug out of ``metrics.extra`` because harbor only
99
+ preserves the creation split there (fidelity gap 6). The two adapters
100
+ spell that key differently (``cache_creation_input_tokens`` for Claude
101
+ Code, ``cache_write_input_tokens`` for Codex), so the column coalesces
102
+ them: one name reaches SQL, and a cost query does not branch on which
103
+ agent wrote the session.
104
+ * ``is_sidechain`` / ``is_compact_summary`` / ``source_uuids`` come from
105
+ ``step.extra`` per the CONTRACT enrichment pass.
106
+ * ``agent_id`` names the subagent a sidechain step belongs to (NULL on the
107
+ main chain); ``images`` lists the attachments pasted into a user
108
+ message, each already moved to the corpus blob store.
109
+ """
110
+ message_parts = access.json("message", "[*].text")
111
+ return (
112
+ (f"{access.json('step_id')}::BIGINT", "step_id"),
113
+ (f"{access.string('timestamp')}::TIMESTAMP", "ts"),
114
+ (access.string("source"), "source"),
115
+ (access.string("model_name"), "model_name"),
116
+ (
117
+ (
118
+ f"CASE WHEN {access.type_of('message')} = 'ARRAY' "
119
+ f"THEN array_to_string(list_transform({message_parts}, "
120
+ "part -> json_extract_string(part, '$')), '\n\n') "
121
+ f"ELSE {access.string('message')} END"
122
+ ),
123
+ "message",
124
+ ),
125
+ (f"coalesce({access.json('extra', '.is_sidechain')}::BOOLEAN, false)", "is_sidechain"),
126
+ (
127
+ f"coalesce({access.json('extra', '.is_compact_summary')}::BOOLEAN, false)",
128
+ "is_compact_summary",
129
+ ),
130
+ (f"{access.json('metrics', '.prompt_tokens')}::BIGINT", "prompt_tokens"),
131
+ (f"{access.json('metrics', '.completion_tokens')}::BIGINT", "completion_tokens"),
132
+ (f"{access.json('metrics', '.cached_tokens')}::BIGINT", "cached_tokens"),
133
+ (
134
+ (
135
+ f"coalesce({access.json('metrics', '.extra.cache_creation_input_tokens')}, "
136
+ f"{access.json('metrics', '.extra.cache_write_input_tokens')})::BIGINT"
137
+ ),
138
+ "cache_creation",
139
+ ),
140
+ (f"{access.json('llm_call_count')}::BIGINT", "llm_call_count"),
141
+ (access.json("extra", ".source_uuids"), "source_uuids"),
142
+ (access.string("extra", ".agent_id"), "agent_id"),
143
+ (access.json("extra", ".images"), "images"),
144
+ )
145
+
146
+
147
+ def step_key_columns(access: StepMemberAccess) -> tuple[tuple[str, str], ...]:
148
+ """The ``(step_id, ts)`` pair every tool call and result row carries."""
149
+ return (
150
+ (f"{access.json('step_id')}::BIGINT", "step_id"),
151
+ (f"{access.string('timestamp')}::TIMESTAMP", "ts"),
152
+ )
153
+
154
+
155
+ #: Per-call columns over a ``call`` JSON column holding one ATIF ``ToolCall``.
156
+ #: Renamed off the ATIF field names: function_name -> tool_name,
157
+ #: tool_call_id -> tool_use_id, arguments -> tool_input. ``tool_input`` is an
158
+ #: already parsed JSON value, NOT a JSON string, so a query must not
159
+ #: json_extract it twice.
160
+ CALL_COLUMNS: tuple[tuple[str, str], ...] = (
161
+ ("json_extract_string(call, '$.function_name')", "tool_name"),
162
+ ("json_extract_string(call, '$.tool_call_id')", "tool_use_id"),
163
+ ("json_extract(call, '$.arguments')", "tool_input"),
164
+ )
165
+
166
+ #: Per-result columns over a ``res`` JSON column holding one ATIF
167
+ #: ``ObservationResult``. ``source_call_id`` is ATIF's join key back to the
168
+ #: tool_calls array, surfaced under the SAME name ``tool_use_id`` that
169
+ #: ``tool_calls`` exposes, so the two views join with ``USING (tool_use_id)``.
170
+ #: The four typed columns after ``content`` come from ``extra``, where the
171
+ #: converter's result-signals pass writes them: NULL means the transcript did
172
+ #: not say, never "false" or "0" (see ``atif_converter.domain.result_signals``).
173
+ RESULT_COLUMNS: tuple[tuple[str, str], ...] = (
174
+ ("json_extract_string(res, '$.source_call_id')", "tool_use_id"),
175
+ ("json_extract(res, '$.content')", "content"),
176
+ ("json_extract(res, '$.extra.is_error')::BOOLEAN", "is_error"),
177
+ ("json_extract(res, '$.extra.exit_code')::BIGINT", "exit_code"),
178
+ ("json_extract(res, '$.extra.interrupted')::BOOLEAN", "interrupted"),
179
+ ("json_extract(res, '$.extra.images')", "images"),
180
+ )
181
+
182
+
183
+ #: ``session_events`` columns after ``session_id``, over an ``ev`` JSON column
184
+ #: holding one whole ``session_events.jsonl`` line. The registry's JSON path
185
+ #: reads the lines with ``read_json_objects`` and the producer hands each line
186
+ #: in as text, so both spell every column through these same expressions.
187
+ #: ``payload`` is the bounded record body (a parsed JSON value); ``ts`` is NULL
188
+ #: for the record types that carry no timestamp (``cost-state``, ``mode``).
189
+ EVENT_COLUMNS: tuple[tuple[str, str], ...] = (
190
+ ("json_extract(ev, '$.seq')::BIGINT", "seq"),
191
+ ("json_extract_string(ev, '$.ts')::TIMESTAMP", "ts"),
192
+ ("json_extract_string(ev, '$.event_type')", "event_type"),
193
+ ("json_extract_string(ev, '$.subtype')", "subtype"),
194
+ ("json_extract_string(ev, '$.uuid')", "uuid"),
195
+ ("json_extract_string(ev, '$.parent_uuid')", "parent_uuid"),
196
+ ("json_extract_string(ev, '$.tool_use_id')", "tool_use_id"),
197
+ ("coalesce(json_extract(ev, '$.is_sidechain')::BOOLEAN, false)", "is_sidechain"),
198
+ ("json_extract_string(ev, '$.source_file')", "source_file"),
199
+ ("json_extract(ev, '$.payload')", "payload"),
200
+ ("json_extract(ev, '$.payload_bytes')::BIGINT", "payload_bytes"),
201
+ ("coalesce(json_extract(ev, '$.payload_truncated')::BOOLEAN, false)", "payload_truncated"),
202
+ )
203
+
204
+
205
+ def render(columns: tuple[tuple[str, str], ...]) -> SqlFragment:
206
+ """Join ``(expression, alias)`` pairs into a SELECT list body."""
207
+ return SqlFragment(",\n ".join(f"{expression} AS {alias}" for expression, alias in columns))
208
+
209
+
210
+ __all__ = [
211
+ "CALL_COLUMNS",
212
+ "EVENT_COLUMNS",
213
+ "MEMBER_COLUMNS",
214
+ "RESULT_COLUMNS",
215
+ "STEP_MEMBERS",
216
+ "WHOLE_STEP",
217
+ "StepMemberAccess",
218
+ "render",
219
+ "step_columns",
220
+ "step_key_columns",
221
+ ]