# CLAUDE.md — YUKKA Python Package

## Project Overview

A Python wrapper around YUKKA Lab's three APIs (Quant, Metadata, Data/News) that lets clients pull sentiment and financial data directly into their Python environment. The goal is to replace CSV/Excel-based data sharing in pre-sales, enabling prospects to `pip install` and explore data independently.

This package follows the **abstract API pattern** (see: Thomas Schmelzer, *Death by Data Layer*): strategy/analysis code depends on an abstract interface, not on HTTP calls or specific infrastructure. This makes the package testable, extensible, and robust to backend changes.

## Architecture

```
yukka/
├── client.py          # YukkaAPI (ABC) + YukkaClient (live) + DummyClient (offline)
├── auth.py            # JWT Bearer token management
├── quant.py           # Quant API wrapper
├── data.py            # Data/News API wrapper
├── metadata.py        # Metadata API wrapper
├── entities.py        # EntityResolver — name/ISIN → yukka entity ID
├── models.py          # Response models (dataclasses or Pydantic)
├── exceptions.py      # EntityNotFound, AuthError, RateLimitError, etc.
├── utils.py           # Date parsing, Polars helpers, batching logic
├── universes/         # Pre-validated universe files (Parquet)
│   ├── __init__.py    # Convenience loaders: stoxx600(), sp500(), etc.
│   ├── stoxx600.parquet
│   └── sp500.parquet
└── tests/
    ├── test_client.py
    └── ...
```

### Core Pattern

```python
# Abstract interface — defines WHAT data is available
class YukkaAPI(ABC):
    def prices(self, entities, start, end) -> pl.DataFrame: ...
    def sentiment(self, entities, start, end) -> pl.DataFrame: ...
    def news_volume(self, entities, start, end) -> pl.DataFrame: ...

# Live implementation — wraps the three APIs behind one interface
class YukkaClient(YukkaAPI): ...

# Offline implementation — ships sample data, no auth needed
class DummyClient(YukkaAPI): ...
```

Client code writes against `YukkaAPI`. It never knows or cares whether data comes from live APIs, cached Parquet files, or synthetic test data.

## YUKKA API Reference (Summary)

All APIs are REST. Auth is via JWT Bearer token in the `Authorization` header (except Quant, which requires no auth).

### Quant API

- **Base URL:** `https://quant.api.yukkalab.com`
- **Auth:** None required
- **Provides:** Stock prices, volume, market cap, BullBearScore, Style Investing Signals for 75K+ companies
- **Batch endpoints:** `_itemized` POST endpoints for batch processing (max 100 entities per request)
- **Key data:** Daily OHLCV, market cap, proprietary BullBearScore sentiment indicator

### Metadata API

- **Base URL:** `https://metadata.api.yukkalab.com`
- **Auth:** JWT Bearer
- **Purpose:** Entity resolution — maps ISIN, FIGI, or company name → YUKKA entity ID (`type:alpha_id` format)
- **Also provides:** Related companies, C-level executives, location, industry classification
- **Critical for:** Resolving the entity mapping problem (ambiguous names, missing ISINs). This is the bridge between client-provided identifiers and the YUKKA ontology.

### Data / News API

- **Base URL:** `https://data.api.yukkalab.com/9-3-5/2023-12-13-0`
- **Auth:** JWT Bearer
- **Entity format:** `entities="company:alpha_id"` (type:alpha_id)
- **Provides:** Sentiment scores, news volume, events, Risk scores, ESG scores for 250K+ companies
- **Variants:** Cockpit-release and Quant-release versions exist with different data availability windows
- **Docs:** `https://docs.yukkalab.com`

## Entity Resolution

This is the hardest part of the package and deserves the most attention.

**The problem:** Clients provide company names or ISINs. Many don't exist directly in the YUKKA ontology (historically ~660/948 ISINs missing from cross-referenced sources). Names are ambiguous, carry legal suffixes (GmbH, plc, S.A., etc.), or differ across jurisdictions.

**The EntityResolver should:**

1. Try exact ISIN/FIGI lookup via Metadata API
2. Fall back to fuzzy name matching (rapidfuzz) with legal suffix stripping
3. Return clear errors for unresolved entities — never silently approximate
4. Cache resolved mappings to avoid repeated API calls

**Pre-validated universes** (STOXX 600, S&P 500) ship with the package as Parquet files containing pre-resolved `{isin, figi, name, yukka_id}` mappings. These bypass resolution entirely and give clients a guaranteed-clean starting point.

## Key Design Decisions

- **Polars over Pandas** for all DataFrames — consistent with internal team tooling
- **Batch-aware:** Quant API `_itemized` endpoints cap at 100 entities. The wrapper must chunk transparently.
- **DummyClient ships sample data** so prospects can explore without credentials. Include ~6 months of STOXX 600 sentiment + price data as sample.
- **Entity resolution is explicit, not magical.** If a name can't be resolved, raise `EntityNotFound` with suggestions rather than guessing silently. This avoids the sentiment-misalignment bugs we've seen internally.
- **Universes are versioned.** Index constituents change. Parquet files should include an `as_of_date` field.

## Code Style

- Python 3.11+ required
- Line length: 120 characters
- Docstring convention: Google style

## Development Notes

- Python 3.11+
- Core deps: `polars`, `httpx` (async-ready HTTP), `pydantic`, `rapidfuzz`
- Testing: `pytest` with `DummyClient` — no network calls in unit tests
- The package should be pip-installable and eventually publishable to PyPI

## Business Context

This package supports the client acquisition pipeline improvement proposal. Currently, pre-sales involves manual back-and-forth: client sends a universe → quant team resolves entities → quant team pulls data → data shared via SharePoint/CSV. This package collapses that into: client installs package → explores pre-validated universes or pulls custom data directly → self-qualifies before sales engagement deepens.