Skip to content

LearnguAI Data Architecture & Mining Strategy

1. Core Data Models (Schema)

PostgreSQL/Supabase. İki yazım kaynağı var:

  • Pipelinecards, packs, passages, language_pairs (statik, immutable)
  • Runtimeusers, user_progress (dinamik, kullanıcı başına)

users

typescript
interface UserProfile {
  id: string;                                    // UUID
  email: string;
  native_lang: string;                           // 'tr' | 'es' | 'ru' ...
  target_lang: string;                           // şimdilik her zaman 'en'

  // Progress
  level: 'A1' | 'A2' | 'B1' | 'B2' | 'C1' | 'C2';
  xp: number;
  streak_current: number;
  streak_best: number;
  last_active_at: string;                        // ISO Date

  // Settings
  daily_goal_minutes: number;
  is_pro: boolean;
  hard_mode_enabled: boolean;                    // Katman 3: yazarak cevap
}

cards

Pipeline'ın çıktısı. Bir kez yazılır, hiç değişmez.

typescript
// Pedagoji Katman 1 & 2 kart tipleri
type CardType =
  | 'quiz'             // Kelime anlamı (çoktan seçmeli)
  | 'fillGap'          // Boşluk doldurma
  | 'sentenceBuilder'  // Kelime sıralama
  | 'idiom'            // Kalıp & deyim
  | 'errorCorrection'  // Hata tespiti (algoritmik üretim, $0)
  | 'reading';         // Paragraf + anlama sorusu (Katman 2, B1+)

interface Card {
  id: string;          // "c_1024"
  pack_id: string;     // "p_travel_a2"

  // Seviye & Zorluk
  level: 'A1' | 'A2' | 'B1' | 'B2' | 'C1' | 'C2';
  complexity_score: number;    // 0.0–1.0, word frequency bazlı
  quality_score: number;       // 0.0–1.0, pipeline quality gate çıktısı

  // Tip & Konu
  type: CardType;
  topic_domain: string;        // "Travel & Transport"
  topic_leaf: string;          // "Airport > Check-in & Boarding"
  topic_tags: string[];        // ["airport", "travel", "check-in"]

  // İçerik (Polymorphic JSONB)
  content: {
    // quiz | idiom
    question?: string;

    // fillGap | idiom
    sentence?: string;         // "I would like to _____ a ticket."

    // sentenceBuilder
    words?: string[];           // ["book", "to", "like", "I"]

    // quiz | fillGap | idiom | errorCorrection
    options?: string[];         // ["book", "buy", "sell"]
    answer: string;

    // errorCorrection — hangi kural test ediliyor
    grammar_rule?: string;
    // "third_person_s" | "past_simple" | "article_countable" |
    // "preposition_time" | "modal_base" | "question_word_order" ...

    // reading — passage_id'ye referans
    passage_id?: string;        // passages tablosundaki paragraf
  };

  // Yerelleştirme (Lazy Language Pair mimarisi)
  // Key = native_lang kodu, pipeline her dil çifti için doldurur
  translations: {
    [native_lang: string]: {
      question: string;         // Soru/cümle çevirisi
      answer: string;           // Cevap çevirisi
      context: string;          // Bağlam açıklaması
    };
  };
  // Örnek:
  // translations.tr = { question: "Geçici", ... }
  // translations.es = { question: "Efímero", ... }  ← ES-EN açıldığında eklenir
}

passages

reading tipi kartların bağlandığı paragraf tablosu.

typescript
interface Passage {
  id: string;                   // "pass_512"
  level: 'B1' | 'B2' | 'C1' | 'C2';
  topic_domain: string;
  topic_leaf: string;

  // İçerik
  text_en: string;              // 3–6 cümlelik İngilizce paragraf
  word_count: number;
  source: string;               // "wikipedia" | "bbc_learning" | "gemini_generated"

  // Yerelleştirme
  translations: {
    [native_lang: string]: {
      text: string;             // Paragrafın anadilde özeti/çevirisi
    };
  };
}
// Not: Bir passage birden fazla reading kartına kaynak olabilir
//      (farklı sorular, aynı paragraf)

packs

Kart grupları. Curriculum graph'ın birimleri.

typescript
interface Pack {
  id: string;                   // "p_travel_a2"
  title: string;                // "Airport & Travel"
  level: 'A1' | 'A2' | 'B1' | 'B2' | 'C1' | 'C2';
  topic_domain: string;
  topic_leaf: string;

  prerequisites: string[];      // Bu pack'ler %75 tamamlanmadan açılmaz
  card_count: number;
  estimated_minutes: number;
  is_free: boolean;             // Freemium kapısı
}
// Pack içi kart sırası:
// 1. quiz  (kelime tanıtımı)
// 2. fillGap + idiom  (bağlamda kullanım)
// 3. sentenceBuilder + errorCorrection + reading  (üretim & doğrulama)

language_pairs

Lazy Language Pair mimarisi için durum tablosu. TR-EN elle oluşturulur. Diğer tüm çiftler ilk kullanıcı talebinde tetiklenir.

typescript
interface LanguagePair {
  native_lang: string;          // 'es' | 'ru' | 'ja' ...  (PK)
  target_lang: string;          // şimdilik her zaman 'en'  (PK)
  status: 'pending'             // talep alındı, sırada
         | 'generating'         // pipeline çalışıyor
         | 'ready';             // tüm çeviriler tamamlandı

  triggered_by: string;         // FK → users.id (ilk isteyen kullanıcı)
  triggered_at: string;         // ISO Date
  generated_at: string | null;  // tamamlanma zamanı
  card_count: number;           // kaç karta çeviri eklendi
  cost_usd: number;             // üretim maliyeti (log)
}

user_progress

Her kullanıcının her kartla FSRS durumu. En çok büyüyecek tablo.

typescript
interface UserCardProgress {
  user_id: string;              // FK → users.id   (composite PK)
  card_id: string;              // FK → cards.id   (composite PK)

  // FSRS Parametreleri
  // Bkz: R(t,S) = 0.9^(t/S)
  stability: number;            // S: kaç günde %90 → %81'e düşer
  difficulty: number;           // D: kartın kalıcı zorluğu (1–10)
  // Retrievability (R) hesaplanan değer, stored değil:
  // R = 0.9^( (now - last_review_days) / stability )

  // Durum
  status: 'new' | 'learning' | 'review' | 'mastered';
  last_review: string;          // ISO Date
  next_review: string;          // ISO Date  ← INDEX'LENMELI
  lapses: number;               // kaç kez unutuldu

  // Katman 2: Zayıf Alan Hedefleme
  // errorCorrection kartları için hangi grammar_rule'da hata yapıldı
  grammar_rule_failures: Record<string, number>;
  // Örnek: { "article_countable": 4, "third_person_s": 1 }
  // Bu veri adaptive difficulty seçiminde kullanılır
}

// Kritik index:
// CREATE INDEX idx_user_progress_review
// ON user_progress (user_id, next_review);
// 1B satırda bu index olmadan → full scan → felaket
// Bu index ile → milisaniye

2. Low-Cost Data Mining Algorithm ("The Deep Drill")

The goal is to generate high-quality language content without expensive per-user LLM calls. We use a "Miner -> Refiner -> Vault" pipeline.

Philosophy: "Write Once, Serve Millions"

Instead of asking an AI to generate a quiz for every user (Cost: $$$$), we mine 10,000 sentences once, process them into millions of static cards, and serve them from a cheap database (Cost: $).

The Algorithm Steps

Step 1: Raw Ore Extraction (Zero Cost)

Target public domain datasets that contain sentence pairs.

  • Source A: Tatoeba Project: Millions of high-quality sentence pairs (En-Tr, En-Es).
  • Source B: Project Gutenberg: Public domain books.
  • Source C: Wiktionary: Word definitions and usage examples.

Step 2: The Sieve (Heuristic Filtering)

Use purely algorithmic (CPU-only) filters to remove junk. No AI yet.

  1. Length Filter: Discard sentences < 3 words or > 20 words.
  2. Complexity Grading: Assign CEFR level (A1-C2) based on word frequency lists (e.g., "Oxford 3000").
    • If sentence contains 'ephemeral' -> Level C1.
    • If sentence contains only top 500 words -> Level A1.

Step 3: The Refinery (Two Paths)

Path A — Gemini Flash (for quiz, fillGap, idiom, reading)

  • Batching: Don't send 1 sentence. Send 50 sentences in one prompt.
  • Task per card:
    1. Identify the hardest target word
    2. Generate 3 plausible distractors
    3. Verify / generate native language translation
    4. Assign topic_domain + topic_leaf
    5. Return confidence score (quality gate için)
  • Cost Efficiency:
    • 1M Input Tokens (~$0.075 on Gemini Flash) can process ~20,000 sentences.
    • Total cost to build a course with 5,000 cards: ~$0.03.

Lazy Language Pair için ek çalışma: Yeni bir dil çifti (örn. ES-EN) ilk kez talep edildiğinde pipeline mevcut 235K kartın content alanını yeniden işlemez — sadece translations JSONB'ye yeni key ekler:

BATCH PROMPT (500 kart):
"For each English card, generate a Spanish (es) translation entry:
 { question, answer, context }. Return as JSON array."

Maliyet: 235K kart × ~50 token = ~12M token → ~$0.90 (tek seferlik)

Path B — Algorithmic (for errorCorrection) — Zero AI Cost

  • Take a correct sentence from Tatoeba (already filtered and verified).
  • Apply deterministic error transformation rules:
    • third_person_s: "She runs" → "She run"
    • past_simple: "I went" → "I have went" / "I did went" / "I was went"
    • article: "a/an/the" insertion/removal errors
    • preposition: swap with common wrong preposition ("interested in" → "interested about")
  • Generate 3 wrong variants + 1 correct = 1 complete card.
  • Cost: $0.00 per card, infinitely scalable.

Step 4: crystallize (Static JSON)

The output is saved as the static cards table.

  • It never changes.
  • It requires no API keys at runtime.
  • It works offline.

Example Workflow

  1. Input: "The sun sets in the west." (From Tatoeba)
  2. Algorithm:
    • Identify target word: "sets" (Verb).
    • Calculate Level: A1 (Common word).
  3. Refiner AI: Generates distractors ["rises", "falls", "stays"].
  4. Output Card:
    json
    {
      "id": "c_99",
      "level": "A1",
      "type": "fillGap",
      "content": "The sun _____ in the west.",
      "options": ["sets", "rises", "falls", "stays"],
      "answer": "sets"
    }

This approach allows LearnguAI to scale infinitely with near-zero marginal content cost.


3. Content Scale Analysis (TR → EN, A1–C2)

Source Data Capacity

KaynakHam VeriKullanılabilir Kart Tipleri
Tatoeba (EN-TR çiftleri)~450K cümle çiftiquiz, fillGap, sentenceBuilder, errorCorrection
Project Gutenberg (EN metinler)~5M cümlesentenceBuilder, errorCorrection
Wiktionary (EN tanımlar + örnekler)~200K kelime girişiquiz
COCA Phrasal Verb List~5,000+ ifadeidiom
BNC / Macmillan Idiom Lists~3,000+ deyimidiom

Filtre Sonrası Kullanılabilir Miktar (Tatoeba)

450,000 EN-TR cümle çifti

        ▼  uzunluk filtresi (3–20 kelime)
~280,000

        ▼  kalite filtresi (dilbilgisel, özel isim yoğun değil, temiz çeviri)
~180,000 kullanılabilir cümle  ←  tüm card üretiminin omurgası

Kart Tipi Başına Gerçekçi Tahmin

TipAna KaynakÜretim YöntemiTahmini Kart
quizTatoeba + WiktionaryGemini batch~30,000
fillGapTatoebaGemini batch~100,000
sentenceBuilderTatoeba (5–12 kelime)Gemini batch~40,000
idiomCOCA + BNC + Gemini üretimiGemini batch~15,000
errorCorrectionTatoeba + GutenbergAlgoritmik ($0)~50,000
readingWikipedia + BBC Learning + GeminiGemini batch (B1+)~5,000
TOPLAM~240,000

Not: errorCorrection ve sentenceBuilder için Gutenberg'den gelen İngilizce metinler de eklendikçe bu rakam 400,000'e ulaşabilir. Gutenberg kartları TR çevirisi gerektirmediğinden (İngilizce gramer testi) Gemini maliyeti artmaz.

CEFR Dağılımı

SeviyeHedef Kelime HavuzuTahmini Kart PayıTahmini Kart
A1~500 temel kelime%8~18,800
A2~1,200 kelime%12~28,200
B1~2,500 kelime%22~51,700
B2~5,000 kelime%28~65,800
C1~8,000 kelime%20~47,000
C2sınırsız%10~23,500
Toplam%100~235,000

Üretim Maliyeti (Tüm 235K Kart)

quiz + fillGap + idiom (Gemini):
  ~185,000 kart × ortalama 40 token/kart = ~7.4M token
  $0.075 / 1M token (Gemini Flash input) → ~$0.56

sentenceBuilder (Gemini, daha kısa prompt):
  ~40,000 kart → ~$0.12

errorCorrection (algoritmik):
  ~50,000 kart → $0.00

Topic classification (Gemini, Step 3'e eklenir, minimal):
  Batch içinde ek ~10 token/kart → ~$0.09

                    ┌─────────────────────┐
                    │  TOPLAM: ~$0.77     │
                    │  235,000 kart için  │
                    └─────────────────────┘

Gerçekçi İçerik Yayın Planı

MVP Launch    →   1,000 kart  (5 tip × A1–B1 × 4 konu)
Phase 1       →  10,000 kart  (tüm tipler × A1–B2 × 20 konu)
Phase 2       →  50,000 kart  (tüm tipler × A1–C2 × 50 konu)
Full Content  → 235,000 kart  (tek pipeline run, tam kaynak)

4. Pipeline Eksik Parçaları (Scaling İçin Gereken)

Mevcut pipeline 5 demo kartı çalıştırıyor. 235,000 karta ve anlamlı bir öğrenme deneyimine ulaşmak için şu 5 bileşen eksik veya tanımsız:


4.1 Konu Taksonomisi (Topic Taxonomy)

Problem: Kartlar şu an sadece CEFR seviyesiyle etiketleniyor. "Travel A2" gibi bir pack oluşturmak için her kartın konu etiketi olmalı. Etiket olmadan pack sistemi çalışamaz, kullanıcı ilerleme haritası kurulamaz.

Çözüm: 3 katmanlı taksonomi. Pipeline Step 3'e konu sınıflandırma eklenir (mevcut Gemini batch prompt'una ~10 token eklemek yeterli).

Domain → Topic → Subtopic

Daily Life
├── Food & Drink
│   ├── At a Restaurant
│   ├── Cooking & Recipes
│   └── Groceries & Shopping
├── Home & Living
│   ├── Furniture & Rooms
│   └── Chores & Household
└── Health & Body
    ├── Body Parts & Symptoms
    └── Doctor & Pharmacy

Travel & Transport
├── Airport
│   ├── Check-in & Boarding
│   └── Immigration & Customs
├── Accommodation
│   ├── Hotel
│   └── Airbnb & Hostel
└── Directions & Navigation

Work & Career
├── Job Search
│   ├── CV & Cover Letter
│   └── Interviews
├── Office Life
└── Business Communication
    ├── Email & Meetings
    └── Presentations

Technology
├── Computers & Software
├── Internet & Social Media
└── AI & Automation

Society & Culture
├── Customs & Traditions
├── Current Events & News
└── Environment & Climate

Academic
├── Science & Research
├── Literature & Art
└── Philosophy & Ethics

(~80–100 leaf topic toplam)

Schema güncellemesi (cards tablosuna eklenir):

typescript
topic_domain: string;   // "Travel & Transport"
topic_leaf:   string;   // "Airport > Check-in & Boarding"
topic_tags:   string[]; // ["airport", "travel", "check-in", "boarding", "flight"]

Prompt güncellemesi (mevcut Gemini batch'e eklenir):

"For each sentence, also return:
 - topic_domain: one of [Daily Life, Travel, Work, Technology, Society, Academic]
 - topic_leaf: the most specific subtopic"

4.2 Gramer Hata Kataloğu (errorCorrection İçin)

Problem: errorCorrection kartları algoritmik üretilecek ama hangi kuralların nasıl mutasyon üreteceği tanımlanmamış. Bu katalog olmadan bu tip kart üretilemiyor.

Çözüm: Türk öğrencilerin en sık yaptığı İngilizce hatalarına dayalı kural seti.

typescript
interface GrammarRule {
  id: string;
  name_tr: string;
  level: 'A1' | 'A2' | 'B1' | 'B2' | 'C1';
  frequency: 'high' | 'medium' | 'low'; // Türk öğrencilerde görülme sıklığı
  detect: (tokens: string[]) => boolean; // Bu kural bu cümlede test edilebilir mi?
  mutate: (sentence: string) => string[]; // 3 sistematik yanlış varyant üret
}

// İlk 10 kural (toplam ~25 kural hedefleniyor):

{ id: 'third_person_s',
  name_tr: '3. tekil şahıs -s eki',
  level: 'A2', frequency: 'high',
  // "She runs fast." → ["She run fast.", "She is run fast.", "She runned fast."]
}

{ id: 'past_simple_irregular',
  name_tr: 'Düzensiz geçmiş zaman çekimi',
  level: 'A2', frequency: 'high',
  // "I went home." → ["I goed home.", "I have went home.", "I was go home."]
}

{ id: 'present_perfect_vs_past',
  name_tr: 'Present perfect / past simple karışıklığı',
  level: 'B1', frequency: 'high',
  // "I saw him yesterday." → "I have seen him yesterday."
  // (yesterday ile perfect kullanımı Türklerin klasik hatası)
}

{ id: 'article_countable',
  name_tr: 'Sayılabilir isimde article (a/an/the)',
  level: 'A2', frequency: 'high',
  // "I need a pen." → ["I need pen.", "I need the pen.", "I need an pen."]
}

{ id: 'article_uncountable',
  name_tr: 'Sayılamaz isimde article yokluğu',
  level: 'B1', frequency: 'high',
  // "I need information." → "I need an information." / "I need the informations."
}

{ id: 'preposition_in_on_at_time',
  name_tr: 'Zaman edatları: in/on/at',
  level: 'A2', frequency: 'high',
  // "on Monday" → "in Monday" / "at Monday"
}

{ id: 'preposition_in_on_at_place',
  name_tr: 'Yer edatları: in/on/at',
  level: 'A2', frequency: 'high',
  // "at the station" → "in the station" / "on the station"
}

{ id: 'modal_base_form',
  name_tr: 'Modal fiil sonrası yalın mastar',
  level: 'A2', frequency: 'medium',
  // "She can swim." → "She can swims." / "She can to swim." / "She can swimming."
}

{ id: 'question_word_order',
  name_tr: 'Soru cümlesi yardımcı fiil sırası',
  level: 'A2', frequency: 'high',
  // "Where are you going?" → "Where you are going?" / "Where you going?"
}

{ id: 'comparative_form',
  name_tr: '-er / more karşılaştırma yapısı',
  level: 'A2', frequency: 'medium',
  // "more fast" → "faster" / "most fast" → "fastest"
}

{ id: 'passive_voice_be',
  name_tr: 'Edilgen yapıda be fiili',
  level: 'B1', frequency: 'medium',
  // "It was built in 1900." → "It builded in 1900." / "It is built in 1900."
}

// ... devam eden kurallar: conditional, reported speech,
//     gerund vs infinitive, preposition after adjective, vb.

Her kural otomatik olarak Tatoeba corpus'undaki uygun cümleleri tarar ve sistematik yanlış varyantlar üretir. Maliyet: $0.00.


4.3 Deduplication Pipeline

Problem: 180K cümleden kart üretilince anlamsal tekrarlar kaçınılmaz. "The cat sat on the mat" ve "A cat is sitting on the mat" neredeyse aynı fillGap kartını üretir. 235K kartın %15–20'si potansiyel duplicate.

Çözüm: Crystallize adımından önce iki aşamalı dedup.

AŞAMA 1 — Exact Hash Dedup (sıfır maliyet)
  hash = sha256(type + answer + normalize(sentence))
  Eğer hash daha önce görüldüyse → at
  Süre: ~2 dakika / 235K kart

AŞAMA 2 — Semantic Dedup (aynı tip, aynı seviye kartlar arasında)
  Her kartın cümlesi için sentence embedding üret
  (text-embedding-3-small, $0.02 / 1M token → tüm corpus: ~$0.50)

  Cosine similarity > 0.92 → ikisinden kalite skoru düşük olanı at

  Süre: ~30 dakika / 235K kart
  Maliyet: ~$0.50 (tek seferlik)

4.4 Kalite Skoru

Problem: Her üretilen kart iyi değil. Kötü kart türleri:

  • Distractors çok bariz yanlış → kart çok kolay, öğrenme değeri düşük
  • Birden fazla seçenek doğru olabilir → ambiguous, kullanıcıyı yanıltır
  • Gemini çevirisi hatalı → yanlış öğretir

Çözüm: Her kart için otomatik kalite skoru, threshold altındakiler atılır.

typescript
interface CardQualityScore {
  // Distractors ne kadar yanıltıcı?
  // Yöntem: doğru cevap ile her distractor arasındaki edit distance
  // Çok kısa distance → çok benzer → plausible (iyi)
  // Çok uzun distance → çok farklı → obvious wrong (kötü)
  distractor_plausibility: number; // 0.0 – 1.0

  // Doğru cevap gerçekten tek doğru mu?
  // Yöntem: distractors içinde de doğru kabul edilebilecek var mı? (rule-based)
  answer_uniqueness: number; // 0.0 – 1.0

  // Gemini'nin kendi confidence skoru (response'dan parse edilir)
  translation_confidence: number; // 0.0 – 1.0

  overall: number; // weighted average
}

// Eşikler:
// overall < 0.55  → at (discard)
// overall 0.55–0.75 → 'needs_review' flag ile kaydet (human spot-check için)
// overall > 0.75  → direkt crystallize

Pratikte bu eşikler ilk 1,000 kartlık pilot run sonrası kalibre edilir. Hedef: needs_review oranı < %10.


4.5 Curriculum Graph (Pack Önkoşulları)

Problem: 235,000 kart düz bir yığın halinde sunulursa öğrenme sistematik olmaz. Kullanıcı A1 bitmeden C1 kartına denk gelebilir. Duolingo vs Anki farkı tam olarak bu: yapılandırılmış ilerleme.

Çözüm: Pack'ler arasında prerequisite ilişkisi + tamamlanma kapıları.

typescript
interface Pack {
  id: string;
  title: string;
  level: 'A1' | 'A2' | 'B1' | 'B2' | 'C1' | 'C2';
  topic_domain: string;
  topic_leaf: string;
  prerequisites: string[];   // Bu pack'ler %75 tamamlanmadan açılmaz
  card_count: number;
  is_free: boolean;
  estimated_minutes: number; // tahmini tamamlama süresi
}

Curriculum graph (TR→EN için önerilen iskelet):

FREE ──────────────────────────────────────────────────────────
[A1 Greetings & Basics]

        ├──► [A1 Numbers & Time]

        └──► [A1 Family & People]


            [A2 Daily Routines]──► [A2 Food & Drink]


             [A2 Shopping]──────── [A2 Health Basics]

PRO ────────────────┼──────────────────────────────────────────

              [B1 Travel]──► [B1 Transport & Directions]

                    ├──► [B1 Work & Career]

                    └──► [B1 Technology]


                          [B2 Business]──► [B2 Society]

                                └──► [B2 Academic Writing]


                                       [C1 Advanced]──► [C1 Idioms Mastery]

                                              └──► [C2 Native-Level]

Her pack içinde kart sırası da rastgele değil:

  1. quiz kartları önce (kelime tanıtımı)
  2. fillGap ve idiom ortada (bağlamda kullanım)
  3. sentenceBuilder ve errorCorrection sonda (üretim ve doğrulama)

5. Güncellenmiş Tam Pipeline

┌─────────────────────────────────────────────────────────────────┐
│  STEP 0: RAW DATA INGESTION                                     │
│  Tatoeba EN-TR + Gutenberg + Wiktionary + COCA/BNC              │
└─────────────────────────┬───────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│  STEP 1: THE SIEVE (CPU only, $0)                               │
│  • Uzunluk filtresi (3–20 kelime)                               │
│  • CEFR seviye tahmini (word frequency listesi)                 │
│  • Kart tipi uygunluk tespiti (cümle uzunluğu, yapı)           │
│  • Dil algılama (sadece İngilizce cümleler)                     │
└─────────────────────────┬───────────────────────────────────────┘

              ┌────────────┴────────────┐
              │                         │
              ▼                         ▼
┌─────────────────────┐   ┌─────────────────────────────────────┐
│  PATH B             │   │  PATH A                             │
│  errorCorrection    │   │  quiz / fillGap / sentenceBuilder / │
│  (Algoritmik, $0)   │   │  idiom  (Gemini Flash)              │
│                     │   │                                     │
│  Gramer hata kataloğ│   │  BATCH PROMPT (50 cümle):           │
│  kuralları uygulanır│   │  1. En zor hedef kelimeyi bul       │
│  3 yanlış varyant   │   │  2. 3 plausible distractor üret     │
│  üretilir           │   │  3. TR çevirisini doğrula           │
│                     │   │  4. Konu etiketini belirle          │
│  $0.00 / kart       │   │  5. Kalite skoru döndür             │
└──────────┬──────────┘   └──────────────┬──────────────────────┘
           │                             │
           └──────────────┬──────────────┘


┌─────────────────────────────────────────────────────────────────┐
│  STEP 3: QUALITY GATE                                           │
│  • overall_score hesapla                                        │
│  • < 0.55 → discard                                             │
│  • 0.55–0.75 → needs_review flag                               │
│  • > 0.75 → pass                                                │
└─────────────────────────┬───────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│  STEP 4: DEDUPLICATION                                          │
│  • Exact hash dedup (~2 dakika, $0)                             │
│  • Semantic similarity dedup (~30 dakika, ~$0.50 one-time)     │
└─────────────────────────┬───────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│  STEP 5: CRYSTALLIZE                                            │
│  • cards tablosuna yaz (immutable)                              │
│  • Pack'lere ata (topic + level bazlı)                          │
│  • Curriculum graph'taki yerine yerleştir                       │
│  • CDN cache'e al (offline erişim için)                         │
└─────────────────────────────────────────────────────────────────┘

Toplam süre (235K kart):  ~4–6 saat (single run)
Toplam maliyet:           ~$1.30
Tekrar ne zaman:          Yeni kaynak eklendikçe, yılda 1–2 kez