4 Commits

Author SHA1 Message Date
bilalgursen
7518baccfa fix: bayat dev-mock raporlar tek kapıdan (kullanicininRaporu) ayıklanıyor
All checks were successful
Deploy / deploy (push) Successful in 9m25s
Dev'de API anahtarı yokken üretilen mock rapor DB'ye kaydediliyordu;
anahtar sonradan eklenince kullanıcı başına tek rapor kuralı yüzünden o
bayat mock gerçek AI çıktısıymış gibi servis edilmeye devam ediyordu.

Rapor tablosunu okuyan her yer (sonuc/listem/yazdir/odeme-sonuc sayfaları,
liste action'ları, soru API'si, dev panel) artık src/lib/rapor-kaydi.ts
içindeki kullanicininRaporu() kapısından geçiyor: gerçek AI aktifken
mock üretimi kayıt "yok" sayılır, kullanıcı gerçek listeyi yeniden üretir.
Yeni mock kayıtlar devMock işareti taşır; eskiler metin ön ekinden tanınır.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 12:45:56 +03:00
bilalgursen
620e41edae feat: iyzico ile Öde + kart logoları (Visa/Mastercard) footer ve paket sayfasına eklendi
Some checks failed
Deploy / deploy (push) Has been cancelled
iyzico işyeri denetimi web sitesinde "iyzico ile Öde", Visa ve MasterCard
logolarının görünür olmasını istedi. Resmî iyzico logo paketindeki renkli
logo bandı (iyzico ile Öde + Mastercard + Visa + Amex + Troy) site
footer'ına ve paket sayfasındaki satın alma kartına eklendi.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 12:20:57 +03:00
bilalgursen
50dc415987 feat: yüksek aranma hacimli 3 yeni rehber yazısı (sonuç tarihi, tercih robotu, AİS onay)
Some checks failed
Deploy / deploy (push) Has been cancelled
Tercih dönemi sorgu araştırmasına göre en çok aranan üç boşluğu dolduran
mermaid şemalı rehber yazıları: yerleştirme sonuçlarının açıklanma penceresi,
tercih robotu / YÖK Atlas kullanımı ve AİS'te tercih bildirme-onaylama akışı.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 12:08:29 +03:00
bilalgursen
be4b2c6000 refactor: update Dockerfile and authentication flow for improved user experience
All checks were successful
Deploy / deploy (push) Successful in 15m6s
- Updated Dockerfile to create a cache directory for Next.js runtime, ensuring proper permissions and ownership.
- Enhanced the authentication flow by updating the magic link expiration time from 5 to 15 minutes, improving user experience and reducing login issues.
- Added user-friendly error handling for invalid or expired magic links, providing clear feedback on the login page.
- Adjusted email templates to reflect the new 15-minute validity period for magic links.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 02:05:14 +03:00
32 changed files with 822 additions and 172 deletions

View File

@@ -1,7 +1,7 @@
{
"enabledPlugins": {
"superpowers@superpowers-marketplace": true,
"prd-development@pm-skills": true
"superpowers@superpowers-marketplace": false,
"prd-development@pm-skills": false
},
"extraKnownMarketplaces": {
"superpowers-marketplace": {

View File

@@ -1,7 +0,0 @@
---
name: grill-me
description: A relentless interview to sharpen a plan or design.
disable-model-invocation: true
---
Run a `/grilling` session.

View File

@@ -1,22 +0,0 @@
---
name: grilling
description: Grill the user relentlessly about a plan, decision, or idea. Use when the user wants to stress-test their thinking, or uses any 'grill' trigger phrases.
---
Interview the user relentlessly until you reach a shared understanding. Map this as a **design tree**: every decision branches into the decisions that hang off it.
Work the tree in **rounds**. The **frontier** is every decision whose prerequisites are already settled — the questions you can ask _now_ without guessing at answers you haven't heard yet. Ask the whole frontier in one round: number each question and give your recommended answer. Then wait for the user's answers before the next round.
Each question should be formatted like so:
```
❓ **Q1** - **<question title>**: <question body, might be multiple paragraphs, including multiple choices>
➡️ <your recommended answer>
```
Each round the user answers reshapes the tree — settled decisions push the frontier outward and unblock questions that depended on them. Recompute the frontier and ask the next round. A question whose answer depends on another question still open in this round belongs to a _later_ round, not this one.
Finding _facts_ is your job, never the user's. When a frontier question needs a fact from the environment (filesystem, tools, etc.), dispatch a sub-agent to find it — don't ask the user for anything you could look up yourself. Don't block on it: a running exploration is an unsettled prerequisite, so only the questions downstream of it wait for the sub-agent to report — ask the rest of the frontier now. The _decisions_ are the user's — put each to them and wait.
The session is done when the frontier is empty: every branch of the design tree visited, nothing left silently assumed. Do not act on it until the user confirms you have reached a shared understanding.

View File

@@ -69,10 +69,11 @@ COPY drizzle ./drizzle
COPY scripts/db-goc.mjs ./scripts/db-goc.mjs
COPY scripts/docker-entrypoint.sh ./docker-entrypoint.sh
# SQLite veri dizini
# SQLite veri dizini + Next'in runtime'da yazdığı image/fetch cache dizini
# (.next'in geri kalanı root'ta kalır; salt-okunur olması sorun değil)
RUN chmod +x /app/docker-entrypoint.sh && \
mkdir -p /app/data && \
chown -R nextjs:nodejs /app/data /app/seed
mkdir -p /app/data /app/.next/cache && \
chown -R nextjs:nodejs /app/data /app/seed /app/.next/cache
USER nextjs

View File

@@ -0,0 +1,58 @@
---
baslik: AİS'te Tercih Nasıl Yapılır, Nasıl Onaylanır? (Son Kontrol Listesi)
aciklama: ÖSYM AİS tercih ekranı adım adım — kod girme, sıralama, "Tercihleri Bildir" onayı ve göndermeden önceki 5 maddelik son kontrol tek şemada.
tarih: 2026-08-05
---
Listeni kurdun; şimdi işin resmî kısmı var: tercihleri ÖSYM'nin sistemine girmek ve **onaylamak**. Her yıl adayların bir kısmı listeyi özenle kurup son adımda takılıyor — tercihler girilir ama bildirilmez, kod yanlış yazılır, değişiklikten sonra yeniden onay unutulur. Bu yazı AİS ekranını adım adım anlatıyor ve göndermeden önceki son kontrol listesini veriyor.
## Nereden, ne zamana kadar?
Tercihler yalnızca iki resmî kanaldan girilir: **ais.osym.gov.tr** ve **ÖSYM Aday İşlemleri mobil uygulaması**. Giriş T.C. kimlik numarası ve aday şifresiyle. 2026'da pencere **29 Temmuz - 10 Ağustos, saat 23.59** ([takvimin tamamı](/rehber/yks-tercih-takvimi-2026)). Son saate bırakma: kapanış akşamı sistem yoğunluğu gerçektir ve 23.59'dan sonra hiçbir işlem alınmaz.
## Adım adım AİS tercih ekranı
```mermaid
%% aria: ÖSYM AİS tercih bildirme akış şeması. Adımlar sırayla: aday şifresiyle AİS'e giriş yapılır, 2026 YKS Tercih ekranıılır, kılavuzdaki program kodları tek tek listeye eklenir, satırlar istek sırasına göre dizilir, ardından son kontrol yapılır. Son kontrolden sonra Tercihleri Bildir butonuna basılır. Kritik doğrulama şudur: ekranda tercihleriniz ÖSYM'ye bildirilmiştir uyarısı görünüyorsa işlem tamamdır ve bu kutu yeşildir; uyarı görünmüyorsa tercihler kaydedilmiş ama bildirilmemiş olabilir, bu durumda tercihlerin geçersiz sayılır ve bu kutu kırmızıdır. Son tarihe kadar listede değişiklik yapılabilir ama her değişiklikten sonra Tercihleri Bildir adımı yeniden yapılmalıdır; bu döngü sarı ile işaretlidir çünkü en sık unutulan adımdır.
%% altyazi: Kaydetmek yetmez — ekranda "bildirilmiştir" uyarısını görmeden sistemden çıkma.
flowchart TD
A("AİS'e gir:<br/>T.C. kimlik + aday şifresi") --> B("2026-YKS Tercih<br/>ekranını aç")
B --> C("Program kodlarını<br/>kılavuzdan tek tek ekle")
C --> D("Satırları İSTEK sırana<br/>göre diz")
D --> E("Son kontrolü yap") --> F("Tercihleri Bildir<br/>butonuna bas")
F --> G{"Ekranda 'Tercihleriniz<br/>ÖSYM'ye bildirilmiştir'<br/>uyarısı var mı?"}
G -- "evet" --> H("İşlem tamam:<br/>ekran görüntüsü al")
G -- "hayır" --> I("Tercihler GEÇERSİZ:<br/>bildirme adımını<br/>tamamla")
H --> J{"Son güne kadar<br/>değişiklik yaptın mı?"}
J -- "evet" --> F
J -- "hayır" --> K("10 Ağustos 23.59'da<br/>liste kilitlenir")
classDef uyari fill:#fef3c7,stroke:#f59e0b,color:#92400e
classDef garanti fill:#d1fae5,stroke:#10b981,color:#065f46
classDef hayal fill:#fee2e2,stroke:#ef4444,color:#991b1b
class E,G,J uyari
class H,K garanti
class I hayal
```
**1) Giriş.** ais.osym.gov.tr adresine T.C. kimlik numaran ve aday şifrenle gir. Şifren yoksa/unuttuysan hemen yenile — bunu son güne bırakmak en gereksiz risk.
**2) Tercih ekranı.**ılan sayfada "2026-YKS Tercih" seçeneğine tıkla.
**3) Kodları ekle.** Her programın kılavuzda ve [tercih robotu](/tercih-robotu) çıktısında görünen bir **program kodu** var. Kodu ekleyince ekranda çıkan program adını ve üniversiteyi kendi listenle karşılaştır — tek rakam farkı seni başka şehre, başka bölüme yazar.
**4) İstek sırana göre diz.** Merkezi yerleştirme listeyi yukarıdan aşağı okur ve seni yerleştirebileceği **en üstteki** satıra yerleştirir ([sistem nasıl çalışır](/rehber/merkezi-yerlestirme-nasil-calisir)). Sıra yerleşme ihtimaline göre değil, *istemene* göre olmalı.
**5) Bildir ve doğrula.** "Tercihleri Bildir" butonuna bas ve ekranda **"2026-YKS Tercihleriniz ÖSYM'ye bildirilmiştir"** uyarısını gör. Bu uyarıyı görmeden kapattığın oturumda tercihlerin kaydedilmiş ama **bildirilmemiş** olabilir — bildirilmeyen tercih geçersizdir. Uyarı ekranının görüntüsünü al, sakla.
**6) Değişiklik yaptıysan yeniden bildir.** Son tarihe kadar listeyi istediğin kadar güncelleyebilirsin; ama her değişiklikten sonra bildirme adımı **yeniden** yapılır. Sistemdeki geçerli liste, en son "bildirilmiştir" uyarısını aldığın listedir.
## Göndermeden önce: 5 maddelik son kontrol
1. **Her satırın koşulunu okudun mu?** Yaş, cinsiyet, sağlık raporu gibi [özel koşullar](/rehber/ozel-kosullu-programlar) ve [başarı sırası şartları](/rehber/siralama-sartlari-2026) sağlanmıyorsa o satır ya boşa gider ya da yerleşsen bile kayıtta yanar.
2. **Üst sıralarda ölü tercih var mı?** Tabanı senden çok iyi programı listenin *sonuna* yazmak satırı çöpe atar ([ölü tercih nedir](/rehber/olu-tercih-nedir)).
3. **Listenin sonu gerçekten garanti mi?** Son 4-6 satır, yerleşirsen kayıt yaptıracağın programlar olmalı ([hayal/dengeli/garanti dağılımı](/rehber/tercih-listesi-nasil-yapilir)).
4. **Kayıt yaptırmayacağın satır var mı?** Varsa sil: oraya yerleşirsen hem yılın gider hem [ek yerleştirmeye](/rehber/ek-yerlestirme-2026) başvuramazsın.
5. **Kodları tek tek doğruladın mı?** Ekrandaki program adı + üniversite + şehir üçlüsünü kendi listenle satır satır karşılaştır.
Listen henüz oturmadıysa önce evreni gör: [tercih robotuna](/tercih-robotu) sıralamanı gir, sıralamana düşen programları hayal/dengeli/garanti dilimleriyle çıkar, sonra AİS'e kodları bu listeden aktar. AİS'te düşünülmez — AİS'e *bitmiş* liste girilir.

View File

@@ -0,0 +1,62 @@
---
baslik: Tercih Robotu Nedir? YÖK Atlas Tercih Sihirbazı Nasıl Kullanılır?
aciklama: Tercih robotu neyi çözer, neyi çözemez? YÖK Atlas'ı adım adım kullanma rehberi ve robota liste teslim etmeden önce bilmen gereken dört tuzak — tek şemada.
tarih: 2026-08-06
---
Tercih döneminin en çok aranan araçlarından biri "tercih robotu". Ama iki farklı şey aynı adla anılıyor: YÖK'ün resmî veri ekranı **YÖK Atlas tercih sihirbazı** ve sıralamanı girince sana program listeleyen **tercih robotları**. Bu yazı ikisinin de ne işe yaradığını, YÖK Atlas'ın adım adım nasıl kullanıldığını ve bir robotun çıktısını hangi süzgeçten geçirmen gerektiğini anlatıyor.
## Tercih robotu ne yapar?
Özünde her tercih robotu aynı işi yapar: **başarı sıralamanı** alır, geçmiş yılların taban sıralamalarıyla karşılaştırır ve 23.000'i aşkın programın içinden senin bandına düşenleri süzer. Elle günler süren tarama işini saniyelere indirir. İyi bir robot bunun üstüne üç şey daha katar:
- Programları tek yılın tabanına göre değil, **çok yıllı trende** göre dilimlemek ([tabanlar yıldan yıla nasıl oynuyor](/rehber/taban-siralamalar-nasil-degisir)),
- Sonuçları **hayal / dengeli / garanti** mantığıyla ayırmak ([listenin üç bölgesi](/rehber/tercih-listesi-nasil-yapilir)),
- Kılavuzdaki [özel koşulları ve sıralama şartlarını](/rehber/siralama-sartlari-2026) süzgece dahil etmek.
Robotun yapamayacağı şey ise senin yerine **istemek**: hangi bölümü gerçekten okuyacağını, hangi şehirde dört yıl yaşayabileceğini, bütçenin neye yettiğini robot bilemez. Robot evreni daraltır; sırayı sen kurarsın.
## YÖK Atlas tercih sihirbazı adım adım
YÖK Atlas (yokatlas.yok.gov.tr), YÖK'ün resmî yerleştirme verilerini açtığı ücretsiz ekrandır. Kullanımı:
1. **Düzeyini seç.** 4 yıllık için "Lisans Tercih Sihirbazı", 2 yıllık için "Önlisans Tercih Sihirbazı" ([önlisans mı lisans mı](/rehber/onlisans-mi-lisans-mi)).
2. **Puan türünü ve sıralamanı gir.** SAY, EA, SÖZ, DİL ya da TYT — ve puanını değil, **başarı sıralamanı** ([neden sıralama](/rehber/taban-puan-mi-siralama-mi)).
3. **Filtrele.** Şehir, devlet/vakıf, öğretim türü, burs oranı gibi filtrelerle evreni daralt.
4. **Program sayfalarına in.** Her programın sayfasında kontenjan, taban, yerleşenlerin sıralama aralığı, akademik kadro ve doluluk geçmişi var. Asıl hazine listede değil, bu detay sayfalarındadır.
## Robot çıktısını okurken dört tuzak
```mermaid
%% aria: Tercih robotu kullanım akışı şeması. Doğru akış şöyledir: başarı sıralamanı girersin, filtrelerle aday havuzunu daraltırsın, robot programları hayal, dengeli ve garanti dilimlerine ayırır, sonra son kontrol sana geçer: kılavuz koşullarını, çok yıllı taban trendini ve kendi isteklerini kendin doğrularsın; bu kontrol sarı ile işaretlidir çünkü atlanması en sık hatadır. Akışın dışında dört tuzak kutusu kırmızı ile işaretlidir: puanla arama yapmak, tek yılın tabanına güvenmek, listeyi robotun verdiği sırayla göndermek ve kılavuz koşullarını hiç okumamak. Bu dört hatadan herhangi biri listeyi çürütür. Son kontrolden geçen liste yeşil ile işaretlenmiş sağlam liste kutusuna ulaşır.
%% altyazi: Robot evreni daraltır, sırayı sen kurarsın — son kontrol devredilemez.
flowchart TD
A("Başarı sıralamanı gir<br/>puanı değil") --> B("Filtrele:<br/>şehir, devlet/vakıf,<br/>öğretim dili")
B --> C("Robot dilimler:<br/>hayal / dengeli / garanti")
C --> D{"Son kontrol sende:<br/>kılavuz koşulları,<br/>çok yıllı trend,<br/>gerçek isteklerin"}
D -- "kontrolü yaptın" --> E("Sağlam liste:<br/>her satırın gerekçesi var")
D -- "atladın" --> F("Tuzaklar devrede")
T1("Puanla arama") -.-> F
T2("Tek yılın tabanına<br/>güvenmek") -.-> F
T3("Robotun sırasını<br/>aynen göndermek") -.-> F
T4("Kılavuz koşullarını<br/>okumamak") -.-> F
classDef uyari fill:#fef3c7,stroke:#f59e0b,color:#92400e
classDef garanti fill:#d1fae5,stroke:#10b981,color:#065f46
classDef hayal fill:#fee2e2,stroke:#ef4444,color:#991b1b
class D uyari
class E garanti
class F,T1,T2,T3,T4 hayal
```
**1) Puanla arama yapmak.** Puanlar sınavın zorluğuyla her yıl kayar; aynı puan bir yıl 80 bininci, ertesi yıl 120 bininci sıraya denk gelir. Robota da YÖK Atlas'a da **sıralamanı** ver.
**2) Tek yılın tabanına güvenmek.** Geçen yılın tabanı tek başına bir fotoğraftır, film değil. Kontenjanı artan program gevşer, kapanan varyantın talebi komşusuna taşar. En az son 3-4 yılın trendine bakmayan çıktı eksiktir.
**3) Robotun sırasını aynen göndermek.** Robotlar genelde yerleşme ihtimaline göre sıralar; oysa merkezi yerleştirmede liste **istek sırana** göre dizilir ([sistem nasıl çalışır](/rehber/merkezi-yerlestirme-nasil-calisir)). İhtimal sırasıyla gönderilen liste, üst sıralara [ölü tercih](/rehber/olu-tercih-nedir) gömer.
**4) Kılavuz koşullarını okumamak.** Yaş şartı, cinsiyet şartı, sağlık raporu, zorunlu hizmet gibi [özel koşullar](/rehber/ozel-kosullu-programlar) robota her zaman tam yansımaz. Yazacağın her satırın kılavuz koşul maddelerini bir kez kendin oku.
## YÖK Atlas mı, tercih robotu mu?
İkisi rakip değil, ardışık: robot tarama işini saniyeye indirir, YÖK Atlas tek programın derinine iner. Verimli akış şöyle: [tercih robotuna](/tercih-robotu) sıralamanı gir, hayal/dengeli/garanti dilimlerine ayrılmış aday havuzunu çıkar; sonra listende ciddi yer verdiğin programların YÖK Atlas sayfalarınııp kadro, doluluk geçmişi ve koşulları tek tek doğrula. Robotla dakikada evreni gör, Atlas'la satır satır emin ol — liste ikisinin kesişiminden çıkar.

View File

@@ -0,0 +1,63 @@
---
baslik: 2026 YKS Tercih Sonuçları Ne Zaman Açıklanacak? (Sonuç Ekranı Rehberi)
aciklama: Yerleştirme sonuçları için beklenen tarih aralığı, sonucun nereden sorgulanacağı ve ekranı gördüğün anda yapman gerekenler — iki senaryo tek şemada.
tarih: 2026-08-07
---
Tercihler 10 Ağustos 23.59'da kapandı mı, arama motorlarındaki soru değişir: **"Tercih sonuçları ne zaman açıklanacak?"** Bu yazı üç şeyi netleştiriyor: sonuçların beklendiği tarih aralığı, sonucu nereden öğreneceğin ve ekranı gördüğün anda — yerleşmiş ya da yerleşememiş olarak — atman gereken adımlar.
## Ne zaman açıklanacak?
ÖSYM, yerleştirme sonuçları için gün ilan etmez; sonuçlar hazır olduğunda duyuruyla erişime açılır. Ama takvimin iki ucu belli olduğu için aralığı daraltmak kolay:
- Tercihler **10 Ağustos 2026** gecesi kapandı.
- Üniversite kayıtları **24-28 Ağustos** tarihlerinde yapılacak (e-kayıt 24-26 Ağustos).
Kayıt 24 Ağustos'ta başladığına göre sonuçların **ondan önce, Ağustos'un ikinci yarısında**ıklanması gerekiyor. Geçmiş yıllarda da yerleştirme sonuçları tercih bitiminden yaklaşık 1,5-2 hafta sonra geldi. Yani beklenen pencere dar: **15-23 Ağustos arası.** Kesin gün için tek resmî kaynak ÖSYM duyurusudur; takvimin tamamı [2026 tercih takvimi](/rehber/yks-tercih-takvimi-2026) yazısında.
## Nereden öğreneceksin?
Sonuç açıklandığında iki resmî kanaldan sorgulayabilirsin:
- **sonuc.osym.gov.tr** — T.C. kimlik numaran ve aday şifrenle.
- **ÖSYM Aday İşlemleri Mobil Uygulaması** — aynı girişle telefonda.
Şifreni unuttuysan bugünden yenile; sonuç günü şifre sıfırlama ekranları yoğunluktan yavaşlar. Sonuç belgesinde yerleştiğin programın adı, kodu ve yerleştirme puanın yazar; hiçbir tercihine yerleşemediysen bunu da açıkça belirtir.
## Ekranı gördüğün an: iki senaryo
```mermaid
%% aria: Yerleştirme sonucu açıklandıktan sonraki akış şeması. Sonuç ekranında iki ihtimal vardır. Bir programa yerleştiysen önce yerleştiğin satırın hangi tercihin olduğuna bakarsın; ardından kayıt penceresini kaçırmaman gerekir: 24 ile 26 Ağustos arasında e-Devlet üzerinden belgesiz e-kayıt, 24 ile 28 Ağustos arasında üniversitede şahsen kayıt yapılabilir. Kayıt yaptırırsan öğrencilik başlar. Kayıt yaptırmazsan bu yılki hakkın yanar ve yerleştiğin için ek yerleştirmeye de başvuramazsın; bu kutu kırmızı ile işaretlidir. Hiçbir programa yerleşemediysen yol kapanmaz: Eylül'de açılan ek yerleştirme dönemine yalnızca boşta kalanlar başvurabilir ve bu ikinci şans yeşil ile işaretlidir. Ek yerleştirme hazırlığına sonuç gününden itibaren başlamak gerekir.
%% altyazi: Yerleşenin tek riski kaydı kaçırmak; yerleşemeyenin tek işi ek yerleştirmeye hazırlanmak.
flowchart TD
S("Sonuç ekranııldı") --> Q{"Bir programa<br/>yerleştin mi?"}
Q -- "evet" --> Y("Yerleştiğin satırı oku:<br/>kaçıncı tercihindi,<br/>program kodu ne")
Y --> K{"Kayıt penceresi:<br/>e-kayıt 24-26 Ağustos<br/>şahsen 24-28 Ağustos"}
K -- "kayıt yaptırdın" --> O("Öğrenciliğin başladı")
K -- "kayıt yaptırmadın" --> H("Bu yılki hak yandı:<br/>ek yerleştirmeye de<br/>başvuramazsın")
Q -- "hayır" --> E("Ek yerleştirme · Eylül<br/>yalnızca boşta kalanlar<br/>başvurabilir")
E --> P("Hazırlığa bugün başla:<br/>sıralamana uygun<br/>alternatif havuzu çıkar")
classDef uyari fill:#fef3c7,stroke:#f59e0b,color:#92400e
classDef garanti fill:#d1fae5,stroke:#10b981,color:#065f46
classDef hayal fill:#fee2e2,stroke:#ef4444,color:#991b1b
class O,E garanti
class K,P uyari
class H hayal
```
## Yerleştiysen: tek işin kaydı kaçırmamak
Tebrikler — ama süreç kayıtla biter, sonuç ekranıyla değil. Üniversitelerin çoğu e-Devlet üzerinden **belgesiz, yolsuz, sırasız e-kayıt** kabul ediyor; penceresi şahsen kayıttan iki gün önce kapanıyor. Hangi belgelerin gerektiği, e-kaydın adım adım nasıl yapıldığı ve "kayıt yaptırmazsam ne olur" sorusunun net cevabı [üniversite kayıt rehberinde](/rehber/universite-kayit-rehberi-2026).
Kritik uyarı: **yerleştiğin programa kayıt yaptırmazsan ek yerleştirmeye başvuramazsın** ve gelecek yıl OBP katkın yarıya düşer ([OBP nasıl hesaplanır](/rehber/obp-nedir-nasil-hesaplanir)). "Beğenmedim, ek tercihte daha iyisini yazarım" planı bu yüzden çalışmaz — ek yerleştirme kapısı yalnızca **hiçbir programa yerleşememiş** adaylara açıktır.
## Yerleşemediysen: oyun bitmedi, ikinci yarı Eylül'de
Hiçbir tercihe yerleşememek yılın sonu değil. Kayıtlar kapandığında binlerce kontenjan boş kalır ve ÖSYM bunlar için **ek yerleştirme** açar — genellikle Eylül içinde, birkaç günlük dar bir pencereyle. Kimlerin başvurabileceği ve ikinci şansı doğru kullanmanın yolu [ek yerleştirme rehberinde](/rehber/ek-yerlestirme-2026); boşta kalmanın tipik sebepleri ve her birinin çıkış yolu ise [Yerleşemedim, ne yapmalıyım?](/rehber/yerlesemedim-ne-yapmaliyim) yazısında.
Ek yerleştirmede en büyük hata kılavuzu bekleyip son gün panikle liste kurmak. Boş kontenjan kılavuzu yayımlanmadan da hazırlanabilirsin: sıralamana uygun program evrenini şimdiden çıkarır, kılavuz gelince yalnızca kesişimi alırsın.
## Beklerken yapılacak en akıllıca iş
Sonuç ekranıılana kadar elindeki en değerli veri hâlâ **başarı sıralaman**. Hangi senaryoda olursan ol — ek yerleştirme ihtimaline hazırlık ya da sadece "nereye yerleşmiş olabilirim" merakı — [tercih robotuna](/tercih-robotu) sıralamanı girerek sıralamana düşen programları hayal/dengeli/garanti dilimleriyle görebilirsin. Sonuç günü geldiğinde sürpriz değil, doğrulama yaşarsın.

