Bursa'daki hangi sağlık işletmesinin web sitesi yok, sosyal medyası yok,
ama ulaşılabilir bir telefonu var? Tek tıkla, kamuya açık veriden, ölçülebilir bir cevap.
989 işletme tarandı → 785 nitelikli aday → 1 dakika 15 saniye.
▶ Actions → scan → Run workflow ile bulutta çalıştır
- Actions → scan → Run workflow (bbox boş bırakılırsa Bursa varsayılanı).
- Koşu bitince Summary sekmesinde ilk lead'ler görünür.
- Artifacts → scan-results içinde
leads.csv,lms.db,brief.mdvecontract_report.md30 gün saklanır.
Workflow tanımı: .github/workflows/scan.yml —
doctor ön kontrolü → scan --track (SQLite'a koşu kaydı) → validate (veri
sözleşmesi) → brief → özet + artifact.
Canlı doğrulama (2026-08-16): GitHub Actions üzerinde gerçek Overpass API'ye karşı uçtan uca koştu — 989 işletme tarandı, 785 nitelikli lead üretildi (1m 15s,
scan #2).
Overpass API (OpenStreetMap)
│ build_query() → Overpass QL
│ fetch_raw(): 3 endpoint × 3 deneme, üstel backoff, Retry-After
▼
parse_response() → Business[]
│ digital_maturity_score()
▼
qualified_leads() → CSV + SQLite
│ render_brief()
▼
data/outreach_brief.md (saha görüşme brifingi)
| Sinyal | Puan | Skor | Öncelik | |
|---|---|---|---|---|
| Web sitesi | +40 | ≤ 25 | high | |
| Sosyal medya | +25 | 26–55 | medium | |
| Telefon | +15 | > 55 | low | |
| E-posta | +10 | |||
| Çalışma saatleri | +10 |
Düşük skor = zayıf dijital varlık = daha iyi aday. Ağırlıklar
src/lms/models.py içinde tek bir yerde tanımlıdır; kara kutu değildir,
değiştirilebilir ve testlerle sabitlenmiştir.
Overpass genel sunucuları düzenli olarak 429/504 döner. fetch_raw:
- Sırayla
OVERPASS_URL+OVERPASS_MIRRORS(varsayılan 2 yedek) dener. - Her endpoint için
MAX_RETRIES(varsayılan 3) deneme yapar. - Beklemeyi
BACKOFF_SECONDS × 2^nile artırır; sunucuRetry-Aftergönderirse o değer önceliklidir. - Hepsi başarısızsa
OverpassUnavailableErrorfırlatır → çıkış kodu3.
| Kod | Anlamı |
|---|---|
| 0 | Başarılı |
| 1 | Beklenen hata (dosya yok, boş sonuç, hatalı ayar) |
| 2 | Beklenmeyen hata (bug — issue açın) |
| 3 | Veri kaynağına ulaşılamadı (tüm Overpass endpoint'leri) |
Bu kodlar cron/CI içinde ayırt edici davranış için sabittir ve testlidir.
Çıktı, elle doğrulanması gereken bir aday listesidir. Aşağıdakiler gerçek kısıtlardır, ileride kapatılacak "eksikler" değil:
- OSM kapsama boşluğu. Bursa'daki her sağlık işletmesi OSM'de kayıtlı değildir. "Web sitesi yok" sonucu, "OSM'de web sitesi etiketi yok" demektir — işletmenin gerçekten sitesi olmadığını kanıtlamaz.
- Instagram aktifliği ölçülmez. "Son 60 gündür paylaşım yapmamış hesaplar" gibi bir filtre yoktur. Instagram'ın resmî API'si üçüncü taraf hesaplar için bu veriyi vermez; kazımak Hizmet Şartları ihlalidir. Bu adım manuel bir zenginleştirmedir.
- Skor bir hipotezdir. Ağırlıklar sezgiseldir, dönüşüm verisiyle kalibre edilmemiştir.
- Tekilleştirme basittir. Aynı işletmenin iki farklı OSM kaydı varsa ikisi de listeye girer.
Hukuki uyarı (KVKK / İYS). Sadece kamuya açık veri toplanır. Kişisel veri
(isim, telefon) saklandığı anda KVKK yükümlülüğü doğar: amaç sınırlaması,
saklama süresi, silme talebine yanıt. İzinsiz toplu ticari e-posta/SMS göndermek
İYS mevzuatını ihlal eder. Bu araç iletişim göndermez, sadece liste üretir;
iletişim kurmak kullanıcının sorumluluğundadır. Google Maps HTML'i kazınmaz —
bu Google Hizmet Şartları'nı ihlal eder. .env ve üretilen data/*.csv,
data/*.sqlite3 dosyaları .gitignore içindedir. Ayrıntılar:
SECURITY.md.
Veri: OpenStreetMap, Overpass API üzerinden. OSM verisi ODbL ile lisanslıdır; türetilmiş veriyi yayımlarsan atıf ve lisans yükümlülüğün vardır. Overpass genel sunucusu ortak bir kaynaktır: gereksiz tekrarlı sorgu atma, sonuçları yerelde sakla.
Sadece gerçekten doğrulanmış olanlar işaretlidir:
| Aşama | Durum |
|---|---|
| Planlandı | ✅ |
| Uygulandı | ✅ 8 CLI komutu, retry + mirror, history, contract, Parquet, PG |
| Test edildi | ✅ 179 test, tamamı offline, hepsi yeşil |
| Doğrulandı | ✅ Canlı Overpass taraması GitHub Actions'ta uçtan uca koştu (2026-08-16: 989 işletme, 785 lead, 1m 15s — scan #2) |
| Dağıtıldı | ✅ workflow_dispatch ile bulutta tek tıkla koşuyor; çıktılar artifact olarak 30 gün saklanıyor |
| Üretime hazır | 🟡 Kısmi — zamanlanmış (cron) koşu ve dönüşüm verisiyle skor kalibrasyonu yok; skor hâlâ hipotez |
Yerel kurulum ve 9 kullanım senaryosu
git clone https://github.com/umutseve4/local-market-scanner.git
cd local-market-scanner
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env # .env asla commit edilmez# 1) Bursa sağlık tesislerini tara (varsayılan bbox = Bursa ili)
PYTHONPATH=src python -m lms.cli scan --out data/bursa_health.csv --sqlite
# 2) Ağa çıkmadan, kayıtlı örnek veriyle dene
PYTHONPATH=src python -m lms.cli scan \
--fixture tests/fixtures/overpass_sample.json --out data/sample.csv
# 3) Aday listesini üret
PYTHONPATH=src python -m lms.cli leads \
--csv data/bursa_health.csv --max-score 25 --out data/leads.csv
# 4) Saha görüşme brifingi (Markdown) üret
PYTHONPATH=src python -m lms.cli brief \
--csv data/bursa_health.csv --limit 25 --out data/outreach_brief.md
# 5) Ayarları ve Overpass erişilebilirliğini teşhis et
PYTHONPATH=src python -m lms.cli doctor
PYTHONPATH=src python -m lms.cli doctor --offline # ağa hiç çıkmaz
# 6) Taramayı geçmişe kaydet ve koşular arası farkı gör (v0.3.0)
PYTHONPATH=src python -m lms.cli scan --fixture tests/fixtures/overpass_sample.json \
--out data/sample.csv --track --db-path data/market.sqlite3
PYTHONPATH=src python -m lms.cli runs --db-path data/market.sqlite3
PYTHONPATH=src python -m lms.cli runs --db-path data/market.sqlite3 --changes 1
# 7) Veri sözleşmesini doğrula (9 kural; FAIL → çıkış kodu 1)
PYTHONPATH=src python -m lms.cli validate --csv data/sample.csv --report data/report.json
# 8) Parquet'e aktar (Hive partition: scan_date=YYYY-MM-DD/) — pip install .[export]
PYTHONPATH=src python -m lms.cli export --csv data/sample.csv --out-dir data/parquet
# 9) PostgreSQL'e yükle (idempotent upsert) — pip install .[pg]
docker compose up -d # yerel Postgres 16 + şema
PYTHONPATH=src python -m lms.cli load-pg --csv data/sample.csv \
--dsn postgresql://localhost:5432/lmsFarklı bir bölge için:
PYTHONPATH=src python -m lms.cli scan --bbox 40.15,28.90,40.28,29.20Makefile aynı komutları kısayola bağlar: make scan, make leads, make brief,
make doctor, make test, make lint, make coverage.
brief her aday için tek ekranda: eksik dijital varlıklar, önerilen teklif ve
görüşmeyi açacak bir cümle üretir. Fiyat aralıkları src/lms/outreach.py içinde
tek yerdedir.
Ortam değişkenleri
Tümü isteğe bağlıdır; tam liste ve açıklamalar .env.example içinde.
| Değişken | Varsayılan | Ne işe yarar |
|---|---|---|
OVERPASS_URL |
https://overpass-api.de/api/interpreter |
Birincil endpoint |
OVERPASS_MIRRORS |
2 yerleşik yedek | Virgülle ayrık ek endpoint'ler |
REQUEST_TIMEOUT |
180 |
Saniye cinsinden istek zaman aşımı |
MAX_RETRIES |
3 |
Endpoint başına deneme (≥ 1) |
BACKOFF_SECONDS |
2.0 |
Üstel backoff tabanı |
REQUESTS_CA_BUNDLE |
boş | Kurumsal TLS proxy için CA paketi |
DB_PATH |
data/market.sqlite3 |
SQLite dosya yolu |
LMS_PG_DSN |
boş | load-pg için PostgreSQL DSN'i |
Geçersiz bir değer (örn. MAX_RETRIES=many) sessizce yok sayılmaz; ConfigError
fırlatılır ve çıkış kodu 1 olur.
Test dağılımı — 179 test, ağ erişimi yok
PYTHONPATH=src python -m unittest discover -s tests -v # bağımlılıksız
PYTHONPATH=src pytest -q # requirements ile
make coverage # %80 alt sınırOverpass yanıtı tests/fixtures/overpass_sample.json dosyasından okunur; HTTP
katmanı FakeSession/FakeResponse ile taklit edilir.
| Test dosyası | Neyi sabitler |
|---|---|
test_models.py |
Skor ağırlıkları, telefon/URL normalizasyonu |
test_scoring.py |
Aday filtresi ve sıralama |
test_overpass.py |
Overpass QL üretimi, yanıt ayrıştırma |
test_overpass_retry.py |
Retry, mirror geçişi, backoff, Retry-After |
test_storage.py |
CSV yazımı, SQLite upsert (idempotent) |
test_config_validation.py |
Ayar doğrulaması, env parse, anahtar sızıntısı |
test_outreach.py |
Brifing çıktısı (deterministik tarih) |
test_cli_commands.py |
4 alt komut uçtan uca + çıkış kodu eşlemesi |
test_history.py |
Koşu kaydı, diff (new/changed/unchanged) |
test_contract.py |
9 sözleşme kuralı + JSON/Markdown rapor |
test_exports.py |
Parquet partition düzeni, eksik pyarrow hatası |
test_pg_loader.py |
Upsert SQL'i, batch, eksik psycopg hatası |
test_cli_v030.py |
runs/validate/export/load-pg + --version |
Proje yapısı
src/lms/
config.py # Settings, doğrulama, Bursa bbox, OSM etiket filtreleri
errors.py # tipli hata hiyerarşisi + MissingDependencyError
models.py # Business, skorlama, telefon/URL normalizasyonu
scoring.py # filtreleme ve sıralama
outreach.py # Markdown saha brifingi üretimi
storage.py # CSV + SQLite
history.py # scan --track: koşu kaydı + koşular arası diff
contract.py # 9 kurallı veri sözleşmesi + JSON/Markdown rapor
exports.py # Hive-partitioned Parquet export (pyarrow opsiyonel)
pg_loader.py # PostgreSQL bulk upsert (psycopg opsiyonel)
cli.py # 8 komut: scan/leads/brief/doctor/runs/validate/export/load-pg
sources/overpass.py # Overpass QL, retry + mirror fallback, check_status
tests/ # 179 test, ağ erişimi yok
sql/schema.sql # PostgreSQL şeması (businesses + scan_runs + business_history)
docs/ARCHITECTURE.md # modül haritası, veri akışı, tasarım kararları
.github/workflows/ # ci.yml (test+lint+pg) ve scan.yml (bulut taraması)
docker-compose.yml # tek komutla yerel Postgres 16 + şema
Geliştirme akışı: CONTRIBUTING.md · Sır yönetimi ve KVKK: SECURITY.md · Sürüm geçmişi: CHANGELOG.md
MIT — bkz. LICENSE. Veri OSM'den gelir ve ODbL'ye tabidir.