csv-parser: add _extract_mapping_via_llm with provider-failure wrapping
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
b99f46d2fc
commit
c77b3564f3
2 changed files with 148 additions and 0 deletions
|
|
@ -220,3 +220,73 @@ def test_apply_mapping_skips_blank_and_unparseable_rows():
|
|||
|
||||
pie = _apply_mapping(headers, data_rows, mapping)
|
||||
assert [p.slice for p in pie.positions] == ["AAPL", "NVDA"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_extract_mapping_via_llm_parses_valid_json():
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
from app.services.llm_csv_parser import _extract_mapping_via_llm
|
||||
from app.services.openrouter import LogResult
|
||||
|
||||
fake_result = LogResult(
|
||||
content='{"ticker_col": "Symbol", "qty_col": "Quantity", '
|
||||
'"cost_col": "Avg Price", "currency_col": "Currency", '
|
||||
'"name_col": null, "broker_label": "IBKR Activity Statement"}',
|
||||
model="deepseek/deepseek-v4-flash",
|
||||
prompt_tokens=100,
|
||||
completion_tokens=50,
|
||||
cost_usd=0.0001,
|
||||
)
|
||||
fake_client = MagicMock()
|
||||
fake_call_llm = AsyncMock(return_value=fake_result)
|
||||
|
||||
import app.services.llm_csv_parser as mod
|
||||
mod.call_llm = fake_call_llm # monkeypatch
|
||||
|
||||
headers = ["Symbol", "Quantity", "Avg Price", "Currency"]
|
||||
samples = [["AAPL", "100", "150.25", "USD"]]
|
||||
mapping, log = await _extract_mapping_via_llm(fake_client, headers, samples)
|
||||
|
||||
assert mapping["ticker_col"] == "Symbol"
|
||||
assert mapping["qty_col"] == "Quantity"
|
||||
assert mapping["broker_label"] == "IBKR Activity Statement"
|
||||
assert log.model == "deepseek/deepseek-v4-flash"
|
||||
fake_call_llm.assert_awaited_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_extract_mapping_via_llm_malformed_json_raises():
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
from app.services.llm_csv_parser import LLMParseError, _extract_mapping_via_llm
|
||||
from app.services.openrouter import LogResult
|
||||
|
||||
fake_result = LogResult(
|
||||
content="Sure thing — here is the mapping! ticker=Symbol",
|
||||
model="deepseek/deepseek-v4-flash",
|
||||
prompt_tokens=10,
|
||||
completion_tokens=20,
|
||||
cost_usd=0.00005,
|
||||
)
|
||||
fake_client = MagicMock()
|
||||
fake_call_llm = AsyncMock(return_value=fake_result)
|
||||
|
||||
import app.services.llm_csv_parser as mod
|
||||
mod.call_llm = fake_call_llm
|
||||
|
||||
with pytest.raises(LLMParseError, match="JSON"):
|
||||
await _extract_mapping_via_llm(fake_client, ["Symbol"], [["AAPL"]])
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_extract_mapping_via_llm_provider_failure_wraps():
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
from app.services.llm_csv_parser import LLMParseError, _extract_mapping_via_llm
|
||||
|
||||
fake_client = MagicMock()
|
||||
fake_call_llm = AsyncMock(side_effect=RuntimeError("provider down"))
|
||||
|
||||
import app.services.llm_csv_parser as mod
|
||||
mod.call_llm = fake_call_llm
|
||||
|
||||
with pytest.raises(LLMParseError, match="provider"):
|
||||
await _extract_mapping_via_llm(fake_client, ["Symbol"], [["AAPL"]])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue