P4 · Deterministic Candidate Ranking & Explainability

Technical spec · all specs

Source: docs/specs/P4-candidate-ranking.md
Updated: 2026-06-22

P4 · Deterministic Candidate Ranking & Explainability

1. Purpose

Implement the deterministic ranking system that orders trusted TroveSnap Vision candidates for a specific user, organization, scan type, and ranking intent. The vision model identifies candidates and returns evidence. P3 determines which evidence is trustworthy. P4 converts that trusted evidence, together with versioned application context, into: an eligibility decision; a deterministic priority band; normalized ranking features; an exact final score; a stable total ordering; an explainable contribution breakdown; a reproducible ranking receipt.

The final ordering must never be "because the model ranked it first." TroveSnap must be able to explain which evidence affected the score; which application signals were used; which ranking policy applied; which weights and thresholds applied; why one candidate outranked another; how missing data was handled; which tie-breakers were used; whether a partial validation result affected ordering. The same trusted candidate set, ranking context, policy versions, and engine version must produce the same ordered result.

2. Product Principle

The model discovers possibilities. TroveSnap decides what deserves attention.

Crowded scene
  → trusted candidates
  → deterministic ordering
  → review the most relevant items first
  → isolate selected items
  → spend more inference only where justified

Ranking surfaces the candidates most likely to matter for the active purpose without pretending potential value, demand, or identity is known with certainty.

3. Critical Corrections to the Initial Design

3.1 The model does not own the final feature vector

The initial sketch assumed the model returns opaque components such as:

{ "visual_confidence": 0.8, "text_match": 0.7, "completeness": 0.6, "condition_certainty": 0.5 }

That is insufficient: providers may interpret those concepts differently; scores may not be calibrated across models; text_match is ambiguous; completeness depends on the scan contract; condition certainty is not equally relevant to every candidate; demand cannot be inferred from the image; value potential must not be confused with an estimated price; the same provider may change score behavior over time.

P4 therefore derives ranking features from validated P1 evidence wherever possible. A model may supply primitive evidence values explicitly defined by P1 — identity confidence; watchlist-match confidence; visibility; identity certainty; appraisal state; appraisal reason codes; supporting claim evidence. It may not supply the authoritative final ranking score.

3.2 A weighted sum alone is not the whole ranking system

P4 requires: (1) candidate eligibility; (2) policy resolution; (3) feature extraction; (4) missing-data handling; (5) optional deterministic priority bands; (6) weighted scoring; (7) stable tie-breaking; (8) deterministic explanation; (9) complete provenance.

3.3 Ranking requires more than evidence and weights

Reproducibility requires all of these to remain fixed: trusted result; validation receipt; ranking intent; user/org profile; watchlist versions; demand snapshot; feature-extractor version; ranking-policy version; weight-set version; taxonomy version; engine version; rounding rules; tie-break rules. "Same evidence plus same weights" is not sufficient if feature derivation or context changed.

3.4 Model priority is not TroveSnap priority

If a P1 candidate contains a model-returned priority, P4 treats it only as a provider hint. It must not be surfaced as TroveSnap's final priority. The authoritative outputs are:

ranking:
  priority_band: priority
  final_score: 842
  rank: 2

A future P1 revision should consider renaming provider-returned priority to model_priority_hint (see §38).

4. Scope

4.1 Included in P4

(1) The rankable-candidate contract. (2) Ranking-intent resolution. (3) Ranking-policy resolution. (4) Versioned feature definitions. (5) Deterministic feature extraction. (6) Separation of vision evidence and application signals. (7) Eligibility and exclusion rules. (8) Optional priority-band rules. (9) Versioned weight sets. (10) Missing-feature policies. (11) Integer scoring and rounding. (12) Score caps and thresholds where configured. (13) Stable total-order tie-breaking. (14) Candidate explanation records. (15) Ranking receipts and hashes. (16) Partial-result ranking behavior. (17) Demand-snapshot integration. (18) Personal and organizational ranking profiles. (19) Ranking observability. (20) Golden, property-based, mutation, and replay tests.

4.2 Excluded from P4

P4 does not: invoke vision providers; validate provider output; repair invalid candidates; identify objects; infer buyer demand from an image; aggregate raw demand events; retrieve comparable sales; calculate appraisal values; create offers; apply markdowns; create inventory; merge duplicate candidates; promote candidates into durable items; determine sold/listing state; automatically change production weights from behavioral data; apply hidden manual preferences; generate explanations with another LLM. P4 may consume normalized signals from those systems when explicitly supplied.

5. Initial Rankable Scan Types

rankable_scan_types:
  - table_hunt
  - room_scan

Later uses (appraisal candidate queues; inventory review queues; post-sale disposition candidates; buyer hunt results; manual review queues) must use separately versioned ranking policies rather than reusing scene-ranking weights without evaluation. item_scan, mark_scan, and condition_scan generally produce one subject and do not require candidate ordering in the initial P4 implementation.

6. Ranking Architecture

P3 TrustedScanResult + RankingContext
  ▼
Candidate Eligibility
  ▼
Ranking Policy Resolution
  ▼
Deterministic Feature Extraction
  ├─ validated vision evidence
  ├─ watchlist/profile inputs
  ├─ supplied demand snapshot
  ├─ validation warnings
  └─ scan context
  ▼
Missing-Feature Resolution
  ▼
Priority-Band Rules
  ▼
Fixed-Point Weighted Scoring
  ▼
Stable Tie-Breaking
  ▼
Ranking Result + Explanation + Receipt

7. Primary Interfaces

7.1 Ranking engine

interface CandidateRankingEngine {
  rank(trustedResult: TrustedScanResult, context: RankingContext, policyRef: RankingPolicyRef): RankingResult;
}

The function must be pure with respect to its supplied inputs. It must not read current time implicitly; query a database; fetch demand data; inspect mutable global config; use randomness; invoke an LLM; depend on input array order; call a provider. All required context must be supplied explicitly.

7.2 Ranking context

interface RankingContext {
  tenantId: string;
  rankingIntent: RankingIntent;
  actorProfile?: { id: string; version: number };
  watchlists: WatchlistSnapshotRef[];
  demandSnapshot?: DemandSignalSnapshot;
  saleContext?: { saleId?: string; zoneId?: string; zoneType?: string; location?: string };
  directives?: RankingDirective[];
  requestedAt: string;
}

requestedAt is recorded for provenance but must not affect scoring unless an explicitly versioned time-decay feature receives a frozen reference time.

7.3 Ranking intents

ranking_intent:
  - personal_hunt
  - estate_organizer
  - appraisal_triage
  - buyer_discovery

Initial P4 acceptance requires personal_hunt and estate_organizer.

8. Ranking Policy Resolution

Resolved using scan type + scan version + ranking intent + policy version.

policy_key:
  scan_type: table_hunt
  scan_version: 2.0.0
  ranking_intent: personal_hunt
  policy_version: 1.0.0

One global weight set is not sufficient. A person hunting specific collectibles and an estate-sale organizer deciding what to inspect first have different objectives.

9. Ranking Policy Contract

ranking_policy:
  id: table_hunt.personal_hunt
  version: 1.0.0
  applies_to:
    scan_type: table_hunt
    scan_versions: ["2.x"]
    ranking_intent: personal_hunt
  feature_schema:
    id: scene-candidate-features
    version: 1.0.0
  extractor:
    id: scene-candidate-extractor
    version: 1.0.0
  eligibility_rules:
    - candidate_is_trusted
    - candidate_has_valid_primary_sighting
    - candidate_is_selectable
  priority_band_rules:
    - id: critical_watchlist
      band: critical
      when:
        watchlist_priority: critical
        match_type_in: [exact, brand_model]
    - id: appraisal_priority
      band: priority
      when:
        appraisal_state: priority
  weights:
    id: personal-hunt-default
    version: 1.0.0
  missing_feature_policy:
    id: scene-ranking-missing-v1
  tie_break_policy:
    id: candidate-total-order-v1
  score_scale:
    min: 0
    max: 1000
  priority_thresholds:
    critical: 850
    high: 700
    medium: 450
    low: 0

Published policies are immutable. Changing a rule, weight, extractor, threshold, or tie-break requires a new version.

10. Rankable Candidate Input

P4 consumes only candidates retained by P3.

rankable_candidate:
  candidate_id: cand_7
  source:
    trusted_result_id: result_812
    validation_receipt_id: validation_771
  label: sculptural lounge chair
  category: furniture.chair.lounge
  identity:
    certainty: possible
    confidence: 86
  sightings:
    - image_id: img_room_02
      visibility: clear
      primary: true
      region:
        bbox: [510, 165, 825, 770]
  watchlist_matches: []
  appraisal:
    state: candidate
    reasons:
      - unusual_design
  next_scan:
    type: item_scan
    version: 2.0.0
  validation:
    partial: false
    candidate_warnings: []

