Skip to content

Commit ea5b150

Browse files
committed
Prediction-semantics correctness + Theta_A numerical-robustness audit (pre-expansion)
PART 1 (central/within-theta inconsistency). OLD: when the best-fit theta traded, central=median(best_prices) and within=best[p10,p90]; when it traded zero, central=median(per_theta_median) but within=[p10,p90] of a specific theta whose median != median-of-medians -> the central estimate could fall OUTSIDE its own within-theta interval. FIX: pick a single representative trading near-fit configuration (trading theta whose conditional-on-trade median is closest to the cross-theta median; deterministic tie-break by smallest median) and report BOTH central_structural_estimate and within_theta_negotiation_interval from it, so central lies inside its interval by construction. Added central_estimate_definition + representative_theta_rule metadata. PART 2 conditional-on-trade explicit: price_estimate_condition=conditional_on_trade + permanent statement (price computed conditional on B>=S, not an unconditional expected sale); primary label 'Central structural estimate, conditional on simulated trade'; reconciling counts near_fit=trading+nontrading, price_envelope_theta_count==trading; non-trading thetas never receive synthetic zero prices; honest null preserved when no theta trades. PART 3 trade-share: trade_probability_band/simulated_trade_share_band is the Monte-Carlo B>=S share with trade_share_calibration=not_empirically_calibrated_to_observed_uyap_no_trade_outcomes; never a probability of sale / sale likelihood (zero share != population probability zero). PART 4 numerical robustness: reproducible search-budget stability study (GET /structural/stability; budgets 750/1500/3000; CRN; tolerance/bounds/criterion UNCHANGED) with a documented rule -> near_fit_search_stability=INSUFFICIENT_COVERAGE (|Theta_A| grows with budget). This is the EXPECTED numerical signature of a rank-4/6 underidentified region (large near-flat manifold along ~2 weakly-constrained directions), not a code defect; STABLE is not forced. Improved the reproducible sampler (multi-start Nelder-Mead descent to a per-budget Q_min + iterative local refinement; numerical exploration only). 9 regression tests (parts 1-7,9 + reproducible stability). Frozen: STRUCTURALLY_UNDERIDENTIFIED, rank 4/6, Theta_A/tolerance, SMM moments, 2 UYAP + 2 KAP, TOKI external, uyap_sale_prob excluded, TCMB anchor, Nash. 207 tests passing.
1 parent 9b697b8 commit ea5b150

8 files changed

Lines changed: 410 additions & 69 deletions

