Metadata-Version: 2.4
Name: hub-equity
Version: 1.0.0
Summary: Hub-Equity Python SDK: programmatic access to XBRL / iXBRL financial data (SEC EDGAR + ESEF Europe) via the public REST API.
Author-email: Hub-Equity <support@hub-equity.com>
License-Expression: Apache-2.0
Project-URL: Homepage, https://hub-equity.com
Project-URL: Documentation, https://docs.hub-equity.com
Project-URL: Repository, https://github.com/GLaine1/Hub-Equity_MVP
Project-URL: Issues, https://github.com/GLaine1/Hub-Equity_MVP/issues
Keywords: xbrl,ixbrl,financial-data,sec-edgar,esef,ifrs,us-gaap
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Office/Business :: Financial
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Requires-Dist: httpx>=0.25
Requires-Dist: pydantic>=2
Provides-Extra: pandas
Requires-Dist: pandas>=2.0; extra == "pandas"
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: pytest-cov>=4; extra == "dev"
Requires-Dist: respx>=0.21; extra == "dev"
Requires-Dist: ruff>=0.1.0; extra == "dev"
Dynamic: license-file

# hub-equity: Python SDK

Typed Python client for the **Hub-Equity** financial-data API: machine-readable
**XBRL / iXBRL** from primary filings, **SEC EDGAR** (US) and **ESEF** (Europe),
normalized into canonical financial concepts, with the **provenance preserved**.

What sets the data apart, and what this SDK exposes:

- **Source-linked.** Every value carries the filing it came from (`form_type`,
  `filing_date`, a viewer URL). You can trace any number back to the filer's
  document, not a black box that silently rewrites as-filed history.
- **Point-in-time.** Query a metric *as it was known on a date* (`get_metric_as_of`):
  filings filed after the cutoff are excluded. No lookahead bias for backtests.
- **XBRL-native.** Calculation linkbase (with filer weights *and* verifier-corrected
  weights), dimensional structure, and the raw filer-declared facts, not just
  flattened labels.
- **Restatement-aware.** Per-concept deltas across 10-K/A amendments.
- **Cross-issuer, calendar-aligned.** `compare` matches periods by calendar year
  (Bloomberg / FactSet convention), so Apple (Sep FYE) and a Dec-FYE peer line up.

The SDK is a thin, typed wrapper over the public REST API: it ships **no data and
no secrets**; you bring your own API key. Full API reference: <https://docs.hub-equity.com>.

> **Maturity.** The data engine and public REST API behind this SDK run in
> production and power Hub-Equity's own chat. This PyPI package is a newly
> published client for that API; the surface is stable (SemVer 1.0), but as a
> distributed package it is fresh, hence the Beta classifier.

## Install

```bash
pip install hub-equity
# Optional pandas helpers:
pip install 'hub-equity[pandas]'
```

Python 3.10+. Runtime deps: `httpx`, `pydantic`.

## Authentication

```python
from hub_equity import HubEquity

# Explicit key
client = HubEquity(api_key="hubq_live_...")

# Or set HUBEQUITY_API_KEY env var (recommended for notebooks)
client = HubEquity()

# Or no key: anonymous freemium access (lower rate limits, premium endpoints gated)
client = HubEquity(api_key="")
```

Get a key at <https://hub-equity.com/settings/api-keys> (shown once, store it securely).
Set `HUBEQUITY_API_URL` to point the client at a non-default host (defaults to
`https://api.hub-equity.com`).

**Entity arguments accept a ticker (e.g. `"AAPL"`) or a Hub-Equity UUID** on every
`entities/{id}/...` method. The one exception is `compare`, which takes UUIDs only
(resolve a ticker via the entity detail endpoint or the web app first).

## Quickstart

```python
from hub_equity import HubEquity

with HubEquity() as client:
    rev = client.get_metric_history("AAPL", "REVENUE", years=5)
    print(f"{rev.entity_name}: {rev.hub_label} (CAGR {rev.cagr_pct:.1f}%)")
    for p in rev.points:
        print(f"  FY{p.fiscal_year}: {p.value:,.0f} {p.currency}"
              f"  ({p.yoy_growth_pct:+.1f}% YoY)  ← {p.source.form_type} {p.source.filing_date}")
        # p.source.viewer_url / p.source.external_url -> trace back to the filing
```

## Methods

| Area | Method | Returns |
|---|---|---|
| **Time-series** | `get_metric_history(entity, code, *, years=5, period_type='FY')` | `MetricHistory`: values + YoY + CAGR |
| **Point-in-time** | `get_metric_as_of(entity, code, as_of, *, years=5, period_type='FY')` | `AsOfResponse`: series as known on a date |
| **Compare** | `compare(entity_ids, hub_concept_codes, fiscal_year, fiscal_period_type='FY')` | `CompareEntitiesResult`: N×M matrix |
| **Calc linkbase** | `get_calculation_sections(filing_id)` | `CalculationSectionsResponse`: calc roles |
| | `get_calculation_tree(filing_id, link_role)` | `CalculationTree`: edges + weights |
| **Dimensions** | `get_entity_dimensions(entity)` | `EntityDimensionsResponse`: axes + members |
| **Raw facts** | `get_filing_facts(filing_id, *, section, concept, is_extension, limit, offset)` | `RawFactsResponse` |
| **Restatements** | `get_restatement_diff(entity, *, fiscal_year, hub_concept_code, min_diff_pct=0.01, kind)` | `AmendmentDiffResult` |
| **Quality** | `get_filing_quality_grade(filing_id)` | `QualityGradeResponse`: A-F + 4 drivers |
| **Concepts** | `search_concepts(q, *, taxonomy, category, limit, offset, cursor)` | `ConceptSearchResponse` |
| | `iter_concepts(q, *, taxonomy, category, page_size=100)` | generator of `ConceptSummary` |
| | `get_concept(hub_concept_code)` | `ConceptDetail`: forward catalog entry (label, description, category, taxonomy scope) |

Every method returns a typed `pydantic` model (autocomplete + validation), with
`extra="allow"` so a newer API never breaks an older SDK.

### Point-in-time: no lookahead bias

```python
# What did Apple's FY2022 revenue look like as known on 2023-01-15,
# before any later 10-K/A restated it?
snap = client.get_metric_as_of("AAPL", "REVENUE", as_of="2023-01-15", years=3)
for p in snap.points:
    print(f"FY{p.fiscal_year}: {p.value:,.0f} {p.currency}"
          f"  (from {p.form_type} filed {p.filing_date})")
```

### Compare issuers (calendar-year aligned, premium)

```python
# compare takes UUIDs; resolve tickers first (entity detail endpoint / web app).
matrix = client.compare(
    entity_ids=["<apple-uuid>", "<msft-uuid>"],
    hub_concept_codes=["REVENUE", "NET_INCOME"],
    fiscal_year=2024,
)
for row in matrix.rows:
    cell = row.cells.get("REVENUE")
    if cell:
        print(f"{row.name} ({row.ticker}): {cell.value:,.0f} {cell.currency}"
              f"  [{cell.extraction_method}]")
```

### Audit a number to its filing

```python
# Filing-level quality pre-screen
grade = client.get_filing_quality_grade("<filing-uuid>")
print(grade.letter_grade, grade.overall_score)
for d in grade.drivers:
    print(f"  {d.name}: {d.score:.2f} (w={d.weight})")

# then drill to the raw filer-declared facts (qname, unit, decimals, dimensions).
facts = client.get_filing_facts("<filing-uuid>", concept="Revenue", limit=50)
for f in facts.facts:
    print(f.concept_qname, f.value_numeric, f.unit, f.dimensions)
```

### pandas

```python
import pandas as pd

hist = client.get_metric_history("MSFT", "REVENUE", years=10)
df = pd.DataFrame([p.model_dump() for p in hist.points])
df["source_form"] = [p.source.form_type for p in hist.points]
```

## Reliability

- Retries `429` (rate limit) + `5xx` (server) with exponential backoff
  (3 retries, base 1 s, cap 10 s); honors `Retry-After` when present.
- After exhausting retries on `429`, raises `RateLimitError` (subclass of
  `HubEquityError`) with the last `retry_after` attached.
- Other 4xx raise `HubEquityError` immediately (no retries), with `.status_code`.

```python
from hub_equity import HubEquity, HubEquityError, RateLimitError

with HubEquity() as client:
    try:
        rev = client.get_metric_history("AAPL", "REVENUE")
    except RateLimitError as exc:
        print(f"Rate-limited, retry after {exc.retry_after}s")
    except HubEquityError as exc:
        print(f"{exc.status_code}: {exc}")
```

## Rate limits

| Plan | Per minute | Per day |
|---|---|---|
| Free / anon | 60 | 5 000 |
| Pro | 300 | 100 000 |
| Enterprise | 1 000 | 500 000 |

Bucketed per API key (per JWT user, or per IP for anonymous callers). Premium
endpoints (`compare`, point-in-time) require a Pro/Enterprise key.

## Versioning

Semantic versioning. The `1.x` surface is stable: no breaking change to an
existing method or model without a major bump. Pin a version in production
(`hub-equity==1.0.0`). Response models use `extra="allow"`, so additive API
changes ship in minor releases without breaking older SDK installs.

## License

Apache-2.0. See [LICENSE](LICENSE).