P4 must not rank candidates removed by P3; untrusted canonical drafts; candidates without required geometry; candidates that fail policy eligibility; candidates from a rejected or failed P3 outcome. Candidates retained in a trusted partial result may be ranked per the partial-result policy.

11. Ranking Feature Contract

Every ranking feature is normalized to an integer from 0 through 1000.

ranking_feature:
  id: watchlist_relevance
  value: 860
  availability: available
  source:
    type: validated_evidence
    paths:
      - result.payload.candidates[cand_7].watchlist_matches[0]
  extractor:
    rule_id: watchlist-relevance-v1
    rule_version: 1
  explanation_codes:
    - high_priority_watchlist_match
    - visual_pattern_match

11.1 Feature availability

feature_availability:
  - available
  - unavailable
  - not_applicable
  - suppressed_by_policy

A missing feature must not silently become zero unless the ranking policy explicitly defines that behavior.

11.2 Feature sources

feature_source:
  - validated_evidence
  - deterministic_derivation
  - actor_profile
  - supplied_watchlist
  - application_snapshot
  - validation_receipt
  - explicit_directive

11.3 Required initial features

scene_candidate_features:
  - watchlist_relevance
  - demand_signal
  - appraisal_potential
  - evidence_quality
  - visibility

Optional future features: scan_actionability, evidence_gap_cost, regional_relevance, novelty_signal, disposition_fit. New features require a new feature-schema and policy version.

12. Feature Derivation

12.1 Watchlist relevance

How strongly the candidate matches a supplied personal/org interest. Inputs may include validated watchlist match score; match type; watchlist-entry priority; corroborating match clues; exact vs semantic; applicable actor profile.

watchlist_feature_inputs:
  - provider_match_confidence
  - match_type_factor
  - watchlist_entry_priority
  - corroborating_evidence_count

Match-type and priority factors live in configuration, not in provider prompts or scattered code. A candidate with no relevant match receives the policy-defined no-match value. P4 may not invent a watchlist match.

12.2 Demand signal

Externally observed buyer/market interest — not derived from the image model. P4 accepts a normalized, versioned snapshot from the demand subsystem (F).

demand_snapshot:
  id: demand_2026_06_19_1800
  version: 4
  generated_at: 2026-06-19T18:00:00Z
  normalization_policy: regional-demand-v3
  candidate_signals:
    cand_7:
      normalized_score: 720
      source_scope: category_and_region
      confidence: medium
  hash: sha256:...

The snapshot must be tenant/scope-authorized and frozen for the ranking run; absence of demand data is distinct from zero demand; the model's claim that an item appears popular is not demand evidence; demand counts may not be invented by P4.

12.3 Appraisal potential

Whether deeper evidence collection or appraisal appears justified — not an estimated monetary value. Deterministically derived from validated appraisal state + reason codes + identity certainty + visible maker/model/signature/mark + collectible category + high-value variance + possible scarcity + complete-set evidence + unusual design + insufficient identification. State supplies a base; configured reason codes contribute bounded values; duplicates don't stack; unsupported free-form reasons contribute nothing; blocked may cap/lower; insufficient_identification may raise need-for-another-scan while lowering certainty. Never described to users as an appraisal or price.

12.4 Evidence quality

How trustworthy/complete the candidate's supporting evidence is for ranking — derived by TroveSnap, not an opaque provider completeness. Inputs: identity confidence/certainty; number/quality of claims; validated watchlist evidence; clear primary sighting; corroborating sightings; P3 candidate warnings; partial status; evidence conflicts; missing required info. P3 warnings influence this only through explicit versioned mappings — a warning must not silently change ranking.

12.5 Visibility

Whether the candidate is clear enough to review and scan next. Inputs: primary-sighting visibility; primary bbox size; occlusion; blur; distance; substantially-in-image; usable next-scan target. For room candidates the primary sighting controls the base; extra sightings improve evidence quality but should not auto-increase visibility. Visibility ≠ identity confidence (a clear unknown object: visibility: 950, evidence_quality: 500; a recognizable but occluded one: visibility: 350, evidence_quality: 750).

12.6 Optional scan actionability

A future policy may consider whether a valid next scan exists; requested photos are simple to obtain; one follow-up image would resolve the gap; the item is accessible; the candidate is blocked pending manual input. Derived from typed P1 next-scan directives + supplied workflow context, never from provider prose.

