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,9 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ """atif-analytics: LLM analytics over the materialized ATIF corpus.
4
+
5
+ Deliberately import-lean: pipelines are imported from their modules
6
+ (``atif_analytics.application.analyze`` etc.), never re-exported here, so a
7
+ bare package import stays off the boto3 subtree (workspace lean-import
8
+ convention).
9
+ """
@@ -0,0 +1,3 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ """Application layer: prompts, use-case pipelines, analyze orchestration."""
@@ -0,0 +1,158 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+ """The ``analyze`` orchestration composing the LLM pipelines.
4
+
5
+ Stage order (materializing is atif-corpus's job and embedding is
6
+ atif-embed's, so neither appears here):
7
+
8
+ 1. classify (small tier; interactive sessions only)
9
+ 2. conflicts (large tier; interactive sessions only)
10
+ 3. friction (small tier; human turns only)
11
+ 4. perceived (medium tier; >= 2 human-AI pairs)
12
+
13
+ Every stage honors ``dry_run``, which defaults to True (cost guard): the
14
+ stages then return plan dicts instead of running. ``skip_*`` flags opt out
15
+ per stage.
16
+
17
+ The trajectory pipeline and the three structural ones (cluster, terms,
18
+ community) were removed on 2026-09-27: no program read their output, the
19
+ trajectory labels were 91% neutral, clustering left 48.5% of messages as
20
+ noise, and the structural lane peaked near 17 GB every hour.
21
+
22
+ Heavy pipeline imports are DEFERRED into the body so importing this module
23
+ never drags boto3 onto the CLI's fast path.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ from typing import TYPE_CHECKING, Any
29
+
30
+ from loguru import logger
31
+
32
+ if TYPE_CHECKING:
33
+ from atif_analytics.domain.ports import SessionSource
34
+ from atif_analytics.infrastructure.settings import AnalyticsSettings
35
+
36
+
37
+ def run_analyze(
38
+ settings: AnalyticsSettings,
39
+ *,
40
+ since_days: int | None = 30,
41
+ limit: int | None = None,
42
+ dry_run: bool = True,
43
+ skip_classify: bool = False,
44
+ skip_conflicts: bool = False,
45
+ skip_friction: bool = False,
46
+ skip_perceived: bool = False,
47
+ source: SessionSource | None = None,
48
+ ) -> dict[str, Any]:
49
+ """Run the LLM analytics stages; return a per-stage summary dict.
50
+
51
+ ``skip_*`` flags subtract individual stages. ``source`` is where session
52
+ data comes from (atif-cli passes the lake's when the lake holds the
53
+ corpus); the default reads the per-session files. The summary's
54
+ ``session_source`` names which one ran.
55
+ """
56
+ summary: dict[str, Any] = {"dry_run": dry_run}
57
+
58
+ # One shared corpus reader across every stage: the parsed-steps memo is
59
+ # the expensive part, and every LLM stage walks the same sessions.
60
+ from atif_analytics.infrastructure.corpus_reader import CorpusReader
61
+
62
+ reader = CorpusReader(settings.corpus_root, caps=settings.transcript_caps(), source=source)
63
+ summary["session_source"] = reader.source_name
64
+
65
+ from atif_analytics.application.use_cases._shared import RunBudget
66
+ from atif_analytics.application.use_cases.classify import classify_sessions
67
+ from atif_analytics.application.use_cases.conflicts import detect_conflicts
68
+ from atif_analytics.application.use_cases.friction import detect_user_friction
69
+ from atif_analytics.application.use_cases.perceived import detect_perceived_errors
70
+
71
+ # THE BUDGET CEILING (unattended-cron money guard). Two independent
72
+ # caps, both settings-driven (ATIF_SQL_LLM_MAX_SESSIONS_PER_RUN /
73
+ # ATIF_SQL_LLM_MAX_COST_USD_PER_RUN) and CLI-overridable:
74
+ # * llm_max_sessions_per_run — enforced DURING each pipeline's
75
+ # newest-first admission walk (fresh sessions win), so a deferred
76
+ # session is never rendered;
77
+ # * llm_max_cost_usd_per_run — one RunBudget shared by every stage,
78
+ # priced from the providers' UsageAccumulator RUNNING ACTUALS.
79
+ #
80
+ # The cost cap is a STOP-DISPATCH trigger, not a hard cap: actual
81
+ # spend only becomes visible after a call returns, so the run can end
82
+ # above the ceiling by whatever was in flight at the crossing — at
83
+ # most BUDGET_CHECK_BATCH units on the priciest watched model. It is
84
+ # re-read every batch (not once per write chunk, which is wider than
85
+ # the session ceiling), so the overshoot does not scale with the
86
+ # batch size. When crossed, remaining LLM work stops: the ceiling hit
87
+ # is logged, nothing is stamped (no checkpoint/cache row) for
88
+ # unstarted sessions, and the report flags budget_exhausted=true.
89
+ budget = RunBudget(settings.llm_max_cost_usd_per_run)
90
+ summary["llm_budget"] = {
91
+ "max_sessions_per_run": settings.llm_max_sessions_per_run,
92
+ "max_cost_usd_per_run": settings.llm_max_cost_usd_per_run,
93
+ }
94
+
95
+ from atif_analytics.infrastructure.sqlite_state import checkpointer
96
+
97
+ #: Consecutive budget-skips before a stage's starvation escalates
98
+ #: to ERROR (the tail stages of a backlogged corpus can otherwise
99
+ #: starve silently behind WARNING noise).
100
+ max_consecutive_budget_skips = 3
101
+
102
+ state_db = settings.layout().state_db_path
103
+ stages = [
104
+ ("classify", classify_sessions, skip_classify),
105
+ ("conflicts", detect_conflicts, skip_conflicts),
106
+ ("friction", detect_user_friction, skip_friction),
107
+ ("perceived", detect_perceived_errors, skip_perceived),
108
+ ]
109
+ for name, fn, skip in stages:
110
+ if skip:
111
+ continue
112
+ if not dry_run and budget.exhausted():
113
+ # Starvation visibility: persist the skip so consecutive
114
+ # streaks survive across runs, and escalate to ERROR when
115
+ # the SAME stage has been budget-starved 3+ runs in a row.
116
+ streak = checkpointer.record_budget_skip(state_db, name)
117
+ log = logger.error if streak >= max_consecutive_budget_skips else logger.warning
118
+ log(
119
+ "analyze/{}: SKIPPED — cost ceiling hit "
120
+ "(${:.2f} spent >= ${:.2f} ceiling); nothing stamped "
121
+ "(budget-skipped {} consecutive runs)",
122
+ name,
123
+ budget.spent_usd(),
124
+ budget.max_cost_usd,
125
+ streak,
126
+ )
127
+ summary[name] = {"skipped": "budget_exhausted", "consecutive_skips": streak}
128
+ continue
129
+ n = fn(
130
+ settings,
131
+ since_days=since_days,
132
+ limit=limit,
133
+ dry_run=dry_run,
134
+ reader=reader,
135
+ budget=budget,
136
+ )
137
+ if not dry_run and not budget.exhausted():
138
+ # Clear only when the stage finished INSIDE its budget. A stage
139
+ # that runs but aborts mid-chunk on the ceiling was starved just
140
+ # as surely as one skipped before it started, and clearing on
141
+ # "it ran at all" resets the streak every run, so the 3-strike
142
+ # escalation above can never fire for the stage that needs it
143
+ # most. A stage that spends its last cent doing all of its work
144
+ # also holds its streak; that errs toward the louder log, which
145
+ # is the right direction for a spend guard.
146
+ checkpointer.clear_budget_skips(state_db, name)
147
+ logger.info("analyze/{}: {} (dry_run={})", name, n, dry_run)
148
+ summary[name] = n
149
+
150
+ summary["budget_exhausted"] = False if dry_run else budget.exhausted()
151
+ if not dry_run:
152
+ summary["llm_spent_usd"] = round(budget.spent_usd(), 4)
153
+
154
+ logger.info("analyze: done")
155
+ return summary
156
+
157
+
158
+ __all__ = ["run_analyze"]