File tree

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[![Data refresh](https://github.com/onatozmenn/sold/actions/workflows/kfe-refresh.yml/badge.svg)](https://github.com/onatozmenn/sold/actions/workflows/kfe-refresh.yml)
55
[![Python](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/)
66
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
7-
[![Tests](https://img.shields.io/badge/tests-198%20passing-brightgreen.svg)](tests/)
7+
[![Tests](https://img.shields.io/badge/tests-207%20passing-brightgreen.svg)](tests/)
88
[![Data](https://img.shields.io/badge/data-TCMB%20%C2%B7%20T%C3%9C%C4%B0K-informational.svg)](#data-sources)
99

1010
> Infer the **realized transaction price** of a Turkish home from its **asking** price — a provenance-aware valuation engine.
@@ -301,7 +301,7 @@ tests/ # offline unit / end-to-end tests
301301
## Testing
302302

303303
```bash
304-
pytest -q # 198 tests, fully offline (no network or API key required)
304+
pytest -q # 207 tests, fully offline (no network or API key required)
305305
```
306306

307307
## Methodology & References
@@ -335,6 +335,7 @@ Negotiation-margin figures from Turkish market reporting: İstanbul ≈ 10%, Ank
335335
- [x] **UYAP outcome-taxonomy correction, invalid `uyap_sale_prob` removed, pivot to PARTIAL IDENTIFICATION + identification-aware prediction** — the actual authenticated e-Satış interface exposes four top-level states (`Satıldı`, `Birinci Alıcıya Süre Verildi`, `Malın Satışının Düşmesi`, `İhale Sonucu Girilmemiştir`); **no fifth status was invented** to unlock a sale probability. Only `Satıldı` is a terminal completed sale; settlement-pending / missing-result are **censored** (not `sold=false`), and `Malın Satışının Düşmesi` is reason-dependent (withdrawal/`Satıştan Vazgeçilmesi` is administrative, **not** a market no-trade). Because the public taxonomy cannot separate a comparable negative auction-trade class, **`uyap_sale_prob` was removed from `m_obs`, the simulated moments, and the Jacobian** (documented reason: *public UYAP outcome taxonomy does not currently identify a comparable negative auction trade class*; raw taxonomy + reason preserved for future research, never replaced with a guessed rate). The conditional moments `uyap_win_over_appraisal_mean/sd` are kept, explicitly interpreted as *winning_bid/appraised_value conditional on an observed completed sale*. **Measured effect: removing the degenerate `sale_prob=1.0` moment dropped `m_obs` 5→4 but improved conditioning — condition number `1.7e17 → 61.8` (the spurious ~0 singular value vanished), `rank(J_combined)` stays 4/dim 6.** The final inference gate pivots from forced point identification to **partial identification** `Θ_I = {θ : Q(θ) ≤ Q_min + tol}` (explicit, sensitivity-tested tolerance `tol = max(1e-4, rel·|Q_min|)`, common random numbers, reproducible sampling) via `sold structural partial` — measured `mu_b`/`sigma_b`/`auction_shift` point-like, `mu_s`/`sigma_s`/`eta` **set-identified**, with parameter trade-off correlations. `sold structural value --partial` produces an **identification-aware** closing range that separates *within-θ negotiation uncertainty* from *between-θ identification uncertainty* (`identification_status = PARTIALLY_IDENTIFIED`), never called an observed price or measured ordinary-resale accuracy. θ was **not** shrunk to recover rank; TOKİ stays external; no ML / weak-supervision / SaleProbability / fourth source added. The public-source UYAP no-trade hunt is closed
336336
- [x] **Econometric terminology correction (near-fit set) + input-conflict diagnostic; core frozen** — the near-minimum SMM criterion level set was renamed `admissible_near_fit_set` (`Θ_A`) and is explicitly **not** described as a formally estimated identified set, a confidence region, or any coverage claim: *the set of economically admissible structural parameter vectors whose SMM criterion lies within the documented near-fit tolerance of the best observed-moment fit* (the tolerance `max(1e-4, rel·|Q_min|)` is a documented numerical/sensitivity rule, **not** a sampling-calibrated cutoff). Local point-identification diagnostics are preserved (`Jacobian rank = 4`, `dim(θ) = 6`) and the reported status is **`STRUCTURALLY_UNDERIDENTIFIED`**. The prediction envelope across `Θ_A` is a **near-fit structural parameter uncertainty envelope** / *structural sensitivity range* — separating *within-θ negotiation uncertainty* from *between-θ near-fit parameter uncertainty* — never a confidence interval or a measured-coverage prediction. A `FUTURE_METHODOLOGY_NOTE` records that a formally calibrated confidence region would need an inference procedure whose criterion cutoff accounts for sampling uncertainty. Added an **input-conflict diagnostic**: `ask_to_fair_value_ratio = asking_price / fair_value` is computed and an explicit `input_conflict` warning is emitted when it falls outside documented configurable bounds (default `[0.5, 2.0]`) — the prediction is **never silently clamped or rejected**; the six economic explanations (`possible_input_error`, `geographic_anchor_mismatch`, `property_characteristic_mismatch`, `distressed_or_nonstandard_sale`, `fractional_or_encumbered_interest`, `strategic_underpricing`) are surfaced as **candidate diagnostic categories only**, never auto-assigned without evidence. Frozen: UYAP+KAP dual-mechanism core, TOKİ external benchmark, `uyap_sale_prob` excluded, the two genuine UYAP + two genuine KAP observations; no ML / weak-supervision / SaleProbability / fourth source / new mechanism. **Econometric core frozen; next is productization + dataset expansion.**
337337
- [x] **Final product surface over the frozen structural engine (honest, demo-ready)** — a polished single-page structural valuation UI (tabs **Değerle / Model Evidence / Method**) and a machine-readable API replace the legacy development form. `POST /structural/valuate` returns `methodology=structural_econometrics`, `identification_status=STRUCTURALLY_UNDERIDENTIFIED`, `coverage_claim=null`, `central_structural_estimate`, `within_theta_negotiation_interval`, `between_theta_near_fit_band`, `structural_sensitivity_range`, `ask_to_fair_value_ratio`, `input_conflict` (+ warning), genuine `2/2/5` UYAP/KAP/TOKİ counts, `jacobian_rank`/`parameter_dimension`/`near_fit_parameter_count` — and **never** a `confidence_interval` or `accuracy` field. The result hierarchy shows the central estimate, within-θ vs between-θ uncertainty, and the sensitivity envelope with the visible statement *"This is not a confidence interval and carries no frequentist coverage claim."* `GET /structural/evidence` reports the genuine public evidence honestly (UYAP conditional-on-completed-sale, KAP corporate-negotiated ≠ ordinary-resale ground truth, TOKİ external = 0 SMM moments) and **does not** display test counts as evidence; `GET /structural/method` explains the mechanism (`TCMB anchor → fair value`, `UYAP/KAP → moments`, `SMM → Θ_A`, `trade iff B≥S`, `P=η·B+(1−η)·S`, `simulation across Θ_A → structural sensitivity range`) with `η` **not** measured from KAP and appraised value **not** the auction reserve; the `[0.5, 2.0]` ask/fair conflict bounds are documented as **configurable product diagnostics, not econometric thresholds**. Input-conflict surfaces an explicit warning with the ratio and the six **candidate** explanation categories (never auto-assigned), and the estimate is **never silently clamped or rejected**. One test-backed correctness fix to the prediction summary: the identification-aware envelope no longer collapses to null when only the single best-fit θ trades zero — it is built from the near-fit configurations that actually trade, while still reporting an honest null when **no** admissible θ trades. Frozen: structural core, TOKİ external, `uyap_sale_prob` excluded, 2 UYAP + 2 KAP; no ML / weak-supervision / SaleProbability / fourth source / new mechanism / auth / billing.
338+
- [x] **Prediction-semantics correctness + Θ_A numerical-robustness audit (pre-expansion)** — traced and fixed the central/within-θ inconsistency: previously the central estimate could be a cross-θ median-of-medians while the within-θ interval came from a different single θ, so the central estimate could fall **outside** its own interval. Now a single **representative trading near-fit configuration** is chosen (the trading θ whose conditional-on-trade median is closest to the cross-θ median, deterministic tie-break) and **both** `central_structural_estimate` and `within_theta_negotiation_interval` are reported from it, so the central estimate lies inside its own interval **by construction** (`central_estimate_definition` / `representative_theta_rule` exposed in metadata). Made conditional-on-trade explicit: `price_estimate_condition=conditional_on_trade`, a permanent statement that the price distribution is computed conditional on `B≥S` (not an unconditional expected sale), primary label *"Central structural estimate, conditional on simulated trade"*, and reconciling counts `near_fit_parameter_count = trading_near_fit_parameter_count + nontrading_near_fit_parameter_count`, `price_envelope_theta_count == trading` (non-trading θ never receive synthetic zero prices; honest null preserved when no θ trades). Corrected the trade field: `trade_probability_band`/`simulated_trade_share_band` is the **Monte-Carlo simulated B≥S share** with `trade_share_calibration=not_empirically_calibrated_to_observed_uyap_no_trade_outcomes` — never a probability of sale / sale likelihood (a simulated share of zero is not proof the population trade probability is zero). Added a reproducible **search-budget stability** study (`GET /structural/stability`, budgets 750/1500/3000, common random numbers, unchanged tolerance/bounds/criterion) with a documented rule → **`near_fit_search_stability = INSUFFICIENT_COVERAGE`**: the finite search finds increasingly many near-fit vectors with budget — the expected numerical signature of a rank-4/6 underidentified region (a large near-flat manifold along ~2 weakly-constrained directions), **not** a code defect. The reproducible sampler was improved (multi-start Nelder-Mead descent to a per-budget `Q_min` + iterative local refinement) — numerical exploration only. Frozen: `STRUCTURALLY_UNDERIDENTIFIED`, rank 4/6, Θ_A/tolerance definition, SMM moments, 2 UYAP + 2 KAP, TOKİ external, `uyap_sale_prob` excluded, TCMB anchor, Nash equation. **Prediction semantics frozen; next is genuine UYAP/KAP evidence expansion.**
338339
- [ ] **SaleProbability** model (`P(sold ≤ N days)`) trained on collected outcomes
339340
- [ ] Live, ToS-reviewed fetchers for the public label sources
340341
- [ ] Broker-vs-benchmark analytics over an aggregate anonymized dataset

src/sold/api/app.py

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,18 @@ def structural_method() -> dict:
406406
return method_overview()
407407

408408

409+
@app.get("/structural/stability")
410+
def structural_stability() -> dict:
411+
"""Θ_A arama-bütçesi KARARLILIK tanılaması (sayısal; ekonometrik sınıflandırma DEĞİL).
412+
413+
Artan aday yoğunluklarında parametre bant-payları + duyarlılık zarfını karşılaştırır →
414+
STABLE / SEARCH_SENSITIVE / INSUFFICIENT_COVERAGE. Tolerans/bound/moment değişmez.
415+
"""
416+
from .structural_product import search_budget_stability
417+
418+
return search_budget_stability()
419+
420+
409421
@app.get("/", response_class=HTMLResponse)
410422
def index() -> str:
411423
return _INDEX_HTML
@@ -531,19 +543,27 @@ def index() -> str:
531543
if(!r.ok){ el.innerHTML = '<div class="card">Hata: '+(d.detail||'bilinmeyen')+'</div>'; return; }
532544
const ic = { input_conflict:d.input_conflict, ask_to_fair_value_ratio:d.ask_to_fair_value_ratio, input_conflict_candidate_explanations:d.input_conflict_candidate_explanations };
533545
const wt = d.within_theta_negotiation_interval, bt = d.between_theta_near_fit_band, sr = d.structural_sensitivity_range;
546+
const sh = d.simulated_trade_share_band || [null,null];
547+
const noTrade = (d.central_structural_estimate == null);
548+
const body = noTrade
549+
? '<div class="status">Bu senaryoda Θ_A boyunca simüle ticaret payı ~0 → koşullu-ticaret fiyatı raporlanmaz (dürüst null). Monte Carlo ~0 payı, popülasyon ticaret olasılığının matematiksel olarak 0 olduğu anlamına GELMEZ.</div>'
550+
: ('<div class="row"><span class="k">Within-θ negotiation uncertainty</span><span class="v">'+tl(wt[0])+' – '+tl(wt[1])+'</span></div>' +
551+
'<div class="expl">Sabit bir yapısal parametre yapılandırmasında simüle alıcı/satıcı değerlerinin ve pazarlık sürecinin doğurduğu değişim.</div>' +
552+
'<div class="row"><span class="k">Between-θ near-fit uncertainty</span><span class="v">'+tl(bt[0])+' – '+tl(bt[1])+'</span></div>' +
553+
'<div class="expl">Ekonomik olarak kabul edilebilir birden çok yapısal parametre yapılandırması, mevcut kamu momentlerine neredeyse eşit iyi uyar.</div>' +
554+
'<div class="envelope"><div class="lbl muted">Structural sensitivity range (iki belirsizlik birlikte)</div>' +
555+
'<div class="rng">'+tl(sr[0])+' – '+tl(sr[1])+'</div>' +
556+
'<div class="nocov">'+d.no_coverage_statement+'</div></div>');
534557
el.innerHTML = '<div class="card">' +
535-
'<div class="estimate"><div class="lbl">Central structural estimate · inferred transaction-price estimate</div>' +
558+
'<div class="estimate"><div class="lbl">Central structural estimate, conditional on simulated trade</div>' +
536559
'<div class="big">'+tl(d.central_structural_estimate)+'</div>' +
537-
'<div class="muted">İlan '+tl(d.asking_price)+' · fair value (TCMB çıpa) '+tl(d.fair_value)+'</div></div>' +
538-
renderConflict(ic) +
539-
'<div class="row"><span class="k">Within-θ negotiation uncertainty</span><span class="v">'+tl(wt[0])+' – '+tl(wt[1])+'</span></div>' +
540-
'<div class="expl">Sabit bir yapısal parametre yapılandırmasında simüle alıcı/satıcı değerlerinin ve pazarlık sürecinin doğurduğu değişim.</div>' +
541-
'<div class="row"><span class="k">Between-θ near-fit uncertainty</span><span class="v">'+tl(bt[0])+' – '+tl(bt[1])+'</span></div>' +
542-
'<div class="expl">Ekonomik olarak kabul edilebilir birden çok yapısal parametre yapılandırması, mevcut kamu momentlerine neredeyse eşit iyi uyar.</div>' +
543-
'<div class="envelope"><div class="lbl muted">Structural sensitivity range (iki belirsizlik birlikte)</div>' +
544-
'<div class="rng">'+tl(sr[0])+' – '+tl(sr[1])+'</div>' +
545-
'<div class="nocov">'+d.no_coverage_statement+'</div></div>' +
546-
'<div class="status">Identification status: <b>'+d.identification_status+'</b> · Jacobian rank '+d.jacobian_rank+' / '+d.parameter_dimension+
560+
'<div class="muted">İlan '+tl(d.asking_price)+' · fair value (TCMB çıpa) '+tl(d.fair_value)+'</div>' +
561+
'<div class="expl">'+d.conditional_on_trade_statement+'</div></div>' +
562+
renderConflict(ic) + body +
563+
'<div class="row"><span class="k">Model-implied simulated trade-share band</span><span class="v">'+(sh[0]==null?'—':(sh[0]+' – '+sh[1]))+'</span></div>' +
564+
'<div class="expl">Monte Carlo simüle B≥S payı — GÖZLENEN UYAP no-trade sonuçlarına KALİBRE DEĞİL; satış olasılığı / satış ihtimali DEĞİL.</div>' +
565+
'<div class="row"><span class="k">Near-fit configs (trading / total)</span><span class="v">'+d.trading_near_fit_parameter_count+' / '+d.near_fit_parameter_count+'</span></div>' +
566+
'<div class="status">Identification status: <b>'+d.identification_status+'</b> · Jacobian rank '+d.jacobian_rank+' / '+d.parameter_dimension+' · near-fit search: '+d.near_fit_search_stability+
547567
'<div class="expl">The available genuine public moments do not uniquely determine all structural parameters, so the system reports sensitivity across near-fit parameter configurations instead of pretending one parameter vector is known exactly.</div></div>' +
548568
'</div>';
549569
} catch(err){ el.innerHTML = '<div class="card">İstek başarısız: '+err+'</div>'; }

0 commit comments

Comments
 (0)