Binary file not shown.

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="210px" height="31px" viewBox="0 0 210 31" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>iyzico_ile_ode_colored_horizontal</title>
<defs>
<linearGradient x1="90.2554899%" y1="50%" x2="0%" y2="50%" id="linearGradient-1">
<stop stop-color="#1E64FF" offset="0%"></stop>
<stop stop-color="#1E64FF" stop-opacity="0" offset="100%"></stop>
</linearGradient>
</defs>
<g id="05---Landings" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="004.0.0-Brand-Kit" transform="translate(-1015.000000, -1867.000000)">
<g id="Group-2-Copy" transform="translate(135.000000, 1674.000000)">
<g id="Group-4-Copy-3" transform="translate(800.000000, 83.000000)">
<g id="Group" transform="translate(80.000000, 86.000000)">
<g id="iyzico-ile-Ode_OneLine_TR" transform="translate(0.000000, 24.452381)">
<g id="Group-3" transform="translate(-0.000000, 3.761905)" fill="url(#linearGradient-1)">
<path d="M2.8125,0 C1.25925,0 0,1.26299683 0,2.82142857 L0,19.75 C0,21.3084317 1.25925,22.5714286 2.8125,22.5714286 L24.0455,22.5714286 C25.09425,22.5714286 26.05575,21.986327 26.54,21.052873 L30.9325,12.5888381 C31.1405,12.1875683 31.24625,11.7489302 31.25,11.3097905 L31.25,11.2616381 C31.24625,10.8224984 31.1405,10.3838603 30.9325,9.98284127 L26.54,1.51855556 C26.05575,0.585352381 25.09425,0 24.0455,0 L2.8125,0 Z" id="Fill-1"></path>
</g>
<g id="Group-21" transform="translate(39.814286, 0.000000)" fill="#1E64FF">
<path d="M20.3121807,8.01261426 C20.8578941,6.95906777 22.1521777,6.54811119 23.2041402,7.09665045 C24.2564599,7.64626456 24.6648521,8.94595505 24.116103,10.0014721 L24.116103,10.0014721 L14.334155,28.9375416 C13.9659413,29.6455191 13.2602282,30.0614917 12.5195152,30.0926628 C12.1554086,30.1105772 11.7832664,30.0355158 11.4379098,29.8522514 C10.3857687,29.3044287 9.97773368,28.0047382 10.5248756,26.949042 L10.5248756,26.949042 L13.2909424,21.5890503 L7.30005957,10.0014721 C6.75291762,8.94595505 7.16380979,7.64626456 8.2147009,7.09665045 C9.26541343,6.54811119 10.562197,6.95906777 11.1070175,8.01261426 L11.1070175,8.01261426 L15.7093313,16.9135683 Z M59.6828651,6.56683175 C62.0035755,6.56683175 64.185179,7.47330093 65.8267835,9.12035184 C66.6614249,9.96143342 66.6614249,11.3241826 65.8267835,12.1654434 C64.9873206,13.0040169 63.6285728,13.0040169 62.7914314,12.1654434 C61.959647,11.3306318 60.8589346,10.8734561 59.6828651,10.8734561 C58.5100099,10.8734561 57.4075118,11.3306318 56.5780489,12.1654434 C55.7476931,12.9948806 55.2914439,14.1028869 55.2914439,15.2809385 C55.2914439,16.4579153 55.7476931,17.5646676 56.5780489,18.3978668 C57.4075118,19.2276623 58.5100099,19.6914664 59.6828651,19.6914664 C60.8589346,19.6914664 61.959647,19.2276623 62.7914314,18.3978668 C63.6285728,17.5558895 64.9873206,17.5558895 65.8267835,18.3978668 C66.6614249,19.238411 66.6614249,20.6020559 65.8267835,21.4391963 C64.185179,23.0866055 62.0035755,23.9945078 59.6828651,23.9945078 C57.3650118,23.9945078 55.1844798,23.0866055 53.5441254,21.4391963 C51.9035924,19.7971614 50.9994867,17.6073039 50.9994867,15.2809385 C50.9994867,12.9520651 51.9035924,10.7674028 53.5441254,9.12035184 C55.1844798,7.47330093 57.3650118,6.56683175 59.6828651,6.56683175 Z M77.0293898,6.56677801 C81.8183104,6.56677801 85.7141967,10.4755233 85.7141967,15.2808848 C85.7141967,20.0853505 81.8183104,23.9946332 77.0293898,23.9946332 C72.2424335,23.9946332 68.3470828,20.0853505 68.3470828,15.2808848 C68.3470828,10.4755233 72.2424335,6.56677801 77.0293898,6.56677801 Z M2.36954993,6.85444761 C3.55436938,6.85444761 4.5149035,7.81824291 4.5149035,9.0038186 L4.5149035,9.0038186 L4.5149035,21.5929915 C4.5149035,22.7826875 3.55436938,23.7439748 2.36954993,23.7439748 C1.18473047,23.7439748 0.223839218,22.7826875 0.223839218,21.5929915 L0.223839218,21.5929915 L0.223839218,9.0038186 C0.223839218,7.81824291 1.18473047,6.85444761 2.36954993,6.85444761 Z M38.4392951,6.85393075 C38.4637799,6.85397691 38.4845421,6.85402542 38.5013999,6.85407635 L38.5542396,6.85441179 C39.0669173,6.83685566 39.584595,7.00095166 40.0072729,7.35816501 C40.9137,8.11827345 41.0322712,9.47815636 40.2722725,10.387671 L40.2722725,10.387671 L32.6990708,19.4394644 L38.6254895,19.4394644 C39.8112018,19.4394644 40.7731645,20.4057677 40.7731645,21.5929557 C40.7731645,22.7828308 39.8112018,23.743939 38.6254895,23.743939 L38.6254895,23.743939 L28.0990785,23.743939 C27.6122936,23.743939 27.1215801,23.5814553 26.7197951,23.2403649 C25.813368,22.4781067 25.6937253,21.1218067 26.4560455,20.2090675 L26.4560455,20.2090675 L34.0279972,11.1603195 L29.0778268,11.1603195 C27.8915788,11.1603195 26.9324733,10.1940162 26.9324733,9.00396191 C26.9324733,7.81820708 27.8915788,6.85441179 29.0778268,6.85441179 L29.0778268,6.85441179 L30.3890569,6.85405101 C30.4611906,6.85403248 30.535603,6.85401357 30.6121121,6.85399436 Z M46.0431021,6.85444761 C47.230243,6.85444761 48.1884557,7.81824291 48.1884557,9.0038186 L48.1884557,9.0038186 L48.1884557,21.5929915 C48.1884557,22.7826875 47.230243,23.7439748 46.0431021,23.7439748 C44.8593541,23.7439748 43.9002486,22.7826875 43.9002486,21.5929915 L43.9002486,21.5929915 L43.9002486,9.0038186 C43.9002486,7.81824291 44.8593541,6.85444761 46.0431021,6.85444761 Z M77.0293898,10.8734023 C74.6077867,10.8734023 72.63904,12.8500784 72.63904,15.2808848 C72.63904,17.7109746 74.6077867,19.6914127 77.0293898,19.6914127 C79.4526,19.6914127 81.4242039,17.7109746 81.4242039,15.2808848 C81.4242039,12.8500784 79.4526,10.8734023 77.0293898,10.8734023 Z M2.36954993,-2.34732868e-15 C3.67776203,-2.34732868e-15 4.74008169,1.06519085 4.74008169,2.37921287 C4.74008169,3.69054773 3.67776203,4.75770917 2.36954993,4.75770917 C1.06133782,4.75770917 -8.89798461e-05,3.69054773 -8.89798461e-05,2.37921287 C-8.89798461e-05,1.06519085 1.06133782,-2.34732868e-15 2.36954993,-2.34732868e-15 Z M46.0431021,-2.34732868e-15 C47.3538142,-2.34732868e-15 48.4150625,1.06519085 48.4150625,2.37921287 C48.4150625,3.69054773 47.3538142,4.75770917 46.0431021,4.75770917 C44.7334615,4.75770917 43.6722132,3.69054773 43.6722132,2.37921287 C43.6722132,1.06519085 44.7334615,-2.34732868e-15 46.0431021,-2.34732868e-15 Z" id="Combined-Shape"></path>
</g>
<path d="M171.256395,5.40878903 C175.631543,5.40878903 179.956126,9.08690718 179.956126,14.4030587 C179.956126,19.7192102 175.631543,23.3954641 171.256395,23.3954641 C166.881864,23.3954641 162.557281,19.7192102 162.557281,14.4030587 C162.557281,9.08690718 166.881864,5.40878903 171.256395,5.40878903 Z M193.107162,5.23261966 C193.831728,5.23261966 194.457014,5.83787174 194.457014,6.56802698 L194.457014,22.0355112 C194.457014,22.7669092 193.831728,23.3709185 193.107162,23.3709185 C192.381979,23.3709185 191.781976,22.7669092 191.781976,22.0355112 L191.781976,20.6752475 C191.607463,21.808697 190.056583,23.3951534 187.681855,23.3951534 C184.907536,23.3951534 181.583163,21.2289227 181.583163,16.6708898 C181.583163,12.1855617 184.932202,9.96899684 187.681855,9.96899684 C189.531811,9.96899684 191.132024,10.9272091 191.781976,12.3868981 L191.781976,11.3795946 C191.781976,11.3044042 191.781976,11.2031145 191.807259,11.1279241 L191.807259,6.56802698 C191.807259,5.83787174 192.407878,5.23261966 193.107162,5.23261966 Z M135.174825,10.0454301 C135.724262,10.0454301 136.199084,10.5239148 136.199084,11.1036891 L136.199084,22.2871817 C136.199084,22.8930552 135.724262,23.3709185 135.174825,23.3709185 C134.600105,23.3709185 134.124666,22.8930552 134.124666,22.2871817 L134.124666,11.1036891 C134.124666,10.5239148 134.600105,10.0454301 135.174825,10.0454301 Z M140.024302,5.23274394 C140.574973,5.23274394 141.049795,5.71247146 141.049795,6.26614662 L141.049795,22.287306 C141.049795,22.8416026 140.574973,23.3449436 140.024302,23.3449436 C139.448966,23.3449436 138.97476,22.8416026 138.97476,22.287306 L138.97476,6.26614662 C138.97476,5.71247146 139.448966,5.23274394 140.024302,5.23274394 Z M149.70007,9.96912113 C152.549621,9.96912113 155.973274,12.2366415 155.973274,16.3180539 C155.973274,17.1227782 155.498452,17.5018375 154.649322,17.5018375 L145.450103,17.4515034 C145.674564,20.2726989 147.824831,21.6074849 149.974481,21.6074849 C151.123921,21.6074849 152.048899,21.2800025 152.873363,20.6492726 C153.248904,20.3982235 153.473983,20.2726989 153.748393,20.2726989 C154.224448,20.2726989 154.523525,20.6001813 154.523525,21.0531883 C154.523525,21.3303366 154.398961,21.6074849 153.973471,21.9853014 C152.973877,22.8664589 151.500078,23.3449436 149.923915,23.3449436 C146.724106,23.3449436 143.300453,21.229047 143.300453,16.695249 C143.300453,12.1614511 146.849903,9.96912113 149.70007,9.96912113 Z M203.182706,9.96899684 C206.032256,9.96899684 209.457143,12.2365172 209.457143,16.2414964 C209.457143,17.274899 208.881806,17.6781933 207.806981,17.6781933 L199.432843,17.6520941 C199.707253,19.994805 201.532544,21.1543536 203.532347,21.1543536 C204.55784,21.1543536 205.257741,20.9026831 206.032256,20.4241984 C206.457129,20.1973842 206.732156,20.0209041 207.081798,20.0209041 C207.657135,20.0209041 208.007393,20.4241984 208.007393,20.9778736 C208.007393,21.3059773 207.882213,21.6583161 207.332159,22.1107016 C206.382514,22.8930552 205.006763,23.3448193 203.431833,23.3448193 C200.183309,23.3448193 196.708473,21.2550218 196.708473,16.6951247 C196.708473,12.1613268 200.333155,9.96899684 203.182706,9.96899684 Z M188.107345,12.2607522 C186.28267,12.2607522 184.232301,13.6980705 184.232301,16.6708898 C184.232301,19.667944 186.28267,21.1033981 188.107345,21.1033981 C189.807455,21.1033981 191.907156,19.7934685 191.907156,16.6708898 C191.907156,13.5215904 189.807455,12.2607522 188.107345,12.2607522 Z M171.256395,7.97893545 C168.206433,7.97893545 165.382165,10.5745596 165.382165,14.4030587 C165.382165,18.2315578 168.206433,20.8010828 171.256395,20.8010828 C174.306357,20.8010828 177.131242,18.2315578 177.131242,14.4030587 C177.131242,10.5745596 174.331024,7.97893545 171.256395,7.97893545 Z M149.70007,11.8091123 C147.849497,11.8091123 145.674564,13.067465 145.450103,15.8886605 L153.948805,15.8886605 C153.724343,13.067465 151.574077,11.8091123 149.70007,11.8091123 Z M203.182706,12.2868513 C201.532544,12.2868513 199.657921,13.3451104 199.432843,15.7381553 L206.882003,15.7381553 C206.657541,13.3451104 204.85815,12.2868513 203.182706,12.2868513 Z M135.174825,4.98094913 C135.724262,4.98094913 136.224367,5.45943384 136.224367,6.03920816 C136.224367,6.61836108 135.724262,7.0974672 135.174825,7.0974672 C134.600105,7.0974672 134.1,6.61836108 134.1,6.03920816 C134.1,5.45943384 134.600105,4.98094913 135.174825,4.98094913 Z M174.331024,0.824035565 C175.130822,0.824035565 175.780773,1.47900034 175.780773,2.26135391 C175.780773,3.04184325 175.130822,3.69680803 174.331024,3.69680803 C173.531225,3.69680803 172.881274,3.04184325 172.881274,2.26135391 C172.881274,1.47900034 173.531225,0.824035565 174.331024,0.824035565 Z M168.182383,0.824035565 C168.981565,0.824035565 169.6309,1.47900034 169.6309,2.26135391 C169.6309,3.04184325 168.981565,3.69680803 168.182383,3.69680803 C167.381969,3.69680803 166.732017,3.04184325 166.732017,2.26135391 C166.732017,1.47900034 167.381969,0.824035565 168.182383,0.824035565 Z" id="Combined-Shape" fill="#495057"></path>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="429px" height="32px" viewBox="0 0 429 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>logo-band</title>
<defs>
<radialGradient cx="17.5632448%" cy="17.5027381%" fx="17.5632448%" fy="17.5027381%" r="134.355667%" gradientTransform="translate(0.175632,0.175027),scale(0.694444,1.000000),translate(-0.175632,-0.175027)" id="radialGradient-1">
<stop stop-color="#9DD5F6" offset="0%"></stop>
<stop stop-color="#98D3F5" offset="7.11%"></stop>
<stop stop-color="#89CEF3" offset="15.75%"></stop>
<stop stop-color="#70C6EF" offset="25.16%"></stop>
<stop stop-color="#4EBBEA" offset="35.14%"></stop>
<stop stop-color="#23ADE3" offset="45.46%"></stop>
<stop stop-color="#0DA6E0" offset="50%"></stop>
<stop stop-color="#2E77BC" offset="100%"></stop>
</radialGradient>
<linearGradient x1="-0.000322580643%" y1="49.995%" x2="99.9996774%" y2="49.995%" id="linearGradient-2">
<stop stop-color="#1D1C45" offset="0%"></stop>
<stop stop-color="#174489" offset="100%"></stop>
</linearGradient>
<linearGradient x1="90.2554899%" y1="50%" x2="0%" y2="50%" id="linearGradient-3">
<stop stop-color="#1E64FF" offset="0%"></stop>
<stop stop-color="#1E64FF" stop-opacity="0" offset="100%"></stop>
</linearGradient>
</defs>
<g id="👉Method-View" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="PWI_Method_02_FooterAlternatives" transform="translate(-746.000000, -914.000000)">
<g id="logo-band-iyzicoileode" transform="translate(746.000000, 914.000000)">
<g id="Troy-Logo" transform="translate(370.000000, 3.000000)">
<g id="Group_1_" transform="translate(1.437077, 22.724340)" fill="#323E48">
<path d="M0.8,0.7 L0.4,0.7 C0.4,0.7 0.3,0.7 0.3,0.8 L0.3,0.9 L0.1,0.9 L0.1,0.7 C0.1,0.6 0.2,0.5 0.3,0.5 L1.6,0.5 C1.7,0.5 1.8,0.5 1.7,0.7 L1.7,0.9 L1.5,0.9 L1.5,0.8 C1.5,0.8 1.5,0.7 1.5,0.7 L1,0.7 L0.6,2.5 L0.4,2.5 L0.8,0.7 Z" id="Path_6_"></path>
<path d="M1.8,1.3 C1.8,1.3 1.7,1.2 1.8,1.3 L1.6,1.2 L1.6,1 L1.8,1 C1.9,1 2,1 1.9,1.2 L1.8,1.9 C1.8,2.1 1.8,2.3 2,2.3 C2.3,2.3 2.6,2 2.6,1.7 L2.7,1 L3,1 L2.7,2.2 C2.7,2.2 2.7,2.3 2.7,2.3 L2.8,2.3 L2.8,2.5 L2.7,2.5 C2.6,2.5 2.5,2.4 2.6,2.3 L2.6,2.2 C2.6,2.1 2.6,2.1 2.6,2.1 L2.6,2.1 C2.5,2.3 2.3,2.5 2,2.5 C1.6,2.5 1.5,2.3 1.6,2 L1.8,1.3 Z M2.2,0.5 L2.4,0.5 L2.3,0.8 L2.2,0.8 L2.2,0.5 Z M2.6,0.5 L2.8,0.5 L2.8,0.8 L2.6,0.8 L2.6,0.5 Z" id="Shape_5_" fill-rule="nonzero"></path>
<path d="M3.4,1.3 C3.4,1.3 3.4,1.2 3.4,1.3 L3.3,1.2 L3.3,1 L3.5,1 C3.6,1 3.7,1 3.6,1.2 L3.6,1.3 C3.6,1.4 3.6,1.4 3.6,1.4 L3.6,1.4 C3.7,1.2 3.9,1 4.1,1 C4.1,1 4.2,1 4.2,1 L4.2,1.2 C4.2,1.2 4.2,1.2 4.1,1.2 C3.9,1.2 3.7,1.4 3.6,1.6 C3.6,1.7 3.5,1.8 3.5,1.9 L3.4,2.5 L3.2,2.5 L3.4,1.3 Z" id="Path_7_"></path>
<path d="M4.5,0.8 C4.5,0.7 4.5,0.7 4.5,0.8 L4.3,0.7 L4.3,0.5 L4.5,0.5 C4.6,0.5 4.7,0.5 4.6,0.7 L4.5,1.6 L4.6,1.6 C4.7,1.6 4.7,1.6 4.8,1.5 L5.3,1 L5.5,1 L5,1.6 C4.9,1.7 4.9,1.7 4.9,1.7 L4.9,1.7 C4.9,1.7 4.9,1.7 5,1.8 L5.1,2.3 C5.1,2.3 5.1,2.4 5.2,2.4 L5.3,2.4 L5.3,2.6 L5.1,2.6 C5,2.6 4.9,2.6 4.9,2.5 L4.7,1.8 C4.7,1.7 4.6,1.7 4.6,1.7 L4.5,1.7 L4.3,2.5 L4.1,2.5 L4.5,0.8 Z" id="Path_8_"></path>
<path d="M6.1,1.3 C6.1,1.3 6.1,1.2 6,1.2 L5.7,1.2 L5.8,1 L6.3,1 C6.6,1 6.7,1 6.6,1.2 L6,2.2 C6,2.2 6,2.3 6.1,2.3 L6.4,2.3 L6.3,2.5 L5.9,2.5 C5.6,2.5 5.5,2.4 5.6,2.3 L6.1,1.3 Z M6.5,0.5 L6.9,0.5 L6.8,0.8 L6.4,0.8 L6.5,0.5 Z" id="Shape_6_" fill-rule="nonzero"></path>
<path d="M6.2,2.9 C6.3,2.9 6.4,2.8 6.5,2.7 L6.6,2.5 L6.4,1.3 C6.4,1.3 6.4,1.2 6.3,1.2 L6.3,1.2 L6.3,1 L6.4,1 C6.5,1 6.6,1 6.6,1.1 L6.8,2 C6.8,2.1 6.8,2.2 6.8,2.2 L6.8,2.2 C6.8,2.2 6.8,2.1 6.9,2 L7.4,1.1 C7.5,1 7.5,1 7.6,1 L7.7,1 L7.7,1.2 L7.7,1.2 C7.7,1.2 7.6,1.2 7.6,1.3 L6.6,2.8 C6.5,2.9 6.3,3 6.1,3 C5.9,3 5.9,2.9 5.9,2.9 L6,2.8 C6,2.8 6,2.9 6.2,2.9" id="Path_9_"></path>
<path d="M8.5,1 C8.9,1 9.1,1.3 9,1.7 C9,1.7 9,1.8 9,1.8 L7.9,1.8 C7.8,2.1 8,2.3 8.3,2.3 C8.6,2.3 8.8,2.1 8.8,2.1 L8.9,2.2 C8.9,2.2 8.7,2.4 8.3,2.4 C7.9,2.4 7.6,2.1 7.7,1.7 C7.8,1.3 8.1,1 8.5,1 Z M8.8,1.6 C8.9,1.3 8.7,1.2 8.5,1.2 C8.3,1.2 8,1.4 7.9,1.6 L8.8,1.6 Z" id="Shape_7_" fill-rule="nonzero"></path>
<polygon id="Path_10_" points="9.5 0.5 9.7 0.5 9.5 1 9.3 1"></polygon>
<path d="M9.9,1.3 C9.9,1.3 9.9,1.2 9.9,1.3 L9.8,1.2 L9.8,1 L10,1 C10.1,1 10.2,1 10.1,1.2 L10.1,1.3 C10.1,1.4 10.1,1.4 10.1,1.4 L10.1,1.4 C10.2,1.3 10.4,1 10.7,1 C11.1,1 11.1,1.2 11.1,1.5 L11,2.2 C11,2.2 11,2.3 11,2.3 L11.1,2.3 L11.1,2.5 L10.9,2.5 C10.8,2.5 10.7,2.4 10.8,2.3 L10.9,1.6 C10.9,1.4 11,1.2 10.7,1.2 C10.5,1.2 10.2,1.4 10.1,1.6 C10.1,1.7 10.1,1.7 10,1.8 L9.9,2.5 L9.7,2.5 L9.9,1.3 Z" id="Path_11_"></path>
<path d="M11.9,1.3 C11.9,1.3 11.9,1.2 11.8,1.2 L11.5,1.2 L11.6,1 L12.1,1 C12.4,1 12.5,1 12.4,1.2 L11.9,2.2 C11.9,2.2 11.9,2.3 12,2.3 L12.3,2.3 L12.2,2.5 L11.7,2.5 C11.4,2.5 11.3,2.4 11.4,2.3 L11.9,1.3 Z M12.3,0.5 L12.7,0.5 L12.6,0.7 L12.2,0.7 L12.3,0.5 Z" id="Shape_8_" fill-rule="nonzero"></path>
<path d="M12.3,1.3 C12.3,1.3 12.3,1.2 12.3,1.3 L12.1,1.2 L12.1,1 L12.3,1 C12.4,1 12.5,1 12.4,1.2 L12.4,1.3 C12.4,1.4 12.4,1.4 12.4,1.4 L12.4,1.4 C12.5,1.3 12.8,1 13.1,1 C13.4,1 13.5,1.2 13.4,1.5 L13.3,2.2 C13.3,2.2 13.3,2.3 13.3,2.3 L13.4,2.3 L13.4,2.5 L13.2,2.5 C13.1,2.5 13,2.4 13.1,2.3 L13.2,1.6 C13.2,1.4 13.3,1.2 13,1.2 C12.8,1.2 12.5,1.4 12.4,1.6 C12.4,1.7 12.4,1.7 12.3,1.8 L12.1,2.5 L12,2.5 L12.3,1.3 Z" id="Path_12_"></path>
<path d="M15.8,0.5 C16.4,0.5 16.7,0.9 16.6,1.5 C16.5,2.1 15.9,2.5 15.4,2.5 C14.8,2.5 14.5,2.1 14.6,1.5 C14.7,0.9 15.2,0.5 15.8,0.5 Z M15.3,2.3 C15.7,2.3 16.2,1.9 16.3,1.5 C16.4,1 16.1,0.7 15.7,0.7 C15.3,0.7 14.8,1.1 14.7,1.5 C14.6,2 14.9,2.3 15.3,2.3 Z M15.6,0.1 L15.8,0.1 L15.7,0.3 L15.5,0.3 L15.6,0.1 Z M16,0.1 L16.2,0.1 L16.1,0.3 L15.9,0.3 L16,0.1 Z" id="Shape_9_" fill-rule="nonzero"></path>
<path d="M17.5,1 C17.8,1 17.9,1.3 17.9,1.3 L17.9,1.3 C17.9,1.3 17.9,1.3 17.9,1.2 L18,0.7 C18,0.7 18,0.6 18,0.6 L17.9,0.6 L17.9,0.4 L18.1,0.4 C18.2,0.4 18.3,0.4 18.2,0.6 L17.9,2.1 C17.9,2.1 17.9,2.2 17.9,2.2 L18,2.2 L18,2.4 L17.8,2.4 C17.7,2.4 17.6,2.3 17.7,2.2 C17.7,2.1 17.7,2.1 17.7,2.1 L17.7,2.1 C17.7,2.1 17.5,2.4 17.2,2.4 C16.8,2.4 16.7,2.1 16.8,1.7 C16.8,1.3 17.1,1 17.5,1 M17.8,1.8 C17.9,1.5 17.8,1.2 17.5,1.2 C17.3,1.2 17,1.4 16.9,1.8 C16.8,2.1 17,2.4 17.2,2.4 C17.4,2.3 17.7,2.2 17.8,1.8" id="Shape_10_" fill-rule="nonzero"></path>
<path d="M19.2,1 C19.6,1 19.7,1.3 19.7,1.6 C19.7,1.6 19.7,1.7 19.7,1.7 L18.6,1.7 C18.5,2 18.7,2.3 19,2.3 C19.3,2.3 19.5,2.1 19.5,2.1 L19.6,2.2 C19.6,2.2 19.3,2.4 19,2.4 C18.6,2.4 18.3,2.1 18.4,1.7 C18.4,1.3 18.8,1 19.2,1 Z M19.4,1.6 C19.5,1.3 19.3,1.2 19.1,1.2 C18.9,1.2 18.6,1.3 18.5,1.6 L19.4,1.6 Z" id="Shape_11_" fill-rule="nonzero"></path>
<path d="M20,1.3 C20.1,1.3 20,1.2 20,1.3 L19.8,1.2 L19.8,1 L20,1 C20.1,1 20.2,1 20.1,1.2 L20.1,1.3 C20.1,1.4 20.1,1.4 20.1,1.4 L20.1,1.4 C20.3,1.2 20.6,1 20.8,1 C21.1,1 21.2,1.1 21.2,1.3 L21.2,1.3 C21.3,1.2 21.5,1 21.8,1 C22.1,1 22.2,1.2 22.1,1.5 L22,2.2 C22,2.2 22,2.3 22,2.3 L22.1,2.3 L22.1,2.5 L21.9,2.5 C21.8,2.5 21.7,2.4 21.8,2.3 L22,1.6 C22.1,1.4 22,1.2 21.8,1.2 C21.6,1.2 21.4,1.4 21.3,1.6 C21.3,1.7 21.2,1.7 21.2,1.8 L21.1,2.5 L20.9,2.5 L21.1,1.7 C21.1,1.5 21.2,1.3 20.9,1.3 C20.7,1.3 20.5,1.5 20.4,1.7 C20.4,1.8 20.3,1.8 20.3,1.9 L20,2.5 L19.8,2.5 L20,1.3 Z" id="Path_13_"></path>
<path d="M23.3,1 C23.7,1 23.8,1.3 23.8,1.6 C23.8,1.6 23.8,1.7 23.8,1.7 L22.7,1.7 C22.6,2 22.8,2.3 23.1,2.3 C23.4,2.3 23.6,2.1 23.6,2.1 L23.7,2.2 C23.7,2.2 23.4,2.4 23.1,2.4 C22.7,2.4 22.4,2.1 22.5,1.7 C22.5,1.3 22.9,1 23.3,1 Z M23.5,1.6 C23.6,1.3 23.4,1.2 23.2,1.2 C23,1.2 22.7,1.3 22.6,1.6 L23.5,1.6 Z" id="Shape_12_" fill-rule="nonzero"></path>
<path d="M25.2,1.6 L24.9,0.7 C24.9,0.6 24.8,0.6 24.8,0.6 L24.8,0.6 L24.8,0.4 L24.9,0.4 C25,0.4 25.1,0.4 25.1,0.6 L25.3,1.1 C25.3,1.2 25.4,1.3 25.4,1.3 L25.4,1.3 C25.4,1.3 25.5,1.2 25.6,1.1 L26,0.7 C26.1,0.6 26.2,0.5 26.3,0.5 L26.4,0.5 L26.4,0.7 L26.4,0.7 C26.3,0.7 26.3,0.7 26.3,0.8 L25.6,1.7 L25.4,2.5 L25,2.5 L25.2,1.6 Z" id="Path_14_"></path>
<path d="M26.9,1 C27.3,1 27.6,1.3 27.5,1.7 C27.4,2.1 27,2.4 26.6,2.4 C26.2,2.4 25.9,2.1 26,1.7 C26.1,1.4 26.5,1 26.9,1 Z M26.6,2.3 C26.9,2.3 27.2,2.1 27.3,1.7 C27.4,1.4 27.2,1.2 26.9,1.2 C26.6,1.2 26.3,1.4 26.2,1.7 C26.1,2.1 26.3,2.3 26.6,2.3 Z M26.7,0.5 L26.9,0.5 L26.8,0.7 L26.6,0.7 L26.7,0.5 Z M27.1,0.5 L27.3,0.5 L27.3,0.7 L27.1,0.7 L27.1,0.5 Z" id="Shape_13_" fill-rule="nonzero"></path>
<path d="M27.9,1.3 C27.9,1.3 27.9,1.2 27.9,1.3 L27.7,1.2 L27.7,1 L28,1 C28.1,1 28.2,1 28.1,1.2 L28.1,1.3 C28.1,1.4 28.1,1.4 28.1,1.4 L28.1,1.4 C28.2,1.3 28.4,1 28.7,1 C29,1 29.1,1.2 29,1.5 L28.9,2.2 C28.9,2.2 28.9,2.3 29,2.3 L29.1,2.3 L29.1,2.5 L28.9,2.5 C28.8,2.5 28.7,2.4 28.8,2.3 L28.9,1.6 C28.9,1.4 29,1.2 28.7,1.2 C28.5,1.2 28.2,1.4 28.1,1.6 C28,1.7 28,1.7 28,1.8 L27.9,2.5 L27.7,2.5 L27.9,1.3 Z" id="Path_15_"></path>
<path d="M29.6,1.2 L29.4,1.2 L29.4,1 L29.6,1 L29.7,0.6 L29.9,0.6 L29.8,1 L30.2,1 L30.2,1.2 L29.8,1.2 L29.6,1.9 C29.5,2.2 29.7,2.3 29.8,2.3 C29.8,2.3 29.9,2.3 29.9,2.3 L29.9,2.5 C29.9,2.5 29.9,2.5 29.8,2.5 C29.6,2.5 29.3,2.4 29.4,2 L29.6,1.2 Z" id="Path_16_"></path>
<path d="M31.1,1 C31.5,1 31.6,1.3 31.6,1.6 C31.6,1.6 31.6,1.7 31.6,1.7 L30.5,1.7 C30.4,2 30.6,2.3 30.9,2.3 C31.2,2.3 31.4,2.1 31.4,2.1 L31.5,2.2 C31.5,2.2 31.3,2.4 30.9,2.4 C30.5,2.4 30.2,2.1 30.3,1.7 C30.3,1.3 30.7,1 31.1,1 Z M31.4,1.6 C31.5,1.3 31.3,1.2 31.1,1.2 C30.9,1.2 30.6,1.3 30.5,1.6 L31.4,1.6 Z" id="Shape_14_" fill-rule="nonzero"></path>
<path d="M32,1.3 C32,1.3 32,1.2 32,1.3 L31.8,1.2 L31.8,1 L32,1 C32.1,1 32.2,1 32.1,1.2 L32.1,1.3 C32.1,1.4 32.1,1.4 32.1,1.4 L32.1,1.4 C32.3,1.2 32.5,1 32.7,1 C33,1 33.1,1.1 33.1,1.3 L33.1,1.3 C33.2,1.2 33.5,1 33.7,1 C34,1 34.1,1.2 34,1.6 L33.9,2.3 C33.9,2.3 33.9,2.4 33.9,2.4 L34,2.4 L34,2.6 L33.8,2.6 C33.7,2.6 33.6,2.5 33.7,2.4 L33.8,1.7 C33.9,1.5 33.8,1.3 33.6,1.3 C33.4,1.3 33.2,1.5 33.1,1.7 C33.1,1.8 33,1.8 33,1.9 L32.9,2.6 L32.7,2.6 L32.9,1.8 C32.9,1.6 33,1.4 32.7,1.4 C32.5,1.4 32.3,1.6 32.2,1.8 C32.2,1.9 32.2,1.9 32.1,2 L32,2.7 L31.8,2.7 L32,1.3 Z" id="Path_17_"></path>
<path d="M34.9,1.3 C34.9,1.3 34.9,1.2 34.8,1.2 L34.5,1.2 L34.6,1 L35.1,1 C35.4,1 35.5,1 35.4,1.2 L34.9,2.2 C34.9,2.2 34.9,2.3 35,2.3 L35.3,2.3 L35.2,2.5 L34.7,2.5 C34.4,2.5 34.3,2.4 34.4,2.3 L34.9,1.3 Z M35.3,0.5 L35.7,0.5 L35.6,0.7 L35.2,0.7 L35.3,0.5 Z" id="Shape_15_" fill-rule="nonzero"></path>
</g>
<g id="Group_2_">
<g id="Path_18_">
<path d="M54,4.4 C53.2,4.4 52.3,4.9 51.9,5.7 L48.1,14.4 L46.7,5.7 C46.5,4.9 46,4.4 45.1,4.4 L40.2,4.4 L44.4,18.9 C44.5,19.2 44.5,19.5 44.4,19.8 C44.2,21 43.1,22 41.9,22 L39.2,22 C38.5,22 38.1,22.4 37.8,23.4 L37.2,27 L42,27 C44.5,27 47.4,25.8 49.4,22.3 L59,4.4 L54,4.4 Z" id="Path" fill="#323E48"></path>
<path d="M6.8,0 C8.2,0 8.9,0.6 8.7,2 L8.3,4.3 L11.6,4.3 L10.9,8.4 L7.5,8.4 L6.6,13.5 C6.3,15.3 8,15.5 9.1,15.5 C9.3,15.5 9.5,15.5 9.6,15.5 L8.8,20 C8.6,20 8.3,20.1 7.8,20.1 C5.3,20.1 0.6,19.4 1.5,14.2 L2.5,8.4 L0,8.4 L0.7,4.3 L3.1,4.3 L3.9,0 L6.8,0 Z" id="Path" fill="#323E48"></path>
<g id="Group" transform="translate(24.190798, 4.196481)" fill="#00ADBB">
<path d="M10.4,0.3 L9.5,5.1 C10.5,5.6 11.3,6.7 11.3,7.9 C11.3,9.5 10.1,10.9 8.5,11.1 L7.7,15.9 C7.8,15.9 8,15.9 8.2,15.9 C12.6,15.9 16.2,12.3 16.2,7.9 C16.1,4.4 13.7,1.3 10.4,0.3 Z" id="Path"></path>
<path d="M6.6,10.8 C5.6,10.3 4.8,9.2 4.8,8 C4.8,6.4 6,5 7.6,4.8 L8.5,0 C8.4,0 8.2,0 8,0 C3.6,0 0,3.6 0,8 C0,11.6 2.4,14.6 5.7,15.6 L6.6,10.8 Z" id="Path"></path>
</g>
<path d="M13.5,4.4 L16.3,4.4 C17.7,4.4 18.4,5 18.2,6.4 L17.9,8.1 C19,6 21.2,4.2 23.6,4.2 C23.9,4.2 24.2,4.3 24.2,4.3 L23.3,9.5 C23.3,9.5 22.9,9.4 22.2,9.4 C20.9,9.4 18.8,9.8 17.6,12.1 C17.3,12.7 17.1,13.4 17,14.2 L16,20 L10.7,20 L13.5,4.4 Z" id="Path" fill="#323E48"></path>
</g>
<path d="M59,20.7 C59,21.2 58.8,21.6 58.5,21.9 C58.2,22.2 57.8,22.4 57.3,22.4 C56.8,22.4 56.4,22.2 56.1,21.9 C55.8,21.6 55.6,21.2 55.6,20.7 C55.6,20.2 55.8,19.8 56.1,19.5 C56.4,19.2 56.8,19 57.3,19 C57.8,19 58.2,19.2 58.5,19.5 C58.8,19.8 59,20.2 59,20.7 Z M58.7,20.7 C58.7,20.3 58.6,20 58.3,19.7 C58,19.4 57.7,19.3 57.3,19.3 C56.9,19.3 56.6,19.4 56.3,19.7 C56,20 55.9,20.3 55.9,20.7 C55.9,21.1 56,21.4 56.3,21.7 C56.6,22 56.9,22.1 57.3,22.1 C57.7,22.1 58,22 58.3,21.7 C58.6,21.4 58.7,21.1 58.7,20.7 Z M58,21.6 L57.7,21.6 L57.3,20.8 L57,20.8 L57,21.6 L56.7,21.6 L56.7,19.8 L57.3,19.8 C57.5,19.8 57.6,19.9 57.7,20 C57.8,20.1 57.9,20.2 57.9,20.4 C57.9,20.7 57.8,20.8 57.5,20.9 L58,21.6 Z M57.7,20.3 C57.7,20.2 57.7,20.1 57.6,20.1 C57.5,20.1 57.4,20 57.3,20 L57,20 L57,20.6 L57.3,20.6 C57.4,20.6 57.5,20.6 57.6,20.5 C57.6,20.5 57.7,20.4 57.7,20.3 Z" id="Shape_16_" fill="#323E48" fill-rule="nonzero"></path>
</g>
</g>
<g id="American_Express" transform="translate(294.000000, 0.000000)">
<polyline id="Path_5_" fill="url(#radialGradient-1)" points="43.2 0 0 0 0 30 43.2 30 43.2 20.1"></polyline>
<g id="Group" transform="translate(0.000000, 6.600000)" fill="#FFFFFF" fill-rule="nonzero">
<path d="M3.9,4 L3.1,2 L2.3,4 L3.9,4 Z M22,3.2 C21.8,3.3 21.6,3.3 21.4,3.3 L20,3.3 L20,2.2 L21.5,2.2 C21.7,2.2 21.9,2.2 22.1,2.3 C22.3,2.4 22.4,2.5 22.4,2.7 C22.3,2.9 22.2,3.1 22,3.2 Z M32.4,4 L31.6,2 L30.8,4 L32.4,4 Z M13,6.2 L11.8,6.2 L11.8,2.2 L10.1,6.2 L9,6.2 L7.3,2.2 L7.3,6.2 L4.8,6.2 L4.4,5.1 L1.9,5.1 L1.4,6.2 L0.1,6.2 L2.2,1.1 L4,1.1 L6,5.9 L6,1.1 L8,1.1 L9.6,4.5 L11,1.1 L13,1.1 L13,6.2 Z M17.9,6.2 L13.9,6.2 L13.9,1.1 L17.9,1.1 L17.9,2.2 L15.1,2.2 L15.1,3.1 L17.8,3.1 L17.8,4.1 L15.1,4.1 L15.1,5.1 L17.9,5.1 L17.9,6.2 Z M23.6,2.5 C23.6,3.3 23.1,3.7 22.8,3.8 C23.1,3.9 23.3,4.1 23.4,4.2 C23.6,4.5 23.6,4.7 23.6,5.1 L23.6,6.1 L22.4,6.1 L22.4,5.5 C22.4,5.2 22.4,4.8 22.2,4.5 C22,4.3 21.8,4.3 21.3,4.3 L20,4.3 L20,6.1 L18.8,6.1 L18.8,1.1 L21.6,1.1 C22.2,1.1 22.7,1.1 23.1,1.3 C23.4,1.6 23.6,1.9 23.6,2.5 Z M25.5,6.2 L24.3,6.2 L24.3,1.1 L25.5,1.1 L25.5,6.2 Z M39.7,6.2 L38,6.2 L35.7,2.4 L35.7,6.2 L33.3,6.2 L32.8,5.1 L30.3,5.1 L29.8,6.2 L28.4,6.2 C27.8,6.2 27.1,6.1 26.7,5.6 C26.3,5.2 26.1,4.6 26.1,3.7 C26.1,2.9 26.2,2.3 26.7,1.7 C27.1,1.3 27.7,1.1 28.5,1.1 L29.7,1.1 L29.7,2.2 L28.6,2.2 C28.2,2.2 27.9,2.3 27.7,2.5 C27.5,2.7 27.4,3.1 27.4,3.6 C27.4,4.1 27.5,4.5 27.7,4.8 C27.9,5 28.2,5.1 28.5,5.1 L29,5.1 L30.7,1.1 L32.5,1.1 L34.5,5.9 L34.5,1.1 L36.3,1.1 L38.4,4.6 L38.4,1.1 L39.6,1.1 L39.7,6.2 L39.7,6.2 Z M0.1,7.2 L2.1,7.2 L2.6,6.1 L3.6,6.1 L4.1,7.2 L8.1,7.2 L8.1,6.3 L8.5,7.2 L10.6,7.2 L11,6.3 L11,7.2 L21,7.2 L21,5.4 L21.2,5.4 C21.3,5.4 21.4,5.4 21.4,5.6 L21.4,7.2 L26.6,7.2 L26.6,6.8 C27,7 27.7,7.2 28.5,7.2 L30.7,7.2 L31.2,6.1 L32.2,6.1 L32.7,7.2 L36.9,7.2 L36.9,6.1 L37.5,7.2 L40.9,7.2 L40.9,0.1 L37.6,0.1 L37.6,1 L37,0.1 L33.6,0.1 L33.6,1 L33.2,0.2 L28.6,0.2 C27.8,0.2 27.1,0.3 26.6,0.6 L26.6,0.1 L23.4,0.1 L23.4,0.5 C23,0.2 22.5,0.1 22,0.1 L10.3,0.1 L9.6,1.9 L8.8,0.1 L5.1,0.1 L5.1,1 L4.7,0.1 L1.6,0.1 L0.1,3.5 L0.1,7.2 Z" id="Shape_3_"></path>
<path d="M42.8,10.9 L40.6,10.9 C40.4,10.9 40.2,10.9 40.1,11 C40,11.1 39.9,11.2 39.9,11.4 C39.9,11.6 40,11.7 40.2,11.8 C40.3,11.8 40.5,11.9 40.7,11.9 L41.3,11.9 C42,11.9 42.4,12 42.7,12.3 C42.7,12.3 42.8,12.4 42.8,12.4 L42.8,10.9 Z M42.8,14.3 C42.5,14.7 41.9,14.9 41.2,14.9 L38.9,14.9 L38.9,13.8 L41.2,13.8 C41.4,13.8 41.6,13.8 41.7,13.7 C41.8,13.6 41.8,13.5 41.8,13.4 C41.8,13.3 41.7,13.1 41.7,13.1 C41.6,13 41.5,13 41.3,13 C40.2,13 38.8,13 38.8,11.4 C38.8,10.7 39.3,9.9 40.5,9.9 L42.9,9.9 L42.9,8.9 L40.7,8.9 C40,8.9 39.5,9.1 39.2,9.3 L39.2,8.8 L35.9,8.8 C35.4,8.8 34.8,8.9 34.5,9.2 L34.5,8.8 L28.6,8.8 L28.6,9.2 C28.1,8.9 27.3,8.8 27,8.8 L23,8.8 L23,9.2 C22.6,8.8 21.8,8.8 21.3,8.8 L17,8.8 L16,9.9 L15.1,8.8 L8.6,8.8 L8.6,15.9 L14.9,15.9 L15.9,14.8 L16.9,15.9 L20.8,15.9 L20.8,14.2 L21.2,14.2 C21.7,14.2 22.3,14.2 22.9,14 L22.9,15.9 L26.1,15.9 L26.1,14.1 L26.3,14.1 C26.5,14.1 26.5,14.1 26.5,14.3 L26.5,15.9 L36.3,15.9 C36.9,15.9 37.6,15.7 37.9,15.4 L37.9,15.9 L41,15.9 C41.6,15.9 42.3,15.8 42.8,15.6 L42.8,14.3 L42.8,14.3 Z M38,12.2 C38.2,12.4 38.4,12.8 38.4,13.3 C38.4,14.4 37.7,14.9 36.5,14.9 L34.2,14.9 L34.2,13.8 L36.5,13.8 C36.7,13.8 36.9,13.8 37,13.7 C37.1,13.6 37.1,13.5 37.1,13.4 C37.1,13.3 37,13.1 37,13.1 C36.9,13 36.8,13 36.6,13 C35.5,13 34.1,13 34.1,11.4 C34.1,10.7 34.6,9.9 35.8,9.9 L38.2,9.9 L38.2,11 L36,11 C35.8,11 35.6,11 35.5,11.1 C35.4,11.2 35.3,11.3 35.3,11.5 C35.3,11.7 35.4,11.8 35.6,11.9 C35.7,11.9 35.9,12 36.1,12 L36.7,12 C37.3,11.8 37.7,12 38,12.2 Z M27.2,11.9 C27,12 26.8,12 26.6,12 L25.1,12 L25.1,10.9 L26.6,10.9 C26.8,10.9 27,10.9 27.2,11 C27.4,11.1 27.4,11.2 27.4,11.5 C27.4,11.7 27.3,11.8 27.2,11.9 Z M27.9,12.5 C28.2,12.6 28.4,12.8 28.5,12.9 C28.7,13.1 28.7,13.4 28.7,13.8 L28.7,14.8 L27.5,14.8 L27.5,14.2 C27.5,13.9 27.5,13.4 27.3,13.2 C27.1,13 26.9,13 26.4,13 L25.1,13 L25.1,14.8 L23.9,14.8 L23.9,9.8 L26.7,9.8 C27.3,9.8 27.7,9.8 28.1,10 C28.5,10.2 28.7,10.5 28.7,11.1 C28.7,12 28.2,12.4 27.9,12.5 Z M29.4,9.8 L33.4,9.8 L33.4,10.8 L30.6,10.8 L30.6,11.7 L33.3,11.7 L33.3,12.7 L30.6,12.7 L30.6,13.7 L33.4,13.7 L33.4,14.8 L29.4,14.8 L29.4,9.8 Z M21.3,12.2 L19.8,12.2 L19.8,10.9 L21.4,10.9 C21.8,10.9 22.1,11.1 22.1,11.5 C22.1,11.9 21.8,12.2 21.3,12.2 Z M18.6,14.4 L16.8,12.3 L18.6,10.3 L18.6,14.4 Z M13.8,13.8 L10.9,13.8 L10.9,12.8 L13.5,12.8 L13.5,11.8 L10.9,11.8 L10.9,10.9 L13.9,10.9 L15.2,12.4 L13.8,13.8 Z M23.3,11.5 C23.3,12.9 22.3,13.2 21.2,13.2 L19.7,13.2 L19.7,14.9 L17.4,14.9 L16,13.2 L14.5,14.9 L9.7,14.9 L9.7,9.8 L14.5,9.8 L16,11.5 L17.5,9.8 L21.3,9.8 C22.3,9.8 23.3,10.1 23.3,11.5 Z" id="Shape_4_"></path>
</g>
</g>
<g id="visa" transform="translate(200.000000, 4.000000)" fill="url(#linearGradient-2)">
<path d="M30.7,0.4 L26.6,19.7 L21.6,19.7 L25.7,0.4 L30.7,0.4 Z M51.8,12.8 L54.4,5.5 L55.9,12.8 L51.8,12.8 Z M57.4,19.7 L62,19.7 L58,0.4 L53.7,0.4 C52.7,0.4 51.9,1 51.6,1.8 L44,19.7 L49.3,19.7 L50.3,16.8 L56.7,16.8 L57.4,19.7 Z M44.3,13.4 C44.3,8.3 37.2,8 37.3,5.7 C37.3,5 38,4.3 39.4,4.1 C40.1,4 42.1,3.9 44.3,5 L45.2,0.9 C44,0.4 42.5,0 40.5,0 C35.5,0 32.1,2.6 32,6.4 C32,9.2 34.5,10.7 36.4,11.7 C38.4,12.6 39,13.3 39,14.1 C39,15.4 37.4,16 36,16 C33.5,16 32,15.3 30.9,14.8 L30,19 C31.2,19.5 33.3,20 35.6,20 C40.8,20 44.3,17.4 44.3,13.4 Z M23.5,0.4 L15.4,19.7 L10.1,19.7 L6.1,4.3 C5.9,3.3 5.6,3 4.9,2.6 C3.7,1.9 1.7,1.3 0,1 L0.1,0.4 L8.6,0.4 C9.7,0.4 10.7,1.1 10.9,2.4 L13,13.6 L18.2,0.4 L23.5,0.4 Z" id="Fill-1"></path>
</g>
<g id="mastercard-seeklogo.com" transform="translate(129.000000, 0.000000)">
<path d="M7.1,29.9 L7.1,27.9 C7.1,27.1 6.6,26.6 5.8,26.6 C5.4,26.6 5,26.7 4.7,27.2 C4.5,26.8 4.1,26.6 3.6,26.6 C3.3,26.6 2.9,26.7 2.7,27.1 L2.7,26.7 L2,26.7 L2,29.9 L2.7,29.9 L2.7,28.1 C2.7,27.5 3,27.3 3.5,27.3 C4,27.3 4.2,27.6 4.2,28.1 L4.2,29.9 L4.9,29.9 L4.9,28.1 C4.9,27.5 5.2,27.3 5.7,27.3 C6.2,27.3 6.4,27.6 6.4,28.1 L6.4,29.9 L7.1,29.9 Z M17.5,26.7 L16.4,26.7 L16.4,25.7 L15.7,25.7 L15.7,26.7 L15,26.7 L15,27.3 L15.6,27.3 L15.6,28.8 C15.6,29.5 15.9,30 16.7,30 C17,30 17.3,29.9 17.6,29.8 L17.4,29.2 C17.2,29.3 17,29.4 16.8,29.4 C16.5,29.4 16.3,29.2 16.3,28.9 L16.3,27.5 L17.4,27.5 L17.4,26.7 L17.5,26.7 Z M23.4,26.6 C23,26.6 22.7,26.8 22.6,27.1 L22.6,26.7 L21.9,26.7 L21.9,29.9 L22.6,29.9 L22.6,28.1 C22.6,27.6 22.8,27.3 23.3,27.3 C23.4,27.3 23.6,27.3 23.7,27.4 L23.9,26.7 C23.8,26.6 23.6,26.6 23.4,26.6 Z M14.4,26.9 C14.1,26.7 13.6,26.6 13.1,26.6 C12.3,26.6 11.8,27 11.8,27.6 C11.8,28.1 12.2,28.4 12.9,28.5 L13.2,28.5 C13.6,28.6 13.8,28.7 13.8,28.8 C13.8,29 13.5,29.2 13.1,29.2 C12.6,29.2 12.3,29 12,28.9 L11.7,29.4 C12.1,29.7 12.6,29.8 13.1,29.8 C14,29.8 14.6,29.4 14.6,28.8 C14.6,28.2 14.2,27.9 13.5,27.8 L13.2,27.8 C12.9,27.8 12.7,27.7 12.7,27.5 C12.7,27.3 12.9,27.1 13.3,27.1 C13.7,27.1 14.1,27.3 14.3,27.4 L14.4,26.9 Z M33.1,26.6 C32.7,26.6 32.4,26.8 32.3,27.1 L32.3,26.7 L31.6,26.7 L31.6,29.9 L32.3,29.9 L32.3,28.1 C32.3,27.6 32.5,27.3 33,27.3 C33.1,27.3 33.3,27.3 33.4,27.4 L33.6,26.7 C33.4,26.6 33.2,26.6 33.1,26.6 Z M24.1,28.3 C24.1,29.3 24.8,30 25.8,30 C26.3,30 26.6,29.9 26.9,29.6 L26.7,29 C26.4,29.2 26.2,29.3 25.9,29.3 C25.3,29.3 24.9,28.9 24.9,28.3 C24.9,27.7 25.3,27.3 25.9,27.3 C26.2,27.3 26.5,27.4 26.7,27.6 L27,27 C26.7,26.7 26.3,26.6 25.9,26.6 C24.8,26.6 24.1,27.3 24.1,28.3 Z M30.6,28.3 L30.6,26.7 L29.9,26.7 L29.9,27.1 C29.7,26.8 29.3,26.6 28.9,26.6 C28,26.6 27.3,27.3 27.3,28.3 C27.3,29.3 28,30 28.9,30 C29.4,30 29.7,29.8 29.9,29.5 L29.9,29.9 L30.6,29.9 L30.6,28.3 Z M28.1,28.3 C28.1,27.7 28.5,27.3 29.1,27.3 C29.7,27.3 30.1,27.7 30.1,28.3 C30.1,28.9 29.7,29.3 29.1,29.3 C28.4,29.3 28.1,28.8 28.1,28.3 Z M19.7,26.6 C18.8,26.6 18.1,27.3 18.1,28.3 C18.1,29.3 18.8,30 19.7,30 C20.2,30 20.6,29.9 21,29.6 L20.7,29 C20.4,29.2 20.1,29.3 19.8,29.3 C19.4,29.3 18.9,29.1 18.8,28.5 L21.2,28.5 L21.2,28.2 C21.2,27.3 20.6,26.6 19.7,26.6 Z M19.7,27.2 C20.1,27.2 20.4,27.5 20.5,28 L18.8,28 C18.9,27.5 19.2,27.2 19.7,27.2 Z M37.1,28.3 L37.1,25.5 L36.4,25.5 L36.4,27.2 C36.2,26.9 35.8,26.7 35.4,26.7 C34.5,26.7 33.8,27.4 33.8,28.4 C33.8,29.4 34.5,30.1 35.4,30.1 C35.9,30.1 36.2,29.9 36.4,29.6 L36.4,30 L37.1,30 L37.1,28.3 Z M34.5,28.3 C34.5,27.7 34.9,27.3 35.5,27.3 C36.1,27.3 36.5,27.7 36.5,28.3 C36.5,28.9 36.1,29.3 35.5,29.3 C34.9,29.3 34.5,28.8 34.5,28.3 Z M11,28.3 L11,26.7 L10.3,26.7 L10.3,27.1 C10.1,26.8 9.7,26.6 9.3,26.6 C8.4,26.6 7.7,27.3 7.7,28.3 C7.7,29.3 8.4,30 9.3,30 C9.8,30 10.1,29.8 10.3,29.5 L10.3,29.9 L11,29.9 L11,28.3 Z M8.4,28.3 C8.4,27.7 8.8,27.3 9.4,27.3 C10,27.3 10.4,27.7 10.4,28.3 C10.4,28.9 10,29.3 9.4,29.3 C8.8,29.3 8.4,28.8 8.4,28.3 Z" id="Shape" fill="#000000" fill-rule="nonzero"></path>
<rect id="Rectangle" fill="#FF5F00" x="14.2" y="2.5" width="10.5" height="18.7"></rect>
<path d="M14.9,11.9 C14.9,8.1 16.7,4.7 19.5,2.5 C17.4,1 14.8,0 12,0 C5.4,0 0,5.3 0,11.9 C0,18.5 5.4,23.8 12,23.8 C14.8,23.8 17.4,22.8 19.4,21.3 C16.7,19.1 14.9,15.7 14.9,11.9 Z" id="Path" fill="#EB001B"></path>
<path d="M39,11.9 C39,18.5 33.6,23.8 27,23.8 C24.2,23.8 21.6,22.8 19.6,21.3 C22.4,19.1 24.2,15.7 24.2,11.9 C24.2,8.1 22.4,4.7 19.6,2.5 C21.5,1 24.1,0 26.9,0 C33.6,0 39,5.4 39,11.9 Z" id="Path_1_" fill="#F79E1B"></path>
</g>
<g id="Group" transform="translate(0.000000, 0.761905)">
<path d="M52.80358,3.95309044 C53.4023794,3.64708353 54.1410993,3.8755569 54.4534379,4.46280716 L54.4534379,4.46280716 L57.0770217,9.41994225 L59.7009065,4.46280716 C60.013245,3.8755569 60.7519649,3.64708353 61.3513661,3.95309044 C61.9510682,4.25850775 62.1836671,4.98284203 61.8716295,5.57009228 L61.8716295,5.57009228 L56.2922635,16.1170126 C56.0822324,16.5105764 55.6811271,16.7434718 55.2589584,16.7605705 C55.0513345,16.770299 54.8385953,16.7269628 54.6412021,16.6261397 C54.0418009,16.3210172 53.808901,15.5966829 54.1212396,15.0091379 L54.1212396,15.0091379 L55.6976768,12.0227698 L52.2824139,5.57009228 C51.9700754,4.98284203 52.2029752,4.25909736 52.80358,3.95309044 Z M82.1554924,3.65852194 C83.4779664,3.65852194 84.7216035,4.16322698 85.6568138,5.07918409 C86.1355524,5.547923 86.1355524,6.30704419 85.6568138,6.77489869 C85.1789779,7.24304799 84.4047514,7.24304799 83.9266146,6.77489869 C83.4532922,6.31117145 82.8247033,6.05587089 82.1554924,6.05587089 C81.4856796,6.05587089 80.8570907,6.31117145 80.3834674,6.77489869 C79.910446,7.23862592 79.649562,7.85506181 79.649562,8.51070668 C79.649562,9.16605674 79.910446,9.78219783 80.3834674,10.2465147 C80.8570907,10.7096523 81.4856796,10.9652477 82.1554924,10.9652477 C82.8247033,10.9652477 83.4532922,10.7096523 83.9266146,10.2465147 C84.4047514,9.77807057 85.1789779,9.77807057 85.6568138,10.2465147 C86.1355524,10.714664 86.1355524,11.4731956 85.6568138,11.9413449 C84.7216035,12.857302 83.4779664,13.362007 82.1554924,13.362007 C80.8324165,13.362007 79.5887794,12.857302 78.6535691,11.9413449 C77.7183588,11.025093 77.2032108,9.80666659 77.2032108,8.51070668 C77.2032108,7.21504158 77.7183588,5.9957308 78.6535691,5.07918409 C79.5887794,4.16322698 80.8324165,3.65852194 82.1554924,3.65852194 Z M92.0477184,3.65861038 C94.7781233,3.65861038 97,5.83574048 97,8.51079512 C97,11.1858498 94.7781233,13.3620954 92.0477184,13.3620954 C89.3176145,13.3620954 87.0957378,11.1858498 87.0957378,8.51079512 C87.0957378,5.83574048 89.3176145,3.65861038 92.0477184,3.65861038 Z M49.4700729,3.81748044 C50.1453019,3.81748044 50.6932485,4.35372955 50.6932485,5.01586011 L50.6932485,5.01586011 L50.6932485,12.0263075 C50.6932485,12.6878484 50.1453019,13.2246871 49.4700729,13.2246871 C48.7945429,13.2246871 48.2465963,12.6878484 48.2465963,12.0263075 L48.2465963,12.0263075 L48.2465963,5.01586011 C48.2465963,4.35372955 48.7945429,3.81748044 49.4700729,3.81748044 Z M70.1055423,3.817392 C70.3968176,3.80736865 70.6926065,3.89993722 70.9333298,4.09745613 C71.451186,4.52315361 71.5182876,5.27903195 71.0843837,5.78668504 L71.0843837,5.78668504 L66.7667092,10.8275446 L70.147368,10.8275446 C70.822898,10.8275446 71.3708446,11.3643833 71.3708446,12.026219 C71.3708446,12.68776 70.822898,13.2245987 70.147368,13.2245987 L70.147368,13.2245987 L64.1428244,13.2245987 C63.8647889,13.2245987 63.5858507,13.1332094 63.3562608,12.9445346 C62.8387055,12.5191319 62.7719048,11.7629588 63.2058087,11.2561901 L63.2058087,11.2561901 L67.5240851,6.21415134 L64.6997981,6.21415134 C64.0248699,6.21415134 63.4769234,5.67790223 63.4769234,5.01606648 C63.4769234,4.35393591 64.0248699,3.817392 64.6997981,3.817392 L64.6997981,3.817392 Z M74.376336,3.81748044 C75.0521669,3.81748044 75.5995117,4.35372955 75.5995117,5.01586011 L75.5995117,5.01586011 L75.5995117,12.0263075 C75.5995117,12.6878484 75.0521669,13.2246871 74.376336,13.2246871 C73.7008061,13.2246871 73.1528595,12.6878484 73.1528595,12.0263075 L73.1528595,12.0263075 L73.1528595,5.01586011 C73.1528595,4.35372955 73.7008061,3.81748044 74.376336,3.81748044 Z M92.0477184,6.05595933 C90.6665681,6.05595933 89.542089,7.15705356 89.542089,8.51079512 C89.542089,9.86394707 90.6665681,10.9653361 92.0477184,10.9653361 C93.4291697,10.9653361 94.5530469,9.86394707 94.5530469,8.51079512 C94.5530469,7.15705356 93.4291697,6.05595933 92.0477184,6.05595933 Z M49.4700729,-1.99840144e-13 C50.2160144,-1.99840144e-13 50.8217346,0.59344115 50.8217346,1.32485073 C50.8217346,2.05655512 50.2160144,2.64970147 49.4700729,2.64970147 C48.7229277,2.64970147 48.1181102,2.05655512 48.1181102,1.32485073 C48.1181102,0.59344115 48.7229277,-1.99840144e-13 49.4700729,-1.99840144e-13 Z M74.376336,-1.99840144e-13 C75.1228794,-1.99840144e-13 75.7279978,0.59344115 75.7279978,1.32485073 C75.7279978,2.05655512 75.1228794,2.64970147 74.376336,2.64970147 C73.6294918,2.64970147 73.0243734,2.05655512 73.0243734,1.32485073 C73.0243734,0.59344115 73.6294918,-1.99840144e-13 74.376336,-1.99840144e-13 Z" id="Combined-Shape" fill="#1E64FF"></path>
<path d="M66.708643,20.4523488 C68.7783092,20.4523488 70.8240554,22.1905313 70.8240554,24.7028054 C70.8240554,27.2150795 68.7783092,28.952381 66.708643,28.952381 C64.6392684,28.952381 62.5935222,27.2150795 62.5935222,24.7028054 C62.5935222,22.1905313 64.6392684,20.4523488 66.708643,20.4523488 Z M77.04516,20.3690958 C77.3879166,20.3690958 77.6837082,20.6551221 77.6837082,21.0001744 L77.6837082,28.3097028 C77.6837082,28.6553423 77.3879166,28.9407813 77.04516,28.9407813 C76.7021117,28.9407813 76.4182801,28.6553423 76.4182801,28.3097028 L76.4182801,27.6668777 C76.3357268,28.2025163 75.6020819,28.9522341 74.4787155,28.9522341 C73.1663225,28.9522341 71.5937262,27.9285301 71.5937262,25.774523 C71.5937262,23.6548743 73.1779908,22.6073838 74.4787155,22.6073838 C75.3538387,22.6073838 76.1108202,23.0602098 76.4182801,23.7500206 L76.4182801,23.2739954 C76.4182801,23.2384623 76.4182801,23.1905955 76.4302401,23.1550624 L76.4302401,21.0001744 C76.4302401,20.6551221 76.7143635,20.3690958 77.04516,20.3690958 Z M48.6265568,22.6435041 C48.8864684,22.6435041 49.1110833,22.8696235 49.1110833,23.1436097 L49.1110833,28.4286357 C49.1110833,28.7149556 48.8864684,28.9407813 48.6265568,28.9407813 C48.3546852,28.9407813 48.1297785,28.7149556 48.1297785,28.4286357 L48.1297785,23.1436097 C48.1297785,22.8696235 48.3546852,22.6435041 48.6265568,22.6435041 Z M50.9206046,20.3691545 C51.1810996,20.3691545 51.4057145,20.5958612 51.4057145,20.8575136 L51.4057145,28.4286944 C51.4057145,28.6906405 51.1810996,28.9285063 50.9206046,28.9285063 C50.6484413,28.9285063 50.4241181,28.6906405 50.4241181,28.4286944 L50.4241181,20.8575136 C50.4241181,20.5958612 50.6484413,20.3691545 50.9206046,20.3691545 Z M55.497732,22.6074425 C56.8457134,22.6074425 58.4652746,23.6790133 58.4652746,25.607782 C58.4652746,25.9880737 58.2406597,26.1672072 57.8389782,26.1672072 L53.4872825,26.1434206 C53.5934641,27.4766438 54.6106489,28.1074287 55.627542,28.1074287 C56.1712851,28.1074287 56.6088467,27.9526691 56.9988599,27.6546027 C57.1765099,27.5359634 57.2829832,27.4766438 57.4127932,27.4766438 C57.6379915,27.4766438 57.7794698,27.6314034 57.7794698,27.8454827 C57.7794698,27.9764557 57.7205448,28.1074287 57.5192665,28.2859749 C57.0464083,28.7023869 56.3492269,28.9285063 55.6036219,28.9285063 C54.0899506,28.9285063 52.4703894,27.9285889 52.4703894,25.7860346 C52.4703894,23.6434803 54.149459,22.6074425 55.497732,22.6074425 Z M81.8114016,22.6073838 C83.1593829,22.6073838 84.7795276,23.6789546 84.7795276,25.5716029 C84.7795276,26.059962 84.5073643,26.2505483 83.9989177,26.2505483 L80.0375269,26.2382145 C80.1673369,27.3453184 81.0307917,27.8932908 81.9767998,27.8932908 C82.4619098,27.8932908 82.792998,27.7743578 83.1593829,27.5482385 C83.3603695,27.441052 83.4904712,27.3576522 83.6558694,27.3576522 C83.9280327,27.3576522 84.0937227,27.5482385 84.0937227,27.8098909 C84.0937227,27.9649442 84.0345061,28.1314502 83.7743028,28.3452358 C83.3250729,28.7149556 82.674273,28.9284475 81.9292515,28.9284475 C80.3925352,28.9284475 78.7487622,27.9408639 78.7487622,25.7859758 C78.7487622,23.6434215 80.4634202,22.6073838 81.8114016,22.6073838 Z M74.6799939,23.6904074 C73.8168307,23.6904074 72.8469026,24.3696464 72.8469026,25.774523 C72.8469026,27.1908524 73.8168307,27.8692105 74.6799939,27.8692105 C75.484232,27.8692105 76.4774968,27.2501721 76.4774968,25.774523 C76.4774968,24.2862465 75.484232,23.6904074 74.6799939,23.6904074 Z M66.708643,21.6669328 C65.2658566,21.6669328 63.9298353,22.8935569 63.9298353,24.7028054 C63.9298353,26.5120539 65.2658566,27.7263442 66.708643,27.7263442 C68.1514293,27.7263442 69.4877424,26.5120539 69.4877424,24.7028054 C69.4877424,22.8935569 68.1630976,21.6669328 66.708643,21.6669328 Z M55.497732,23.4769742 C54.6223172,23.4769742 53.5934641,24.0716387 53.4872825,25.4048619 L57.5075982,25.4048619 C57.4014166,24.0716387 56.3842318,23.4769742 55.497732,23.4769742 Z M81.8114016,23.7027411 C81.0307917,23.7027411 80.1440002,24.2028467 80.0375269,25.3337371 L83.5613561,25.3337371 C83.4551745,24.2028467 82.6039714,23.7027411 81.8114016,23.7027411 Z M48.6265568,20.2501629 C48.8864684,20.2501629 49.1230433,20.4762822 49.1230433,20.7502684 C49.1230433,21.0239609 48.8864684,21.250374 48.6265568,21.250374 C48.3546852,21.250374 48.1181102,21.0239609 48.1181102,20.7502684 C48.1181102,20.4762822 48.3546852,20.2501629 48.6265568,20.2501629 Z M68.1630976,18.2857143 C68.5414425,18.2857143 68.8489025,18.5952335 68.8489025,18.9649533 C68.8489025,19.3337922 68.5414425,19.6433114 68.1630976,19.6433114 C67.7847527,19.6433114 67.4772928,19.3337922 67.4772928,18.9649533 C67.4772928,18.5952335 67.7847527,18.2857143 68.1630976,18.2857143 Z M65.25448,18.2857143 C65.6325332,18.2857143 65.9397014,18.5952335 65.9397014,18.9649533 C65.9397014,19.3337922 65.6325332,19.6433114 65.25448,19.6433114 C64.8758434,19.6433114 64.5683834,19.3337922 64.5683834,18.9649533 C64.5683834,18.5952335 64.8758434,18.2857143 65.25448,18.2857143 Z" id="Combined-Shape" fill="#495057"></path>
<g fill="url(#linearGradient-3)" id="Group-3">
<path d="M3.78070866,0 C1.69274929,0 0,1.70531217 0,3.80952381 L0,26.6666667 C0,28.7708783 1.69274929,30.4761905 3.78070866,30.4761905 L32.3232107,30.4761905 C33.732995,30.4761905 35.0254932,29.6861799 35.6764472,28.4258201 L41.581074,16.9975873 C41.8606784,16.4557884 42.0028331,15.8635344 42.007874,15.2706032 L42.007874,15.2055873 C42.0028331,14.6126561 41.8606784,14.0204021 41.581074,13.4789418 L35.6764472,2.05037037 C35.0254932,0.790349206 33.732995,0 32.3232107,0 L3.78070866,0 Z" id="Fill-1"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -8,6 +8,7 @@ import {
krediBittiIsaretle,
} from "@/lib/credits";
import { anahtarVar, SORUMLULUK_REDDI } from "@/lib/ai/client";
import { kullanicininRaporu } from "@/lib/rapor-kaydi";
import { sohbetAkisi } from "@/lib/ai/cagri";
import { listeOzetiCikar, type RaporSonuc } from "@/lib/ai/rapor";
import type { RaporParams } from "@/lib/rapor-havuzu";
@@ -122,10 +123,8 @@ export async function POST(request: NextRequest) {
return Response.json({ error: "Geçersiz mesaj." }, { status: 400 });
}
// Soru sormak için önce liste oluşturulmuş olmalı
const rapor = await appDb.query.reports.findFirst({
where: eq(schema.reports.userId, session.user.id),
});
// Soru sormak için önce liste oluşturulmuş olmalı (bayat dev mock sayılmaz)
const rapor = await kullanicininRaporu(session.user.id);
if (!rapor?.result) {
return Response.json(
{ error: "Önce listeni oluşturmalısın.", code: "NO_REPORT" },

View File

@@ -0,0 +1,102 @@
import type { Metadata } from "next";
import { redirect } from "next/navigation";
import { ArrowRight } from "lucide-react";
import { PixelCorner } from "@/components/pixel-decor";
import { Button } from "@/components/ui/button";
export const metadata: Metadata = {
title: "Girişini onayla",
robots: { index: false, follow: false },
};
/** Yalnızca site içi (göreli) callback'lere izin ver — open redirect kalkanı. */
function guvenliYol(deger: string | undefined, varsayilan: string): string {
return deger && deger.startsWith("/") && !deger.startsWith("//")
? deger
: varsayilan;
}
/**
* Maildeki giriş bağlantısının indiği onay sayfası. Doğrulama ucu
* (/api/auth/magic-link/verify) GET'lendiği anda token'ı tüketir; mail
* güvenlik tarayıcıları ve tarayıcı ön-yüklemeleri linki kullanıcıdan önce
* açıp token'ı yakabildiği için maildeki link token'ı TÜKETMEYEN bu sayfaya
* gelir. Verify ancak buradaki form gönderilince tetiklenir — tarayıcı botları
* sayfayı GET'lese bile form'u göndermez, token yanmaz.
*/
export default async function DogrulaPage({
searchParams,
}: {
searchParams: Promise<{
token?: string;
callbackURL?: string;
errorCallbackURL?: string;
newUserCallbackURL?: string;
}>;
}) {
const { token, callbackURL, errorCallbackURL, newUserCallbackURL } =
await searchParams;
if (!token) redirect("/giris");
const callback = guvenliYol(callbackURL, "/");
const hataCallback = guvenliYol(
errorCallbackURL,
`/giris?hata=gecersiz-baglanti&callback=${encodeURIComponent(callback)}`,
);
const yeniKullaniciCallback = guvenliYol(newUserCallbackURL, callback);
return (
<div className="-mb-28 flex min-h-dvh flex-col bg-slate-50 text-slate-900">
<main className="mx-auto flex w-full max-w-md flex-1 flex-col items-stretch justify-center px-6 py-8">
<div className="relative">
<div className="relative rounded-2xl border border-slate-200 bg-white p-8 shadow-sm">
<h1 className="font-heading text-xl font-bold">Girişini onayla</h1>
<p className="mt-2 text-sm text-slate-600">
Bu bağlantı senin için üretildi ve{" "}
<span className="font-semibold">tek kullanımlık</span>. Aşağıdaki
butona tıkladığında KolayTercih hesabına giriş yapacaksın.
</p>
<form
method="GET"
action="/api/auth/magic-link/verify"
className="mt-6"
>
<input type="hidden" name="token" value={token} />
<input type="hidden" name="callbackURL" value={callback} />
<input
type="hidden"
name="errorCallbackURL"
value={hataCallback}
/>
<input
type="hidden"
name="newUserCallbackURL"
value={yeniKullaniciCallback}
/>
<Button
type="submit"
className="h-11 w-full cursor-pointer bg-orange-500 text-white transition-colors duration-200 hover:bg-orange-600"
>
Giriş yap
<ArrowRight className="size-4" aria-hidden />
</Button>
</form>
<p className="mt-4 text-xs leading-relaxed text-slate-500">
Bu isteği sen yapmadıysan bu sayfayı kapatabilirsin butona
tıklanmadığı sürece hesabında hiçbir şey değişmez.
</p>
</div>
<div
aria-hidden
className="pointer-events-none absolute inset-0 z-10 text-primary"
>
<PixelCorner className="absolute -right-4 -top-4 size-9 [&_rect]:[fill-opacity:0.65]" />
<PixelCorner className="absolute -bottom-4 -right-4 size-9 rotate-90 [&_rect]:[fill-opacity:0.65]" />
<PixelCorner className="absolute -bottom-4 -left-4 size-9 rotate-180 [&_rect]:[fill-opacity:0.65]" />
<PixelCorner className="absolute -left-4 -top-4 size-9 -rotate-90 [&_rect]:[fill-opacity:0.65]" />
</div>
</div>
</main>
</div>
);
}

View File

@@ -74,6 +74,10 @@ export function GirisForm({ callbackURL }: { callbackURL: string }) {
const { error } = await authClient.signIn.magicLink({
email,
callbackURL,
// Geçersiz/süresi dolmuş token'da kullanıcı ham ?error=INVALID_TOKEN
// parametresiyle baş başa kalmasın: giriş sayfası bu değeri dostça bir
// mesaja çevirir, callback korunur (bkz. src/app/giris/page.tsx).
errorCallbackURL: `/giris?hata=gecersiz-baglanti&callback=${encodeURIComponent(callbackURL)}`,
});
setBusy(null);
if (error) {
@@ -93,7 +97,7 @@ export function GirisForm({ callbackURL }: { callbackURL: string }) {
<p className="mt-1">
<span className="font-medium">{email}</span> adresine bir giriş
bağlantısı gönderdik. Gelen kutunu (ve spam klasörünü) kontrol et
bağlantı 5 dakika geçerli.
bağlantı 15 dakika geçerli.
</p>
</div>
<div className="flex items-center justify-center gap-4 text-sm">

View File

@@ -14,11 +14,15 @@ export const metadata: Metadata = {
export default async function GirisPage({
searchParams,
}: {
searchParams: Promise<{ callback?: string }>;
searchParams: Promise<{ callback?: string; hata?: string }>;
}) {
const { callback } = await searchParams;
const { callback, hata } = await searchParams;
const callbackURL =
callback && callback.startsWith("/") ? callback : "/";
// Magic link doğrulaması başarısız olunca errorCallbackURL buraya düşürür
// (bkz. giris-form.tsx ve /giris/dogrula) — ham INVALID_TOKEN yerine
// dostça bir mesaj gösterip yeni bağlantı istemeye yönlendiriyoruz.
const baglantiHatasi = hata === "gecersiz-baglanti";
const session = await getSession();
if (session) redirect(callbackURL);
@@ -33,6 +37,20 @@ export default async function GirisPage({
<main className="mx-auto flex w-full max-w-md flex-1 flex-col items-stretch justify-center px-6 py-8">
<div className="relative">
<div className="relative rounded-2xl border border-slate-200 bg-white p-8 shadow-sm">
{baglantiHatasi && (
<div
role="alert"
className="mb-5 rounded-xl border border-red-200 bg-red-50 p-4 text-sm text-red-800"
>
<p className="font-semibold">
Giriş bağlantısı geçersiz ya da süresi dolmuş
</p>
<p className="mt-1">
Bağlantılar 15 dakika geçerli ve tek kullanımlık. Aşağıdan
yeni bir bağlantı isteyebilirsin.
</p>
</div>
)}
<GirisBaslik funnel={funnel} />
<p className="mt-2 text-sm text-slate-600">
{funnel ? (

View File

@@ -4,6 +4,7 @@ import { redirect } from "next/navigation";
import { MessageCircleQuestion, Sparkles } from "lucide-react";
import { verifySession, getCurrentUser } from "@/lib/session";
import { appDb, schema } from "@/lib/appdb";
import { kullanicininRaporu } from "@/lib/rapor-kaydi";
import { URUNLER, MAX_REVIZYON } from "@/lib/credits";
import type { RaporSonuc } from "@/lib/ai/rapor";
import type { RaporParams } from "@/lib/rapor-havuzu";
@@ -44,9 +45,7 @@ export default async function ListemPage({
const [user, params, satir, sohbetGecmisi] = await Promise.all([
getCurrentUser(),
searchParams,
appDb.query.reports.findFirst({
where: eq(schema.reports.userId, session.user.id),
}),
kullanicininRaporu(session.user.id),
appDb
.select({
id: schema.chatMessages.id,
@@ -118,8 +117,8 @@ export default async function ListemPage({
className="mb-6 flex items-center gap-3 rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm font-medium text-emerald-800"
>
<Sparkles className="size-4 shrink-0" aria-hidden />
Paketin aktif 24 tercihinin tamamı, gerekçeler, riskler ve PDF
artık ık.
Paketin aktif listendeki tercihlerin tamamı, gerekçeler, riskler ve
PDF artık ık.
</div>
) : null}
{/* Başlık ve revizyon/PDF aksiyonları listenin kendi başlık satırında

View File

@@ -18,12 +18,14 @@ flowchart TD
H --> S{"Havuzda 24'ten az program mı kaldı?"}
S -- "evet" --> G("Filtreler adım adım gevşetilir<br/>önce il, sonra alan — listede açıkça belirtilir")
G --> H
S -- "hayır" --> Y("Yapay zekâ havuzdan tam 24 tercih seçer<br/>havuzda olmayan bölümü yazamaz, her satıra gerekçe yazar")
H --> U("Havuz yine de dar kalırsa<br/>liste eldeki programlarla kurulur, dağılım havuza uyarlanır ve sana açıkça söylenir")
S -- "hayır" --> Y("Yapay zekâ havuzdan en fazla 24 tercih seçer<br/>havuzda olmayan bölümü yazamaz, her satıra gerekçe yazar")
U --> Y
Y --> R("Risk notu ve trend özeti<br/>gerçek sıralama geçmişinden hesaplanır, yapay zekâ yorumu değildir")
R --> L("24 tercihlik liste")
L --> L1("üstte 46 hayal")
L --> L2("ortada 1214 dengeli")
L --> L3("sonda 57 garanti")
R --> L("En fazla 24 tercihlik liste")
L --> L1("üstte hayal — hedef 5, havuz elverdiğince")
L --> L2("ortada dengeli — hedef 13, havuz elverdiğince")
L --> L3("sonda garanti — hedef 6, havuz elverdiğince")
classDef havuz fill:#eff6ff,stroke:#3b82f6,color:#1e3a8a
classDef uyari fill:#fffbeb,stroke:#f59e0b,color:#92400e
@@ -31,7 +33,7 @@ flowchart TD
classDef dengeli fill:#fef3c7,stroke:#f59e0b,color:#92400e
classDef garanti fill:#d1fae5,stroke:#10b981,color:#065f46
class H,Y havuz
class G uyari
class G,U uyari
class L1 hayal
class L2 dengeli
class L3 garanti`;
@@ -40,14 +42,15 @@ export function HuniSemasi() {
return (
<ElCizimiSema
tanim={SEMA}
ariaLabel="Sihirbaz akış şeması: sıralamanla açılan programlar alan, il ve devlet/vakıf adımlarıyla aday havuzuna daraltılır; havuz 24'ün altına düşerse filtreler gevşetilir, sonra yapay zekâ havuzdan üstte hayal, ortada dengeli, sonda garanti olacak şekilde 24 tercih seçip her satıra gerekçe yazar; risk notu ve trend özeti gerçek sıralama geçmişinden hesaplanır."
ariaLabel="Sihirbaz akış şeması: sıralamanla açılan programlar alan, il ve devlet/vakıf adımlarıyla aday havuzuna daraltılır; havuz 24'ün altına düşerse filtreler gevşetilir, yine de dar kalırsa liste eldeki programlarla kurulur ve dağılım havuza uyarlanır; yapay zekâ havuzdan üstte hayal, ortada dengeli, sonda garanti olacak şekilde en fazla 24 tercih seçip her satıra gerekçe yazar; risk notu ve trend özeti gerçek sıralama geçmişinden hesaplanır."
figcaption={
<>
Her adım havuzu biraz daha daraltır; yapay zekâ listeyi bu havuzun{" "}
<span className="font-medium text-slate-700">
dışına asla çıkmadan
</span>{" "}
kurar.
kurar havuz dar kalırsa liste eldeki programlarla kurulur ve bunu
listende ıkça görürsün.
</>
}
/>

View File

@@ -150,7 +150,9 @@ export default function MeraklisinaPage() {
<strong>aday havuzu</strong>{" "}
olur ve yapay zekâya öyle gider: ona boş bir sayfa değil, bu havuz
verilir. Aşağıdaki şema akışın tamamını gösterir havuz
24&apos;ün altına düşerse filtrelerin nasıl gevşetildiği de dahil.
24&apos;ün altına düşerse filtrelerin nasıl gevşetildiği, yine de
yetmezse listenin eldeki programlarla, sana ıkça belirtilerek
nasıl kurulduğu da dahil.
</p>
<HuniSemasi />
</section>

View File

@@ -4,6 +4,7 @@ import { eq } from "drizzle-orm";
import { ArrowRight, CheckCircle2, XCircle } from "lucide-react";
import { verifySession } from "@/lib/session";
import { appDb, schema } from "@/lib/appdb";
import { kullanicininRaporu } from "@/lib/rapor-kaydi";
import { odemeyiSonuclandir } from "@/lib/odeme";
import { Button } from "@/components/ui/button";
import { PagePixelDivider } from "@/components/pixel-decor";
@@ -48,12 +49,7 @@ export default async function OdemeSonucPage({
// Rapor zaten üretildiyse ödül bir tık uzakta: kullanıcıyı doğrudan
// rapor sayfasına döndür ki blur çözülme animasyonuyla tam listeyi görsün.
const rapor = basarili
? await appDb.query.reports.findFirst({
where: eq(schema.reports.userId, session.user.id),
columns: { id: true },
})
: null;
const rapor = basarili ? await kullanicininRaporu(session.user.id) : null;
const listeHref = rapor ? "/listem?acildi=1" : null;
return (

View File

@@ -1,3 +1,4 @@
import Image from "next/image";
import Link from "next/link";
import type { Metadata } from "next";
import {
@@ -142,7 +143,14 @@ export default async function PaketPage({
</div>
)}
</div>
<p className="mt-4 text-center text-xs text-slate-500">
<Image
src="/odeme/iyzico-logo-band.svg"
alt="iyzico ile Öde — Mastercard, Visa, American Express ve Troy ile güvenli ödeme"
width={429}
height={32}
className="mx-auto mt-5 h-5 w-auto"
/>
<p className="mt-3 text-center text-xs text-slate-500">
iyzico güvenli ödeme · Kart bilgilerin bize hiç ulaşmaz ·{" "}
<Link
href="/kosullar#iade"

View File

@@ -1,9 +1,8 @@
import type { Metadata } from "next";
import { eq } from "drizzle-orm";
import Image from "next/image";
import { redirect } from "next/navigation";
import { verifySession, getCurrentUser } from "@/lib/session";
import { appDb, schema } from "@/lib/appdb";
import { kullanicininRaporu } from "@/lib/rapor-kaydi";
import { PUAN_TURLERI } from "@/lib/db";
import type { RaporSonuc } from "@/lib/ai/rapor";
import type { RaporParams } from "@/lib/rapor-havuzu";
@@ -25,9 +24,7 @@ export default async function YazdirPage() {
// Kullanıcı satırı ile rapor sorgusu bağımsız — paralel çöz
const [user, satir] = await Promise.all([
getCurrentUser(),
appDb.query.reports.findFirst({
where: eq(schema.reports.userId, session.user.id),
}),
kullanicininRaporu(session.user.id),
]);
if (!user) redirect("/giris");
if (!user.hasPaket) redirect("/paket");
@@ -47,13 +44,21 @@ export default async function YazdirPage() {
<header className="border-b-2 border-slate-900 pb-4">
<h1 className="font-heading text-2xl font-bold">
24 Tercihlik Kişisel Liste ve Risk Raporu
{rapor.tercihler.length} Tercihlik Kişisel Liste ve Risk Raporu
</h1>
<p className="mt-1 text-sm text-slate-600">
Başarı sıralaması: {params.sira.toLocaleString("tr-TR")} · Puan
türü: {PUAN_TURLERI[params.tur]}
{params.il ? ` · İl: ${params.il}` : ""} · {tarih}
</p>
{rapor.kapsam?.uyarlandi ? (
<p className="mt-1 text-sm text-slate-600">
Not: Tercihlerine göre {rapor.kapsam.toplam} uygun program bulundu;
liste {rapor.kapsam.dagilim.hayal} hayal ·{" "}
{rapor.kapsam.dagilim.dengeli} dengeli ·{" "}
{rapor.kapsam.dagilim.garanti} garanti olarak kuruldu.
</p>
) : null}
</header>
<section className="mt-5">

View File

@@ -3,6 +3,7 @@
import { and, eq, sql } from "drizzle-orm";
import { getSession, getCurrentUser } from "@/lib/session";
import { appDb, schema } from "@/lib/appdb";
import { kullanicininRaporu } from "@/lib/rapor-kaydi";
import { PUAN_TURLERI, type PuanTuruKey } from "@/lib/db";
import {
spendCredits,
@@ -145,9 +146,7 @@ export async function listeOlustur(input: {
"Önceki denemen başarısız olduğu için kredin iade edilmişti. Tekrar dener misin?",
};
}
const mevcut = await appDb.query.reports.findFirst({
where: eq(schema.reports.userId, user.id),
});
const mevcut = await kullanicininRaporu(user.id);
if (mevcut?.result) {
const rapor = mevcut.result as RaporSonuc;
return {
@@ -225,9 +224,7 @@ export async function listeRevize(input: {
return { ok: false, code: "HATA", error: "Geçersiz istek." };
}
const rapor = await appDb.query.reports.findFirst({
where: eq(schema.reports.userId, user.id),
});
const rapor = await kullanicininRaporu(user.id);
if (!rapor?.result) {
return { ok: false, code: "HATA", error: "Önce bir liste oluşturmalısın." };
}
@@ -274,9 +271,7 @@ export async function listeRevize(input: {
"Önceki denemen başarısız olduğu için kredin iade edilmişti. Tekrar dener misin?",
};
}
const guncel = await appDb.query.reports.findFirst({
where: eq(schema.reports.userId, user.id),
});
const guncel = await kullanicininRaporu(user.id);
if (guncel?.result) {
return {
ok: true,

View File

@@ -1,5 +1,4 @@
import type { Metadata } from "next";
import { eq } from "drizzle-orm";
import {
PUAN_TURLERI,
rankWindowFacets,
@@ -7,7 +6,7 @@ import {
type PuanTuruKey,
} from "@/lib/db";
import { getSession } from "@/lib/session";
import { appDb, schema } from "@/lib/appdb";
import { kullanicininRaporu } from "@/lib/rapor-kaydi";
import { tadimlikSec } from "@/lib/tadimlik-havuzu";
import { SihirbazBolumu } from "./sihirbaz-cagri-karti";
import { TadimlikSatiri } from "./tadimlik-satiri";
@@ -55,10 +54,7 @@ export default async function SonucPage({
// için varlık bilgisi gerekir.
const session = await getSession();
const raporSatiri = session
? await appDb.query.reports.findFirst({
where: eq(schema.reports.userId, session.user.id),
columns: { result: true },
})
? await kullanicininRaporu(session.user.id)
: null;
const facetler = rankWindowFacets(sira, turKey);

View File

@@ -333,9 +333,18 @@ export function RaporListesi({
</p>
) : null}
{rapor.kapsam?.uyarlandi ? (
<p className="mt-3 rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-900">
Tercihlerine göre {rapor.kapsam.toplam} uygun program bulundu; listen{" "}
{rapor.kapsam.dagilim.hayal} hayal · {rapor.kapsam.dagilim.dengeli}{" "}
dengeli · {rapor.kapsam.dagilim.garanti} garanti olarak kuruldu. Daha
fazla seçenek için alan veya il filtreni genişletebilirsin.
</p>
) : null}
{tekIl ? (
<p className="mt-3 rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-900">
Listenin 24 tercihi de tek şehirde (
Listenin {rapor.tercihler.length} tercihi de tek şehirde (
{tekIl.toLocaleLowerCase("tr-TR")}). Boşta kalma riskini azaltmak için
birkaç farklı şehir eklemeyi düşünebilirsin.
</p>

View File

@@ -1,3 +1,4 @@
import Image from "next/image";
import Link from "next/link";
import { getAllBolumler } from "@/lib/katalog";
@@ -155,6 +156,15 @@ export function SiteFooter() {
İletişim
</Link>
</nav>
{/* iyzico işyeri sözleşmesi gereği: "iyzico ile Öde" + kart logoları
sitede görünür olmalı. Resmî logo bandı (iyzico logo paketi). */}
<Image
src="/odeme/iyzico-logo-band.svg"
alt="iyzico ile Öde — Mastercard, Visa, American Express ve Troy ile güvenli ödeme"
width={429}
height={32}
className="mx-auto mt-6 h-6 w-auto"
/>
</div>
</div>
</footer>

View File

@@ -75,6 +75,14 @@ export async function yapilandirilmisUret<T extends z.ZodType>(opts: {
system: string;
kullanici: string;
sema: T;
// Sağlayıcıya giden json_schema bu şemadan üretilir (verilmezse sema).
// Lokal doğrulama HER ZAMAN sema ile yapılır: tel şeması sıkı uzunluk
// kısıtları taşıyabilir (grammar zorlaması), sağlayıcı bu kısıtları yok
// sayarsa çıktı yine gevşek şemayla kurtarılır. Not: strict json_schema'da
// minItems/maxItems bazı sağlayıcılarda 400 döndürebilir; mevcut sağlayıcı
// (deepseek) kabul ediyor — sağlayıcı değişiminde geri çekilme yolu
// wireSema'dan uzunluk kısıtlarını kaldırmaktır.
wireSema?: z.ZodType;
maxTokens: number;
}): Promise<z.infer<T> | null> {
// Tek sinyal tüm iç denemeleri kapsar: süre dolunca bekleyen retry'lar da
@@ -98,13 +106,16 @@ export async function yapilandirilmisUret<T extends z.ZodType>(opts: {
json_schema: {
name: "yanit",
strict: true,
schema: jsonSemasi(opts.sema),
schema: jsonSemasi(opts.wireSema ?? opts.sema),
},
},
},
{ signal: sinyal },
),
);
// OpenRouter üst sağlayıcı arızasında HTTP 200 gövdesinde {"error": ...}
// dönebiliyor; choices o durumda yok — TypeError yerine null (retry yolu).
if (!Array.isArray(yanit.choices) || yanit.choices.length === 0) return null;
const metin = yanit.choices[0]?.message?.content;
if (!metin) return null;
try {
@@ -147,7 +158,7 @@ export async function* sohbetAkisi(opts: {
);
for await (const parca of akis) {
// Yalnızca delta.content akar; varsa delta.reasoning kullanıcıya sızmaz
const metin = parca.choices[0]?.delta?.content;
const metin = parca.choices?.[0]?.delta?.content;
if (metin) yield metin;
}
// openai SDK akış ortasındaki abort'u yutup iteratörü NORMAL bitiriyor

View File

@@ -5,6 +5,10 @@ import {
havuzOlustur,
havuzuKompaktJson,
turLabel,
dilimHedefiHesapla,
IDEAL_HEDEF,
type DilimHedef,
type Dilim,
type RaporParams,
type AdayProgram,
} from "../rapor-havuzu";
@@ -27,26 +31,56 @@ const RaporSchema = z.object({
});
// Yapay zekâdan istenen küçük şema (kullanıcı kararı, 6 Ağu 2026): riskNotu
// ve trendOzeti gerçek sıra geçmişinden deterministik üretilir; 5/13/6'lık
// dilim iskeleti ve tercih sırası da koddan gelir. Dilim başına SABİT uzunluklu
// ayrı listeler istenir ki dağılım şema düzeyinde (grammar/JSON-schema ile)
// zorlansın — reasoning kapalıyken model serbest dağılımı tutturamıyor
// (ölçüldü: 13 hayal / 0 garanti dönmüştü). AI yalnızca seçer + gerekce yazar.
// ve trendOzeti gerçek sıra geçmişinden deterministik üretilir; dilim iskeleti
// ve tercih sırası da koddan gelir. Dilim başına SABİT uzunluklu ayrı listeler
// istenir ki dağılım şema düzeyinde (grammar/JSON-schema ile) zorlansın —
// reasoning kapalıyken model serbest dağılımı tutturamıyor (ölçüldü: 13 hayal
// / 0 garanti dönmüştü). Uzunluklar havuza uyarlanmış hedeften gelir (havuzda
// dilim başına yeterli program olmayabiliyor; sabit 5/13/6 o durumda aritmetik
// olarak imkânsızdı ve üretim her seferinde patlıyordu). AI yalnızca seçer +
// gerekce yazar.
const AiTercihSchema = z.object({
programId: z.string(),
gerekce: z.string(),
});
const AiRaporSchema = z.object({
hayal: z.array(AiTercihSchema).length(5),
dengeli: z.array(AiTercihSchema).length(13),
garanti: z.array(AiTercihSchema).length(6),
/** Sağlayıcıya giden tel şeması: dilim uzunlukları hedefe sabitlenir. */
function aiSemaWire(hedef: DilimHedef) {
return z.object({
hayal: z.array(AiTercihSchema).length(hedef.hayal),
dengeli: z.array(AiTercihSchema).length(hedef.dengeli),
garanti: z.array(AiTercihSchema).length(hedef.garanti),
genelDegerlendirme: z.string(),
uyarilar: z.array(z.string()).min(2).max(3),
});
}
// Lokal parse şeması: uzunluk kısıtsız. Sağlayıcı tel şemasındaki kısıtları
// yok sayarsa çıktı reddedilmez; normalize + kurtarma katmanı düzeltir.
const AiRaporGevsek = z.object({
hayal: z.array(AiTercihSchema),
dengeli: z.array(AiTercihSchema),
garanti: z.array(AiTercihSchema),
genelDegerlendirme: z.string(),
uyarilar: z.array(z.string()).min(2).max(3),
uyarilar: z.array(z.string()),
});
/**
* Listenin havuza uyarlanma bilgisi. uyarlandi=true ise dağılım ideal
* 5/13/6'dan sapmıştır (havuz yetersizdi) ve UI dürüst bir not gösterir.
* Eski kayıtlı raporlarda yok → opsiyonel.
*/
export type RaporKapsam = {
toplam: number;
dagilim: Record<Dilim, number>;
uyarlandi: boolean;
};
// uyarilar şemada zorunlu ama eski kayıtlı raporlarda yok → tipte opsiyonel
export type RaporSonuc = Omit<z.infer<typeof RaporSchema>, "uyarilar"> & {
uyarilar?: string[];
kapsam?: RaporKapsam;
// Dev mock üretimi işareti: gerçek AI aktifken bayat mock kayıtları
// ayıklamak için (bkz. src/lib/rapor-kaydi.ts)
devMock?: boolean;
// Render için havuzdan zenginleştirilmiş program bilgisi
// efektifSira: COALESCE(sira2025, sira2024) — eski kayıtlı raporlarda yok
// siraGecmisi: [2021..2025] taban sıralamaları — deterministik trend oku
@@ -63,9 +97,13 @@ export type RaporSonuc = Omit<z.infer<typeof RaporSchema>, "uyarilar"> & {
>;
};
const SISTEM = `Sen KolayTercih'in kıdemli YKS tercih danışmanısın. Görevin: sana verilen
function sistemMetni(hedef: DilimHedef): string {
const adet = (d: Dilim) =>
hedef[d] === 0 ? "BOŞ liste [] döndür" : `TAM ${hedef[d]} program`;
const toplam = hedef.hayal + hedef.dengeli + hedef.garanti;
return `Sen KolayTercih'in kıdemli YKS tercih danışmanısın. Görevin: sana verilen
GERÇEK YÖK Atlas aday havuzundan adaya en uygun programları seçip üç listeyi
doldurmak: hayal (TAM 5 program), dengeli (TAM 13), garanti (TAM 6).
doldurmak: hayal (${adet("hayal")}), dengeli (${adet("dengeli")}), garanti (${adet("garanti")}).
KURALLAR (kesin):
- SADECE havuzdaki programId'leri kullan; havuzda olmayan program ASLA önerme.
@@ -74,7 +112,7 @@ KURALLAR (kesin):
değeri "hayal" olan programları koy; dengeli ve garanti için de aynısı.
- Seçerken adayın ilgi alanlarını ve illerini, kontenjan/yerleşen hareketini
ve bölüm çeşitliliğini gözet.
- METİN DİYETİ: aday 24 satır okuyacak; gerekce TEK kısa cümle (en fazla ~15
- METİN DİYETİ: aday ${toplam} satır okuyacak; gerekce TEK kısa cümle (en fazla ~15
kelime, Türkçe): bu programın bu aday için neden mantıklı olduğu. Dolgu söz
yok; ASLA "garanti", "kesin", "yüzde yüz" deme.
- Adayın notlarındaki şehir/bölüm tercihlerine saygı göster ama dengeyi bozma.
@@ -82,9 +120,53 @@ KURALLAR (kesin):
buraya yazma.
- uyarilar: adaya 2-3 pratik, eyleme dönük uyarı; her biri tek kısa cümle.
(Tercih sırası, risk notu ve trend özeti koddan hesaplanır; sen yazmazsın.)`;
}
export class RaporUretimHatasi extends Error {}
// Liste kurmak için gereken mutlak taban. Havuz bunun altındaysa hata ekranı
// meşrudur; üstündeyse "ne olursa olsun liste kurulur" (kullanıcı kararı,
// 7 Ağu 2026) — eksik dilimler uyarlanır ve UI'da dürüstçe belirtilir.
// Not: ACIK_SATIR=3 maskesiyle tam 3 satırlık liste fiilen açık iner;
// kabul edilen kenar durumu.
const MIN_LISTE = 3;
const DILIMLER: Dilim[] = ["hayal", "dengeli", "garanti"];
function kapsamUret(hedef: DilimHedef): RaporKapsam {
return {
toplam: hedef.hayal + hedef.dengeli + hedef.garanti,
dagilim: { ...hedef },
uyarlandi: DILIMLER.some((d) => hedef[d] !== IDEAL_HEDEF[d]),
};
}
type AiSecim = z.infer<typeof AiTercihSchema>;
/**
* AI çıktısını havuza göre normalize eder: havuzda olmayan id'ler ve
* tekrarlar atılır, her program havuzdaki GERÇEK dilimine yeniden atanır
* (modelin yanlış listeye koyması artık ret sebebi değil, düzeltilir).
*/
function normalizeEt(
parsed: z.infer<typeof AiRaporGevsek>,
havuzMap: Map<string, AdayProgram>,
): Record<Dilim, AiSecim[]> {
const gruplar: Record<Dilim, AiSecim[]> = {
hayal: [],
dengeli: [],
garanti: [],
};
const gorulen = new Set<string>();
for (const t of [...parsed.hayal, ...parsed.dengeli, ...parsed.garanti]) {
const p = havuzMap.get(t.programId);
if (!p || gorulen.has(t.programId)) continue;
gorulen.add(t.programId);
gruplar[p.dilim].push(t);
}
return gruplar;
}
// --- Deterministik satır metinleri: risk ve trend gerçek YÖK Atlas sıra
// geçmişinden hesaplanır; model bu alanlarda jenerik dolgu yazıyordu, kod
// kesin rakam verir ("45B düzeyinden 38B düzeyine indi").
@@ -161,6 +243,9 @@ function mockRapor(
params: RaporParams,
revizyonNotu?: string,
): RaporSonuc {
// Prod ile aynı hedef hesabı: mock, uyarlanmış dağılımı da birebir taklit eder
const hedef = dilimHedefiHesapla(havuz);
const toplamHedef = hedef.hayal + hedef.dengeli + hedef.garanti;
const efektif = (p: AdayProgram) => p.sira2025 ?? p.sira2024 ?? 0;
const dilimden = (dilim: AdayProgram["dilim"], adet: number) =>
havuz
@@ -168,18 +253,13 @@ function mockRapor(
.sort((a, b) => efektif(a) - efektif(b))
.slice(0, adet);
// 5 hayal + 13 dengeli + 6 garanti; eksik dilim varsa havuzdan tamamla
const secilen = [
...dilimden("hayal", 5),
...dilimden("dengeli", 13),
...dilimden("garanti", 6),
...dilimden("hayal", hedef.hayal),
...dilimden("dengeli", hedef.dengeli),
...dilimden("garanti", hedef.garanti),
];
for (const p of havuz) {
if (secilen.length >= 24) break;
if (!secilen.includes(p)) secilen.push(p);
}
const tercihler = secilen.slice(0, 24).map((p, i) => ({
const tercihler = secilen.map((p, i) => ({
programId: p.id,
sira: i + 1,
dilim: p.dilim,
@@ -190,7 +270,7 @@ function mockRapor(
}));
const programlar: RaporSonuc["programlar"] = {};
for (const p of secilen.slice(0, 24)) {
for (const p of secilen) {
programlar[p.id] = programBilgisi(p);
}
@@ -198,14 +278,16 @@ function mockRapor(
tercihler,
genelDegerlendirme: `(Dev mock — Yapay Zeka anahtarı tanımlı değil, bu rapor gerçek Yapay Zeka çıktısı değildir.) ${params.sira.toLocaleString(
"tr-TR",
)}. sıra için hayal/dengeli/garanti dağılımlı 24 tercihlik örnek liste kuruldu.${
)}. sıra için hayal/dengeli/garanti dağılımlı ${toplamHedef} tercihlik örnek liste kuruldu.${
revizyonNotu ? ` Revizyon isteğin not alındı: "${revizyonNotu}".` : ""
}`,
uyarilar: [
"(Dev mock) Tercih dönemi kapanmadan listeyi ÖSYM sistemine kendin gir.",
"(Dev mock) Garanti dilimindeki son 3 tercihi asla silme.",
"(Dev mock) Garanti dilimindeki son tercihleri asla silme.",
],
programlar,
kapsam: kapsamUret(hedef),
devMock: true,
};
}
@@ -215,12 +297,18 @@ export async function raporUret(
oncekiListeOzeti?: string,
): Promise<RaporSonuc> {
const { havuz, gevsetildi } = havuzOlustur(params);
if (havuz.length < 24) {
if (havuz.length < MIN_LISTE) {
throw new RaporUretimHatasi(
"Bu sıralama ve filtrelerle yeterli program bulunamadı. Daha geniş kategori veya il seçmeyi dene.",
);
}
// Dilim hedefleri havuzun gerçek dağılımına uyarlanır: sabit 5/13/6 bazı
// sıralamalarda (ör. çok iyi derece → dengeli dilim boş) imkânsızdı ve
// üretim her seferinde doğrulamada patlıyordu.
const hedef = dilimHedefiHesapla(havuz);
const toplamHedef = hedef.hayal + hedef.dengeli + hedef.garanti;
const secimVar =
(params.kategoriler?.length ?? 0) > 0 ||
(params.iller?.length ?? 0) > 0 ||
@@ -230,7 +318,7 @@ export async function raporUret(
const gevsetmeNotu: string[] = [];
if (gevsetildi.includes("il")) {
gevsetmeNotu.push(
"Adayın seçtiği illerde yeterli program yoktu; havuza başka illerden de program eklendi. Seçili illere öncelik ver ama listeyi 24'e tamamlamak için diğerlerini de kullan.",
`Adayın seçtiği illerde yeterli program yoktu; havuza başka illerden de program eklendi. Seçili illere öncelik ver ama listeyi ${toplamHedef}'e tamamlamak için diğerlerini de kullan.`,
);
}
if (gevsetildi.includes("kategori")) {
@@ -266,60 +354,98 @@ export async function raporUret(
.join("\n\n");
const havuzMap = new Map(havuz.map((p) => [p.id, p]));
const efektif = (id: string) => {
const p = havuzMap.get(id)!;
return p.sira2025 ?? p.sira2024 ?? Number.MAX_SAFE_INTEGER;
};
let sonuc: z.infer<typeof AiRaporSchema> | null = null;
let secim: Record<Dilim, AiSecim[]> | null = null;
let sonSecim: Record<Dilim, AiSecim[]> | null = null;
let genel = "";
let uyarilar: string[] = [];
let sonHata = "";
for (let deneme = 0; deneme < 2; deneme++) {
const parsed = await yapilandirilmisUret({
system: SISTEM,
system: sistemMetni(hedef),
kullanici:
deneme === 0
? kullaniciMesaji
: `${kullaniciMesaji}\n\nÖNCEKİ DENEMENDE HATA VARDI: ${sonHata} Kurallara tam uy.`,
sema: AiRaporSchema,
sema: AiRaporGevsek,
wireSema: aiSemaWire(hedef),
// Küçük şema (24 satır × gerekce + genel + uyarılar) ~1-1.5k token;
// reasoning kapalıyken 4000 rahat bir tavan, kaçak uzun üretimi de keser.
maxTokens: 4000,
});
if (!parsed) {
// Dilim uzunlukları (5/13/6) şemada sabit: sayı tutmazsa da buraya düşer
sonHata = ıktı şemaya uymadı (hayal 5, dengeli 13, garanti 6 olmalı).";
sonHata = `Çıktı şemaya uymadı (hayal ${hedef.hayal}, dengeli ${hedef.dengeli}, garanti ${hedef.garanti} olmalı).`;
continue;
}
// Halüsinasyon koruması: id'ler havuzda, benzersiz ve doğru dilimde olmalı
const gruplar = [
["hayal", parsed.hayal],
["dengeli", parsed.dengeli],
["garanti", parsed.garanti],
] as const;
const idler = gruplar.flatMap(([, liste]) => liste.map((t) => t.programId));
const bilinmeyen = idler.filter((id) => !havuzMap.has(id));
if (bilinmeyen.length > 0) {
sonHata = `Havuzda olmayan programId kullanıldı: ${bilinmeyen.slice(0, 3).join(", ")}.`;
continue;
// Halüsinasyon koruması normalize içinde: havuz-dışı id ve tekrarlar
// atılır, dilimler havuzdan yeniden atanır. Ret yalnızca sayı tutmayınca.
const norm = normalizeEt(parsed, havuzMap);
sonSecim = norm;
genel = parsed.genelDegerlendirme;
uyarilar = parsed.uyarilar.slice(0, 3);
if (DILIMLER.every((d) => norm[d].length === hedef[d])) {
secim = norm;
break;
}
if (new Set(idler).size !== idler.length) {
sonHata = "Aynı programId birden fazla kez kullanıldı; her program tek kez seçilmeli.";
continue;
}
const yanlisDilim = gruplar.flatMap(([dilim, liste]) =>
liste
.filter((t) => havuzMap.get(t.programId)!.dilim !== dilim)
.map((t) => `${t.programId} (${dilim} listesinde ama havuzdaki dilimi farklı)`),
);
if (yanlisDilim.length > 0) {
sonHata = `Programlar yanlış dilim listesine konmuş: ${yanlisDilim.slice(0, 3).join(", ")}. Havuzdaki "dilim" alanına birebir uy.`;
continue;
}
sonuc = parsed;
break;
sonHata = `Dilim sayıları tutmadı (beklenen: hayal ${hedef.hayal}, dengeli ${hedef.dengeli}, garanti ${hedef.garanti}; havuz-dışı ve tekrar eden id'ler sayılmaz). Havuzdaki "dilim" alanına ve adetlere birebir uy.`;
}
if (!sonuc) {
throw new RaporUretimHatasi(
"Rapor üretilemedi (doğrulama iki denemede de başarısız). Tekrar dener misin? Revizyon hakkın kullanılmadı.",
if (!secim) {
// AI hiç kullanılabilir program seçemedi (parse iki kez null ya da tüm
// id'ler havuz-dışı): gerçek üretim hatası, hata ekranı meşru.
if (!sonSecim) {
throw new RaporUretimHatasi(
"Rapor üretilemedi (doğrulama iki denemede de başarısız). Tekrar dener misin? Revizyon hakkın kullanılmadı.",
);
}
const son = sonSecim;
const eldeki = son.hayal.length + son.dengeli.length + son.garanti.length;
if (eldeki === 0) {
throw new RaporUretimHatasi(
"Rapor üretilemedi (doğrulama iki denemede de başarısız). Tekrar dener misin? Revizyon hakkın kullanılmadı.",
);
}
// Kurtarma ("ne olursa olsun liste kurulur"): fazla dilim en iyi taban
// sıralamalılar kalacak şekilde kırpılır, eksik dilim havuzun seçilmemiş
// programlarından deterministik tamamlanır. hedef[d] ≤ havuzdaki sayı
// olduğu için tamamlama her zaman yeterli aday bulur.
console.warn(
`[rapor] kurtarma: AI ${eldeki} kullanılabilir program döndü, hedef ${toplamHedef} (hayal ${son.hayal.length}/${hedef.hayal}, dengeli ${son.dengeli.length}/${hedef.dengeli}, garanti ${son.garanti.length}/${hedef.garanti})`,
);
const kullanilan = new Set(
DILIMLER.flatMap((d) => son[d].map((t) => t.programId)),
);
const kurtarilan: Record<Dilim, AiSecim[]> = {
hayal: [],
dengeli: [],
garanti: [],
};
for (const d of DILIMLER) {
const liste = [...son[d]]
.sort((a, b) => efektif(a.programId) - efektif(b.programId))
.slice(0, hedef[d]);
if (liste.length < hedef[d]) {
const adaylar = havuz
.filter((p) => p.dilim === d && !kullanilan.has(p.id))
.sort((a, b) => efektif(a.id) - efektif(b.id));
for (const p of adaylar) {
if (liste.length >= hedef[d]) break;
kullanilan.add(p.id);
liste.push({
programId: p.id,
gerekce:
"Sıralamana ve seçimlerine uygun olduğu için listene eklendi.",
});
}
}
kurtarilan[d] = liste;
}
secim = kurtarilan;
}
const programlar: RaporSonuc["programlar"] = {};
@@ -328,19 +454,11 @@ export async function raporUret(
}
// Sıra ve satır metinleri deterministik: dilim içi iyi taban sıralamasından
// kötüye dizilir (hayal -> dengeli -> garanti zaten bu yönde akar), sira
// 1-24 koddan atanır; riskNotu + trendOzeti gerçek veriden üretilir.
const efektif = (id: string) => {
const p = havuzMap.get(id)!;
return p.sira2025 ?? p.sira2024 ?? Number.MAX_SAFE_INTEGER;
};
const tercihler = (
[
["hayal", sonuc.hayal],
["dengeli", sonuc.dengeli],
["garanti", sonuc.garanti],
] as const
).flatMap(([dilim, liste]) =>
[...liste].sort((a, b) => efektif(a.programId) - efektif(b.programId))
// koddan atanır; riskNotu + trendOzeti gerçek veriden üretilir.
const nihai = secim;
const tercihler = DILIMLER.flatMap((dilim) =>
[...nihai[dilim]]
.sort((a, b) => efektif(a.programId) - efektif(b.programId))
.map((t) => ({ dilim, ...t })),
).map((t, i) => {
const p = havuzMap.get(t.programId)!;
@@ -355,9 +473,12 @@ export async function raporUret(
});
return {
tercihler,
genelDegerlendirme: sonuc.genelDegerlendirme,
uyarilar: sonuc.uyarilar,
genelDegerlendirme:
genel.trim() ||
`Listen ${toplamHedef} tercihten oluşuyor; hayal, dengeli ve garanti dilimleri sıralamana göre dengelendi.`,
uyarilar: uyarilar.length > 0 ? uyarilar : undefined,
programlar,
kapsam: kapsamUret(hedef),
};
}

View File

@@ -6,20 +6,37 @@ import { grantCredits, DENEME_KREDISI } from "./credits";
import { magicLinkEpostasi } from "./eposta";
import { epostaGonder } from "./eposta-gonder";
/**
* Verify URL'ini maile konacak onay sayfası URL'ine çevirir. Doğrulama linki
* GET'lendiği anda token'ı tüketen tek kullanımlık bir uçtur; mail güvenlik
* tarayıcıları (SafeLinks vb.) ve tarayıcı ön-yüklemeleri linki kullanıcıdan
* önce "ziyaret edip" token'ı yakıyordu. Maildeki link bu yüzden token'ı
* tüketmeyen /giris/dogrula sayfasına gider; verify ancak oradaki butonla
* tetiklenir.
*/
function onaySayfasiUrl(verifyUrl: string): string {
const kaynak = new URL(verifyUrl);
const hedef = new URL("/giris/dogrula", kaynak.origin);
hedef.search = kaynak.search;
return hedef.toString();
}
async function sendMagicLinkEmail(email: string, url: string) {
if (process.env.NODE_ENV !== "production") {
// Dev paneli "anında giriş" için son üretilen linki yakalar (bkz.
// src/lib/dev/actions.ts) — prod'da asla çalışmaz.
// src/lib/dev/actions.ts) — panel onay adımını atlayıp doğrudan verify
// URL'ine gittiği için ham link saklanır. Prod'da asla çalışmaz.
(globalThis as { __sonMagicLink?: string }).__sonMagicLink = url;
}
const mailLinki = onaySayfasiUrl(url);
if (!process.env.RESEND_API_KEY) {
// Dev fallback: Resend anahtarı yoksa linki terminale yaz
console.log(`\n[giris] Magic link for ${email}:\n${url}\n`);
console.log(`\n[giris] Magic link for ${email}:\n${mailLinki}\n`);
return;
}
// Gönderim hatası better-auth'a fırlatılır — aksi halde kullanıcı
// "gönderildi" ekranını görür ama mail hiç çıkmaz.
await epostaGonder(email, magicLinkEpostasi(url));
await epostaGonder(email, magicLinkEpostasi(mailLinki));
}
export const auth = betterAuth({
@@ -54,6 +71,10 @@ export const auth = betterAuth({
},
plugins: [
magicLink({
// Varsayılan 5 dk, mail gecikmesi + onay adımı için 15 dk'ya çıkarıldı.
// Süreyi değiştirirken e-posta metnini (src/lib/eposta.ts) ve giriş
// ekranındaki "gönderildi" mesajını (giris-form.tsx) da güncelle.
expiresIn: 900,
sendMagicLink: async ({ email, url }) => {
await sendMagicLinkEmail(email, url);
},

View File

@@ -7,6 +7,7 @@
import { eq } from "drizzle-orm";
import { headers } from "next/headers";
import { appDb, schema } from "@/lib/appdb";
import { kullanicininRaporu } from "@/lib/rapor-kaydi";
import { auth } from "@/lib/auth";
import { getCurrentUser } from "@/lib/session";
import { URUNLER } from "@/lib/credits";
@@ -33,10 +34,7 @@ export async function devDurum(): Promise<DevDurum> {
devKontrol();
const user = await getCurrentUser();
if (!user) return { girisli: false };
const rapor = await appDb.query.reports.findFirst({
where: eq(schema.reports.userId, user.id),
columns: { params: true },
});
const rapor = await kullanicininRaporu(user.id);
const params = rapor?.params as { sira?: number; tur?: string } | undefined;
return {
girisli: true,

View File

@@ -126,7 +126,7 @@ export function magicLinkEpostasi(url: string): EpostaIcerik {
</h1>
<p style="margin:0 0 24px;">
Aşağıdaki butona tıklayarak KolayTercih hesabına giriş yapabilirsin.
Bağlantı <strong style="color:${LACIVERT};">5 dakika</strong> geçerli ve
Bağlantı <strong style="color:${LACIVERT};">15 dakika</strong> geçerli ve
yalnızca bir kez kullanılabilir.
</p>
${buton(guvenliUrl, "Giriş yap")}
@@ -142,7 +142,7 @@ export function magicLinkEpostasi(url: string): EpostaIcerik {
return {
subject: "Merhaba, giriş bağlantını gönderdik",
html: kabuk({
onizleme: "Giriş bağlantın 5 dakika geçerli.",
onizleme: "Giriş bağlantın 15 dakika geçerli.",
govde,
baslikEtiketi: "Merhaba, giriş bağlantını gönderdik",
}),
@@ -152,7 +152,7 @@ export function magicLinkEpostasi(url: string): EpostaIcerik {
"Hesabına giriş yapmak için bu adresi tarayıcında aç:",
url,
"",
"Bağlantı 5 dakika geçerli ve yalnızca bir kez kullanılabilir.",
"Bağlantı 15 dakika geçerli ve yalnızca bir kez kullanılabilir.",
"Bu isteği sen yapmadıysan bu e-postayı yok sayabilirsin.",
].join("\n"),
};

View File

@@ -34,6 +34,46 @@ export interface HavuzSonuc {
const HEDEF = 24;
const LIMIT = 60;
export type DilimHedef = Record<Dilim, number>;
// Listenin ideal iskeleti: 5 hayal / 13 dengeli / 6 garanti = 24 tercih.
export const IDEAL_HEDEF: DilimHedef = { hayal: 5, dengeli: 13, garanti: 6 };
/**
* Havuzun gerçek dilim dağılımına uyarlanmış liste hedefi. İdeal 5/13/6'dan
* başlar; bir dilimde yeterli aday yoksa eksik, kapasitesi kalan dilimlere
* dağıtılır (öncelik: dengeli → garanti → hayal — omurga önce, sigorta sonra,
* en riskli dilim en son). Havuz 24'ün altındaysa hedef havuzun tamamıdır.
* Deterministiktir: aynı havuz her zaman aynı hedefi üretir.
*/
export function dilimHedefiHesapla(havuz: AdayProgram[]): DilimHedef {
const mevcut: DilimHedef = { hayal: 0, dengeli: 0, garanti: 0 };
for (const p of havuz) mevcut[p.dilim]++;
const toplamHedef = Math.min(HEDEF, havuz.length);
const hedef: DilimHedef = {
hayal: Math.min(IDEAL_HEDEF.hayal, mevcut.hayal),
dengeli: Math.min(IDEAL_HEDEF.dengeli, mevcut.dengeli),
garanti: Math.min(IDEAL_HEDEF.garanti, mevcut.garanti),
};
const oncelik: Dilim[] = ["dengeli", "garanti", "hayal"];
let eksik = toplamHedef - hedef.hayal - hedef.dengeli - hedef.garanti;
while (eksik > 0) {
let dagitildi = false;
for (const d of oncelik) {
if (eksik <= 0) break;
if (mevcut[d] > hedef[d]) {
hedef[d]++;
eksik--;
dagitildi = true;
}
}
if (!dagitildi) break;
}
return hedef;
}
// Prompt'a giden nihai havuz: dilim başına aday sayısı. Arama LIMIT ile geniş
// yapılır (filtre/gevşetme zenginliği için), filtre sonrası buraya kırpılır —
// 20×3=60 program ≈ 8k token; 180 program 24k token tutuyor ve lokal/ücretsiz

34
src/lib/rapor-kaydi.ts Normal file
View File

@@ -0,0 +1,34 @@
import { eq } from "drizzle-orm";
import { appDb, schema } from "@/lib/appdb";
import { anahtarVar } from "@/lib/ai/client";
import type { RaporSonuc } from "@/lib/ai/rapor";
// Kullanıcının kayıtlı raporunu okuyan TEK kapı. Rapor tablosunu okuyan her
// yer (sayfalar, action'lar, soru API'si) buradan geçmeli; doğrudan
// appDb.query.reports.findFirst kullanma.
//
// Neden: dev'de API anahtarı yokken üretilen mock rapor DB'ye kaydediliyor
// (akışın tamamı anahtarsız test edilebilsin diye — bilinçli). Ama anahtar
// sonradan eklenince kullanıcı başına tek rapor kuralı yüzünden o bayat mock,
// gerçek Yapay Zeka çıktısıymış gibi servis edilmeye devam ediyordu. Gerçek AI
// aktifken mock üretimi bir rapor "yok" sayılır; kullanıcı yeni (gerçek)
// listeyi üretir ve kayıt üzerine yazılır.
function bayatMockMu(sonuc: RaporSonuc): boolean {
const mockUretimi =
sonuc.devMock === true ||
// devMock alanı eklenmeden önce kaydedilmiş mock raporlar
sonuc.genelDegerlendirme?.startsWith("(Dev mock");
if (!mockUretimi) return false;
const mockHalaAktif =
process.env.NODE_ENV !== "production" && !anahtarVar();
return !mockHalaAktif;
}
export async function kullanicininRaporu(userId: string) {
const satir = await appDb.query.reports.findFirst({
where: eq(schema.reports.userId, userId),
});
if (satir?.result && bayatMockMu(satir.result as RaporSonuc)) return null;
return satir ?? null;
}

View File

@@ -48,5 +48,8 @@ export function raporMaskele(rapor: RaporSonuc): MaskeliRapor {
kilitliUyariSayisi: Math.max(0, (rapor.uyarilar?.length ?? 0) - 1),
// Program adı, üniversite ve 20212025 taban sıralamaları ham veridir.
programlar: rapor.programlar,
// Kapsam (kaç program bulundu / dağılım uyarlandı mı) dürüstlük bilgisi:
// paketsiz kullanıcı da görür, maskelenmez.
kapsam: rapor.kapsam,
};
}