13. Features the Model Must Not Supply Authoritatively

prohibited_authoritative_components:
  - final_score
  - final_rank
  - market_demand
  - buyer_interest
  - likely_sale_price
  - organizer_priority
  - personal_priority
  - profit_score
  - listing_priority
  - sold_likelihood
  - disposition_priority

A provider may return image-derived evidence that contributes to these later decisions, but the application owns the resulting score.

14. Missing-Feature Policy

Missing data requires an explicit policy. It must not depend on value ?? 0.

missing_feature_strategy:
  - zero
  - neutral
  - redistribute_weight
  - disqualify
  - use_configured_prior
missing_feature_policy:
  id: scene-ranking-missing-v1
  features:
    watchlist_relevance:
      strategy: zero
    demand_signal:
      strategy: redistribute_weight_when_snapshot_absent
    appraisal_potential:
      strategy: zero
    evidence_quality:
      strategy: disqualify
    visibility:
      strategy: disqualify

14.1 Snapshot-wide vs candidate-specific absence

The policy must distinguish: no demand snapshot for the whole run; a snapshot exists but no signal for one candidate; feature not applicable; feature suppressed by policy. demand_signal: {availability: unavailable, reason: no_demand_snapshot} differs from demand_signal: {availability: available, value: 0, reason: observed_no_demand}.

14.2 Weight redistribution

If a policy redistributes unavailable weight: redistribution applies deterministically; the active denominator is recorded; uses all remaining eligible weighted features; applies consistently across the cohort; candidate-specific missingness must not create an unexplained advantage. The effective weight vector is recorded in the receipt.

15. Eligibility Rules

Eligibility occurs before scoring.

eligibility_rules:
  - trusted_candidate_required
  - valid_primary_sighting_required
  - selectable_candidate_required
  - supported_category_required

Outcomes: eligible | ineligible | eligible_with_limits. An ineligible candidate is not assigned a normal numeric rank; it remains in the audit result with its exclusion reason. P4 must not quietly delete candidates from the record.

16. Priority Bands

Some business rules should not be hidden inside a weighted sum.

priority_band:
  - critical
  - priority
  - standard
  - uncertain
  - excluded
priority_band_rules:
  - id: critical_personal_match
    band: critical
    when:
      watchlist_entry_priority: critical
      watch_match_type_in: [exact, brand_model]
  - id: organizer_appraisal_priority
    band: priority
    when:
      appraisal_state: priority
  - id: uncertain_partial_candidate
    band: uncertain
    when:
      candidate_validation_warning_in: [low_visibility, identity_conflict]

Band precedence is defined in the policy. Candidates are ordered first by band then by numeric score unless the policy explicitly defines otherwise. A critical exact watchlist match should not disappear below generic high-confidence furniture merely because the furniture is easier to see. Band rules are optional and intent-specific.

17. Versioned Weight Sets

Weights live in configuration, immutable after publication, in integer basis points.

weight_set:
  id: personal-hunt-default
  version: 1.0.0
  weights:
    watchlist_relevance: 6000
    demand_signal: 1000
    appraisal_potential: 1500
    evidence_quality: 1000
    visibility: 500
  total: 10000
weight_set:
  id: estate-organizer-default
  version: 1.0.0
  weights:
    watchlist_relevance: 3000
    demand_signal: 2500
    appraisal_potential: 2500
    evidence_quality: 1000
    visibility: 1000
  total: 10000

Personal hunt prioritizes personal watchlist relevance, exact/strong matches, items the individual wants to inspect. Estate organizer emphasizes market/local demand, appraisal potential, organizational priority, items worth isolating for sale prep. These are initial configurations, not permanently universal weights.

18. Fixed-Point Scoring

Floating-point arithmetic must not determine persisted results. All feature values and weights are integers (feature: 0–1000, weight: 0–10000).

weighted_numerator = Σ(feature_value × effective_weight)
active_weight_total = Σ(effective_weight)
base_score = floor( (weighted_numerator + floor(active_weight_total / 2)) / active_weight_total )

The resulting score is an integer 0–1000. Example: features 860/400/750/820/900 at weights 6000/1000/1500/1000/500 → numerator 7,955,000 / 10,000 → final_score: 796. Rounding behavior is part of the policy contract and cannot vary by language or runtime.

19. Score Caps and Policy Rules

score_caps:
  - id: unresolved_identity_cap
    when:
      identity_certainty: unknown
      watchlist_match_absent: true
    max_score: 650
  - id: severe_visibility_cap
    when:
      primary_visibility_in: [blurred, distant]
    max_score: 550

Caps must be deterministic; versioned; explainable; applied after base scoring; included in the receipt. Use sparingly. A policy must not contain hidden undocumented adjustments.

20. Stable Tie-Breaking

P4 must produce a total order.

tie_break_policy:
  id: candidate-total-order-v1
  order:
    - priority_band_precedence_ascending
    - final_score_descending
    - watchlist_relevance_descending
    - appraisal_potential_descending
    - evidence_quality_descending
    - visibility_descending
    - candidate_id_lexicographic_ascending

The final candidate-ID comparison guarantees a stable order. Input array order must never act as an implicit tie-break. Random tie-breaking is prohibited. Provider output order is not a ranking signal.

21. Ranking Result

ranking_result:
  id: ranking_441
  request_id: req_8f29
  source:
    trusted_result_id: result_812
    trusted_result_hash: sha256:...
    validation_receipt_id: validation_771
    validation_receipt_hash: sha256:...
  context:
    ranking_intent: personal_hunt
    actor_profile:
      id: paul_personal
      version: 17
    demand_snapshot:
      id: demand_2026_06_19_1800
      version: 4
      hash: sha256:...
  policy:
    ranking_policy:
      id: table_hunt.personal_hunt
      version: 1.0.0
    feature_schema:
      id: scene-candidate-features
      version: 1.0.0
    feature_extractor:
      id: scene-candidate-extractor
      version: 1.0.0
    weight_set:
      id: personal-hunt-default
      version: 1.0.0
    missing_feature_policy:
      id: scene-ranking-missing-v1
    tie_break_policy:
      id: candidate-total-order-v1
    engine_version: 1.0.0
  candidates:
    - candidate_id: cand_2
      rank: 1
      priority_band: critical
      final_score: 796
      display_priority: high
      components:
        watchlist_relevance: { value: 860, effective_weight: 6000, contribution_numerator: 5160000 }
        demand_signal: { value: 400, effective_weight: 1000, contribution_numerator: 400000 }
        appraisal_potential: { value: 750, effective_weight: 1500, contribution_numerator: 1125000 }
        evidence_quality: { value: 820, effective_weight: 1000, contribution_numerator: 820000 }
        visibility: { value: 900, effective_weight: 500, contribution_numerator: 450000 }
      band_rules:
        - critical_personal_match
      explanation:
        headline_code: critical_watchlist_match
        reason_codes: [high_priority_watchlist_match, collectible_category, clear_primary_sighting]
        top_contributors: [watchlist_relevance, appraisal_potential, evidence_quality]
      limitations: []
  excluded_candidates: []
  ranking_hash: sha256:...

22. Explainability

22.1 Internal explanation

Full detail: feature values; source references; extractor rules; weights; weighted contributions; band rules; caps; missing-data decisions; tie-break decisions; policy versions. Used for debugging, audit, provider comparison, weight review, regression analysis, support tooling.

22.2 User-facing explanation

A concise explanation such as "High-priority watchlist match / Possible collectible with visible maker evidence / Clear enough to scan separately." Generated from deterministic templates + reason codes. Must not expose provider chain-of-thought; hidden prompts; arbitrary model rationale; raw internal scoring formulas (unless an advanced detail view is opened).

22.3 UI exposure decision

Full component breakdown available in internal/advanced views; normal users see the 2–3 strongest reasons + meaningful warnings; the UI may show a score or priority label but must not imply false precision; "82% valuable" is prohibited; a ranking score is a prioritization score, not a probability or appraisal.

23. Partial and Warned Results

P4 may rank candidates from accepted, accepted_with_warnings, partial. It may not rank from retake_required, insufficient_evidence, rejected, failed. For a partial result: only retained trusted candidates are ranked; removed candidates remain absent; the receipt records the source was partial; candidate warnings feed evidence quality only through versioned mappings; the UI receives the source limitation; ranking must not imply the candidate set is complete. P4 does not revalidate or restore candidates removed by P3.

24. Demand Integration Boundary

The demand subsystem (F) owns event collection; dedup; identity resolution; aggregation windows; geographic scope; category-level demand; user-interest counts; conversion; normalization; privacy; confidence. P4 owns accepting a frozen normalized snapshot; verifying its declared version/scope; matching supplied candidate/category signals; applying the policy; recording the snapshot hash. P4 must never directly query mutable demand tables during a ranking function (that would make the result non-reproducible). A new demand snapshot produces a new ranking event; it does not retroactively mutate an older receipt.

25. Explicit Ranking Directives

Manual/application directives must not be disguised as scoring weights.

ranking_directive:
  - pin_candidate
  - suppress_candidate
  - exclude_candidate
  - require_review

Computed score remains unchanged; directive recorded separately; displayed order may reflect a pinning layer; the UI must distinguish computed rank from manually adjusted order; hidden administrator preferences are prohibited. Manual reordering belongs to the review workflow, not the core ranking formula.

26. Ranking Versioning

ranking_versions:
  ranking_policy: 1.0.0
  feature_schema: 1.0.0
  feature_extractor: 1.0.0
  weight_set: 1.0.0
  missing_feature_policy: 1
  priority_band_policy: 1
  tie_break_policy: 1
  explanation_templates: 1
  engine: 1.0.0
  taxonomy: 12

New versions required when feature meaning changes; a feature is added/removed; derivation changes; a weight changes; a band rule changes; a missing-data rule changes; tie-break order changes; rounding changes; thresholds change; explanation reason semantics change. Historical ranking records are never recomputed silently.

27. Canonical Serialization and Hashing

(1) Candidate inputs referenced by stable IDs. (2) Ranking ignores input array order. (3) Feature/component keys serialized in canonical order. (4) Integer math. (5) Dates don't affect scoring unless explicitly supplied to a versioned time feature. (6) No runtime-local number formatting. (7) Canonical JSON serialization before hashing. (8) The result records a deterministic ranking_hash. Two equivalent executions under the same versions produce the same canonical payload and hash. Operational metadata (e.g. execution duration) may be stored outside the hashed payload.

28. Configuration Validation

A ranking policy cannot be published unless all referenced features exist; all weights are nonnegative integers; ≥1 active weight is positive; weight totals match requirements; priority-band precedence is complete; thresholds are monotonic; tie-break fields are valid; missing-feature behavior is defined; extractor versions exist; explanation codes resolve; scan type/version are compatible; caps remain within the score range; no duplicate policy IDs; the policy passes all golden fixtures. Production policies are immutable; editing creates a new version.

29. Learning and Calibration Loop

P4 collects outcome metrics that let humans improve future policies: candidate selected for item scan; dismissed; watchlist match accepted/rejected; promoted to inventory; appraisal requested; appraisal candidate rejected; item sold; manual reorder; pinned; suppressed; organizer review time; top-ranked conversion; rank position at conversion. Analyzed offline; must not automatically mutate live weights.

Observed outcomes → offline analysis → proposed policy/weight-set version → replay against golden + historical sets → human review → explicit publication

No invisible self-modifying production ranker is permitted.

30. Storage and Audit

Persist: (1) ranking input reference; (2) trusted-result hash; (3) validation-receipt hash; (4) ranking context; (5) watchlist/profile versions; (6) demand-snapshot reference + hash; (7) every ranking-policy version; (8) extracted feature vector; (9) missing-feature decisions; (10) priority-band decisions; (11) effective weights; (12) exact scoring numerator + denominator; (13) final score; (14) tie-break path; (15) final rank; (16) excluded candidates + reasons; (17) explanation codes; (18) ranking hash; (19) explicit manual directives separately. (Storage schema lives in P6 — see §38.)

tie_break:
  candidate_ids: [cand_3, cand_8]
  equal_through: [priority_band, final_score, watchlist_relevance, appraisal_potential, evidence_quality, visibility]
  resolved_by:
    field: candidate_id
    winner: cand_3

31. Observability

trovesnap.candidate.rank
  ├─ trovesnap.ranking.policy_resolve
  ├─ trovesnap.ranking.eligibility
  ├─ trovesnap.ranking.feature_extract
  ├─ trovesnap.ranking.missing_resolve
  ├─ trovesnap.ranking.band_assign
  ├─ trovesnap.ranking.score
  ├─ trovesnap.ranking.tie_break
  ├─ trovesnap.ranking.explain
  └─ trovesnap.ranking.persist

Metrics: candidates ranked/excluded per scan; feature availability rate; feature-value + final-score distributions; priority-band distributions; tie rate + tie-break-field usage; partial-result ranking rate; watchlist/demand/appraisal contribution by intent; top-one/top-three selection rate; item-scan + appraisal conversion by rank position; watchlist-match acceptance; manual reorder/pin/suppress rate; policy version usage; ranking latency; ranking-hash mismatch rate; score drift across policy versions. No image content, private watchlist text, or buyer-identifying data in trace attributes.

32. Deterministic Test Plan

P4 requires no live provider calls.

32.1 Golden score tests — fixed candidate sets with exact expected feature values, effective weights, numerators, final scores, bands, ordering, explanation codes, ranking hash; cover both personal_hunt and estate_organizer. 32.2 Input-order invariancerank([A,B,C]) = rank([C,A,B]) (excluding non-deterministic operational metadata). 32.3 Idempotence — same frozen inputs twice → same vectors, scores, ordering, explanations, hash. 32.4 Monotonicity — increasing only a positively-weighted feature must not lower the score; bands tested separately. 32.5 Bounds0 ≤ feature ≤ 1000, 0 ≤ final_score ≤ 1000; property tests over random valid vectors/weights. 32.6 Tie-break — candidates tied through each level resolved by candidate ID; provider order has no effect. 32.7 Missing-feature — no snapshot for run; snapshot without candidate signal; not-applicable; required feature unavailable; redistribution; prior; disqualification; verify exact recorded effective weights. 32.8 Feature-extraction — exact/semantic/no watchlist match; priority levels; appraisal reason mapping; identity uncertainty; partial warning; clear vs occluded; multiple room sightings; unsupported model priority hint; invented demand excluded. 32.9 Priority-band — critical exact match; appraisal-priority; standard; uncertain partial; precedence over score; no matching rule. 32.10 Configuration — reject unknown feature IDs; negative/all-zero weights; missing missing-data policy; invalid scan version; circular/duplicate rules; nonmonotonic thresholds; invalid tie-break fields; unpublished extractor; modified immutable policy. 32.11 Partial-result — only P3-retained candidates ranked; partial limitations preserved; warnings influence score only via registered mappings; rejected candidates cannot reappear; ranking doesn't imply completeness. 32.12 Cross-runtime — same golden input → same canonical output across implementations (integer math, rounding, serialization, sorting, hashing). 32.13 Historical replay — replay recorded trusted results through current/proposed policy or alternate intent; report rank/score/band changes, top-K movement, and the reason for every movement. No provider calls.

33. No Real-API Requirement

P4 is pure once it receives a trusted P3 result, frozen ranking context, versioned policies, and an optional supplied demand snapshot. No live provider calls required. Integration testing uses recorded trusted P3 fixtures. Provider-specific ranking drift is evaluated upstream by replaying different providers' trusted results through the same P4 policy.

34. Open Questions Resolved

34.1 One global weight set or per scan type? Scoped by scan type, scan version, ranking intent. personal_hunt and estate_organizer need different policies. 34.2 Should component breakdown be exposed? Full detail stored + available internally; advanced review may show component values; ordinary UI shows concise reasons; score not presented as value probability. 34.3 Should the model return component scores? Only P1-defined primitive evidence/confidence; P4 derives authoritative features; opaque provider concepts (profit_score, market_demand, final priority) prohibited. 34.4 Where does demand come from? A normalized application snapshot from F or another trusted demand subsystem; P4 doesn't infer/aggregate. 34.5 What happens when demand data is missing? Versioned missing-feature policy; distinguishable from observed zero. 34.6 Does provider order influence ranking? No. 34.7 How are ties resolved? Versioned total-order tie-break ending in stable candidate-ID ordering. 34.8 Does model-returned priority become final priority? No — ignored or advisory; P4 computes authoritative score/band/order. 34.9 Can ranking weights change automatically? No — outcomes inform offline analysis; production changes require an explicitly published version. 34.10 Do manual pins alter the score? No — directives may alter displayed ordering; computed score/rank preserved separately. 34.11 Are historical rankings recalculated when demand changes? No — new context produces a new ranking event; historical receipts immutable.

35. Required Artifacts

(1) CandidateRankingEngine; (2) RankingContext schema; (3) RankingResult schema; (4) RankingReceipt schema; (5) ranking-intent enum; (6) ranking-policy schema; (7) weight-set schema; (8) feature-schema registry; (9) feature-extractor registry; (10) missing-feature policy; (11) eligibility-rule registry; (12) priority-band rule engine; (13) fixed-point scoring; (14) stable tie-break; (15) deterministic explanation generator; (16) canonical ranking serializer; (17) ranking hash; (18) personal-hunt policy; (19) estate-organizer policy; (20) demand-snapshot input contract; (21) partial-result ranking policy; (22) explicit directive contract; (23) OTel spans + metrics; (24) golden feature/ranking fixtures; (25) property-based tests; (26) configuration validation tests; (27) historical replay tooling; (28) policy comparison report; (29) operator + configuration documentation.

36. Acceptance Criteria

P4 is complete when: (1) only P3 trusted results enter the engine; (2) the initial engine supports table_hunt and room_scan; (3) ranking intent is explicit; (4) separate initial policies exist for personal_hunt and estate_organizer; (5) provider output order has no effect; (6) model-returned final score/priority is not authoritative; (7) features deterministically derived from validated evidence + supplied context; (8) demand accepted only from a versioned trusted snapshot; (9) missing demand distinguishable from zero; (10) watchlist relevance uses only supplied/validated watchlists; (11) appraisal potential not represented as an estimated price; (12) evidence quality incorporates P3 warnings only via registered versioned rules; (13) visibility distinct from identity confidence; (14) every feature value integer 0–1000; (15) every published weight nonnegative integer; (16) fixed-point integer arithmetic; (17) rounding documented + tested; (18) policy/extractor/weight/missing-data/tie-break independently versioned; (19) eligibility before scoring; (20) ineligible candidates recorded with reasons; (21) optional priority bands deterministic + versioned; (22) band precedence explicit; (23) missing-feature behavior explicit for every weighted feature; (24) effective redistributed weights recorded; (25) every candidate receives eligibility, feature vector, final score when eligible, band, stable rank, explanation codes, component details; (26) equal scores resolve via stable total-order tie-break; (27) candidate ID is the final tie-break; (28) input array order doesn't affect output; (29) partial trusted results ranked only under the partial-ranking policy; (30) candidates removed by P3 cannot reappear; (31) user-facing explanations deterministic + template-based; (32) score never labeled as value probability; (33) manual pins/suppressions separate from computed score; (34) the result records all relevant profile/watchlist/taxonomy/demand/policy/engine versions; (35) the result references trusted-result + validation-receipt hashes; (36) canonical serialization produces a deterministic ranking hash; (37) identical frozen inputs produce byte-stable payloads; (38) production policies immutable; (39) policy changes require new versions; (40) no live provider calls required for testing; (41) golden score/ordering tests pass exactly; (42) property tests prove bounds, input-order invariance, idempotence, monotonicity; (43) historical replay can compare two policy versions and explain every movement; (44) observability tracks selection/conversion/override/rank-position outcomes without auto-modifying production weights.

37. Definition of Done

P4 is done when TroveSnap can take a trusted table or room candidate set and produce an exact, stable, personalized, auditable order for either a personal hunter or an estate-sale organizer. For every ranked candidate, TroveSnap must answer: Why is this candidate eligible? Which evidence affected its score? Which watchlist/demand context was used? Which policy/weights applied? How was missing data handled? Did a priority-band rule apply? What exact arithmetic produced the score? Why did it outrank the next candidate? Which tie-breaker resolved an equality? Can this be reproduced later? The P4 result is a deterministic, profile-aware candidate prioritization and explanation system that preserves the separation between model evidence; application demand; user/organizational intent; appraisal potential; and final TroveSnap ordering.

38. Cross-Spec Reconciliations

Adopting P4 closes the typed chain P1→P2→P3→P4 (it consumes the P3 branded TrustedScanResult). Seams to resolve before freeze:

  1. P4 → P1 (model_priority_hint). A future P1 revision should rename the candidate priority field to model_priority_hint so a provider hint can't be mistaken for TroveSnap's authoritative priority (§3.4). Recorded in P1 §21.
  2. P4 ↔ F (demand-snapshot contract). P4 defines the input shape it accepts (§12.2, §24); F owns producing the normalized, versioned, hashed demand snapshot. F must build to this contract.
  3. P4 ↔ P6 (storage). Ranking receipts, actor/org profiles, watchlist snapshots, and demand snapshots (§30) are seller-intelligence-layer data in P6 — append-only, version-referenced, hashed; historical receipts immutable.

39. Work-Item Split

This gate splits (both gate P4, owner Paul); P4 is pure/deterministic so it's lighter than P2/P3: