# Performance Guide

How to get the fastest, most efficient results from the alphainfo SDK.

---

## Which endpoint to use

| Scenario | Endpoint | Why |
|----------|----------|-----|
| One signal, full depth | `analyze()` | Default — thorough multiscale analysis |
| One signal, low latency | `analyze(use_multiscale=False)` | ~20x faster, single-scale only |
| Multiple independent signals | `analyze_batch()` | 1 HTTP call for up to 100 signals |
| Compare N signals pairwise | `analyze_matrix()` | N*(N-1)/2 comparisons in 1 call |
| Multi-channel sensor/ECG | `analyze_vector()` | Per-channel + aggregated score, counts as 1 analysis |
| Health/uptime check | `health()` | Lightweight, no auth needed |

### Decision tree

```
How many signals?
├── 1 signal
│   ├── Need fastest response? → analyze(use_multiscale=False)  ~1s
│   └── Need full accuracy?    → analyze()                     ~20-30s
├── 2-100 independent signals
│   └── analyze_batch()                                        ~30-60s total
├── Need pairwise comparison (2-50 signals)?
│   └── analyze_matrix()                                       ~varies
└── Multi-channel (same entity, e.g. 3-lead ECG)?
    └── analyze_vector()                                       ~20-30s
```

---

## Latency expectations

| Mode | Typical latency | Best for |
|------|----------------|----------|
| `use_multiscale=True` (default) | 20-30s | Batch processing, accuracy-critical |
| `use_multiscale=False` | < 1s | Real-time monitoring, screening |
| `analyze_batch()` (100 signals) | 30-60s | Bulk processing |
| `health()` | < 200ms | Monitoring, uptime checks |

**Key insight:** The latency comes from multiscale analysis on the server, not network. Even from the same datacenter, `use_multiscale=True` takes 20-30s because it runs the signal through multiple window sizes.

---

## HTTP/2 for connection efficiency

Install the optional HTTP/2 dependency:

```bash
pip install alphainfo[http2]
```

HTTP/2 is auto-detected — no code changes needed:

```python
# Automatically uses HTTP/2 if h2 is installed
client = AlphaInfo(api_key="ai_xxx")

# Or force it explicitly
client = AlphaInfo(api_key="ai_xxx", http2=True)   # raises if h2 not installed
client = AlphaInfo(api_key="ai_xxx", http2=False)   # force HTTP/1.1
```

**Benefits:**
- Connection multiplexing — multiple concurrent requests over one TCP connection
- Header compression — less overhead per request
- Most impactful with `AsyncAlphaInfo` + concurrent calls

---

## Retry tuning

The SDK retries automatically on transient failures (5xx, timeouts, rate limits). Default config:

```python
client = AlphaInfo(
    api_key="ai_xxx",
    max_retries=3,           # attempts after first failure
    retry_base_delay=1.0,    # first retry after 1s
    retry_max_delay=32.0,    # cap at 32s between retries
)
```

Backoff schedule (defaults): 1s → 2s → 4s → give up.

**For latency-sensitive apps:**

```python
client = AlphaInfo(
    api_key="ai_xxx",
    max_retries=1,            # fail fast
    retry_base_delay=0.5,     # shorter initial delay
    retry_max_delay=2.0,      # tight cap
    timeout=10.0,             # shorter timeout
)
```

**For batch/background processing:**

```python
client = AlphaInfo(
    api_key="ai_xxx",
    max_retries=5,            # more patience
    retry_base_delay=2.0,     # slower ramp
    retry_max_delay=60.0,     # willing to wait
    timeout=120.0,            # long timeout for big signals
)
```

---

## Async concurrency

For the highest throughput, use `AsyncAlphaInfo` with `asyncio.gather()`:

```python
import asyncio
from alphainfo import AsyncAlphaInfo

async def analyze_many(signals):
    async with AsyncAlphaInfo(api_key="ai_xxx") as client:
        tasks = [
            client.analyze(signal=sig, sampling_rate=250.0)
            for sig in signals
        ]
        results = await asyncio.gather(*tasks, return_exceptions=True)

    for i, r in enumerate(results):
        if isinstance(r, Exception):
            print(f"Signal {i} failed: {r}")
        else:
            print(f"Signal {i}: {r.confidence_band}")

asyncio.run(analyze_many(my_signals))
```

**Note:** This sends all requests concurrently. If you have 100+ signals, consider using `analyze_batch()` instead — it's a single request that processes them server-side.

### When to use async vs batch

| Approach | Use when |
|----------|----------|
| `analyze_batch()` | All signals share the same `sampling_rate` and `domain` |
| `asyncio.gather()` + `analyze()` | Signals have different rates/domains, or you need per-signal control |
| Sequential `analyze()` | Simplest code, lowest throughput, fine for < 5 signals |

---

## Client reuse

**Always reuse your client instance.** Creating a new `AlphaInfo()` per request wastes time establishing TCP connections.

```python
# Module-level singleton
_client = None

def get_client():
    global _client
    if _client is None:
        _client = AlphaInfo(api_key=os.environ["ALPHAINFO_API_KEY"])
    return _client
```

Or use dependency injection (FastAPI example):

```python
from functools import lru_cache

@lru_cache
def get_alphainfo():
    return AlphaInfo(api_key=settings.ALPHAINFO_API_KEY)

@app.post("/check-signal")
def check_signal(signal: list[float], client=Depends(get_alphainfo)):
    return client.analyze(signal=signal, sampling_rate=250.0)
```

---

## Signal size optimization

Bigger signals take longer on the server. If you have very long signals (10k+ samples), consider:

1. **Windowing** — Analyze overlapping windows of 500-1000 samples
2. **Downsampling** — Reduce sample rate if original is high-frequency
3. **Fast mode** — `use_multiscale=False` for initial screening

```python
# Window a long signal into 500-sample chunks with 50% overlap
window_size = 500
step = 250
for i in range(0, len(long_signal) - window_size, step):
    window = long_signal[i:i + window_size]
    result = client.analyze(
        signal=window,
        sampling_rate=250.0,
        use_multiscale=False,  # fast screening
    )
    if result.confidence_band != "stable":
        # Deep analysis on interesting windows
        deep = client.analyze(
            signal=window,
            sampling_rate=250.0,
            use_multiscale=True,
        )
```

---

## Quota management

Check remaining quota after each request:

```python
result = client.analyze(signal=data, sampling_rate=250.0)

info = client.rate_limit_info
if info and info.remaining < 10:
    print(f"Warning: only {info.remaining} analyses left this month")
```

**Quota-efficient patterns:**
- `analyze_vector()` with 64 channels counts as **1** analysis
- `analyze_batch()` with 100 signals counts as **100** analyses
- `analyze_matrix()` with N signals counts as **N*(N-1)/2** analyses
- `health()` and `plans()` don't consume quota
