# Yuz Tanima Kisiler Galerisi - Google Photos Tarzi Implementation Plan

> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Soyagaci kisiler sayfasini Google Photos "People & Pets" deneyimine benzer sekilde tamamen yeniden yazmak - cok kullanicili, kullanici bazli etiketleme, paylasimda fallback cozumleme, admin panelinde global yonetim.

**Architecture:** Bagimsiz `yuz-tanima/` ortak modulu (interfaces + services + shared components) olusturulur. Bu modul soyagaci (kullanici UI), admin panel ve paylasim sistemi tarafindan kullanilir. Backend mevcut FikoYTIstekKok + MongoDB aggregate pattern'i ile calisir, yeni koleksiyonlar eklenir. Socket.IO ile canli kuyruk bildirimleri.

**Tech Stack:** Angular 20 (standalone components, signals, OnPush), Ng-Zorro UI, FikoYTIstekKok + DbBaseService, MongoDB aggregate pipelines, Socket.IO, InsightFace (backend)

**Spec:** `docs/superpowers/specs/2026-03-10-yuz-tanima-kisiler-galerisi-design.md`

---

## Dosya Haritasi

### Yeni Dosyalar

| Dosya | Sorumluluk |
|-------|-----------|
| `src/app/uygulamalar/yuz-tanima/interfaces/yuz-tanima.interface.ts` | Tum interface ve type tanimlari |
| `src/app/uygulamalar/yuz-tanima/services/yuz-tanima-api.service.ts` | DbBaseService extend, MongoDB CRUD |
| `src/app/uygulamalar/yuz-tanima/services/yuz-gruplama.service.ts` | Etiket cozumleme, birlestirme mantigi |
| `src/app/uygulamalar/yuz-tanima/services/yuz-kuyruk-socket.service.ts` | Socket.IO kuyruk + push |
| `src/app/uygulamalar/yuz-tanima/utils/etiket-cozumle.ts` | Pure function: etiket fallback |
| `src/app/uygulamalar/yuz-tanima/components/yuz-avatar/yuz-avatar.component.ts` | Yuvarlak yuz crop avatar |
| `src/app/uygulamalar/yuz-tanima/components/kisi-grid/kisi-grid.component.ts` | Google Photos tarzi kisi grid |
| `src/app/uygulamalar/yuz-tanima/components/birlestirme-karti/birlestirme-karti.component.ts` | Merge suggestion karti |
| `src/app/uygulamalar/yuz-tanima/components/yuz-etiketleme-popup/yuz-etiketleme-popup.component.ts` | Fotograf uzerinde bbox + etiketleme |
| `src/app/uygulamalar/yuz-tanima/components/kisi-chip-filtre/kisi-chip-filtre.component.ts` | Chip filter bar |
| `src/app/uygulamalar/yuz-tanima/components/index.ts` | Public exports |
| `src/app/uygulamalar/soyagaci/kisi-fotograflari/kisi-fotograflari.component.ts` | Kisi fotograf galerisi sayfasi |
| `src/app/uygulamalar/admin/yuz-tanima/pano/pano.component.ts` | Admin dashboard |
| `src/app/uygulamalar/admin/yuz-tanima/global-havuz/global-havuz.component.ts` | Global embedding havuzu |
| `src/app/uygulamalar/admin/yuz-tanima/kullanici-goruntule/kullanici-goruntule.component.ts` | Kullanici galeri gorunumu |

### Degistirilecek Dosyalar

| Dosya | Degisiklik |
|-------|-----------|
| `src/app/uygulamalar/soyagaci/kisiler-galerisi/kisiler-galerisi.component.ts` | Bastan yazilacak |
| `src/app/uygulamalar/soyagaci/soyagaci.routes.ts` | Yeni route ekle: `kisiler/:grupId` |
| `src/app/uygulamalar/admin/yuz-tanima/yuz-tanima.routes.ts` | Yeni route'lar: pano, global-havuz, kullanici |
| `src/app/uygulamalar/admin/yuz-tanima/kuyruk/kuyruk.component.ts` | Yeni kuyruk sistemi ile guncelle |
| `src/app/uygulamalar/admin/yuz-tanima/kisiler/kisiler.component.ts` | Global havuz ile guncelle |
| `src/service/fikoyt-ui/src/lib/paylasim-sistemi/fikoyt-kart-kok/icerikler/icerikler.component.ts` | Etiket cozumleme entegrasyonu |

---

## Chunk 1: Foundation - Interfaces & Services

### Task 1: Interface Tanimlari

**Files:**
- Create: `src/app/uygulamalar/yuz-tanima/interfaces/yuz-tanima.interface.ts`

- [ ] **Step 1: Interface dosyasini olustur**

```typescript
// yuz-tanima.interface.ts

// ===== Embedding Koleksiyonu =====
export interface IYuzBbox {
  x: number;   // % sol (0-100)
  y: number;   // % ust (0-100)
  w: number;   // % genislik (0-100)
  h: number;   // % yukseklik (0-100)
}

export interface IYuzVeri {
  index: number;
  bbox: IYuzBbox;
  embedding?: number[];     // 512-dim float (sadece backend)
  guven: number;            // 0-1
  kisiGrupId?: string;
}

export interface IYuzEmbedding {
  _id: string;
  resimId: string;
  kaynakTip: 'paylasim' | 'soyagaci' | 'profil';
  kaynakId: string;
  kaynakSahibiId: string;
  yuzler: IYuzVeri[];
  durum: YuzIslemDurumu;
  islemTarihi?: string;
  olusturmaTarihi: string;
}

// ===== Kisi Gruplari Koleksiyonu =====
export interface IOrnekResim {
  resimId: string;
  bbox: IYuzBbox;
}

export interface IKisiGrup {
  _id: string;
  temsilciEmbedding?: number[];
  yuzSayisi: number;
  ornekResimler: IOrnekResim[];
  birlestirmeler: string[];
  olusturmaTarihi: string;
}

// ===== Kullanici Etiketleri =====
export interface IKullaniciEtiket {
  _id: string;
  kullaniciId: string;
  kisiGrupId: string;
  isim: string;
  soyagaciKisiId?: string;
  resimUrl?: string;
  gizli: boolean;
  olusturmaTarihi: string;
  guncellemeTarihi: string;
}

// ===== Birlestirme Onerileri =====
export type BirlestirmeDurumu = 'beklemede' | 'onaylandi' | 'reddedildi';

export interface IBirlestirmeOnerisi {
  _id: string;
  kullaniciId: string;
  grup1Id: string;
  grup2Id: string;
  benzerlik: number;
  durum: BirlestirmeDurumu;
  olusturmaTarihi: string;
  // UI icin zenginlestirilmis
  grup1?: IKisiGrup;
  grup2?: IKisiGrup;
  grup1Etiket?: IKullaniciEtiket;
  grup2Etiket?: IKullaniciEtiket;
}

// ===== Islem Kuyrugu =====
export type YuzIslemDurumu = 'beklemede' | 'isleniyor' | 'tamamlandi' | 'hata';

export interface IIslemKuyrugu {
  _id: string;
  resimId: string;
  kaynakTip: string;
  kaynakId: string;
  kaynakSahibiId: string;
  oncelik: number;
  durum: YuzIslemDurumu;
  hata?: string;
  baslamaZamani?: string;
  bitisZamani?: string;
  olusturmaTarihi: string;
  // UI icin
  kullaniciAdi?: string;
  sonuc?: { yuzSayisi: number; sureMs: number };
}

// ===== Cozumlenmis Etiket (UI icin) =====
export type EtiketKaynagi = 'kendi' | 'paylasan' | 'bilinmeyen';

export interface ICozumlenmisEtiket {
  bbox: IYuzBbox;
  kisiGrupId?: string;
  isim: string;
  kaynak: EtiketKaynagi;
  kaynakKullaniciAdi?: string;
  soyagaciKisiId?: string;
  resimUrl?: string;
}

// ===== Galeri Kisi Karti (Grid gorunumu) =====
export interface IGaleriKisi {
  kisiGrupId: string;
  isim: string;
  fotografSayisi: number;
  ornekResim: IOrnekResim;
  soyagaciKisiId?: string;
  resimUrl?: string;
  etiketlendi: boolean;
}

// ===== Kisi Fotograf Galerisi =====
export interface IKisiFotograf {
  _id: string;
  resimId: string;
  resimUrl: string;
  tarih: string;
  bbox: IYuzBbox;
  birlikteGorunenler: { kisiGrupId: string; isim: string; resimUrl?: string }[];
  kaynakTip: string;
}

// ===== Admin Istatistik =====
export interface IYuzTanimaIstatistik {
  toplamYuz: number;
  taninanKisi: number;
  bilinmeyenYuz: number;
  kuyrukBekleyen: number;
  aktifKullanici: number;
}

// ===== Sayfali Sonuc =====
export interface ISayfaliSonuc<T> {
  toplam: number;
  liste: T[];
}

// ===== Socket Event Tipleri =====
export interface IKuyrukIlerlemeEvent {
  resimId: string;
  yuzde: number;
  asama: string;
}

export interface IKuyrukTamamlandiEvent {
  resimId: string;
  yuzSayisi: number;
  eslesmeler: { kisiGrupId: string; yeniMi: boolean }[];
}

export interface IOneriYeniEvent {
  oneriId: string;
  grup1: IKisiGrup;
  grup2: IKisiGrup;
  benzerlik: number;
}
```

