feat(fase0): synthetic reference domain (D4) + backend profile skeleton (D2)
Completes Fase 0 (skeleton & decision-lock): - reference_domain.py + data/reference_projects.json: a synthetic "anleggskostnad" portfolio (3 fictional construction-cost projects with cost line items) as the framework's bundled reference input. Plain typed loader (frozen dataclasses); the JSON-Schema data-source *contract* (B5) is deliberately deferred to Fase 2. - backends.py: Profile (azure|local) + ChatBackend Protocol seam + AzureFoundryBackend/LocalBackend stubs + get_backend() selector (fail-fast on unknown profile). Empty skeleton per D2 — create_chat_client raises NotImplementedError until live wiring in Fase 1. Return type is the MAF BaseChatClient (the common base of FoundryChatClient/OpenAIChatClient). Quality gate green: ruff format + check, mypy (src) clean, 12 pytest passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H9FyyENxebxVThjrn9et8C
This commit is contained in:
parent
491a746bba
commit
b57aa83a30
5 changed files with 282 additions and 0 deletions
39
tests/test_backends.py
Normal file
39
tests/test_backends.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
"""Tests for the backend profile skeleton (D2)."""
|
||||
|
||||
import pytest
|
||||
|
||||
from portfolio_optimiser.backends import (
|
||||
AzureFoundryBackend,
|
||||
ChatBackend,
|
||||
LocalBackend,
|
||||
Profile,
|
||||
get_backend,
|
||||
)
|
||||
|
||||
|
||||
def test_get_backend_by_string() -> None:
|
||||
assert isinstance(get_backend("azure"), AzureFoundryBackend)
|
||||
assert isinstance(get_backend("local"), LocalBackend)
|
||||
|
||||
|
||||
def test_get_backend_by_enum() -> None:
|
||||
assert get_backend(Profile.AZURE).profile is Profile.AZURE
|
||||
assert get_backend(Profile.LOCAL).profile is Profile.LOCAL
|
||||
|
||||
|
||||
def test_backends_satisfy_seam() -> None:
|
||||
# Structural conformance to the ChatBackend Protocol (the D2 seam).
|
||||
assert isinstance(get_backend("azure"), ChatBackend)
|
||||
assert isinstance(get_backend("local"), ChatBackend)
|
||||
|
||||
|
||||
def test_unknown_profile_fails_fast() -> None:
|
||||
with pytest.raises(ValueError):
|
||||
get_backend("on-prem")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("profile", ["azure", "local"])
|
||||
def test_create_chat_client_is_skeleton(profile: str) -> None:
|
||||
# Fase 0: the seam is defined but not wired — must fail loudly, not silently.
|
||||
with pytest.raises(NotImplementedError):
|
||||
get_backend(profile).create_chat_client(model="dummy-model")
|
||||
38
tests/test_reference_domain.py
Normal file
38
tests/test_reference_domain.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
"""Tests for the synthetic reference domain (D4)."""
|
||||
|
||||
import pytest
|
||||
|
||||
from portfolio_optimiser.reference_domain import CostItem, Project, load_reference_projects
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def projects() -> tuple[Project, ...]:
|
||||
return load_reference_projects()
|
||||
|
||||
|
||||
def test_loads_non_empty(projects: tuple[Project, ...]) -> None:
|
||||
assert len(projects) >= 1
|
||||
|
||||
|
||||
def test_project_ids_unique(projects: tuple[Project, ...]) -> None:
|
||||
ids = [p.id for p in projects]
|
||||
assert len(ids) == len(set(ids))
|
||||
|
||||
|
||||
def test_every_project_well_formed(projects: tuple[Project, ...]) -> None:
|
||||
for p in projects:
|
||||
assert p.id and p.name and p.description
|
||||
assert p.currency == "NOK"
|
||||
assert len(p.cost_items) >= 1
|
||||
|
||||
|
||||
def test_cost_item_total_is_quantity_times_unit_cost() -> None:
|
||||
item = CostItem(code="X", description="d", quantity=4.0, unit="m2", unit_cost=215.0)
|
||||
assert item.total_cost == pytest.approx(860.0)
|
||||
|
||||
|
||||
def test_project_total_is_sum_of_items(projects: tuple[Project, ...]) -> None:
|
||||
for p in projects:
|
||||
expected = sum(item.total_cost for item in p.cost_items)
|
||||
assert p.total_cost == pytest.approx(expected)
|
||||
assert p.total_cost > 0
|
||||
Loading…
Add table
Add a link
Reference in a new issue