# Equity/ETF DOCX Instrument Resolver + Review Workflow

Use this when PostFinance/True Wealth/Raiffeisen portfolio documents are too incomplete or ambiguous for productive Equity/ETF import.

## Trigger

- Real broker DOCX/PDF/statement dry run finds many rows without ISIN, no cost basis, no transaction history, or only ticker/name/current holdings.
- User has not approved a final productive Equity/ETF import.
- Need to extract candidates first, then let the user review/confirm mappings before any ledger write.

## Hard rules

- Never commit real broker files, DBs, reports, DOCX/PDF/CSV/XLSX, quantities, values, or source rows.
- Report only aggregate counts: candidates, with/without ISIN, with ticker/currency, exact/probable/needs_review/confirmed/blocked, warnings/errors.
- Do not invent ISIN, exchange, provider symbol, cost basis, or transaction history.
- Ticker alone is never import-ready; ticker without exchange/currency remains review-needed.
- Market value from a broker document is provenance/control only, not current valuation. Later valuation must come from a market-data provider/cache.
- Missing purchase history means later productive write is an `initial_position_snapshot`, not fake buys.
- Missing cost basis must set `cost_basis_uncertain`.
- Every final review decision and every productive import needs an audit log.
- Productive import is blocked until the user explicitly approves it after review.

## Recommended implementation pattern

1. Add/use a runtime staging table such as `instrument_import_candidates` with:
   - source file label, platform/account, source row ref
   - raw name/ticker/ISIN/currency/quantity/asset class
   - extraction source (`deterministic_parser`, `qwen_local`, `manual`)
   - proposed name/ticker/ISIN/exchange/currency/asset class/provider symbol
   - `mapping_status`: `exact_isin_match`, `probable`, `needs_manual_review`, `rejected`, `confirmed`
   - review note and timestamps
2. Deterministic parser first:
   - parse DOCX text/tables structurally
   - identify explicit ISINs when present
   - extract ticker/name/currency/quantity only into staging
   - classify missing-ISIN rows as `needs_manual_review`
3. Optional local Qwen/llama.cpp may assist extraction/review only:
   - default local OpenAI-compatible endpoint observed for this environment: `http://100.101.173.25:11435/v1`, model `qwen-3.6-agent`; verify with `/v1/models` and a synthetic `/chat/completions` prompt before sending any finance-derived text.
   - allowed: summarize table/text blocks, normalize product names, identify fields that are already present in the source, classify candidates as `exact_isin_match` / `probable` / `needs_manual_review`, and draft review notes.
   - forbidden: final mapping choice, guessed ISINs, guessed cost basis, guessed transaction history, quantities/values interpretation, ledger writes, final valuation/performance, or automatic `confirmed` status.
   - sanitize before Qwen: send only candidate ID/source row ID, platform/source, product name, ticker, source ISIN if present, currency, asset class, exchange if present, and current review status. Never send quantities, depot/account values, cost basis, P&L, account/customer numbers, full broker documents, reports, or screenshots.
   - Qwen output must be validated and staged, never written directly to ledger tables. If Qwen proposes an ISIN that was not present in source or deterministically verified, discard the proposed ISIN and keep the candidate review-needed.
   - For larger candidate sets, batch Qwen review conservatively (small batches such as 5 items) with explicit timeout handling; if a full-batch call times out, retry smaller batches rather than concluding the model is unavailable.
4. Resolver priority:
   - ISIN if present
   - ISIN + name
   - ticker + exchange + currency
   - name search with candidate list
   - manual confirmation
5. Dashboard review queue:
   - split User/Admin review into at least two groups:
     - **Sichere ISIN-Treffer**: `exact_isin_match`, show name/ISIN/ticker/currency/source/resolver status, no quantities/values, actions `Bestätigen` and `Ablehnen`; confirmation only sets `confirmed` and writes audit.
     - **Prüfung nötig**: `needs_manual_review`/`probable`, show name/ticker/currency/possible candidates/reason, fields to add/correct ISIN, ticker, exchange, currency, note, and actions `Als bestätigt speichern`, `Zurückstellen`, `Ablehnen`.
   - hide real quantities/values or show only presence flags such as `<present>`
   - actions: confirm, reject, manual search, manual ISIN, ticker/exchange/currency correction, later-review
   - confirm/reject/defer requires or strongly encourages a note and always writes an audit log
   - confirmation from the review queue must not trigger a productive import; final initial snapshot import remains a separate explicitly approved operation
6. Productive import command/function:
   - accepts only `confirmed` candidates
   - writes/updates instrument safely without silent overwrite
   - writes one `initial_position_snapshot` with Decimal/Text quantity
   - sets `cost_basis_uncertain` when needed
   - writes audit log
   - rejects unconfirmed candidates

## Tests to add

- Qwen/local LLM extraction creates staging candidates only; no ledger/position writes.
- Qwen availability probe covers `/v1/models` and synthetic `/chat/completions`; report exact model name without secrets.
- Qwen resolver sends only sanitized candidate metadata; tests should assert quantities/raw quantity fields are absent from the request body.
- Qwen resolver rejects invented/changed ISINs and keeps unverified mappings review-needed or at most `probable`, never `confirmed`.
- Qwen resolver batch/timeouts are handled safely; failure reports aggregate error and does not mutate productive tables.
- Deterministic parser creates candidates.
- Candidate without ISIN stays `needs_manual_review`.
- Candidate with source ISIN can become `exact_isin_match`.
- Ticker alone cannot become import-ready automatically.
- Manual confirmation sets `confirmed` and writes audit.
- Unconfirmed candidate cannot import productively.
- Confirmed candidate can create an `initial_position_snapshot` only through explicit import path.
- Missing cost basis sets `cost_basis_uncertain`.
- Broker files and generated runtime data remain outside Git; Git-safety passes after cache cleanup.

## Operational notes from FinanceManager session

- Verify Qwen with a synthetic prompt before use. In this environment `/v1/models` returned `qwen-3.6-agent` and `/chat/completions` worked; record only endpoint/model/status in reports, never secrets or finance payloads.
- A local Qwen call over all staged candidates may time out even though the endpoint is healthy. The durable pattern is a small-batch sanitized resolver (about 5 candidates per call), aggregate-only output, and no mutation when a batch fails.
- If local Qwen is unavailable, the safe result is deterministic parsing only with aggregate `qwen_available=false`; do not encode this as a durable tool failure.
- Running pytest may recreate `.pytest_cache`; remove it before `git-safety-scan`.
- If normal `git push` via HTTPS cannot prompt, use the repo-specific token workflow reference rather than printing tokens or embedding credentials in remotes.