- [ ] **Step 2: Dosyanin derledigini dogrula**

Run: `npx tsc --noEmit src/app/uygulamalar/yuz-tanima/interfaces/yuz-tanima.interface.ts`
(Hata yoksa gecerli)

- [ ] **Step 3: Commit**

```bash
git add src/app/uygulamalar/yuz-tanima/interfaces/yuz-tanima.interface.ts
git commit -m "feat(yuz-tanima): interface tanimlari eklendi"
```

---

### Task 2: YuzTanimaApiService (DbBaseService extend)

**Files:**
- Create: `src/app/uygulamalar/yuz-tanima/services/yuz-tanima-api.service.ts`
- Reference: `src/app/uygulamalar/firma-yonetim/core/db-base.service.ts`

- [ ] **Step 1: DbBaseService pattern'ini kullanarak servis olustur**

```typescript
// yuz-tanima-api.service.ts
import { Injectable, inject } from '@angular/core';
import { FikoYTService } from 'src/service/fikoyt.service';
import { FikoYTIstekKok } from 'src/service/modules/fikoyt-istek-kok/fikoyt.istek.makinesi.service';
import {
  IYuzEmbedding, IKisiGrup, IKullaniciEtiket, IBirlestirmeOnerisi,
  IIslemKuyrugu, IGaleriKisi, IKisiFotograf, IYuzTanimaIstatistik,
  ISayfaliSonuc, YuzIslemDurumu, IOrnekResim
} from '../interfaces/yuz-tanima.interface';

const DB = {
  embeddingler:   (op: string) => `FikoYT.sistem.DB.yuz_embeddingler.${op}`,
  kisiGruplari:   (op: string) => `FikoYT.sistem.DB.yuz_kisi_gruplari.${op}`,
  etiketler:      (op: string) => `FikoYT.sistem.DB.yuz_kullanici_etiketleri.${op}`,
  onerileri:      (op: string) => `FikoYT.sistem.DB.yuz_birlestirme_onerileri.${op}`,
  kuyruk:         (op: string) => `FikoYT.sistem.DB.yuz_islem_kuyrugu.${op}`,
  hareketler:     (op: string) => `FikoYT.sistem.DB.hareketler.${op}`,
  goUyeler:       (op: string) => `FikoYT.sistem.DB.goUyeler.${op}`,
  soyAgaciKisiler:(op: string) => `FikoYT.sistem.DB.soyAgaciKisiler.${op}`,
} as const;

@Injectable({ providedIn: 'root' })
export class YuzTanimaApiService {
  private FikoYT = inject(FikoYTService);

  // Cache
  private cache = new Map<string, { veri: any; zaman: number }>();
  private cacheTTL = 60_000;

  private cached<T>(anahtar: string, ttl = this.cacheTTL): T | null {
    const entry = this.cache.get(anahtar);
    if (entry && Date.now() - entry.zaman < ttl) return entry.veri as T;
    this.cache.delete(anahtar);
    return null;
  }
  private cacheSet(anahtar: string, veri: any) {
    this.cache.set(anahtar, { veri, zaman: Date.now() });
  }
  cacheTemizle(onek?: string) {
    if (!onek) { this.cache.clear(); return; }
    for (const k of this.cache.keys()) { if (k.startsWith(onek)) this.cache.delete(k); }
  }

  // CRUD Helpers
  private async aggregate<T = any>(komut: string, pipeline: any[]): Promise<T[]> {
    const istek = new FikoYTIstekKok({ metod: 'POST', komut });
    istek.parametreler = { aggregate: pipeline };
    await istek.isteSync();
    return (istek.cevap?.sonuc || []) as T[];
  }

  private async ekle<T = any>(komut: string, veri: any): Promise<T> {
    const istek = new FikoYTIstekKok({ metod: 'POST', komut });
    const fd = new FormData();
    fd.append('req_body', JSON.stringify(veri));
    istek.parametreler = fd;
    await istek.isteSync();
    return istek.cevap?.sonuc as T;
  }

  private async guncelle(komut: string, filter: any, veri: any): Promise<any> {
    const istek = new FikoYTIstekKok({ metod: 'POST', komut });
    const fd = new FormData();
    fd.append('req_body', JSON.stringify({ filter, veri }));
    istek.parametreler = fd;
    await istek.isteSync();
    return istek.cevap?.sonuc;
  }

  private async sil(komut: string, filter: any): Promise<any> {
    const istek = new FikoYTIstekKok({ metod: 'POST', komut });
    const fd = new FormData();
    fd.append('req_body', JSON.stringify({ filter }));
    istek.parametreler = fd;
    await istek.isteSync();
    return istek.cevap?.sonuc;
  }

  private sayfaliPipeline(match: any, sort: any, sayfa: number, limit: number, ekPipeline: any[] = []) {
    return [
      { $match: match },
      ...ekPipeline,
      { $facet: {
        toplam: [{ $count: 'sayi' }],
        liste: [{ $sort: sort }, { $skip: (sayfa - 1) * limit }, { $limit: limit }],
      }},
    ];
  }

  private sayfaliSonucCoz<T>(sonuc: any[]): ISayfaliSonuc<T> {
    const facet = sonuc?.[0];
    return { toplam: facet?.toplam?.[0]?.sayi || 0, liste: (facet?.liste || []) as T[] };
  }

  get aktifKullaniciId(): string {
    return this.FikoYT?.pikoP?.pikoKimlik?._id || '';
  }

  resimUrl(resimId: string, boyut = '96x96'): string {
    return `${this.FikoYT.fapiUrl}/fapiv1/fikoyt.com.FikoYT.sistem.dosyalar.${resimId}.resim?boyut=${boyut}&thumbnail=true`;
  }

  // ===== Kullanici Kisi Gruplari =====

  async kisiGruplari(sayfa = 1, limit = 60): Promise<ISayfaliSonuc<IGaleriKisi>> {
    const kullaniciId = this.aktifKullaniciId;
    const cacheKey = `kisiler:${kullaniciId}:${sayfa}:${limit}`;
    const cached = this.cached<ISayfaliSonuc<IGaleriKisi>>(cacheKey);
    if (cached) return cached;

    const sonuc = await this.aggregate(DB.etiketler('aggregate'), [
      { $match: { kullaniciId, gizli: { $ne: true } } },
      { $lookup: { from: 'yuz_kisi_gruplari', localField: 'kisiGrupId', foreignField: '_id', as: 'grup' } },
      { $unwind: '$grup' },
      { $project: {
        kisiGrupId: 1, isim: 1, soyagaciKisiId: 1, resimUrl: 1,
        fotografSayisi: '$grup.yuzSayisi',
        ornekResim: { $arrayElemAt: ['$grup.ornekResimler', 0] },
        etiketlendi: { $literal: true },
      }},
      { $facet: {
        toplam: [{ $count: 'sayi' }],
        liste: [{ $sort: { fotografSayisi: -1 } }, { $skip: (sayfa - 1) * limit }, { $limit: limit }],
      }},
    ]);

    const sonucObj = this.sayfaliSonucCoz<IGaleriKisi>(sonuc);
    this.cacheSet(cacheKey, sonucObj);
    return sonucObj;
  }

  async etiketsizGruplar(sayfa = 1, limit = 30): Promise<ISayfaliSonuc<IGaleriKisi>> {
    const kullaniciId = this.aktifKullaniciId;
    // Kullanicinin etiketi OLMAYAN ama fotograflarinda gorunen gruplar
    const sonuc = await this.aggregate(DB.embeddingler('aggregate'), [
      { $match: { kaynakSahibiId: kullaniciId, durum: 'tamamlandi' } },
      { $unwind: '$yuzler' },
      { $match: { 'yuzler.kisiGrupId': { $exists: true } } },
      { $group: { _id: '$yuzler.kisiGrupId', sayi: { $sum: 1 }, ornekResim: { $first: { resimId: '$resimId', bbox: '$yuzler.bbox' } } } },
      { $lookup: { from: 'yuz_kullanici_etiketleri', let: { gid: '$_id' }, pipeline: [
        { $match: { $expr: { $and: [{ $eq: ['$kisiGrupId', '$$gid'] }, { $eq: ['$kullaniciId', kullaniciId] }] } } },
      ], as: 'etiket' } },
      { $match: { etiket: { $size: 0 } } },
      { $project: {
        kisiGrupId: '$_id', isim: 'Bilinmeyen', fotografSayisi: '$sayi',
        ornekResim: 1, etiketlendi: { $literal: false },
      }},
      { $facet: {
        toplam: [{ $count: 'sayi' }],
        liste: [{ $sort: { fotografSayisi: -1 } }, { $skip: (sayfa - 1) * limit }, { $limit: limit }],
      }},
    ]);
    return this.sayfaliSonucCoz<IGaleriKisi>(sonuc);
  }

  // ===== Etiket CRUD =====

  async etiketKaydet(kisiGrupId: string, isim: string, soyagaciKisiId?: string): Promise<any> {
    const result = await this.ekle(DB.etiketler('ekle'), {
      kullaniciId: this.aktifKullaniciId,
      kisiGrupId, isim, soyagaciKisiId,
      gizli: false,
      olusturmaTarihi: new Date().toISOString(),
      guncellemeTarihi: new Date().toISOString(),
    });
    this.cacheTemizle('kisiler:');
    return result;
  }

  async etiketGuncelle(etiketId: string, veri: Partial<IKullaniciEtiket>): Promise<any> {
    const result = await this.guncelle(DB.etiketler('guncelle'),
      { _id: etiketId },
      { $set: { ...veri, guncellemeTarihi: new Date().toISOString() } }
    );
    this.cacheTemizle('kisiler:');
    return result;
  }

  async etiketSil(etiketId: string): Promise<any> {
    const result = await this.sil(DB.etiketler('sil'), { _id: etiketId });
    this.cacheTemizle('kisiler:');
    return result;
  }

  async grupGizle(kisiGrupId: string, gizli: boolean): Promise<any> {
    return this.guncelle(DB.etiketler('guncelle'),
      { kullaniciId: this.aktifKullaniciId, kisiGrupId },
      { $set: { gizli } }
    );
  }

  // ===== Birlestirme =====

  async birlestirmeOnerileri(): Promise<IBirlestirmeOnerisi[]> {
    return this.aggregate(DB.onerileri('aggregate'), [
      { $match: { kullaniciId: this.aktifKullaniciId, durum: 'beklemede' } },
      { $lookup: { from: 'yuz_kisi_gruplari', localField: 'grup1Id', foreignField: '_id', as: 'grup1' } },
      { $lookup: { from: 'yuz_kisi_gruplari', localField: 'grup2Id', foreignField: '_id', as: 'grup2' } },
      { $unwind: { path: '$grup1', preserveNullAndEmptyArrays: true } },
      { $unwind: { path: '$grup2', preserveNullAndEmptyArrays: true } },
      { $lookup: { from: 'yuz_kullanici_etiketleri', let: { g1: '$grup1Id' }, pipeline: [
        { $match: { $expr: { $and: [{ $eq: ['$kisiGrupId', '$$g1'] }, { $eq: ['$kullaniciId', this.aktifKullaniciId] }] } } },
      ], as: 'grup1Etiket' } },
      { $lookup: { from: 'yuz_kullanici_etiketleri', let: { g2: '$grup2Id' }, pipeline: [
        { $match: { $expr: { $and: [{ $eq: ['$kisiGrupId', '$$g2'] }, { $eq: ['$kullaniciId', this.aktifKullaniciId] }] } } },
      ], as: 'grup2Etiket' } },
      { $addFields: {
        grup1Etiket: { $arrayElemAt: ['$grup1Etiket', 0] },
        grup2Etiket: { $arrayElemAt: ['$grup2Etiket', 0] },
      }},
      { $sort: { benzerlik: -1 } },
      { $limit: 10 },
    ]);
  }

  async oneriOnayla(oneriId: string): Promise<any> {
    const result = await this.guncelle(DB.onerileri('guncelle'),
      { _id: oneriId },
      { $set: { durum: 'onaylandi' } }
    );
    this.cacheTemizle();
    return result;
  }

  async oneriReddet(oneriId: string): Promise<any> {
    return this.guncelle(DB.onerileri('guncelle'),
      { _id: oneriId },
      { $set: { durum: 'reddedildi' } }
    );
  }

  async gruplaribirlestir(grup1Id: string, grup2Id: string): Promise<any> {
    // grup2'nin tum yuzlerini grup1'e tasi
    await this.guncelle(DB.embeddingler('guncelle'),
      { 'yuzler.kisiGrupId': grup2Id },
      { $set: { 'yuzler.$.kisiGrupId': grup1Id } }
    );
    // grup1'in yuz sayisini guncelle
    const grup2 = await this.aggregate<IKisiGrup>(DB.kisiGruplari('aggregate'), [
      { $match: { _id: grup2Id } },
    ]);
    if (grup2[0]) {
      await this.guncelle(DB.kisiGruplari('guncelle'),
        { _id: grup1Id },
        { $inc: { yuzSayisi: grup2[0].yuzSayisi }, $push: { birlestirmeler: grup2Id } }
      );
    }
    // grup2'yi sil
    await this.sil(DB.kisiGruplari('sil'), { _id: grup2Id });
    // grup2'ye ait etiketleri grup1'e tasi
    await this.guncelle(DB.etiketler('guncelle'),
      { kisiGrupId: grup2Id },
      { $set: { kisiGrupId: grup1Id } }
    );
    this.cacheTemizle();
  }

  // ===== Kisi Fotograf Galerisi =====

  async kisiFotograflari(kisiGrupId: string, sayfa = 1, limit = 30): Promise<ISayfaliSonuc<IKisiFotograf>> {
    const sonuc = await this.aggregate(DB.embeddingler('aggregate'), [
      { $match: { durum: 'tamamlandi' } },
      { $unwind: '$yuzler' },
      { $match: { 'yuzler.kisiGrupId': kisiGrupId } },
      { $sort: { olusturmaTarihi: -1 } },
      { $facet: {
        toplam: [{ $count: 'sayi' }],
        liste: [
          { $skip: (sayfa - 1) * limit },
          { $limit: limit },
          { $project: {
            resimId: 1, tarih: '$olusturmaTarihi', bbox: '$yuzler.bbox',
            kaynakTip: 1,
          }},
        ],
      }},
    ]);
    return this.sayfaliSonucCoz<IKisiFotograf>(sonuc);
  }

  async birlikteGorunenler(kisiGrupId: string): Promise<{ kisiGrupId: string; isim: string; sayi: number }[]> {
    return this.aggregate(DB.embeddingler('aggregate'), [
      { $match: { durum: 'tamamlandi', 'yuzler.kisiGrupId': kisiGrupId } },
      { $unwind: '$yuzler' },
      { $match: { 'yuzler.kisiGrupId': { $ne: kisiGrupId, $exists: true } } },
      { $group: { _id: '$yuzler.kisiGrupId', sayi: { $sum: 1 } } },
      { $lookup: { from: 'yuz_kullanici_etiketleri', let: { gid: '$_id' }, pipeline: [
        { $match: { $expr: { $and: [{ $eq: ['$kisiGrupId', '$$gid'] }, { $eq: ['$kullaniciId', this.aktifKullaniciId] }] } } },
      ], as: 'etiket' } },
      { $project: { kisiGrupId: '$_id', isim: { $ifNull: [{ $arrayElemAt: ['$etiket.isim', 0] }, 'Bilinmeyen'] }, sayi: 1 } },
      { $sort: { sayi: -1 } },
      { $limit: 20 },
    ]);
  }

  // ===== Kuyruk =====

  async kuyrukListesi(durumFiltre?: YuzIslemDurumu, sayfa = 1, limit = 20): Promise<ISayfaliSonuc<IIslemKuyrugu>> {
    const match: any = {};
    if (durumFiltre) match.durum = durumFiltre;
    const sonuc = await this.aggregate(DB.kuyruk('aggregate'),
      this.sayfaliPipeline(match, { olusturmaTarihi: -1 }, sayfa, limit, [
        { $lookup: { from: 'goUyeler', localField: 'kaynakSahibiId', foreignField: '_id', as: 'kullanici' } },
        { $addFields: { kullaniciAdi: { $arrayElemAt: ['$kullanici.kullaniciAdi', 0] } } },
        { $project: { kullanici: 0 } },
      ])
    );
    return this.sayfaliSonucCoz<IIslemKuyrugu>(sonuc);
  }

  async kuyrugaEkle(resimId: string, kaynakTip: string, kaynakId: string): Promise<any> {
    return this.ekle(DB.kuyruk('ekle'), {
      resimId, kaynakTip, kaynakId,
      kaynakSahibiId: this.aktifKullaniciId,
      oncelik: 5, durum: 'beklemede',
      olusturmaTarihi: new Date().toISOString(),
    });
  }

  async kuyrukYenidenIsle(kuyrukId: string): Promise<any> {
    return this.guncelle(DB.kuyruk('guncelle'),
      { _id: kuyrukId },
      { $set: { durum: 'beklemede', hata: null } }
    );
  }

  // ===== Admin =====

  async istatistikler(): Promise<IYuzTanimaIstatistik> {
    const [yuzler, kisiler, bilinmeyen, kuyruk, kullanicilar] = await Promise.all([
      this.aggregate(DB.embeddingler('aggregate'), [
        { $match: { durum: 'tamamlandi' } }, { $unwind: '$yuzler' }, { $count: 'toplam' },
      ]),
      this.aggregate(DB.kisiGruplari('aggregate'), [{ $count: 'toplam' }]),
      this.aggregate(DB.kisiGruplari('aggregate'), [
        { $lookup: { from: 'yuz_kullanici_etiketleri', localField: '_id', foreignField: 'kisiGrupId', as: 'etiketler' } },
        { $match: { etiketler: { $size: 0 } } }, { $count: 'toplam' },
      ]),
      this.aggregate(DB.kuyruk('aggregate'), [
        { $match: { durum: 'beklemede' } }, { $count: 'toplam' },
      ]),
      this.aggregate(DB.etiketler('aggregate'), [
        { $group: { _id: '$kullaniciId' } }, { $count: 'toplam' },
      ]),
    ]);
    return {
      toplamYuz: yuzler[0]?.toplam || 0,
      taninanKisi: kisiler[0]?.toplam || 0,
      bilinmeyenYuz: bilinmeyen[0]?.toplam || 0,
      kuyrukBekleyen: kuyruk[0]?.toplam || 0,
      aktifKullanici: kullanicilar[0]?.toplam || 0,
    };
  }

  async globalHavuz(sayfa = 1, limit = 30): Promise<ISayfaliSonuc<IKisiGrup & { etiketSayisi: number }>> {
    const sonuc = await this.aggregate(DB.kisiGruplari('aggregate'), [
      { $lookup: { from: 'yuz_kullanici_etiketleri', localField: '_id', foreignField: 'kisiGrupId', as: 'etiketler' } },
      { $addFields: { etiketSayisi: { $size: '$etiketler' } } },
      { $project: { etiketler: 0, temsilciEmbedding: 0 } },
      { $facet: {
        toplam: [{ $count: 'sayi' }],
        liste: [{ $sort: { yuzSayisi: -1 } }, { $skip: (sayfa - 1) * limit }, { $limit: limit }],
      }},
    ]);
    return this.sayfaliSonucCoz(sonuc);
  }

  async grupEtiketleri(kisiGrupId: string): Promise<(IKullaniciEtiket & { kullaniciAdi?: string })[]> {
    return this.aggregate(DB.etiketler('aggregate'), [
      { $match: { kisiGrupId } },
      { $lookup: { from: 'goUyeler', localField: 'kullaniciId', foreignField: '_id', as: 'kullanici' } },
      { $addFields: { kullaniciAdi: { $arrayElemAt: ['$kullanici.kullaniciAdi', 0] } } },
      { $project: { kullanici: 0 } },
    ]);
  }

  async kullaniciListesi(): Promise<{ _id: string; kullaniciAdi: string; kisiSayisi: number; yuzSayisi: number }[]> {
    return this.aggregate(DB.etiketler('aggregate'), [
      { $group: { _id: '$kullaniciId', kisiSayisi: { $sum: 1 } } },
      { $lookup: { from: 'goUyeler', localField: '_id', foreignField: '_id', as: 'kullanici' } },
      { $addFields: { kullaniciAdi: { $arrayElemAt: ['$kullanici.kullaniciAdi', 0] } } },
      { $project: { kullanici: 0 } },
      { $sort: { kisiSayisi: -1 } },
    ]);
  }

  async kullaniciKisiGruplari(kullaniciId: string, sayfa = 1, limit = 60): Promise<ISayfaliSonuc<IGaleriKisi>> {
    const sonuc = await this.aggregate(DB.etiketler('aggregate'), [
      { $match: { kullaniciId } },
      { $lookup: { from: 'yuz_kisi_gruplari', localField: 'kisiGrupId', foreignField: '_id', as: 'grup' } },
      { $unwind: '$grup' },
      { $project: {
        kisiGrupId: 1, isim: 1, soyagaciKisiId: 1, resimUrl: 1,
        fotografSayisi: '$grup.yuzSayisi',
        ornekResim: { $arrayElemAt: ['$grup.ornekResimler', 0] },
        etiketlendi: { $literal: true },
      }},
      { $facet: {
        toplam: [{ $count: 'sayi' }],
        liste: [{ $sort: { fotografSayisi: -1 } }, { $skip: (sayfa - 1) * limit }, { $limit: limit }],
      }},
    ]);
    return this.sayfaliSonucCoz<IGaleriKisi>(sonuc);
  }
}
```

- [ ] **Step 2: Derleme kontrolu**

Run: `ng build --configuration development 2>&1 | head -20`

- [ ] **Step 3: Commit**

```bash
git add src/app/uygulamalar/yuz-tanima/services/yuz-tanima-api.service.ts
git commit -m "feat(yuz-tanima): YuzTanimaApiService - DbBaseService pattern ile CRUD"
```

---

### Task 3: Etiket Cozumleme Utility

**Files:**
- Create: `src/app/uygulamalar/yuz-tanima/utils/etiket-cozumle.ts`

- [ ] **Step 1: Pure function olarak etiket cozumleme yaz**

```typescript
// etiket-cozumle.ts
import { IKullaniciEtiket, ICozumlenmisEtiket, IYuzVeri, EtiketKaynagi } from '../interfaces/yuz-tanima.interface';

/**
 * Bir fotograftaki yuzlerin etiketlerini cozumler.
 * Oncelik: goruntuleyenin etiketi > paylasanin etiketi > "Bilinmeyen"
 */
export function etiketleriCozumle(
  yuzler: IYuzVeri[],
  goruntuleyenEtiketleri: Map<string, IKullaniciEtiket>,
  paylasanEtiketleri: Map<string, IKullaniciEtiket>,
  paylasanAdi?: string,
): ICozumlenmisEtiket[] {
  return yuzler
    .filter(y => y.kisiGrupId)
    .map(yuz => {
      const grupId = yuz.kisiGrupId!;

      // 1. Goruntuleyenin etiketi
      const kendiEtiket = goruntuleyenEtiketleri.get(grupId);
      if (kendiEtiket) {
        return {
          bbox: yuz.bbox,
          kisiGrupId: grupId,
          isim: kendiEtiket.isim,
          kaynak: 'kendi' as EtiketKaynagi,
          soyagaciKisiId: kendiEtiket.soyagaciKisiId,
          resimUrl: kendiEtiket.resimUrl,
        };
      }

      // 2. Paylasanin etiketi (fallback)
      const paylasanEtiket = paylasanEtiketleri.get(grupId);
      if (paylasanEtiket) {
        return {
          bbox: yuz.bbox,
          kisiGrupId: grupId,
          isim: paylasanEtiket.isim,
          kaynak: 'paylasan' as EtiketKaynagi,
          kaynakKullaniciAdi: paylasanAdi,
          soyagaciKisiId: paylasanEtiket.soyagaciKisiId,
          resimUrl: paylasanEtiket.resimUrl,
        };
      }

      // 3. Bilinmeyen
      return {
        bbox: yuz.bbox,
        kisiGrupId: grupId,
        isim: 'Bilinmeyen',
        kaynak: 'bilinmeyen' as EtiketKaynagi,
      };
    });
}
```

- [ ] **Step 2: Commit**

```bash
git add src/app/uygulamalar/yuz-tanima/utils/etiket-cozumle.ts
git commit -m "feat(yuz-tanima): etiket cozumleme utility (fallback mantigi)"
```

---

### Task 4: Socket.IO Kuyruk Servisi

**Files:**
- Create: `src/app/uygulamalar/yuz-tanima/services/yuz-kuyruk-socket.service.ts`

- [ ] **Step 1: Socket.IO dinleyici servisi olustur**

```typescript
// yuz-kuyruk-socket.service.ts
import { Injectable, inject, signal, OnDestroy } from '@angular/core';
import { FikoYTService } from 'src/service/fikoyt.service';
import { IKuyrukIlerlemeEvent, IKuyrukTamamlandiEvent, IOneriYeniEvent } from '../interfaces/yuz-tanima.interface';

@Injectable({ providedIn: 'root' })
export class YuzKuyrukSocketService implements OnDestroy {
  private FikoYT = inject(FikoYTService);
  private dinleyiciler: (() => void)[] = [];

  // Signals
  readonly sonIlerleme = signal<IKuyrukIlerlemeEvent | null>(null);
  readonly sonTamamlanan = signal<IKuyrukTamamlandiEvent | null>(null);
  readonly yeniOneri = signal<IOneriYeniEvent | null>(null);
  readonly kuyrukDurumu = signal<{ beklemede: number; isleniyor: number; tamamlanan: number; hata: number } | null>(null);

  // Callback'ler
  private tamamlandiCallbackler: ((e: IKuyrukTamamlandiEvent) => void)[] = [];
  private oneriCallbackler: ((e: IOneriYeniEvent) => void)[] = [];

  baslat() {
    const socket = this.FikoYT?.pikoP?.pikoSocket;
    if (!socket) return;

    const dinle = (event: string, handler: (data: any) => void) => {
      socket.on(event, handler);
      this.dinleyiciler.push(() => socket.off(event, handler));
    };

    dinle('yuz.kuyruk.ilerleme', (data: IKuyrukIlerlemeEvent) => {
      this.sonIlerleme.set(data);
    });

    dinle('yuz.kuyruk.tamamlandi', (data: IKuyrukTamamlandiEvent) => {
      this.sonTamamlanan.set(data);
      this.tamamlandiCallbackler.forEach(cb => cb(data));
    });

    dinle('yuz.kuyruk.hata', (data: any) => {
      console.error('[YuzKuyruk] Hata:', data);
    });

    dinle('yuz.oneri.yeni', (data: IOneriYeniEvent) => {
      this.yeniOneri.set(data);
      this.oneriCallbackler.forEach(cb => cb(data));
    });

    dinle('yuz.admin.kuyrukDurumu', (data: any) => {
      this.kuyrukDurumu.set(data);
    });
  }

  tamamlandiBildir(cb: (e: IKuyrukTamamlandiEvent) => void): () => void {
    this.tamamlandiCallbackler.push(cb);
    return () => { this.tamamlandiCallbackler = this.tamamlandiCallbackler.filter(c => c !== cb); };
  }

  oneriBildir(cb: (e: IOneriYeniEvent) => void): () => void {
    this.oneriCallbackler.push(cb);
    return () => { this.oneriCallbackler = this.oneriCallbackler.filter(c => c !== cb); };
  }

  durdur() {
    this.dinleyiciler.forEach(fn => fn());
    this.dinleyiciler = [];
  }

  ngOnDestroy() {
    this.durdur();
  }
}
```

- [ ] **Step 2: Commit**

```bash
git add src/app/uygulamalar/yuz-tanima/services/yuz-kuyruk-socket.service.ts
git commit -m "feat(yuz-tanima): Socket.IO kuyruk servisi (canli bildirimler)"
```

---

### Task 5: Gruplama Servisi

**Files:**
- Create: `src/app/uygulamalar/yuz-tanima/services/yuz-gruplama.service.ts`

- [ ] **Step 1: Gruplama ve etiket yonetim servisi**

```typescript
// yuz-gruplama.service.ts
import { Injectable, inject, signal, computed } from '@angular/core';
import { YuzTanimaApiService } from './yuz-tanima-api.service';
import { YuzKuyrukSocketService } from './yuz-kuyruk-socket.service';
import {
  IGaleriKisi, IBirlestirmeOnerisi, ISayfaliSonuc, IKullaniciEtiket
} from '../interfaces/yuz-tanima.interface';

@Injectable({ providedIn: 'root' })
export class YuzGruplamaService {
  private api = inject(YuzTanimaApiService);
  private socket = inject(YuzKuyrukSocketService);

  // State signals
  readonly kisiler = signal<IGaleriKisi[]>([]);
  readonly etiketsizler = signal<IGaleriKisi[]>([]);
  readonly onerileri = signal<IBirlestirmeOnerisi[]>([]);
  readonly yukleniyor = signal(false);
  readonly toplam = signal(0);
  readonly etiketsizToplam = signal(0);

  readonly tumKisiler = computed(() => [...this.kisiler(), ...this.etiketsizler()]);

  private socketTemizle: (() => void) | null = null;

  async baslat() {
    this.socket.baslat();
    // Kuyruk tamamlandiginda otomatik yenile
    this.socketTemizle = this.socket.tamamlandiBildir(() => {
      this.yenile();
    });
    await this.yenile();
  }

  async yenile() {
    this.yukleniyor.set(true);
    try {
      const [etiketli, etiketsiz, oneriler] = await Promise.all([
        this.api.kisiGruplari(1, 200),
        this.api.etiketsizGruplar(1, 100),
        this.api.birlestirmeOnerileri(),
      ]);
      this.kisiler.set(etiketli.liste);
      this.toplam.set(etiketli.toplam);
      this.etiketsizler.set(etiketsiz.liste);
      this.etiketsizToplam.set(etiketsiz.toplam);
      this.onerileri.set(oneriler);
    } finally {
      this.yukleniyor.set(false);
    }
  }

  async etiketle(kisiGrupId: string, isim: string, soyagaciKisiId?: string) {
    await this.api.etiketKaydet(kisiGrupId, isim, soyagaciKisiId);
    await this.yenile();
  }

  async isimDegistir(etiketId: string, yeniIsim: string) {
    await this.api.etiketGuncelle(etiketId, { isim: yeniIsim });
    await this.yenile();
  }

  async birlestirmeOnayla(oneriId: string, grup1Id: string, grup2Id: string) {
    await this.api.oneriOnayla(oneriId);
    await this.api.gruplaribirlestir(grup1Id, grup2Id);
    await this.yenile();
  }

  async birlestirmeReddet(oneriId: string) {
    await this.api.oneriReddet(oneriId);
    this.onerileri.update(list => list.filter(o => o._id !== oneriId));
  }

  async grupGizle(kisiGrupId: string) {
    await this.api.grupGizle(kisiGrupId, true);
    this.kisiler.update(list => list.filter(k => k.kisiGrupId !== kisiGrupId));
  }

  temizle() {
    this.socketTemizle?.();
    this.socket.durdur();
  }
}
```

- [ ] **Step 2: Commit**

```bash
git add src/app/uygulamalar/yuz-tanima/services/yuz-gruplama.service.ts
git commit -m "feat(yuz-tanima): YuzGruplamaService - state yonetimi ve is mantigi"
```

---

## Chunk 2: Shared Components

### Task 6: Yuz Avatar Component

**Files:**
- Create: `src/app/uygulamalar/yuz-tanima/components/yuz-avatar/yuz-avatar.component.ts`

- [ ] **Step 1: Yuvarlak yuz crop avatar bleseni**

Mevcut `yuz-crop` component'inden esinlenerek buyuk boyutlu, Google Photos tarzi yuvarlak avatar. Tek dosyada inline template + styles.

Component inputs: `ornekResim: IOrnekResim`, `resimUrl: string`, `isim: string`, `boyut: number` (default 80), `resimUrlFn: (resimId, boyut) => string`

Render mantigi:
1. `resimUrl` varsa → dogrudan img (border-radius: 50%)
2. `ornekResim` varsa → CSS background-image crop (mevcut yuz-crop mantigi)
3. Hicbiri yoksa → harf avatar (gradient background, ismin ilk harfi)

- [ ] **Step 2: Commit**

```bash
git add src/app/uygulamalar/yuz-tanima/components/yuz-avatar/yuz-avatar.component.ts
git commit -m "feat(yuz-tanima): YuzAvatar - yuvarlak yuz crop avatar"
```

---

### Task 7: Birlestirme Karti Component

**Files:**
- Create: `src/app/uygulamalar/yuz-tanima/components/birlestirme-karti/birlestirme-karti.component.ts`

- [ ] **Step 1: Inline kart bleseni**

Component inputs: `oneri: IBirlestirmeOnerisi`, `resimUrlFn`
Component outputs: `onayla`, `reddet`, `ertele`

Layout: iki yuz-avatar yan yana, aralarinda ikon, benzerlik yuzdesi, 3 buton (Ayni / Farkli / ?)
Animasyon: kaybolma efekti (@trigger fadeOut)

- [ ] **Step 2: Commit**

```bash
git add src/app/uygulamalar/yuz-tanima/components/birlestirme-karti/birlestirme-karti.component.ts
git commit -m "feat(yuz-tanima): BirlestirmeKarti - merge suggestion UI"
```

---

### Task 8: Kisi Grid Component

**Files:**
- Create: `src/app/uygulamalar/yuz-tanima/components/kisi-grid/kisi-grid.component.ts`

- [ ] **Step 1: Google Photos tarzi kisi grid bleseni**

Component inputs: `kisiler: IGaleriKisi[]`, `resimUrlFn`, `responsive: boolean`
Component outputs: `kisiTiklandi(kisi)`, `etiketleTiklandi(kisi)`

Layout: CSS grid, responsive (6→4→3 sutun), her hucrede yuz-avatar + isim + fotograf sayisi
Etiketsiz kisiler turuncu kenarlik + "Isim Ver" butonu
Hover: scale(1.05) + box-shadow animasyonu

- [ ] **Step 2: Commit**

```bash
git add src/app/uygulamalar/yuz-tanima/components/kisi-grid/kisi-grid.component.ts
git commit -m "feat(yuz-tanima): KisiGrid - Google Photos tarzi kisi grid"
```

---

### Task 9: Chip Filtre Component

**Files:**
- Create: `src/app/uygulamalar/yuz-tanima/components/kisi-chip-filtre/kisi-chip-filtre.component.ts`

- [ ] **Step 1: Chip filter bar bleseni**

Component inputs: `filtreler: {anahtar, etiket, sayi?, aktif}[]`
Component outputs: `filtreSecildi(anahtar)`

Layout: yatay scroll, NzTag ile chip'ler, aktif chip vurgulu

- [ ] **Step 2: Commit**

```bash
git add src/app/uygulamalar/yuz-tanima/components/kisi-chip-filtre/kisi-chip-filtre.component.ts
git commit -m "feat(yuz-tanima): KisiChipFiltre - chip filter bar"
```

---

### Task 10: Component Exports

**Files:**
- Create: `src/app/uygulamalar/yuz-tanima/components/index.ts`

- [ ] **Step 1: Public exports barrel dosyasi**

```typescript
export { YuzAvatarComponent } from './yuz-avatar/yuz-avatar.component';
export { BirlestirmeKartiComponent } from './birlestirme-karti/birlestirme-karti.component';
export { KisiGridComponent } from './kisi-grid/kisi-grid.component';
export { KisiChipFiltreComponent } from './kisi-chip-filtre/kisi-chip-filtre.component';
```

- [ ] **Step 2: Commit**

```bash
git add src/app/uygulamalar/yuz-tanima/components/index.ts
git commit -m "feat(yuz-tanima): component barrel exports"
```

---

## Chunk 3: Kullanici UI - Kisiler Galerisi & Fotograf Galerisi

### Task 11: Kisiler Galerisi Bastan Yazimi

**Files:**
- Modify: `src/app/uygulamalar/soyagaci/kisiler-galerisi/kisiler-galerisi.component.ts` (bastan yaz)

- [ ] **Step 1: Mevcut dosyayi tamamen yeniden yaz**

Yeni component yapisi:
- `YuzGruplamaService` ve `YuzTanimaApiService` inject
- Signal state: `aramaMetni`, `aktifFiltre` ('tumu'|'etiketli'|'etiketsiz'), `filtrelenmisKisiler` (computed)
- Template bolmleri:
  1. Arama bari + gorunum toggle
  2. Hizli erisim seridi (yatay scroll, en cok fotografli 10 kisi)
  3. Birlestirme onerileri (BirlestirmeKarti listesi)
  4. Chip filtreler (KisiChipFiltre)
  5. Kisi grid (KisiGrid)
- OnInit: `gruplamaServis.baslat()`
- OnDestroy: `gruplamaServis.temizle()`
- Kisi tiklama → router.navigate(['kisiler', kisi.kisiGrupId])
- Etiketleme → NzModal ile isim girisi + opsiyonel soyagaci kisi secimi

Standalone imports: CommonModule, FormsModule, NzInputModule, NzIconModule, NzTagModule, NzModalModule, NzSpinModule, NzEmptyModule, NzBadgeModule, YuzAvatarComponent, BirlestirmeKartiComponent, KisiGridComponent, KisiChipFiltreComponent

- [ ] **Step 2: Derleme kontrolu**

Run: `ng build --configuration development 2>&1 | head -30`

- [ ] **Step 3: Tarayicida test et**

`/kisiler` sayfasina git, grid gorunumunu dogrula, arama ve filtre calistigini kontrol et.

- [ ] **Step 4: Commit**

```bash
git add src/app/uygulamalar/soyagaci/kisiler-galerisi/kisiler-galerisi.component.ts
git commit -m "feat(soyagaci): kisiler galerisi bastan yazildi - Google Photos tarzi"
```

---

### Task 12: Kisi Fotograflari Sayfasi

**Files:**
- Create: `src/app/uygulamalar/soyagaci/kisi-fotograflari/kisi-fotograflari.component.ts`

- [ ] **Step 1: Yeni fotograf galerisi sayfasi olustur**

Component yapisi:
- Route parametresi: `grupId` (ActivatedRoute)
- `YuzTanimaApiService` inject
- Signal state: `fotograflar`, `kisiEtiket`, `birlikteGorunenler`, `yukleniyor`, `sayfa`
- Template bolumleri:
  1. Header: geri butonu + yuz-avatar + isim (inline edit) + "Profil Sayfasi" butonu + 3 nokta menu
  2. Chip filtreler: birlikteGorunenler (mini avatar chips) + yil + konum
  3. Justified photo grid: aya gore gruplanmis
     - Her fotograf: resimUrl ile img, hover'da yuz bbox overlay
     - Tiklama → lightbox/fullscreen
- OnInit: fotograflari ve birlikte gorunenleri yukle
- Profil butonu → router.navigate(['kisi', soyagaciKisiId])
- Infinite scroll veya "Daha Fazla" butonu

- [ ] **Step 2: Route'u ekle**

`soyagaci.routes.ts`'ye ekle:
```typescript
{ path: 'kisiler/:grupId', loadComponent: () => import('./kisi-fotograflari/kisi-fotograflari.component').then(m => m.KisiFotograflariComponent) },
```

- [ ] **Step 3: Derleme ve test**

Run: `ng build --configuration development 2>&1 | head -30`
Tarayicida: `/kisiler/bir-grup-id` sayfasini ac.

- [ ] **Step 4: Commit**

```bash
git add src/app/uygulamalar/soyagaci/kisi-fotograflari/kisi-fotograflari.component.ts
git add src/app/uygulamalar/soyagaci/soyagaci.routes.ts
git commit -m "feat(soyagaci): kisi fotograf galerisi sayfasi (justified grid)"
```

---

## Chunk 4: Admin Panel

### Task 13: Admin Dashboard

**Files:**
- Create: `src/app/uygulamalar/admin/yuz-tanima/pano/pano.component.ts`

- [ ] **Step 1: Admin dashboard bleseni olustur**

Component yapisi:
- `YuzTanimaApiService` inject
- Signal state: `istatistikler`, `yukleniyor`
- Template:
  1. Istatistik kartlari (5 stat card: toplam yuz, taninan kisi, bilinmeyen, kuyruk, aktif kullanici)
  2. Gunluk islem grafigi placeholder (NzStatistic veya basit bar chart)
  3. Son islenenler listesi (kuyruk son 10 tamamlanan)
  4. En aktif kullanicilar listesi
- OnInit: `api.istatistikler()` + `api.kuyrukListesi('tamamlandi', 1, 10)` + `api.kullaniciListesi()`

Ng-Zorro: NzGridModule, NzCardModule, NzStatisticModule, NzListModule, NzAvatarModule, NzTagModule

- [ ] **Step 2: Commit**

```bash
git add src/app/uygulamalar/admin/yuz-tanima/pano/pano.component.ts
git commit -m "feat(admin): yuz tanima dashboard"
```

---

### Task 14: Global Havuz Sayfasi

**Files:**
- Create: `src/app/uygulamalar/admin/yuz-tanima/global-havuz/global-havuz.component.ts`

- [ ] **Step 1: Global embedding havuz bleseni**

Component yapisi:
- `YuzTanimaApiService` inject
- Signal state: `gruplar`, `sayfa`, `toplam`, `seciliGrup`, `grupEtiketleri`, `yukleniyor`
- Template:
  1. Arama + filtreler (buyuk gruplar, etiketli/etiketsiz)
  2. Kisi grid (KisiGrid component reuse, admin mod)
  3. Grup detay panel (tiklayinca acilir):
     - Etiketleyen kullanicilar listesi (kullaniciAdi → isim)
     - Ornek yuzler (5 thumbnail)
     - Islemler: Gruplari Birlestir, Grubu Bol, Sil
- `api.globalHavuz()` ile veri yukle
- `api.grupEtiketleri(grupId)` ile detay yukle

- [ ] **Step 2: Commit**

```bash
git add src/app/uygulamalar/admin/yuz-tanima/global-havuz/global-havuz.component.ts
git commit -m "feat(admin): global embedding havuz sayfasi"
```

---

### Task 15: Kullanici Goruntule Sayfasi

**Files:**
- Create: `src/app/uygulamalar/admin/yuz-tanima/kullanici-goruntule/kullanici-goruntule.component.ts`

- [ ] **Step 1: Kullanici bazli galeri gorunumu**

Component yapisi:
- Route parametresi: `userId` (ActivatedRoute)
- `YuzTanimaApiService` inject
- Signal state: `kullanicilar`, `seciliKullanici`, `kisiler`, `yukleniyor`
- Template:
  1. Kullanici secici (NzSelect + arama)
  2. Secili kullanicinin kisi gruplari (KisiGrid reuse)
  3. Admin ek butonlari: etiket duzenle, grubu kaldir
- `api.kullaniciListesi()` → dropdown
- `api.kullaniciKisiGruplari(userId)` → grid

- [ ] **Step 2: Commit**

```bash
git add src/app/uygulamalar/admin/yuz-tanima/kullanici-goruntule/kullanici-goruntule.component.ts
git commit -m "feat(admin): kullanici bazli yuz tanima goruntuleme"
```

---

### Task 16: Admin Route'larini Guncelle

**Files:**
- Modify: `src/app/uygulamalar/admin/yuz-tanima/yuz-tanima.routes.ts`

- [ ] **Step 1: Yeni route'lari ekle**

Mevcut route'lara eklenecekler:
```typescript
{ path: '', redirectTo: 'pano', pathMatch: 'full' },
{ path: 'pano', loadComponent: () => import('./pano/pano.component').then(m => m.YuzTanimaPanoComponent), data: { baslik: 'Pano' } },
{ path: 'global-havuz', loadComponent: () => import('./global-havuz/global-havuz.component').then(m => m.GlobalHavuzComponent), data: { baslik: 'Global Havuz' } },
{ path: 'kullanici/:userId', loadComponent: () => import('./kullanici-goruntule/kullanici-goruntule.component').then(m => m.KullaniciGoruntuleComponent), data: { baslik: 'Kullanici' } },
// Mevcut route'lar korunur: kuyruk, kisiler, tespitler, bilinmeyenler, ayarlar
```

Default redirect'i `'kuyruk'`'dan `'pano'`'ya degistir.

- [ ] **Step 2: Derleme ve test**

Run: `ng build --configuration development 2>&1 | head -30`

- [ ] **Step 3: Commit**

```bash
git add src/app/uygulamalar/admin/yuz-tanima/yuz-tanima.routes.ts
git commit -m "feat(admin): yuz tanima route'lari guncellendi (pano, global-havuz, kullanici)"
```

---

## Chunk 5: Paylasim Sistemi Entegrasyonu

### Task 17: icerikler.component Etiket Cozumleme

**Files:**
- Modify: `src/service/fikoyt-ui/src/lib/paylasim-sistemi/fikoyt-kart-kok/icerikler/icerikler.component.ts`

- [ ] **Step 1: Yeni etiket cozumleme akisini entegre et**

Degisiklikler:
1. `YuzTanimaApiService` inject et (opsiyonel - lazy inject)
2. Mevcut `otomatikYuzTespitSorgula()` yanina `yuzEtiketleriCozumle()` metodu ekle
3. `etiketleriCozumle()` utility fonksiyonunu kullan
4. Cozumlenmis etiketleri mevcut `benzersizYuzEtiketleri` getter'i ile birlestir

Yeni metod:
```typescript
private async yuzEtiketleriCozumle(resimId: string) {
  // 1. yuz_embeddingler'den bu resimin yuzlerini al
  // 2. Goruntuleyen kullanicinin etiketlerini cek
  // 3. Paylasan kullanicinin etiketlerini cek
  // 4. etiketleriCozumle() ile birlestir
  // 5. Sonucu IYuzEtiketi formatina donustur
}
```

- [ ] **Step 2: Kisi balon guncellemesi**

Etiket kaynagi rozetini ekle: "Senin etiketin" / "X'in etiketi"
"Kendin etiketle" butonunu ekle (kaynak !== 'kendi' ise)

- [ ] **Step 3: Derleme ve test**

Run: `ng build --configuration development 2>&1 | head -30`

- [ ] **Step 4: Commit**

```bash
git add src/service/fikoyt-ui/src/lib/paylasim-sistemi/fikoyt-kart-kok/icerikler/icerikler.component.ts
git commit -m "feat(paylasim): yuz etiket cozumleme entegrasyonu (kullanici bazli fallback)"
```

---

### Task 18: Admin Kuyruk Guncelleme

**Files:**
- Modify: `src/app/uygulamalar/admin/yuz-tanima/kuyruk/kuyruk.component.ts`

- [ ] **Step 1: Yeni kuyruk koleksiyonuna gecis**

Degisiklikler:
1. `YuzTanimaApiService` ve `YuzKuyrukSocketService` inject et
2. Mevcut `yuz_kuyruk` aggregate sorgularini `api.kuyrukListesi()` ile degistir
3. Socket.IO dinleyicilerini `socketServis` uzerinden bagla
4. Mevcut template yapisini koru, sadece veri kaynagini degistir

- [ ] **Step 2: Commit**

```bash
git add src/app/uygulamalar/admin/yuz-tanima/kuyruk/kuyruk.component.ts
git commit -m "refactor(admin): kuyruk bleseni yeni API servisi ile guncellendi"
```

---

### Task 19: Admin Kisiler Guncelleme

**Files:**
- Modify: `src/app/uygulamalar/admin/yuz-tanima/kisiler/kisiler.component.ts`

- [ ] **Step 1: Global havuz ile entegrasyon**

Degisiklikler:
1. `YuzTanimaApiService` inject et
2. Mevcut `yuz_taninan` sorgularini `api.globalHavuz()` ve `api.grupEtiketleri()` ile degistir
3. Birlestirme islemi icin `api.gruplaribirlestir()` kullan
4. Silme islemi icin `api.sil()` kullan
5. Grid gorunumunde `KisiGrid` component'ini reuse et (opsiyonel)

- [ ] **Step 2: Commit**

```bash
git add src/app/uygulamalar/admin/yuz-tanima/kisiler/kisiler.component.ts
git commit -m "refactor(admin): kisiler bleseni global havuz API ile guncellendi"
```

---

## Chunk 6: Son Dokunuslar

### Task 20: Soyagaci Route Kontrolu

**Files:**
- Verify: `src/app/uygulamalar/soyagaci/soyagaci.routes.ts`

- [ ] **Step 1: Tum route'larin dogru calistigini dogrula**

Kontroller:
1. `/kisiler` → KisilerGalerisiComponent (yeni)
2. `/kisiler/:grupId` → KisiFotograflariComponent (yeni)
3. `/kisi/:kisiId` → KisiSayfasiComponent (mevcut, degismedi)
4. Diger route'lar degismedi

- [ ] **Step 2: Build ve lint**

Run: `ng build --configuration development`
Hata yoksa gecerli.

- [ ] **Step 3: Commit (tum kalan degisiklikler)**

```bash
git add -A
git commit -m "feat(yuz-tanima): Google Photos tarzi kisiler galerisi - tum modul tamamlandi"
```

---

## Uygulama Sirasi Ozeti

| Siralama | Task | Bagimlilik |
|----------|------|-----------|
| 1 | Task 1: Interfaces | Yok |
| 2 | Task 2: YuzTanimaApiService | Task 1 |
| 3 | Task 3: Etiket Cozumleme | Task 1 |
| 4 | Task 4: Socket Servisi | Task 1 |
| 5 | Task 5: Gruplama Servisi | Task 2, 4 |
| 6 | Task 6: Yuz Avatar | Task 1 |
| 7 | Task 7: Birlestirme Karti | Task 1, 6 |
| 8 | Task 8: Kisi Grid | Task 1, 6 |
| 9 | Task 9: Chip Filtre | Yok |
| 10 | Task 10: Exports | Task 6-9 |
| 11 | Task 11: Kisiler Galerisi | Task 5, 7, 8, 9 |
| 12 | Task 12: Kisi Fotograflari | Task 2, 6, 9 |
| 13 | Task 13: Admin Dashboard | Task 2 |
| 14 | Task 14: Global Havuz | Task 2, 8 |
| 15 | Task 15: Kullanici Goruntule | Task 2, 8 |
| 16 | Task 16: Admin Routes | Task 13, 14, 15 |
| 17 | Task 17: Paylasim Entegrasyon | Task 2, 3 |
| 18 | Task 18: Kuyruk Guncelleme | Task 2, 4 |
| 19 | Task 19: Kisiler Guncelleme | Task 2 |
| 20 | Task 20: Son Kontrol | Hepsi |

## Paralel Calisma Imkanlari

Bagimlilik grafiğine gore paralel calisabilecek gruplar:
- **Grup A** (Task 1-5): Foundation - sirayla
- **Grup B** (Task 6-10): Shared components - Task 1'den sonra paralel
- **Grup C** (Task 11-12): Kullanici UI - Grup A+B'den sonra
- **Grup D** (Task 13-16): Admin panel - Task 2'den sonra, Grup C ile paralel
- **Grup E** (Task 17-19): Entegrasyon - Task 2-4'den sonra, Grup C/D ile paralel
