Metadata-Version: 2.5
Name: birdlife-ont-core
Version: 0.1.0
Summary: Extract structured data from bird observation notes using LLMs
Author: BirdLife Tools Community
License-Expression: MIT
Keywords: birds,citizen-science,data-extraction,ebird,llm,ornithology
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.12
Requires-Dist: asyncpg>=0.29
Requires-Dist: fastapi>=0.111
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic-settings>=2.0
Requires-Dist: pydantic>=2.0
Requires-Dist: uvicorn>=0.30
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Description-Content-Type: text/markdown

# birdlife-ont-core

Core extraction engine, protocols, and adapters for observation-note-tagger.

## Installation

```bash
pip install birdlife-ont-core
```

> **v0.1.0:** Ships with OllamaAdapter (free, local LLM). Claude/OpenAI adapters coming in v0.2 — or implement your own via the `LLMAdapter` protocol below.

## Protocols (Extension Points)

Build custom adapters by implementing these protocols:

### LLMAdapter

```python
from ont_core.adapters import LLMAdapter
from ont_core.schemas import ExtractionResult


class MyLLMAdapter:
    """Implements LLMAdapter protocol."""

    @property
    def name(self) -> str:
        return "my-llm"

    @property
    def model_version(self) -> str:
        return "v1.0"

    async def extract(self, note: str, species: str) -> ExtractionResult:
        # Your extraction logic
        ...
```

### Parser

```python
from ont_core.parsers import Parser, ParsedObservation
from pathlib import Path
from collections.abc import Iterator


class MyParser:
    """Implements Parser protocol."""

    @property
    def name(self) -> str:
        return "my-format"

    @property
    def file_pattern(self) -> str:
        return "*.myformat"

    def validate_file(self, path: Path) -> bool: ...

    def parse(self, path: Path) -> Iterator[ParsedObservation]: ...

    def count_rows(self, path: Path) -> int: ...
```

### Validator

```python
from ont_core.validators import Validator, ValidationResult
from ont_core.schemas import ExtractionResult


class MyValidator:
    """Implements Validator protocol."""

    def validate(self, result: ExtractionResult, note: str) -> ValidationResult:
        # Your validation logic
        ...
```

## Built-in Adapters

### OllamaAdapter (local LLM)

```python
from ont_core.adapters import OllamaAdapter
from ont_core.config import OllamaConfig

adapter = OllamaAdapter()  # Uses OLLAMA_* env vars

# Or with explicit config
adapter = OllamaAdapter(OllamaConfig(base_url="http://localhost:11434", model="qwen2.5:7b"))

result = await adapter.extract(
    note="Singing male on territory, 2 fledglings nearby", species="European Robin"
)
```

## Factory Pattern

Use the factory for config-driven wiring:

```python
from ont_core.factory import create_llm_adapter, create_extractor

# Adapter selection via ONT_LLM_ADAPTER env var
adapter = create_llm_adapter()

# Full extractor with repositories
extractor = await create_extractor()
```

## Environment Variables

```bash
# Backend selection
ONT_LLM_ADAPTER=ollama     # ollama (v0.1), claude/openai coming in v0.2
ONT_VALIDATOR=grounding    # grounding | composite | none
ONT_REPO_BACKEND=postgres  # postgres | memory

# Ollama
OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_MODEL=qwen2.5:7b
```

## Roadmap

- **v0.1.0** (current): OllamaAdapter, ingest pipeline, extraction engine, validation
- **v0.2.0**: Claude and OpenAI adapters, GraphQL API
- **v0.3.0**: Integration tests, documentation site

## License

MIT
