Skip to content

Commit 857cf87

Browse files
committed
feat(uyap): accelerate nationwide discovery
1 parent f57db80 commit 857cf87

5 files changed

Lines changed: 900 additions & 29 deletions

File tree

README.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ one outstanding window per province:
253253
```powershell
254254
# Phase 1: metadata-only scan across all 81 provinces (no document download)
255255
sold uyap campaign --all-provinces --phase discover `
256+
--concurrency 8 `
256257
--cdp-endpoint http://127.0.0.1:9222
257258
258259
# Phase 2: revisit only windows containing new/incomplete candidates and download only queued KAYIT NOs
@@ -272,31 +273,37 @@ sold uyap campaign --all-provinces --phase discover `
272273

273274
The campaign is faster without weakening provenance:
274275

276+
- discovery uses the authenticated page's observed same-origin history endpoint with at most 8 concurrent
277+
requests by default (`--concurrency 1..16`); `--ui` retains the serial form-driven fallback;
275278
- provinces with measured sold-card yield run first; cold start prioritizes İstanbul, Ankara, İzmir, etc.;
276279
- newest windows run first and completed phase-specific checkpoints are skipped;
280+
- each endpoint response remains bound to its province/date/page payload, while the UI fallback keeps its
281+
click-scoped stale-response guards;
277282
- a discovery page performs one locked candidate-store write instead of one full rewrite per card;
278-
- click-scoped, date-bound response evidence prevents stale AJAX cards or zero banners from being attributed
279-
to the next province/window;
280283
- windows at the 200-result boundary split automatically; unsplittable saturated days remain explicit blockers;
281284
- page coverage and parsed-card cardinality must match source metadata before a window can become complete;
282285
- acquisition scans only queued windows and binds each KAYIT NO to its discovered candidate identity;
283286
- elapsed time, throughput, split counts, deferred work and unresolved blockers are reported.
284287

285288
Document modals remain serial inside one authenticated page. Parallel clicks/downloads on that shared DOM
286-
would risk cross-row artifact binding; the campaign gains throughput by eliminating redundant work instead.
289+
would risk cross-row artifact binding; only metadata discovery is concurrent.
287290
Bounded runs are intentionally resumable: deferred, saturated, truncated, stale, failed or cardinality-mismatched
288291
work is never reported as complete and returns a nonzero status for automation. Neither phase admits evidence:
289292
`sold uyap review` and explicit `sold uyap admit` remain separate.
290293

