Rafael Poyiadzi Claude Opus 4.6 commited on
Commit
59ff43a
·
1 Parent(s): 379a533

Add PostHog analytics with graceful degradation

Browse files

Server-side tracking (started/completed/failed) and client-side JS snippet,
gated behind _POSTHOG_ENABLED so calls are skipped when env vars are unset.
API keys are hashed for distinct_id. Also cleans up unused _extract_answer
and RESEARCH_TRUNCATE_LEN.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files changed (2) hide show
  1. app.py +53 -11
  2. requirements.txt +1 -0
app.py CHANGED
@@ -1,4 +1,5 @@
1
  import ast
 
2
  import json
3
  import os
4
  import tempfile
@@ -6,8 +7,23 @@ from typing import Literal
6
 
7
  import gradio as gr
8
  import pandas as pd
 
9
  from pydantic import BaseModel, Field, create_model
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  from everyrow.ops import agent_map
12
  from everyrow.task import EffortLevel
13
 
@@ -107,6 +123,11 @@ def fields_to_schema_json(fields_list: list[dict]) -> str:
107
  return json.dumps(clean, indent=2) if clean else ""
108
 
109
 
 
 
 
 
 
110
  async def run_agent_map(api_key, file, query, effort_label, fields_list):
111
  if not api_key:
112
  raise gr.Error("Please enter your everyrow API key.")
@@ -122,6 +143,7 @@ async def run_agent_map(api_key, file, query, effort_label, fields_list):
122
  raise gr.Error("The uploaded CSV is empty.")
123
 
124
  effort_level = EFFORT_LEVELS[effort_label]
 
125
 
126
  try:
127
  response_model = build_response_model(fields_list)
@@ -132,11 +154,41 @@ async def run_agent_map(api_key, file, query, effort_label, fields_list):
132
  if response_model is not None:
133
  kwargs["response_model"] = response_model
134
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  result = await agent_map(**kwargs)
136
 
137
  if result.error:
 
 
 
 
 
 
138
  raise gr.Error(f"agent_map failed: {result.error}")
139
 
 
 
 
 
 
 
 
 
 
 
 
140
  output_df = result.data
141
 
142
  if "research" in output_df.columns:
@@ -151,9 +203,6 @@ async def run_agent_map(api_key, file, query, effort_label, fields_list):
151
  return output_df, gr.update(value=tmp.name, visible=True)
152
 
153
 
154
- RESEARCH_TRUNCATE_LEN = 120
155
-
156
-
157
  def _parse_research_val(val):
158
  """Try to parse a research value into a dict."""
159
  if isinstance(val, dict):
@@ -168,14 +217,6 @@ def _parse_research_val(val):
168
  return None
169
 
170
 
171
- def _extract_answer(val):
172
- """Extract the answer string from a research value."""
173
- parsed = _parse_research_val(val)
174
- if parsed is not None:
175
- return parsed.get("answer", str(val))
176
- return str(val)
177
-
178
-
179
  def hide_research(df: pd.DataFrame) -> pd.DataFrame:
180
  """Default view: drop the research column."""
181
  if "research" not in df.columns:
@@ -203,6 +244,7 @@ def expand_research(df: pd.DataFrame) -> pd.DataFrame:
203
 
204
  with gr.Blocks(
205
  title="everyrow annotate – AI Data Annotation & Web Research",
 
206
  css=".error-box { background: #fee; border: 1px solid #c00; border-radius: 8px; padding: 12px; color: #900; }",
207
  ) as demo:
208
  gr.Markdown(
 
1
  import ast
2
+ import hashlib
3
  import json
4
  import os
5
  import tempfile
 
7
 
8
  import gradio as gr
9
  import pandas as pd
10
+ import posthog
11
  from pydantic import BaseModel, Field, create_model
12
 
13
+ _POSTHOG_KEY = os.environ.get("POSTHOG_KEY", "")
14
+ _POSTHOG_HOST = os.environ.get("POSTHOG_HOST", "")
15
+ _POSTHOG_ENABLED = bool(_POSTHOG_KEY and _POSTHOG_HOST)
16
+
17
+ posthog.project_api_key = _POSTHOG_KEY
18
+ posthog.host = _POSTHOG_HOST
19
+
20
+ POSTHOG_HEAD = f"""
21
+ <script>
22
+ !function(t,e){{var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){{function g(t,e){{var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){{t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}}}(p=t.createElement("script")).type="text/javascript",p.async=!0,p.src=s.api_host+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){{var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e}},u.people.toString=function(){{return u.toString(1)+".people (stub)"}},o="init capture register register_once unregister opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing identify alias people.set people.set_once set_config reset opt_in_capturing".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])}},e.__SV=1)}}(document,window.posthog||[]);
23
+ posthog.init('{_POSTHOG_KEY}',{{api_host:'{_POSTHOG_HOST}', person_profiles: 'identified_only'}})
24
+ </script>
25
+ """ if _POSTHOG_ENABLED else ""
26
+
27
  from everyrow.ops import agent_map
28
  from everyrow.task import EffortLevel
29
 
 
123
  return json.dumps(clean, indent=2) if clean else ""
124
 
125
 
126
+ def _posthog_distinct_id(api_key: str) -> str:
127
+ """Hash the API key to use as a stable distinct_id without storing the secret."""
128
+ return hashlib.sha256(api_key.encode()).hexdigest()[:16]
129
+
130
+
131
  async def run_agent_map(api_key, file, query, effort_label, fields_list):
132
  if not api_key:
133
  raise gr.Error("Please enter your everyrow API key.")
 
143
  raise gr.Error("The uploaded CSV is empty.")
144
 
145
  effort_level = EFFORT_LEVELS[effort_label]
146
+ distinct_id = _posthog_distinct_id(api_key)
147
 
148
  try:
149
  response_model = build_response_model(fields_list)
 
154
  if response_model is not None:
155
  kwargs["response_model"] = response_model
156
 
157
+ if _POSTHOG_ENABLED:
158
+ posthog.capture(
159
+ distinct_id=distinct_id,
160
+ event="agent_map_started",
161
+ properties={
162
+ "effort_level": effort_label,
163
+ "row_count": len(df),
164
+ "column_count": len(df.columns),
165
+ "field_count": len(fields_list) if fields_list else 0,
166
+ "app": "everyrow-research-space",
167
+ },
168
+ )
169
+
170
  result = await agent_map(**kwargs)
171
 
172
  if result.error:
173
+ if _POSTHOG_ENABLED:
174
+ posthog.capture(
175
+ distinct_id=distinct_id,
176
+ event="agent_map_failed",
177
+ properties={"error": str(result.error), "app": "everyrow-research-space"},
178
+ )
179
  raise gr.Error(f"agent_map failed: {result.error}")
180
 
181
+ if _POSTHOG_ENABLED:
182
+ posthog.capture(
183
+ distinct_id=distinct_id,
184
+ event="agent_map_completed",
185
+ properties={
186
+ "output_rows": len(result.data),
187
+ "output_columns": len(result.data.columns),
188
+ "app": "everyrow-research-space",
189
+ },
190
+ )
191
+
192
  output_df = result.data
193
 
194
  if "research" in output_df.columns:
 
203
  return output_df, gr.update(value=tmp.name, visible=True)
204
 
205
 
 
 
 
206
  def _parse_research_val(val):
207
  """Try to parse a research value into a dict."""
208
  if isinstance(val, dict):
 
217
  return None
218
 
219
 
 
 
 
 
 
 
 
 
220
  def hide_research(df: pd.DataFrame) -> pd.DataFrame:
221
  """Default view: drop the research column."""
222
  if "research" not in df.columns:
 
244
 
245
  with gr.Blocks(
246
  title="everyrow annotate – AI Data Annotation & Web Research",
247
+ head=POSTHOG_HEAD,
248
  css=".error-box { background: #fee; border: 1px solid #c00; border-radius: 8px; padding: 12px; color: #900; }",
249
  ) as demo:
250
  gr.Markdown(
requirements.txt CHANGED
@@ -1 +1,2 @@
1
  everyrow>=0.2.0
 
 
1
  everyrow>=0.2.0
2
+ posthog>=3.0.0