Metadata-Version: 2.4
Name: raidiantai-analytics
Version: 0.1.0
Summary: Send a tool's usage to Raidiant AI from a Python server, with a tool key.
Author: Raidiant LLC
License-Expression: MIT
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Operating System :: OS Independent
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# raidiantai-analytics

Send your tool's usage to Basecamp from a Python server or script, with a tool key.

**Python 3.10 or later. Standard library only; no runtime dependencies. Server
only:** it sends with a tool key, which is a server-side secret. Versions before
1.0 may change; see the changelog.

## Install

```bash
pip install raidiantai-analytics
```

## Quickstart

You need a tool key and your company's Basecamp address (for example
`https://<your-basecamp-address>`). This reads them from the environment as
`BASECAMP_TOOL_KEY` and `BASECAMP_URL`. Use a test key while you build: it is
checked and answered like a live key, and nothing is counted.

```python
import os

from raidiantai_analytics import Analytics, CheckKeyOk, Diagnostic, email_identity


def show(d: Diagnostic) -> None:
    print(f"analytics: {d.code}" + (f" ({d.reason})" if d.reason else ""))


# One client per process. It never raises: a missing or malformed option
# makes it send nothing and is reported through on_diagnostic.
analytics = Analytics(
    key=os.environ.get("BASECAMP_TOOL_KEY"),
    base_url=os.environ.get("BASECAMP_URL"),
    # Without this, most problems would only be counted, not shown.
    on_diagnostic=show,
)

# Check the key first, so a wrong, stopped or switched-off key shows up now
# instead of as usage that never arrives.
check = analytics.check_key()
if not isinstance(check, CheckKeyOk):
    print(f"The key check failed: {check.code}")
elif check.status != "active" or not check.integrations_enabled:
    print("The key is stopped, or integrations are off for your company.")
else:
    print(f"Sending for {check.tool_name} with a {check.mode} key.")

# Once per meaningful completion of your tool's main job, for the person it was for.
identity = email_identity("alice@example.com")
if identity.ok:
    analytics.for_actor(identity.actor).capture_use()

# Send what is queued. `remaining` is how many events are still waiting.
result = analytics.flush()
print(f"{result.remaining} events still queued.")

# This script is about to exit, so shut the client down.
analytics.shutdown()
```

- Call `capture_use()` once per meaningful completion of your tool's main job.
  Use `capture_custom(name, properties=...)` for anything else; it never counts.
- Pass the `event_id` of your own work (`capture_use(event_id=...)`) when it can
  repeat: a repeat with the same ID never counts twice.
- Pass an email your application has verified.

## Sending and shutting down

Events are sent by a background thread, in batches. The thread never keeps the
interpreter alive.

- `analytics.flush()` sends what is queued and returns a result whose
  `remaining` is how many events are still queued. `0` means the queue is empty,
  not that everything arrived (refused or given-up events leave it too; see
  `get_stats()`). Otherwise it is the number still queued when its deadline passed (10 seconds by default). Those events are not
  lost; the client keeps trying. In serverless code, call `flush()` before your
  handler returns.
- `analytics.shutdown()` belongs **only where the process is about to exit**
  (or use the client as a context manager, `with Analytics(...) as analytics:`).
  It stops accepting events, sends what it can for up to 5 seconds, then
  discards the rest; its `remaining` is how many it discarded. After it, every
  capture is dropped. The package registers no `atexit` handler of its own.

The queue is in memory, so a restart or crash loses what was still queued.

## Keep the key secret

- The key is a **server-side** secret. Read it from the server's environment or
  a secret store; never ship it inside an app your users run.
- **Never commit** it to a repository.
- If it may have been exposed, **rotate** it at once: cancel it in Basecamp and
  make a new one.

## Your key and the full guide

Both need a Basecamp sign-in:

- **Your tool key:** your tool's page in Basecamp, on its **Integration** tab.
- **The full guide:** `https://<your-basecamp-address>/docs/developers/sdk-python`,
  covering what counts as a use, what can be lost and when, privacy, property
  values, pausing, the network and every option. For help, start there.

## Licence

MIT. Copyright (c) 2026 Raidiant LLC. See `LICENSE`.
