betting_brain.py: - HARD_MIN_SAMPLES=50 floor for calibrator bypass - ev_edge < 0 + >= 0.20 hard vetoes - BTTS muted (grid search found no profitable config) - Per-market optimal envelopes (MS, OU25) - Score coherence filter: main_pick must agree with score prediction - HTFT reversal cross-check for MS picks feature_builder.py / data_loader.py: - Real home/away_position from data (was hardcoded 10) - Cup detection wired into UpsetEngine - _estimate_league_position with 300-day season filter New scripts: - diagnostic_backtest.py: per-bet diagnostic backtest with loss patterns - optimize_filters.py: grid search per-market optimal thresholds - analyze_backtest_csv.py: root-cause hypothesis testing on CSV - compare_backtests.py: side-by-side validation with verdict - test_score_coherence.py: smoke test for coherence filter (20/20 pass) Reports: - diagnostic_backtest_20260525_024437 (50-match smoke) - diagnostic_backtest_20260525_035649 (1000-match in-sample) - filter_optimization_patch.json (grid search winners per market) Social poster v3: - satori + resvg HTML/CSS rendering pipeline - Twemoji football/basketball + flag SVGs - caption SEO: 12 curated hashtags per post - image SEO: descriptive filenames + .json metadata sidecar - /health, /preview-png, /run-now endpoints Docs: - mds/SESSION_HANDOFF.md: full session state for cross-machine continuity - mds/SOCIAL_POSTER_SETUP.md: API keys + test commands Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
7.7 KiB
Social Poster — Setup & Operations
Otomatik tahmin kartı üretip Twitter / Facebook / Instagram'a postlayan modül. Cron her 10 dakikada bir çalışır, yaklaşan 10-60 dk içindeki maçları yakalar, AI Engine'den tahmin alır, 1080×1080 görsel üretir, caption üretir, 3 platforma post eder.
1) ENV değişkenleri
.env'ye ekle:
# Master switch — false ise cron çalışmaz, manual endpoint'ler de boş döner.
SOCIAL_POSTER_ENABLED=true
# Hangi sporlar (virgüllü liste). Varsayılan: football,basketball
SOCIAL_POSTER_SPORTS=football,basketball
# Yaklaşan maç penceresi (dakika). Varsayılan 10-60.
SOCIAL_POSTER_WINDOW_MIN=10
SOCIAL_POSTER_WINDOW_MAX=60
# Tek cron koşusunda kaç maç post edilir (rate-limit koruması). Varsayılan 5.
SOCIAL_POSTER_MAX_PER_RUN=5
# Public base URL — Instagram media upload için fotoğrafın HTTPS'ten erişilebilir
# olması ŞART. Localhost ile IG çalışmaz; production domain veya ngrok kullan.
APP_BASE_URL=https://api.iddaai.com
# AI Engine URL (orchestrator)
AI_ENGINE_URL=http://localhost:8000
# ─── Twitter / X ───
TWITTER_API_KEY=...
TWITTER_API_SECRET=...
TWITTER_ACCESS_TOKEN=...
TWITTER_ACCESS_SECRET=...
# ─── Meta (Facebook + Instagram) ───
META_PAGE_ACCESS_TOKEN=... # FB Page'in long-lived access token'ı
META_PAGE_ID=... # FB Page numeric ID
META_IG_USER_ID=... # IG Business account numeric ID
META_GRAPH_API_VERSION=v25.0 # opsiyonel
# ─── Caption AI (opsiyonel — yoksa template caption kullanılır) ───
ENABLE_GEMINI=true
GEMINI_API_KEY=...
GEMINI_MODEL=gemini-1.5-flash
# Veya local Ollama:
OLLAMA_BASE_URL=http://localhost:11434
SOCIAL_POSTER_OLLAMA_MODEL=llama3.1
2) API anahtarlarını alma
Twitter / X
- https://developer.x.com → Project + App oluştur
- App'ın "Keys and tokens" → "API Key", "API Secret" al
- User authentication settings → "Read and write and Direct Message"
- "Access Token and Secret" generate et (bu hesap adına post eder)
- Free tier: 1500 tweet/ay, 50 post/24 saat — 10 dk'lık cron'la günde
~144 koşu × 5 post = 720 potansiyel post → free tier yetmez, Basic plan
($200/ay) lazım. Cron interval'i 30 dk'ya alıp 50/gün kalmak istersen
@Cron("*/30 * * * *")olarak değiştir.
Meta (Facebook + Instagram)
- https://developers.facebook.com → App oluştur (type: Business)
- Facebook Page bağla (mevcut sayfan yoksa oluştur)
- Instagram Business hesabını Facebook Page'e bağla
- Graph API Explorer'dan page access token al (User token değil!)
- Long-lived token'a çevir (60 gün geçerli, refresh edilebilir)
- Page ID:
https://graph.facebook.com/me/accounts?access_token=... - IG User ID:
graph.facebook.com/{pageId}?fields=instagram_business_account&access_token=... - Required permissions:
pages_show_list,pages_manage_posts,pages_read_engagement,instagram_basic,instagram_content_publish
Gemini (caption AI — opsiyonel)
- https://aistudio.google.com → API key (free tier yeterli, günde ~1500 istek)
ENABLE_GEMINI=true+GEMINI_API_KEY=...- Gemini yoksa template caption kullanılır (yine SEO'lu, sadece daha statik)
3) Test komutları
# Servisi başlat
npm run start:dev
# Health endpoint — auth gerekmez
curl http://localhost:3005/social-poster/health | jq
# Manuel preview (görsel + JSON) — superadmin token gerekir
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:3005/social-poster/preview/<matchId>
# Görseli tarayıcıda direkt göster
open http://localhost:3005/social-poster/preview-png/<matchId>?token=$TOKEN
# Manuel post (tek maç, tüm platformlara) — superadmin token
curl -X POST -H "Authorization: Bearer $TOKEN" \
http://localhost:3005/social-poster/post/<matchId>
# Cron'u beklemeden full sweep koş — superadmin token
curl -X POST -H "Authorization: Bearer $TOKEN" \
http://localhost:3005/social-poster/run-now
4) SEO özellikleri
Image dosya adı (SEO)
Eskiden: prediction_basketball_xyz12345_1716595200000.jpg (opaque)
Yeni: sampiyonlar-ligi-unicaja-malaga-vs-aek-20260525.jpg (Google indexable)
Yan dosya: metadata sidecar
Her görsel için aynı dizinde .json:
title,description,og:*,schema.org SportsEvent,picks[]- Sayfada
<head>Open Graph + Twitter Cards bu dosyadan beslenir - Schema.org markup zengin sonuç (Google rich snippet) sağlar
Caption (SEO + hashtags)
Her post 12'ye kadar küratör hashtag içerir:
- Marka:
#MaçTahmini #İddaa #BugünMaç - Spor:
#Futbol #Basketbol #FutbolTahmin - Lig:
#SüperLig #PremierLeague #ŞampiyonlarLigi #EuroLeague #NBA - Bölge:
#Türkiye #İngiltere #İspanya - Takım:
#Galatasaray #Fenerbahçe - Gün:
#PazarTahmini #CumartesiTahmini - Market:
#AltÜst #KGVar #ÇifteŞans #MaçSonucu #Handikap
LLM (Gemini) caption üretiyorsa hashtag'leri çıkarır; sistem kendi hashtag set'ini ekler. Tutarlı index için tek kaynak.
5) İzleme
# Health endpoint — periyodik monitor
curl http://localhost:3005/social-poster/health | jq
# Sample output:
{
"enabled": true,
"sports": ["football", "basketball"],
"window_min_minutes": 10,
"window_max_minutes": 60,
"max_posts_per_run": 5,
"top_leagues_loaded": 42,
"posted_match_count": 137,
"last_run_at": "2026-05-25T03:10:00.123Z",
"last_run_result": { "posted": 4, "skipped": 1, "errors": 0 },
"twitter_available": true,
"meta_facebook_available": true,
"meta_instagram_available": true,
"ai_engine_url": "http://localhost:8000",
"app_base_url": "https://api.iddaai.com"
}
posted_match_count storage/social-poster-posted.json'dan okunur, son 500
match ID hafızada — aynı maçı 2 kere post etmez.
6) Rate limit ipuçları
| Platform | Free limit | Tedbir |
|---|---|---|
| 50 post/24 saat | SOCIAL_POSTER_MAX_PER_RUN=2 + cron */30 → günde ~96 |
|
| ~200 post/saat (Page) | Default config rahat | |
| 25 post/24 saat | MAX_PER_RUN=1 + cron */60 → günde 24, sınırın hemen altında |
IG en sıkı sınır — production için IG ayrı cron'da daha seyrek post yapılması önerilir (kod henüz tek cron, ileride ayrılabilir).
7) Hangi maçlar seçilir?
top_leagues.json dosyasındaki league_id'ler içinden:
- Şu anda 10-60 dakika sonra başlayacak
- Daha önce post edilmemiş
sport: football, basketballfiltresi geçen
top_leagues.json yoksa tüm liglerden maç seçer (hacmi yüksek tutar).
Sadece premium ligler postlamak istersen dosyayı doldur.
8) Görsel formatı
- Boyut: 1080×1080 (Instagram square — Twitter da kabul ediyor)
- Format: JPEG, quality 94
- Tema: Sport'a göre değişir — football yeşil, basketball turuncu
- İçerik: Lig logosu + ülke bayrağı, takım logoları + adları, HT skor, FT skor, top 3 tahmin (confidence ile), risk badge
Card layout image-renderer.service.ts içinde — value-pick yıldız ile
işaretli, scenario top 3 listelenir, footer alt'ta tarih + brand.
9) Sık sorular
Q: Görseller nereye yazılıyor?
public/predictions/ (gitignored). ServeStatic ile /predictions/<file>.jpg
URL'inden erişilir.
Q: Eski görseller temizleniyor mu?
Hayır — manuel temizlik gerekir. Cron eklemek istersen LimitResetterTask
örneği var.
Q: AI Engine çalışmıyorsa ne olur? Cron tahmin alamaz, log'a hata düşer, devam eder. Sonraki koşuda dener.
Q: Bir maç 2 kere post ediliyor mu?
Hayır — postedMatchIds set'i Match ID bazında dedup yapar, dosyaya yazılır
(restart-safe).
Q: Caption Gemini olmadan ne kadar iyi? Template caption tüm bilgileri + 12 hashtag içerir. SEO açısından yeterli, sadece anlatım daha statik. Gemini ile her post için özgün metin.