ai-partner: aggregate privacy-safe analytics #1469

Description

@CraigBuckmaster

Parent epic:#1446 (Amicus — AI Study Partner v1)
Phase: 5 · Size: S · Depends on:#1450 (proxy), #1471 (gap capture), #1468 (audit)

Aggregate-only usage metrics for Amicus. Zero per-user tracking. Measures feature health, conversion, and content coverage to drive roadmap decisions.


Files to create

  • ai-proxy/src/metrics.ts — emits structured metric events from the proxy
  • _tools/amicus_analytics/query.py — aggregation queries against D1
  • _tools/amicus_analytics/weekly_report.py — generates the weekly aggregate dashboard
  • _tools/amicus_analytics/README.md

Files to modify

  • ai-proxy/src/index.ts — call metric emitters on each request lifecycle event
  • app/src/services/amicus/chat.ts — optional: send non-identifying client-side metrics (retrieval latency, etc.) via a dedicated /ai/metrics endpoint

What gets measured

Proxy-side (no per-user identification):

Per-request metrics (aggregated by hour bucket):

  • Total requests (/ai/chat)
  • Success rate (200 status)
  • Rate-limit hits (429)
  • Auth failures (401, 402)
  • Model tier split (haiku vs sonnet %)
  • Mean + p50 + p95 + p99 latency
  • Mean + p95 token counts (input, output)
  • Gap signal rate (% of responses with gap: true)

Per-day metrics:

  • Daily active premium users (count of unique auth tokens with successful request; count only — never store token)
  • Daily active Partner+ users
  • Total LLM cost (derived from token counts + model prices)
  • Upgrade conversion events (Premium → Partner+, captured via separate RevenueCat webhook, joined with Amicus usage)

Client-side (anonymous, batched):

Optional additional metrics via POST /ai/metrics endpoint:

  • Retrieval latency p95
  • Peek open rate (# peeks / # FAB visible)
  • Home card tap rate
  • Mini-conversation turn distribution (1/2/3+ turns before dismiss)
  • Citation tap-through rate

Each payload is anonymous — no user ID, no query content, no response content. Just numbers + categorical tags.

What we do NOT track:

  • Individual user behavior over time (no user profiles beyond auth)
  • Query text or response text
  • Any content that could reidentify a user
  • Device fingerprints
  • Location beyond coarse country (if Cloudflare headers naturally provide it; do not enrich)

D1 schema

Minimal — most metrics live in Cloudflare's built-in analytics. D1 only holds what we need for custom queries:

CREATETABLEamicus_hourly_metrics (
hour_bucket TEXTPRIMARY KEY, -- 'YYYY-MM-DDTHH' UTC
total_requests INTEGER,
success_count INTEGER,
rate_limit_count INTEGER,
auth_fail_count INTEGER,
haiku_count INTEGER,
sonnet_count INTEGER,
p50_latency_ms REAL,
p95_latency_ms REAL,
mean_input_tokens REAL,
mean_output_tokens REAL,
gap_signal_count INTEGER
);
CREATETABLEamicus_daily_users (
dateTEXTPRIMARY KEY,
dau_premium INTEGER,
dau_partner_plus INTEGER,
hashed_token_fingerprints TEXT-- bloom filter or similar; NOT raw tokens
);

The DAU calculation uses a hashed token fingerprint (SHA256 of auth token, truncated to 12 chars). We never store the raw token. The hash is per-day — expires nightly — so cross-day correlation is impossible even server-side.

Weekly report

python _tools/amicus_analytics/weekly_report.py

Produces markdown report:

# Amicus Weekly Report — Week of 2026-06-01
## Usage
- Total requests: 32,482
- Daily active premium users (peak): 3,247 (+8% vs prior week)
- Daily active Partner+ users: 89 (+12% vs prior week)
## Performance
- p95 latency: 1,724ms (target: <2000ms ✓)
- Success rate: 98.7%
- Rate-limit hits: 412 (1.3% of requests)
## Cost
- Total LLM cost: $3,421 (Haiku: $487, Sonnet: $2,934)
- Cost per premium user: $0.31
## Quality signals
- Gap signal rate: 3.9% (target: <5% ✓)
- Classifier needs-review rate: 3.2% (from #1468)
## Content gaps
- Top 5 unresolved corpus-gap clusters:
1. Orthodox perspectives on Romans 9 (47 hits)
2. Septuagint translation decisions in Isaiah 7:14 (31 hits)
...

Report is a markdown file suitable for pasting into a GitHub discussion or commit message.


Acceptance criteria

  • Proxy emits hourly metrics to D1
  • DAU calculation uses hashed token fingerprints; raw tokens never persisted
  • Client-side metrics endpoint works and writes aggregate-only data
  • Weekly report script generates markdown covering usage, perf, cost, quality, content gaps
  • No per-user tracking anywhere in the pipeline
  • README covers weekly ops + privacy posture
  • Privacy assertion test: search the proxy code for any path that stores raw user data → zero hits

Out of scope

  • Real-time dashboards → weekly cadence for v1
  • A/B testing infrastructure → future consideration
  • Per-user metrics → intentionally excluded on privacy grounds

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
       blocks
      (function() {
      function addCopyButtons() {
      document.querySelectorAll('pre code').forEach(function(codeBlock) {
      if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
      codeBlock.parentElement.setAttribute('data-copy-added', 'true');
      var btn = document.createElement('button');
      btn.textContent = 'Copy';
      btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
      btn.onmouseover = function() { this.style.opacity = '1'; };
      btn.onmouseout = function() { this.style.opacity = '0.7'; };
      btn.onclick = function() {
      navigator.clipboard.writeText(codeBlock.textContent).then(function() {
      btn.textContent = 'Copied!';
      setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
      });
      };
      codeBlock.parentElement.style.position = 'relative';
      codeBlock.parentElement.appendChild(btn);
      });
      }
      addCopyButtons();
      // Re-run on dynamic content
      var observer = new MutationObserver(addCopyButtons);
      observer.observe(document.body, { childList: true, subtree: true });
      })();
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      
      Skip to content

      ai-partner: aggregate privacy-safe analytics #1469

      Description

      @CraigBuckmaster

      Parent epic:#1446 (Amicus — AI Study Partner v1)
      Phase: 5 · Size: S · Depends on:#1450 (proxy), #1471 (gap capture), #1468 (audit)

      Aggregate-only usage metrics for Amicus. Zero per-user tracking. Measures feature health, conversion, and content coverage to drive roadmap decisions.


      Files to create

      • ai-proxy/src/metrics.ts — emits structured metric events from the proxy
      • _tools/amicus_analytics/query.py — aggregation queries against D1
      • _tools/amicus_analytics/weekly_report.py — generates the weekly aggregate dashboard
      • _tools/amicus_analytics/README.md

      Files to modify

      • ai-proxy/src/index.ts — call metric emitters on each request lifecycle event
      • app/src/services/amicus/chat.ts — optional: send non-identifying client-side metrics (retrieval latency, etc.) via a dedicated /ai/metrics endpoint

      What gets measured

      Proxy-side (no per-user identification):

      Per-request metrics (aggregated by hour bucket):

      • Total requests (/ai/chat)
      • Success rate (200 status)
      • Rate-limit hits (429)
      • Auth failures (401, 402)
      • Model tier split (haiku vs sonnet %)
      • Mean + p50 + p95 + p99 latency
      • Mean + p95 token counts (input, output)
      • Gap signal rate (% of responses with gap: true)

      Per-day metrics:

      • Daily active premium users (count of unique auth tokens with successful request; count only — never store token)
      • Daily active Partner+ users
      • Total LLM cost (derived from token counts + model prices)
      • Upgrade conversion events (Premium → Partner+, captured via separate RevenueCat webhook, joined with Amicus usage)

      Client-side (anonymous, batched):

      Optional additional metrics via POST /ai/metrics endpoint:

      • Retrieval latency p95
      • Peek open rate (# peeks / # FAB visible)
      • Home card tap rate
      • Mini-conversation turn distribution (1/2/3+ turns before dismiss)
      • Citation tap-through rate

      Each payload is anonymous — no user ID, no query content, no response content. Just numbers + categorical tags.

      What we do NOT track:

      • Individual user behavior over time (no user profiles beyond auth)
      • Query text or response text
      • Any content that could reidentify a user
      • Device fingerprints
      • Location beyond coarse country (if Cloudflare headers naturally provide it; do not enrich)

      D1 schema

      Minimal — most metrics live in Cloudflare's built-in analytics. D1 only holds what we need for custom queries:

      CREATETABLEamicus_hourly_metrics (
      hour_bucket TEXTPRIMARY KEY, -- 'YYYY-MM-DDTHH' UTC
      total_requests INTEGER,
      success_count INTEGER,
      rate_limit_count INTEGER,
      auth_fail_count INTEGER,
      haiku_count INTEGER,
      sonnet_count INTEGER,
      p50_latency_ms REAL,
      p95_latency_ms REAL,
      mean_input_tokens REAL,
      mean_output_tokens REAL,
      gap_signal_count INTEGER
      );
      CREATETABLEamicus_daily_users (
      dateTEXTPRIMARY KEY,
      dau_premium INTEGER,
      dau_partner_plus INTEGER,
      hashed_token_fingerprints TEXT-- bloom filter or similar; NOT raw tokens
      );

      The DAU calculation uses a hashed token fingerprint (SHA256 of auth token, truncated to 12 chars). We never store the raw token. The hash is per-day — expires nightly — so cross-day correlation is impossible even server-side.

      Weekly report

      python _tools/amicus_analytics/weekly_report.py
      

      Produces markdown report:

      # Amicus Weekly Report — Week of 2026-06-01
      ## Usage
      - Total requests: 32,482
      - Daily active premium users (peak): 3,247 (+8% vs prior week)
      - Daily active Partner+ users: 89 (+12% vs prior week)
      ## Performance
      - p95 latency: 1,724ms (target: <2000ms ✓)
      - Success rate: 98.7%
      - Rate-limit hits: 412 (1.3% of requests)
      ## Cost
      - Total LLM cost: $3,421 (Haiku: $487, Sonnet: $2,934)
      - Cost per premium user: $0.31
      ## Quality signals
      - Gap signal rate: 3.9% (target: <5% ✓)
      - Classifier needs-review rate: 3.2% (from #1468)
      ## Content gaps
      - Top 5 unresolved corpus-gap clusters:
      1. Orthodox perspectives on Romans 9 (47 hits)
      2. Septuagint translation decisions in Isaiah 7:14 (31 hits)
      ...
      

      Report is a markdown file suitable for pasting into a GitHub discussion or commit message.


      Acceptance criteria

      • Proxy emits hourly metrics to D1
      • DAU calculation uses hashed token fingerprints; raw tokens never persisted
      • Client-side metrics endpoint works and writes aggregate-only data
      • Weekly report script generates markdown covering usage, perf, cost, quality, content gaps
      • No per-user tracking anywhere in the pipeline
      • README covers weekly ops + privacy posture
      • Privacy assertion test: search the proxy code for any path that stores raw user data → zero hits

      Out of scope

      • Real-time dashboards → weekly cadence for v1
      • A/B testing infrastructure → future consideration
      • Per-user metrics → intentionally excluded on privacy grounds

      Metadata

      Metadata

      Assignees

      No one assigned

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
          Skip to content

          ai-partner: aggregate privacy-safe analytics #1469

          Description

          @CraigBuckmaster

          Parent epic:#1446 (Amicus — AI Study Partner v1)
          Phase: 5 · Size: S · Depends on:#1450 (proxy), #1471 (gap capture), #1468 (audit)

          Aggregate-only usage metrics for Amicus. Zero per-user tracking. Measures feature health, conversion, and content coverage to drive roadmap decisions.


          Files to create

          • ai-proxy/src/metrics.ts — emits structured metric events from the proxy
          • _tools/amicus_analytics/query.py — aggregation queries against D1
          • _tools/amicus_analytics/weekly_report.py — generates the weekly aggregate dashboard
          • _tools/amicus_analytics/README.md

          Files to modify

          • ai-proxy/src/index.ts — call metric emitters on each request lifecycle event
          • app/src/services/amicus/chat.ts — optional: send non-identifying client-side metrics (retrieval latency, etc.) via a dedicated /ai/metrics endpoint

          What gets measured

          Proxy-side (no per-user identification):

          Per-request metrics (aggregated by hour bucket):

          • Total requests (/ai/chat)
          • Success rate (200 status)
          • Rate-limit hits (429)
          • Auth failures (401, 402)
          • Model tier split (haiku vs sonnet %)
          • Mean + p50 + p95 + p99 latency
          • Mean + p95 token counts (input, output)
          • Gap signal rate (% of responses with gap: true)

          Per-day metrics:

          • Daily active premium users (count of unique auth tokens with successful request; count only — never store token)
          • Daily active Partner+ users
          • Total LLM cost (derived from token counts + model prices)
          • Upgrade conversion events (Premium → Partner+, captured via separate RevenueCat webhook, joined with Amicus usage)

          Client-side (anonymous, batched):

          Optional additional metrics via POST /ai/metrics endpoint:

          • Retrieval latency p95
          • Peek open rate (# peeks / # FAB visible)
          • Home card tap rate
          • Mini-conversation turn distribution (1/2/3+ turns before dismiss)
          • Citation tap-through rate

          Each payload is anonymous — no user ID, no query content, no response content. Just numbers + categorical tags.

          What we do NOT track:

          • Individual user behavior over time (no user profiles beyond auth)
          • Query text or response text
          • Any content that could reidentify a user
          • Device fingerprints
          • Location beyond coarse country (if Cloudflare headers naturally provide it; do not enrich)

          D1 schema

          Minimal — most metrics live in Cloudflare's built-in analytics. D1 only holds what we need for custom queries:

          CREATETABLEamicus_hourly_metrics (
          hour_bucket TEXTPRIMARY KEY, -- 'YYYY-MM-DDTHH' UTC
          total_requests INTEGER,
          success_count INTEGER,
          rate_limit_count INTEGER,
          auth_fail_count INTEGER,
          haiku_count INTEGER,
          sonnet_count INTEGER,
          p50_latency_ms REAL,
          p95_latency_ms REAL,
          mean_input_tokens REAL,
          mean_output_tokens REAL,
          gap_signal_count INTEGER
          );
          CREATETABLEamicus_daily_users (
          dateTEXTPRIMARY KEY,
          dau_premium INTEGER,
          dau_partner_plus INTEGER,
          hashed_token_fingerprints TEXT-- bloom filter or similar; NOT raw tokens
          );

          The DAU calculation uses a hashed token fingerprint (SHA256 of auth token, truncated to 12 chars). We never store the raw token. The hash is per-day — expires nightly — so cross-day correlation is impossible even server-side.

          Weekly report

          python _tools/amicus_analytics/weekly_report.py
          

          Produces markdown report:

          # Amicus Weekly Report — Week of 2026-06-01
          ## Usage
          - Total requests: 32,482
          - Daily active premium users (peak): 3,247 (+8% vs prior week)
          - Daily active Partner+ users: 89 (+12% vs prior week)
          ## Performance
          - p95 latency: 1,724ms (target: <2000ms ✓)
          - Success rate: 98.7%
          - Rate-limit hits: 412 (1.3% of requests)
          ## Cost
          - Total LLM cost: $3,421 (Haiku: $487, Sonnet: $2,934)
          - Cost per premium user: $0.31
          ## Quality signals
          - Gap signal rate: 3.9% (target: <5% ✓)
          - Classifier needs-review rate: 3.2% (from #1468)
          ## Content gaps
          - Top 5 unresolved corpus-gap clusters:
          1. Orthodox perspectives on Romans 9 (47 hits)
          2. Septuagint translation decisions in Isaiah 7:14 (31 hits)
          ...
          

          Report is a markdown file suitable for pasting into a GitHub discussion or commit message.


          Acceptance criteria

          • Proxy emits hourly metrics to D1
          • DAU calculation uses hashed token fingerprints; raw tokens never persisted
          • Client-side metrics endpoint works and writes aggregate-only data
          • Weekly report script generates markdown covering usage, perf, cost, quality, content gaps
          • No per-user tracking anywhere in the pipeline
          • README covers weekly ops + privacy posture
          • Privacy assertion test: search the proxy code for any path that stores raw user data → zero hits

          Out of scope

          • Real-time dashboards → weekly cadence for v1
          • A/B testing infrastructure → future consideration
          • Per-user metrics → intentionally excluded on privacy grounds

          Metadata

          Metadata

          Assignees

          No one assigned

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              ai-partner: aggregate privacy-safe analytics #1469

              Description

              @CraigBuckmaster

              Parent epic:#1446 (Amicus — AI Study Partner v1)
              Phase: 5 · Size: S · Depends on:#1450 (proxy), #1471 (gap capture), #1468 (audit)

              Aggregate-only usage metrics for Amicus. Zero per-user tracking. Measures feature health, conversion, and content coverage to drive roadmap decisions.


              Files to create

              • ai-proxy/src/metrics.ts — emits structured metric events from the proxy
              • _tools/amicus_analytics/query.py — aggregation queries against D1
              • _tools/amicus_analytics/weekly_report.py — generates the weekly aggregate dashboard
              • _tools/amicus_analytics/README.md

              Files to modify

              • ai-proxy/src/index.ts — call metric emitters on each request lifecycle event
              • app/src/services/amicus/chat.ts — optional: send non-identifying client-side metrics (retrieval latency, etc.) via a dedicated /ai/metrics endpoint

              What gets measured

              Proxy-side (no per-user identification):

              Per-request metrics (aggregated by hour bucket):

              • Total requests (/ai/chat)
              • Success rate (200 status)
              • Rate-limit hits (429)
              • Auth failures (401, 402)
              • Model tier split (haiku vs sonnet %)
              • Mean + p50 + p95 + p99 latency
              • Mean + p95 token counts (input, output)
              • Gap signal rate (% of responses with gap: true)

              Per-day metrics:

              • Daily active premium users (count of unique auth tokens with successful request; count only — never store token)
              • Daily active Partner+ users
              • Total LLM cost (derived from token counts + model prices)
              • Upgrade conversion events (Premium → Partner+, captured via separate RevenueCat webhook, joined with Amicus usage)

              Client-side (anonymous, batched):

              Optional additional metrics via POST /ai/metrics endpoint:

              • Retrieval latency p95
              • Peek open rate (# peeks / # FAB visible)
              • Home card tap rate
              • Mini-conversation turn distribution (1/2/3+ turns before dismiss)
              • Citation tap-through rate

              Each payload is anonymous — no user ID, no query content, no response content. Just numbers + categorical tags.

              What we do NOT track:

              • Individual user behavior over time (no user profiles beyond auth)
              • Query text or response text
              • Any content that could reidentify a user
              • Device fingerprints
              • Location beyond coarse country (if Cloudflare headers naturally provide it; do not enrich)

              D1 schema

              Minimal — most metrics live in Cloudflare's built-in analytics. D1 only holds what we need for custom queries:

              CREATETABLEamicus_hourly_metrics (
              hour_bucket TEXTPRIMARY KEY, -- 'YYYY-MM-DDTHH' UTC
              total_requests INTEGER,
              success_count INTEGER,
              rate_limit_count INTEGER,
              auth_fail_count INTEGER,
              haiku_count INTEGER,
              sonnet_count INTEGER,
              p50_latency_ms REAL,
              p95_latency_ms REAL,
              mean_input_tokens REAL,
              mean_output_tokens REAL,
              gap_signal_count INTEGER
              );
              CREATETABLEamicus_daily_users (
              dateTEXTPRIMARY KEY,
              dau_premium INTEGER,
              dau_partner_plus INTEGER,
              hashed_token_fingerprints TEXT-- bloom filter or similar; NOT raw tokens
              );

              The DAU calculation uses a hashed token fingerprint (SHA256 of auth token, truncated to 12 chars). We never store the raw token. The hash is per-day — expires nightly — so cross-day correlation is impossible even server-side.

              Weekly report

              python _tools/amicus_analytics/weekly_report.py
              

              Produces markdown report:

              # Amicus Weekly Report — Week of 2026-06-01
              ## Usage
              - Total requests: 32,482
              - Daily active premium users (peak): 3,247 (+8% vs prior week)
              - Daily active Partner+ users: 89 (+12% vs prior week)
              ## Performance
              - p95 latency: 1,724ms (target: <2000ms ✓)
              - Success rate: 98.7%
              - Rate-limit hits: 412 (1.3% of requests)
              ## Cost
              - Total LLM cost: $3,421 (Haiku: $487, Sonnet: $2,934)
              - Cost per premium user: $0.31
              ## Quality signals
              - Gap signal rate: 3.9% (target: <5% ✓)
              - Classifier needs-review rate: 3.2% (from #1468)
              ## Content gaps
              - Top 5 unresolved corpus-gap clusters:
              1. Orthodox perspectives on Romans 9 (47 hits)
              2. Septuagint translation decisions in Isaiah 7:14 (31 hits)
              ...
              

              Report is a markdown file suitable for pasting into a GitHub discussion or commit message.


              Acceptance criteria

              • Proxy emits hourly metrics to D1
              • DAU calculation uses hashed token fingerprints; raw tokens never persisted
              • Client-side metrics endpoint works and writes aggregate-only data
              • Weekly report script generates markdown covering usage, perf, cost, quality, content gaps
              • No per-user tracking anywhere in the pipeline
              • README covers weekly ops + privacy posture
              • Privacy assertion test: search the proxy code for any path that stores raw user data → zero hits

              Out of scope

              • Real-time dashboards → weekly cadence for v1
              • A/B testing infrastructure → future consideration
              • Per-user metrics → intentionally excluded on privacy grounds

              Metadata

              Metadata

              Assignees

              No one assigned

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
                  Skip to content

                  ai-partner: aggregate privacy-safe analytics #1469

                  Description

                  @CraigBuckmaster

                  Parent epic:#1446 (Amicus — AI Study Partner v1)
                  Phase: 5 · Size: S · Depends on:#1450 (proxy), #1471 (gap capture), #1468 (audit)

                  Aggregate-only usage metrics for Amicus. Zero per-user tracking. Measures feature health, conversion, and content coverage to drive roadmap decisions.


                  Files to create

                  • ai-proxy/src/metrics.ts — emits structured metric events from the proxy
                  • _tools/amicus_analytics/query.py — aggregation queries against D1
                  • _tools/amicus_analytics/weekly_report.py — generates the weekly aggregate dashboard
                  • _tools/amicus_analytics/README.md

                  Files to modify

                  • ai-proxy/src/index.ts — call metric emitters on each request lifecycle event
                  • app/src/services/amicus/chat.ts — optional: send non-identifying client-side metrics (retrieval latency, etc.) via a dedicated /ai/metrics endpoint

                  What gets measured

                  Proxy-side (no per-user identification):

                  Per-request metrics (aggregated by hour bucket):

                  • Total requests (/ai/chat)
                  • Success rate (200 status)
                  • Rate-limit hits (429)
                  • Auth failures (401, 402)
                  • Model tier split (haiku vs sonnet %)
                  • Mean + p50 + p95 + p99 latency
                  • Mean + p95 token counts (input, output)
                  • Gap signal rate (% of responses with gap: true)

                  Per-day metrics:

                  • Daily active premium users (count of unique auth tokens with successful request; count only — never store token)
                  • Daily active Partner+ users
                  • Total LLM cost (derived from token counts + model prices)
                  • Upgrade conversion events (Premium → Partner+, captured via separate RevenueCat webhook, joined with Amicus usage)

                  Client-side (anonymous, batched):

                  Optional additional metrics via POST /ai/metrics endpoint:

                  • Retrieval latency p95
                  • Peek open rate (# peeks / # FAB visible)
                  • Home card tap rate
                  • Mini-conversation turn distribution (1/2/3+ turns before dismiss)
                  • Citation tap-through rate

                  Each payload is anonymous — no user ID, no query content, no response content. Just numbers + categorical tags.

                  What we do NOT track:

                  • Individual user behavior over time (no user profiles beyond auth)
                  • Query text or response text
                  • Any content that could reidentify a user
                  • Device fingerprints
                  • Location beyond coarse country (if Cloudflare headers naturally provide it; do not enrich)

                  D1 schema

                  Minimal — most metrics live in Cloudflare's built-in analytics. D1 only holds what we need for custom queries:

                  CREATETABLEamicus_hourly_metrics (
                  hour_bucket TEXTPRIMARY KEY, -- 'YYYY-MM-DDTHH' UTC
                  total_requests INTEGER,
                  success_count INTEGER,
                  rate_limit_count INTEGER,
                  auth_fail_count INTEGER,
                  haiku_count INTEGER,
                  sonnet_count INTEGER,
                  p50_latency_ms REAL,
                  p95_latency_ms REAL,
                  mean_input_tokens REAL,
                  mean_output_tokens REAL,
                  gap_signal_count INTEGER
                  );
                  CREATETABLEamicus_daily_users (
                  dateTEXTPRIMARY KEY,
                  dau_premium INTEGER,
                  dau_partner_plus INTEGER,
                  hashed_token_fingerprints TEXT-- bloom filter or similar; NOT raw tokens
                  );

                  The DAU calculation uses a hashed token fingerprint (SHA256 of auth token, truncated to 12 chars). We never store the raw token. The hash is per-day — expires nightly — so cross-day correlation is impossible even server-side.

                  Weekly report

                  python _tools/amicus_analytics/weekly_report.py
                  

                  Produces markdown report:

                  # Amicus Weekly Report — Week of 2026-06-01
                  ## Usage
                  - Total requests: 32,482
                  - Daily active premium users (peak): 3,247 (+8% vs prior week)
                  - Daily active Partner+ users: 89 (+12% vs prior week)
                  ## Performance
                  - p95 latency: 1,724ms (target: <2000ms ✓)
                  - Success rate: 98.7%
                  - Rate-limit hits: 412 (1.3% of requests)
                  ## Cost
                  - Total LLM cost: $3,421 (Haiku: $487, Sonnet: $2,934)
                  - Cost per premium user: $0.31
                  ## Quality signals
                  - Gap signal rate: 3.9% (target: <5% ✓)
                  - Classifier needs-review rate: 3.2% (from #1468)
                  ## Content gaps
                  - Top 5 unresolved corpus-gap clusters:
                  1. Orthodox perspectives on Romans 9 (47 hits)
                  2. Septuagint translation decisions in Isaiah 7:14 (31 hits)
                  ...
                  

                  Report is a markdown file suitable for pasting into a GitHub discussion or commit message.


                  Acceptance criteria

                  • Proxy emits hourly metrics to D1
                  • DAU calculation uses hashed token fingerprints; raw tokens never persisted
                  • Client-side metrics endpoint works and writes aggregate-only data
                  • Weekly report script generates markdown covering usage, perf, cost, quality, content gaps
                  • No per-user tracking anywhere in the pipeline
                  • README covers weekly ops + privacy posture
                  • Privacy assertion test: search the proxy code for any path that stores raw user data → zero hits

                  Out of scope

                  • Real-time dashboards → weekly cadence for v1
                  • A/B testing infrastructure → future consideration
                  • Per-user metrics → intentionally excluded on privacy grounds

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                      Skip to content

                      ai-partner: aggregate privacy-safe analytics #1469

                      Description

                      @CraigBuckmaster

                      Parent epic:#1446 (Amicus — AI Study Partner v1)
                      Phase: 5 · Size: S · Depends on:#1450 (proxy), #1471 (gap capture), #1468 (audit)

                      Aggregate-only usage metrics for Amicus. Zero per-user tracking. Measures feature health, conversion, and content coverage to drive roadmap decisions.


                      Files to create

                      • ai-proxy/src/metrics.ts — emits structured metric events from the proxy
                      • _tools/amicus_analytics/query.py — aggregation queries against D1
                      • _tools/amicus_analytics/weekly_report.py — generates the weekly aggregate dashboard
                      • _tools/amicus_analytics/README.md

                      Files to modify

                      • ai-proxy/src/index.ts — call metric emitters on each request lifecycle event
                      • app/src/services/amicus/chat.ts — optional: send non-identifying client-side metrics (retrieval latency, etc.) via a dedicated /ai/metrics endpoint

                      What gets measured

                      Proxy-side (no per-user identification):

                      Per-request metrics (aggregated by hour bucket):

                      • Total requests (/ai/chat)
                      • Success rate (200 status)
                      • Rate-limit hits (429)
                      • Auth failures (401, 402)
                      • Model tier split (haiku vs sonnet %)
                      • Mean + p50 + p95 + p99 latency
                      • Mean + p95 token counts (input, output)
                      • Gap signal rate (% of responses with gap: true)

                      Per-day metrics:

                      • Daily active premium users (count of unique auth tokens with successful request; count only — never store token)
                      • Daily active Partner+ users
                      • Total LLM cost (derived from token counts + model prices)
                      • Upgrade conversion events (Premium → Partner+, captured via separate RevenueCat webhook, joined with Amicus usage)

                      Client-side (anonymous, batched):

                      Optional additional metrics via POST /ai/metrics endpoint:

                      • Retrieval latency p95
                      • Peek open rate (# peeks / # FAB visible)
                      • Home card tap rate
                      • Mini-conversation turn distribution (1/2/3+ turns before dismiss)
                      • Citation tap-through rate

                      Each payload is anonymous — no user ID, no query content, no response content. Just numbers + categorical tags.

                      What we do NOT track:

                      • Individual user behavior over time (no user profiles beyond auth)
                      • Query text or response text
                      • Any content that could reidentify a user
                      • Device fingerprints
                      • Location beyond coarse country (if Cloudflare headers naturally provide it; do not enrich)

                      D1 schema

                      Minimal — most metrics live in Cloudflare's built-in analytics. D1 only holds what we need for custom queries:

                      CREATETABLEamicus_hourly_metrics (
                      hour_bucket TEXTPRIMARY KEY, -- 'YYYY-MM-DDTHH' UTC
                      total_requests INTEGER,
                      success_count INTEGER,
                      rate_limit_count INTEGER,
                      auth_fail_count INTEGER,
                      haiku_count INTEGER,
                      sonnet_count INTEGER,
                      p50_latency_ms REAL,
                      p95_latency_ms REAL,
                      mean_input_tokens REAL,
                      mean_output_tokens REAL,
                      gap_signal_count INTEGER
                      );
                      CREATETABLEamicus_daily_users (
                      dateTEXTPRIMARY KEY,
                      dau_premium INTEGER,
                      dau_partner_plus INTEGER,
                      hashed_token_fingerprints TEXT-- bloom filter or similar; NOT raw tokens
                      );

                      The DAU calculation uses a hashed token fingerprint (SHA256 of auth token, truncated to 12 chars). We never store the raw token. The hash is per-day — expires nightly — so cross-day correlation is impossible even server-side.

                      Weekly report

                      python _tools/amicus_analytics/weekly_report.py
                      

                      Produces markdown report:

                      # Amicus Weekly Report — Week of 2026-06-01
                      ## Usage
                      - Total requests: 32,482
                      - Daily active premium users (peak): 3,247 (+8% vs prior week)
                      - Daily active Partner+ users: 89 (+12% vs prior week)
                      ## Performance
                      - p95 latency: 1,724ms (target: <2000ms ✓)
                      - Success rate: 98.7%
                      - Rate-limit hits: 412 (1.3% of requests)
                      ## Cost
                      - Total LLM cost: $3,421 (Haiku: $487, Sonnet: $2,934)
                      - Cost per premium user: $0.31
                      ## Quality signals
                      - Gap signal rate: 3.9% (target: <5% ✓)
                      - Classifier needs-review rate: 3.2% (from #1468)
                      ## Content gaps
                      - Top 5 unresolved corpus-gap clusters:
                      1. Orthodox perspectives on Romans 9 (47 hits)
                      2. Septuagint translation decisions in Isaiah 7:14 (31 hits)
                      ...
                      

                      Report is a markdown file suitable for pasting into a GitHub discussion or commit message.


                      Acceptance criteria

                      • Proxy emits hourly metrics to D1
                      • DAU calculation uses hashed token fingerprints; raw tokens never persisted
                      • Client-side metrics endpoint works and writes aggregate-only data
                      • Weekly report script generates markdown covering usage, perf, cost, quality, content gaps
                      • No per-user tracking anywhere in the pipeline
                      • README covers weekly ops + privacy posture
                      • Privacy assertion test: search the proxy code for any path that stores raw user data → zero hits

                      Out of scope

                      • Real-time dashboards → weekly cadence for v1
                      • A/B testing infrastructure → future consideration
                      • Per-user metrics → intentionally excluded on privacy grounds

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                          Skip to content

                          ai-partner: aggregate privacy-safe analytics #1469

                          Description

                          @CraigBuckmaster

                          Parent epic:#1446 (Amicus — AI Study Partner v1)
                          Phase: 5 · Size: S · Depends on:#1450 (proxy), #1471 (gap capture), #1468 (audit)

                          Aggregate-only usage metrics for Amicus. Zero per-user tracking. Measures feature health, conversion, and content coverage to drive roadmap decisions.


                          Files to create

                          • ai-proxy/src/metrics.ts — emits structured metric events from the proxy
                          • _tools/amicus_analytics/query.py — aggregation queries against D1
                          • _tools/amicus_analytics/weekly_report.py — generates the weekly aggregate dashboard
                          • _tools/amicus_analytics/README.md

                          Files to modify

                          • ai-proxy/src/index.ts — call metric emitters on each request lifecycle event
                          • app/src/services/amicus/chat.ts — optional: send non-identifying client-side metrics (retrieval latency, etc.) via a dedicated /ai/metrics endpoint

                          What gets measured

                          Proxy-side (no per-user identification):

                          Per-request metrics (aggregated by hour bucket):

                          • Total requests (/ai/chat)
                          • Success rate (200 status)
                          • Rate-limit hits (429)
                          • Auth failures (401, 402)
                          • Model tier split (haiku vs sonnet %)
                          • Mean + p50 + p95 + p99 latency
                          • Mean + p95 token counts (input, output)
                          • Gap signal rate (% of responses with gap: true)

                          Per-day metrics:

                          • Daily active premium users (count of unique auth tokens with successful request; count only — never store token)
                          • Daily active Partner+ users
                          • Total LLM cost (derived from token counts + model prices)
                          • Upgrade conversion events (Premium → Partner+, captured via separate RevenueCat webhook, joined with Amicus usage)

                          Client-side (anonymous, batched):

                          Optional additional metrics via POST /ai/metrics endpoint:

                          • Retrieval latency p95
                          • Peek open rate (# peeks / # FAB visible)
                          • Home card tap rate
                          • Mini-conversation turn distribution (1/2/3+ turns before dismiss)
                          • Citation tap-through rate

                          Each payload is anonymous — no user ID, no query content, no response content. Just numbers + categorical tags.

                          What we do NOT track:

                          • Individual user behavior over time (no user profiles beyond auth)
                          • Query text or response text
                          • Any content that could reidentify a user
                          • Device fingerprints
                          • Location beyond coarse country (if Cloudflare headers naturally provide it; do not enrich)

                          D1 schema

                          Minimal — most metrics live in Cloudflare's built-in analytics. D1 only holds what we need for custom queries:

                          CREATETABLEamicus_hourly_metrics (
                          hour_bucket TEXTPRIMARY KEY, -- 'YYYY-MM-DDTHH' UTC
                          total_requests INTEGER,
                          success_count INTEGER,
                          rate_limit_count INTEGER,
                          auth_fail_count INTEGER,
                          haiku_count INTEGER,
                          sonnet_count INTEGER,
                          p50_latency_ms REAL,
                          p95_latency_ms REAL,
                          mean_input_tokens REAL,
                          mean_output_tokens REAL,
                          gap_signal_count INTEGER
                          );
                          CREATETABLEamicus_daily_users (
                          dateTEXTPRIMARY KEY,
                          dau_premium INTEGER,
                          dau_partner_plus INTEGER,
                          hashed_token_fingerprints TEXT-- bloom filter or similar; NOT raw tokens
                          );

                          The DAU calculation uses a hashed token fingerprint (SHA256 of auth token, truncated to 12 chars). We never store the raw token. The hash is per-day — expires nightly — so cross-day correlation is impossible even server-side.

                          Weekly report

                          python _tools/amicus_analytics/weekly_report.py
                          

                          Produces markdown report:

                          # Amicus Weekly Report — Week of 2026-06-01
                          ## Usage
                          - Total requests: 32,482
                          - Daily active premium users (peak): 3,247 (+8% vs prior week)
                          - Daily active Partner+ users: 89 (+12% vs prior week)
                          ## Performance
                          - p95 latency: 1,724ms (target: <2000ms ✓)
                          - Success rate: 98.7%
                          - Rate-limit hits: 412 (1.3% of requests)
                          ## Cost
                          - Total LLM cost: $3,421 (Haiku: $487, Sonnet: $2,934)
                          - Cost per premium user: $0.31
                          ## Quality signals
                          - Gap signal rate: 3.9% (target: <5% ✓)
                          - Classifier needs-review rate: 3.2% (from #1468)
                          ## Content gaps
                          - Top 5 unresolved corpus-gap clusters:
                          1. Orthodox perspectives on Romans 9 (47 hits)
                          2. Septuagint translation decisions in Isaiah 7:14 (31 hits)
                          ...
                          

                          Report is a markdown file suitable for pasting into a GitHub discussion or commit message.


                          Acceptance criteria

                          • Proxy emits hourly metrics to D1
                          • DAU calculation uses hashed token fingerprints; raw tokens never persisted
                          • Client-side metrics endpoint works and writes aggregate-only data
                          • Weekly report script generates markdown covering usage, perf, cost, quality, content gaps
                          • No per-user tracking anywhere in the pipeline
                          • README covers weekly ops + privacy posture
                          • Privacy assertion test: search the proxy code for any path that stores raw user data → zero hits

                          Out of scope

                          • Real-time dashboards → weekly cadence for v1
                          • A/B testing infrastructure → future consideration
                          • Per-user metrics → intentionally excluded on privacy grounds

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                              Skip to content

                              ai-partner: aggregate privacy-safe analytics #1469

                              Description

                              @CraigBuckmaster

                              Parent epic:#1446 (Amicus — AI Study Partner v1)
                              Phase: 5 · Size: S · Depends on:#1450 (proxy), #1471 (gap capture), #1468 (audit)

                              Aggregate-only usage metrics for Amicus. Zero per-user tracking. Measures feature health, conversion, and content coverage to drive roadmap decisions.


                              Files to create

                              • ai-proxy/src/metrics.ts — emits structured metric events from the proxy
                              • _tools/amicus_analytics/query.py — aggregation queries against D1
                              • _tools/amicus_analytics/weekly_report.py — generates the weekly aggregate dashboard
                              • _tools/amicus_analytics/README.md

                              Files to modify

                              • ai-proxy/src/index.ts — call metric emitters on each request lifecycle event
                              • app/src/services/amicus/chat.ts — optional: send non-identifying client-side metrics (retrieval latency, etc.) via a dedicated /ai/metrics endpoint

                              What gets measured

                              Proxy-side (no per-user identification):

                              Per-request metrics (aggregated by hour bucket):

                              • Total requests (/ai/chat)
                              • Success rate (200 status)
                              • Rate-limit hits (429)
                              • Auth failures (401, 402)
                              • Model tier split (haiku vs sonnet %)
                              • Mean + p50 + p95 + p99 latency
                              • Mean + p95 token counts (input, output)
                              • Gap signal rate (% of responses with gap: true)

                              Per-day metrics:

                              • Daily active premium users (count of unique auth tokens with successful request; count only — never store token)
                              • Daily active Partner+ users
                              • Total LLM cost (derived from token counts + model prices)
                              • Upgrade conversion events (Premium → Partner+, captured via separate RevenueCat webhook, joined with Amicus usage)

                              Client-side (anonymous, batched):

                              Optional additional metrics via POST /ai/metrics endpoint:

                              • Retrieval latency p95
                              • Peek open rate (# peeks / # FAB visible)
                              • Home card tap rate
                              • Mini-conversation turn distribution (1/2/3+ turns before dismiss)
                              • Citation tap-through rate

                              Each payload is anonymous — no user ID, no query content, no response content. Just numbers + categorical tags.

                              What we do NOT track:

                              • Individual user behavior over time (no user profiles beyond auth)
                              • Query text or response text
                              • Any content that could reidentify a user
                              • Device fingerprints
                              • Location beyond coarse country (if Cloudflare headers naturally provide it; do not enrich)

                              D1 schema

                              Minimal — most metrics live in Cloudflare's built-in analytics. D1 only holds what we need for custom queries:

                              CREATETABLEamicus_hourly_metrics (
                              hour_bucket TEXTPRIMARY KEY, -- 'YYYY-MM-DDTHH' UTC
                              total_requests INTEGER,
                              success_count INTEGER,
                              rate_limit_count INTEGER,
                              auth_fail_count INTEGER,
                              haiku_count INTEGER,
                              sonnet_count INTEGER,
                              p50_latency_ms REAL,
                              p95_latency_ms REAL,
                              mean_input_tokens REAL,
                              mean_output_tokens REAL,
                              gap_signal_count INTEGER
                              );
                              CREATETABLEamicus_daily_users (
                              dateTEXTPRIMARY KEY,
                              dau_premium INTEGER,
                              dau_partner_plus INTEGER,
                              hashed_token_fingerprints TEXT-- bloom filter or similar; NOT raw tokens
                              );

                              The DAU calculation uses a hashed token fingerprint (SHA256 of auth token, truncated to 12 chars). We never store the raw token. The hash is per-day — expires nightly — so cross-day correlation is impossible even server-side.

                              Weekly report

                              python _tools/amicus_analytics/weekly_report.py
                              

                              Produces markdown report:

                              # Amicus Weekly Report — Week of 2026-06-01
                              ## Usage
                              - Total requests: 32,482
                              - Daily active premium users (peak): 3,247 (+8% vs prior week)
                              - Daily active Partner+ users: 89 (+12% vs prior week)
                              ## Performance
                              - p95 latency: 1,724ms (target: <2000ms ✓)
                              - Success rate: 98.7%
                              - Rate-limit hits: 412 (1.3% of requests)
                              ## Cost
                              - Total LLM cost: $3,421 (Haiku: $487, Sonnet: $2,934)
                              - Cost per premium user: $0.31
                              ## Quality signals
                              - Gap signal rate: 3.9% (target: <5% ✓)
                              - Classifier needs-review rate: 3.2% (from #1468)
                              ## Content gaps
                              - Top 5 unresolved corpus-gap clusters:
                              1. Orthodox perspectives on Romans 9 (47 hits)
                              2. Septuagint translation decisions in Isaiah 7:14 (31 hits)
                              ...
                              

                              Report is a markdown file suitable for pasting into a GitHub discussion or commit message.


                              Acceptance criteria

                              • Proxy emits hourly metrics to D1
                              • DAU calculation uses hashed token fingerprints; raw tokens never persisted
                              • Client-side metrics endpoint works and writes aggregate-only data
                              • Weekly report script generates markdown covering usage, perf, cost, quality, content gaps
                              • No per-user tracking anywhere in the pipeline
                              • README covers weekly ops + privacy posture
                              • Privacy assertion test: search the proxy code for any path that stores raw user data → zero hits

                              Out of scope

                              • Real-time dashboards → weekly cadence for v1
                              • A/B testing infrastructure → future consideration
                              • Per-user metrics → intentionally excluded on privacy grounds

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions