# BUG #2: Fixing Plan — Circular Import Risk (`api_semaphore`)

**Bug ID:** BUG-002  
**Severity:** 🟠 HIGH — Blocker untuk V3 Modularization  
**Date:** 2026-05-12  
**Status:** PLANNING

---

## 1. Root Cause

`extract_pillars_with_llm` di `ingestion.py` mengakses dua **variabel global** secara langsung:

```python
# ingestion.py — line 98 & 103
api_semaphore = threading.BoundedSemaphore(3)   # GLOBAL
DASHSCOPE_MODEL = os.getenv("DASHSCOPE_MODEL")  # GLOBAL

# ingestion.py — line 249 (signature saat ini)
def extract_pillars_with_llm(text: str, client) -> tuple[dict, dict]:
    with api_semaphore:          # ← ambil dari global scope
        response = client.chat.completions.create(
            model=DASHSCOPE_MODEL,  # ← ambil dari global scope
            ...
        )
```

Ketika fungsi ini dipindah ke `llm_extractor.py` (V3), kedua variabel global tersebut tidak ikut pindah — mereka tetap di `ingestion.py`. Jika `llm_extractor.py` mencoba import dari `ingestion.py` untuk mendapatkannya, terjadi **circular import**:

```
ingestion.py  →  import  →  llm_extractor.py
     ↑                             │
     └──────── import ─────────────┘
               ❌ CIRCULAR IMPORT ERROR
```

---

## 2. Scope Fix

**Hanya 1 fungsi yang perlu diubah:** `extract_pillars_with_llm`

| Fungsi | Lokasi Akhir | Pakai `api_semaphore`? | Perlu Fix? |
|--------|-------------|----------------------|------------|
| `extract_pillars_with_llm` | `llm_extractor.py` (V3) | ✅ Ya | ✅ **YES** |
| `embed_text` | `ingestion.py` (tetap) | ✅ Ya | ❌ No |
| `generate_edge_reasoning` | `ingestion.py` (tetap) | ✅ Ya | ❌ No |

---

## 3. Solusi: Dependency Injection

Ubah fungsi agar **tidak bergantung pada global variable**, melainkan menerima keduanya sebagai **parameter**.

### Before (BROKEN untuk V3)
```python
# ingestion.py — line 249
def extract_pillars_with_llm(text: str, client) -> tuple[dict, dict]:
    with api_semaphore:                    # ← global
        response = client.chat.completions.create(
            model=DASHSCOPE_MODEL,         # ← global
            ...
        )
```

### After (FIXED)
```python
# ingestion.py (sekarang) → llm_extractor.py (setelah V3)
def extract_pillars_with_llm(
    text: str,
    client,
    semaphore: threading.BoundedSemaphore,  # ← injected
    model_name: str,                         # ← injected
) -> tuple[dict, dict]:
    with semaphore:                          # ← pakai parameter
        response = client.chat.completions.create(
            model=model_name,               # ← pakai parameter
            ...
        )
```

### Update Call Site (ingestion.py — line 522)
```python
# Before
extraction_result, usage = extract_pillars_with_llm(raw_text, ali_client)

# After
extraction_result, usage = extract_pillars_with_llm(
    raw_text,
    ali_client,
    semaphore=api_semaphore,
    model_name=DASHSCOPE_MODEL,
)
```

---

## 4. Langkah Implementasi

| # | Langkah | Target | Detail |
|---|---------|--------|--------|
| 1 | Ubah signature fungsi | `ingestion.py` line 249 | Tambah 2 parameter: `semaphore` dan `model_name` |
| 2 | Ganti akses global di body fungsi | `ingestion.py` line 292 & 294 | `api_semaphore` → `semaphore`, `DASHSCOPE_MODEL` → `model_name` |
| 3 | Update call site | `ingestion.py` line 522 | Tambah 2 argumen saat memanggil fungsi |
| 4 | Verify syntax | `ingestion.py` | `python3 -m py_compile` |
| 5 | Smoke test | Manual | Pastikan tidak ada `NameError` atau `TypeError` |

---

## 5. Perubahan yang TIDAK Dilakukan

- ❌ Tidak memindahkan fungsi ke `llm_extractor.py` (itu tugas V3)
- ❌ Tidak mengubah logic prompt atau extraction
- ❌ Tidak menyentuh `embed_text` atau `generate_edge_reasoning`
- ❌ Tidak mengubah nilai `api_semaphore` (tetap `BoundedSemaphore(3)`)

---

## 6. Acceptance Criteria

- [ ] Signature `extract_pillars_with_llm` punya 4 parameter: `text`, `client`, `semaphore`, `model_name`
- [ ] Tidak ada akses ke `api_semaphore` global di dalam body fungsi
- [ ] Tidak ada akses ke `DASHSCOPE_MODEL` global di dalam body fungsi
- [ ] Call site di line 522 meng-inject kedua argumen baru
- [ ] `python3 -m py_compile ingestion.py` → PASS
- [ ] Smoke test tidak ada `NameError` atau `TypeError`

---

## 7. Estimasi Waktu

| Task | Waktu |
|------|-------|
| Ubah signature + body | 5 menit |
| Update call site | 2 menit |
| Verify + smoke test | 5 menit |
| **Total** | **12 menit** |
