# Reviewed opportunity shortlist

The third Phase 5 milestone adds **Opportunities** on staff web, iOS and Android. Staff select a live customer wanted record, inspect ranked supply with reasons and differences, open the original customer/supply records, and explicitly review a candidate before preparing an unsaved quotation draft. It uses the existing deterministic matching and inventory rules. No model or external AI API is needed for ranking.

## Ranking and evidence

The request list includes active, unexpired demand belonging to active customer companies, with uncommitted quantity remaining. It orders requests by earliest expiry, with records without an expiry last. This is not a global ranking of customers, sales potential or profit.

For a selected request, candidates must share normalized brand/model and be active, unexpired owned stock or supplier offers from an active supplier company, with positive available/reported quantity. The existing matching comparison supplies exact/near classification, matching reasons, every specification/quantity/price uncertainty and decimal budget headroom. Tracked stock availability excludes held, allocated, quarantined, retired and in-transit devices; active holds and allocations reduce remaining wanted demand. Expired holds no longer reduce availability.

Policy `recorded-fit-v1` sorts the complete filtered candidate selection, in this order:

1. Exact recorded matches before near matches.
2. Fewer recorded differences/uncertainties.
3. Registered owned-stock availability before reported owned stock before supplier-reported offers.
4. Greater quantity coverage, capped at the remaining wanted quantity.
5. Stable supply record ID to break ties.

The UI displays these rules, each candidate's evidence, the number of devices it could cover, and the caveats. This is a transparent workflow ordering, not a probability of success. A cheaper source does not override specification differences or stronger availability evidence. Different currencies are never ranked by converted prices. Budget headroom is buyer budget minus asking price per device in the same currency, using decimal arithmetic; it is not acquisition cost, margin or profit. Missing budgets and different currencies show no comparable headroom. Carrier compatibility, legacy catalogue data and all other existing matching uncertainties remain visible.

Ranks are local to the selected request and source/search filters. Ranking occurs before pagination, so a page is not independently sorted. At most 250 candidates are evaluated per selection. Larger selections produce an explicit error instructing staff to narrow source type or supplier/grade/region/color search; results are never silently truncated or presented as the complete top matches. Query text treats SQL wildcard characters literally. The original Trading → Find matches remains available with its existing newest-updated ordering.

## Review and quotation preparation

All active staff roles can inspect rankings and read the original trading records. Only administrators/traders can prepare a quotation. Each candidate has a fresh review key derived from its complete wanted/supply record views, customer/supplier versions, current staff ID and ranking policy. The key is a change-detection fingerprint, not a permission token or commercial approval.

Staff must explicitly confirm that they reviewed matching differences and availability cautions. `POST /v1/opportunities/review` re-reads current records and quantities, checks active company/source/demand eligibility and compares the fingerprint. A changed version, company, specification or quantity requires a fresh ranking and review. Refresh, paging, filters, source inspection or a failed action clears the UI confirmation. Viewer attempts are rejected by the server.

Preparation performs no database writes, saves no quote and reserves no stock. It opens the existing quotation editor with the current customer/version, selected source/version, quantity equal to the smaller of remaining demand and supply, source currency, and validity limited to one day or the earlier source/wanted expiry. The sale price and terms stay blank; neither the asking price nor the buyer's maximum budget is assumed to be a selling price. Near-match differences remain visible while the initial draft is edited.

Staff can revise these proposed fields under the ordinary quotation workflow. Saving invokes existing server checks for source/customer versions, availability, currency, validity, quantities and exact totals. Closing the seeded unsaved form prompts before discarding it. Draft saving does not submit, approve, export, send or reserve. Separate-administrator approval before export and separate order confirmation/reservation remain unchanged.

This handoff is a starting point for a quotation, not a new contract linking wanted demand to a quote. The saved quote retains its existing customer/source snapshots and version history; it does not persist an opportunity review event or wanted-record link. Wanted demand is revalidated at preparation, not continuously bound to later quote edits or approval. A quote does not commit demand or guarantee source availability. Later order/reservation actions remain authoritative. Persistent opportunity history and quotation attribution are separate extensions.

## API and state

- `GET /v1/opportunities?q=&offset=0&limit=20`: eligible customer requests (maximum page size 50).
- `GET /v1/opportunities/{wanted_id}/ranked?source=all&q=&offset=0&limit=20`: ranking, evidence, total, policy and UTC snapshot time.
- `POST /v1/opportunities/review`: explicit reviewed selection and change fingerprint; returns initial quotation data only.

Authenticated responses are private and `no-store`. Reads are current, not a transactionally frozen cross-record snapshot; availability can change between reads and must be rechecked by subsequent actions. Client queries are keyed by complete filters/page/refresh revision, old replies are ignored, and errors replace stale results instead of displaying false zeros. No polling, outbound transport, background agent, automated action or database migration is added.

## Verification and release boundaries

Tests cover ranking across the complete scope before pagination, exact/near ordering, availability evidence, quantity coverage, missing budgets/FX, active company/status/expiry eligibility, registered device exclusions, exhausted demand, hold expiry, fingerprint changes, explicit review, roles, non-mutating preparation and the visible candidate bound. A shared contract test verifies that quotation handoff preserves versions, source currency and quantity while leaving the selling price blank. Browser checks cover source inspection, draft entry, stale review rejection, filter/refresh reset, errors, phone-width viewer access and the existing quotation approval/revision workflow.

Physical iOS/Android interaction, signing and PostgreSQL runtime/load validation remain pending. Formal invoice issuance stays disabled pending issuer/tax details; live mailbox tests remain deferred and WhatsApp stays uninitialized. No live messages, invoices, deployments or credential changes are part of this milestone.