291-
> **Nationwide live run (2026-07-11).** A manually authenticated, user-controlled Chrome session completed
292-
> metadata discovery for **81/81 provinces** over `2026-07-05..2026-07-11`: **535 source result cards** and
293-
> **3 terminal `Satıldı` candidates**. Candidate-bound native acquisition completed **3/3** (Erzincan 2,
294+
> **Nationwide live run and fast-path reconciliation (2026-07-11).** A manually authenticated,
295+
> user-controlled Chrome session completed metadata discovery for **81/81 provinces** over
296+
> `2026-07-05..2026-07-11`. The bounded same-origin path completed in **6.34 seconds** using **155 requests**
297+
> at concurrency 8 and reconciled **2,190/2,190 source result cards**: all KAYIT IDs were unique, no identity
298+
> appeared in two provinces, and every province matched its declared source count. This corrects the earlier
299+
> serial-UI telemetry of 535 cards; both paths found the same **3 terminal `Satıldı` candidates**.
300+
> Candidate-bound native acquisition completed **3/3** (Erzincan 2,
294301
> Sakarya 1), leaving **0 queued candidates and 0 blockers**. Audit outcomes were one new
295302
> `ADMISSIBLE_COMPLETED_SALE` and two `PENDING_REVIEW`; **no record was admitted automatically**. The live
296303
> adapters bind search results to exact AJAX response/cardinality and document actions to exact KAYIT,
297304
> response URI, modal row and action fingerprint.
298305
299-
> **Live status.** UYAP Live Browser Pilot 1 reached **`PASS`** on the real **2026/263 Esas** record. A later bounded Ankara batch (`2026-06-18..2026-06-24`) collected five terminal-sale cards through native UDF artifacts: two new records were explicitly admitted, two were identified as existing observations (one primary ID and one alias), and one remains blocked for human review. This is **not** an official API, does **not** automate authentication, is **not** unattended continuous ingestion, and does **not** prove universal layout coverage. The automated test suite remains fully offline.
306+
> **Live status.** UYAP Live Browser Pilot 1 reached **`PASS`** on the real **2026/263 Esas** record. A later bounded Ankara batch (`2026-06-18..2026-06-24`) collected five terminal-sale cards through native UDF artifacts: two new records were explicitly admitted, two were identified as existing observations (one primary ID and one alias), and one remains blocked for human review. The history endpoint is an observed internal same-origin interface, **not** an official public API. The workflow does **not** automate authentication, is **not** unattended continuous ingestion, and does **not** prove universal layout coverage. The automated test suite remains fully offline.
300307
301308
### UYAP Live Browser Pilot 1 (live verification)
302309

docs/DEVELOPMENT_HISTORY.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -952,3 +952,27 @@ live-verified under this ownership chain. Final validation: **116 bulk/campaign
952952
**827 tests in the complete suite**. Independent review findings were incorporated into the final ownership
953953
guards before release; the post-fix audit found no P0/P1 correctness blocker. Persisted telemetry reconciles to
954954
81/81 complete provinces, 535 source cards, 3/3 acquired candidates, and an empty acquisition queue.
955+
956+
## 2026-07-11 — Same-origin nationwide discovery acceleration
957+
958+
Metadata discovery was moved from 81 serial UI form submissions to the history page's observed authenticated
959+
same-origin endpoint (`POST /pp/gecmisIhaleler_brd.ajx`). Authentication remains entirely manual. Province
960+
codes are read from the page's native `#historySearchIller` options; request concurrency is bounded to 8 by
961+
default (configurable from 1 through 16); page 1 determines source cardinality; and only required continuation
962+
pages are fetched. Dense windows still enter the existing adaptive date-split tree. Candidate persistence and
963+
phase-specific atomic checkpoints are unchanged, and native UDF acquisition remains serial and exact-KAYIT
964+
bound. The UI path remains available through `--ui`.
965+
966+
The structured sale rule was taken from the live portal renderer rather than inferred from price or description:
967+
`satisDurumu == "0"` maps to visible `Satıldı`. Continuation responses use the portal's observed `-1` sentinel
968+
for an unchanged total count; the adapter retains page 1's total and reconciles every later page against it.
969+
Malformed responses, missing identities, incomplete pagination, count mismatches, session loss and unsplittable
970+
200-result saturation all remain non-complete checkpoints.
971+
972+
An isolated live benchmark for `2026-07-05..2026-07-11` completed **81/81 provinces in 6.34 seconds** with
973+
concurrency 8 and 155 requests. It reconciled **2,190 fetched rows to 2,190 declared source rows**, with 2,190
974+
unique KAYIT IDs, zero cross-province duplicates and zero incomplete checkpoints, while finding the same three
975+
terminal `Satıldı` candidates as the prior run. This demonstrates that the earlier serial-UI telemetry of 535
976+
cards under-counted source coverage; it does not alter the already completed 3/3 candidate-bound acquisitions
977+
or admit any evidence. Focused validation after the change: **120 bulk/campaign tests passed**; the live
978+
two-province benchmark completed in 0.86 seconds and the nationwide benchmark completed in 6.34 seconds.

src/sold/cli.py

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2390,6 +2390,8 @@ def uyap_campaign_cmd(
23902390
max_provinces: Optional[int] = typer.Option(None, "--max-provinces", min=1, help="Bu koşuda en fazla işi olan il"),
23912391
max_windows_per_province: Optional[int] = typer.Option(1, "--max-windows-per-province", min=1, help="İl başına bu koşuda en fazla gerçek arama penceresi"),
23922392
max_records: Optional[int] = typer.Option(None, "--max-records", min=1, help="Acquire fazında toplam işlenecek en fazla kart"),
2393+
fast_discovery: bool = typer.Option(True, "--fast/--ui", help="Keşifte same-origin endpoint havuzu veya seri UI"),
2394+
concurrency: int = typer.Option(8, "--concurrency", min=1, max=16, help="Hızlı keşifte eşzamanlı istek sınırı"),
23932395
dry_run: bool = typer.Option(False, "--dry-run", help="Planı göster; tarayıcıya bağlanma"),
23942396
store_dir: Optional[str] = typer.Option(None, help="Çalışma deposu / checkpoint dizini"),
23952397
genuine_path: Optional[str] = typer.Option(None, help="Genuine uyap.json yolu (yalnız duplicate denetimi)"),
@@ -2398,9 +2400,9 @@ def uyap_campaign_cmd(
23982400
) -> None:
23992401
"""81-il UYAP kampanyası: önce hızlı keşif, sonra yalnız yeni adaylarda native-UDF edinimi.
24002402
2401-
Kimlik doğrulama daima kullanıcı tarafından yapılır. Kampanya admission yapmaz ve aynı sayfa üzerinde
2402-
paralel modal tıklaması kullanmaz; hız, boş pencereleri acquisition'da tekrar taramamak, newest-first
2403-
öncelik, ayrı checkpoint'ler ve yoğun pencereyi otomatik bölmekten gelir.
2403+
Kimlik doğrulama daima kullanıcı tarafından yapılır. Kampanya admission yapmaz. Keşif varsayılan
2404+
olarak oturumun same-origin JSON uç noktasını sınırlı bir havuzla kullanır; modal/native-UDF edinimi
2405+
seri kalır. ``--ui`` keşfi de eski seri form yoluna döndürür.
24042406
"""
24052407
from .ingestion.uyap import BROWSER_PREREQUISITES
24062408
from .ingestion.uyap.bulk import (
@@ -2496,8 +2498,10 @@ def uyap_campaign_cmd(
24962498
if campaign_phase == PHASE_ACQUISITION else []
24972499
)
24982500

2501+
discovery_mode = "fast" if fast_discovery else "ui"
24992502
typer.secho(
2500-
f"UYAP campaign · faz={campaign_phase} · aktif il={len(active_provinces)} · görev={len(plan)}",
2503+
f"UYAP campaign · faz={campaign_phase} · aktif il={len(active_provinces)} · "
2504+
f"görev={len(plan)} · keşif={discovery_mode}",
25012505
fg=typer.colors.CYAN, bold=True,
25022506
)
25032507
by_province: dict[str, int] = {}
@@ -2542,6 +2546,9 @@ def uyap_campaign_cmd(
25422546
"discovery_incomplete": deferred_discovery_tasks,
25432547
"queue_blockers": len(blockers),
25442548
"saturated_unresolved": 0,
2549+
"requests": 0,
2550+
"elapsed_seconds": 0.0,
2551+
"requests_per_minute": 0.0,
25452552
}
25462553
blocking_reason = None
25472554
blocking_reasons = {
@@ -2556,19 +2563,32 @@ def uyap_campaign_cmd(
25562563
}
25572564
remaining = max_records
25582565
if campaign_phase == PHASE_DISCOVERY:
2559-
for province in active_provinces:
2560-
result = collector.run(
2561-
province, resolved_from, resolved_to, discovery_only=True, newest_first=True,
2562-
max_windows=max_windows_per_province,
2563-
)
2566+
if fast_discovery:
2567+
result = collector.run_fast_discovery(plan, concurrency=concurrency)
25642568
totals["windows"] += result["windows_processed"]
25652569
totals["cards"] += result["result_cards_inspected"]
25662570
totals["saturated_unresolved"] += result["saturated_windows_unresolved"]
25672571
totals["incomplete"] += int(result.get("acquisition_windows_incomplete") or 0)
25682572
totals["discovery_incomplete"] += int(result.get("discovery_windows_incomplete") or 0)
2573+
totals["requests"] += int(result.get("request_count") or 0)
2574+
totals["elapsed_seconds"] += float(result.get("elapsed_seconds") or 0.0)
2575+
totals["requests_per_minute"] = float(result.get("requests_per_minute") or 0.0)
25692576
if result.get("stopped_reason") in blocking_reasons:
25702577
blocking_reason = result["stopped_reason"]
2571-
break
2578+
else:
2579+
for province in active_provinces:
2580+
result = collector.run(
2581+
province, resolved_from, resolved_to, discovery_only=True, newest_first=True,
2582+
max_windows=max_windows_per_province,
2583+
)
2584+
totals["windows"] += result["windows_processed"]
2585+
totals["cards"] += result["result_cards_inspected"]
2586+
totals["saturated_unresolved"] += result["saturated_windows_unresolved"]
2587+
totals["incomplete"] += int(result.get("acquisition_windows_incomplete") or 0)
2588+
totals["discovery_incomplete"] += int(result.get("discovery_windows_incomplete") or 0)
2589+
if result.get("stopped_reason") in blocking_reasons:
2590+
blocking_reason = result["stopped_reason"]
2591+
break
25722592
else:
25732593
for task_index, task in enumerate(plan):
25742594
if remaining is not None and remaining <= 0:

0 commit comments

Comments
 (0)