# Round 13 — Tower J10-J12 · Vector / FactCheck / Tone

**Date**: 2026-04-30
**Time**: 1.5h budget
**Status**: ✅ COMPLETE

---

## 🎯 הקומות

| # | Station | תפקיד | Latency |
|---|---|---|---|
| **J10** | Vector Context Search | pgvector חיפוש כתבות דומות | <800ms |
| **J11** | Fact-Check Layer | אימות עובדות מול archive + web | <2s |
| **J12** | Tone Analyzer | זיהוי tone של הודעת המקור (formal/breaking/promotional) | <500ms |

---

## 🔍 J10 · Vector Context Search

### תפקיד
מצא כתבות historical שדומות לטקסט הנוכחי. ה-Writer ישתמש בהן ל-context, citations, ו-related links.

### Implementation — pgvector
מקור verified: `media_platform.jason_items` table (31,590 articles, vector_5w 1536-dim).

```sql
-- Two-stage retrieval
-- Stage 1: vector similarity (top-50)
WITH similar AS (
  SELECT id, title, slug, doc_date,
         vector_5w <=> $1::vector AS distance
  FROM jason_items
  WHERE source = ANY($2::text[])  -- maariv, walla, i24
    AND doc_date > NOW() - INTERVAL '2 years'
  ORDER BY distance
  LIMIT 50
)
-- Stage 2: re-rank by metadata + freshness
SELECT s.*,
       (1 - s.distance) * 0.7 +
       (1.0 / (1 + EXTRACT(DAYS FROM NOW() - s.doc_date) / 365)) * 0.3 AS combined_score
FROM similar s
ORDER BY combined_score DESC
LIMIT 12;
```

### Two-stage scoring rationale
- **Stage 1 (cosine)**: similarity לתוכן (text-level)
- **Stage 2 (re-rank)**: מוסיף **freshness boost** — כתבות מהשבוע האחרון מקבלות ציון גבוה יותר. למה? כי **breaking news context** רלוונטי יותר.

### A2UI streaming
לכל context_match → updateComponents:
```json
{"version":"v0.9","updateComponents":{"surfaceId":"...","components":[
  {"id":"ctx-1","component":"ContextCard","mode":"compact","title":"כיפת ברזל - היסטוריה","score":0.92,"deskColor":"security","image":"img/maariv/articles/2024/iron-dome.jpg","action":{"name":"context_select","context":{"id":"ctx-1"}}}
]}}
```

ה-context grid מתמלא ב-12 cards תוך ~800ms — חוויית "ChatGPT thinking" (cards מופיעים אחד אחד, לא כולם בבת אחת).

### Cost
- Vector embedding (5w concat): שכבר ייצרו ב-J05-J09 → 0
- pgvector query: ~50ms · ~$0
- Re-rank PostgreSQL: ~30ms · ~$0
- **Total cost: ~$0** (compute on EX63 GPU server)

---

## ✅ J11 · Fact-Check Layer

### תפקיד
לכל **factual claim** בטקסט, בדוק:
1. **Internal**: האם דומה במאגר archive? (יציבות / סתירה)
2. **External**: web search (אופציונלי, BBC/NYT level)
3. **Numerical**: אם מספרים — verify (12 סוללות = ל ̌2 מטוסים?)
4. **Named entities**: אם שם → exists in Wikipedia/Wikidata?

### Implementation
**3-stage cascade**:

#### Stage 1: Claim extraction
```python
prompt = """Extract all factual claims from this text.
Output JSON array, each item:
{
  "claim": "...",
  "type": "numerical" | "named" | "event" | "statement",
  "subject": "...",
  "needs_verification": true | false
}
Reject opinions, predictions."""
```

#### Stage 2: Per-claim verification
```python
for claim in claims:
    if claim.type == "named":
        result = await wikidata_lookup(claim.subject)
    elif claim.type == "numerical":
        result = await archive_compare(claim)  # search vector for similar numbers
    elif claim.type == "event":
        result = await archive_event_check(claim)
    # → result: { verdict: "verified" | "disputed" | "unknown", evidence: [...] }
```

#### Stage 3: Aggregate flags
```python
flags = []
for claim, result in claim_results:
    if result.verdict == "disputed":
        flags.append({
            "claim": claim.claim,
            "verdict": "disputed",
            "evidence": result.evidence,
            "severity": "high"
        })
yield _emit("factcheck", {"flags": flags, "total_claims": len(claims), "verified_count": ...})
```

### A2UI emission
```json
{"version":"v0.9","updateComponents":{"surfaceId":"article-output","components":[
  {"id":"fc-3","component":"FactCheckFlag","claim":"12 סוללות","verdict":"disputed","evidence":"מאמרים קודמים מציינים 9 סוללות","severity":"high","placement":{"line":4,"col_start":12,"col_end":24}}
]}}
```

הflag מופיע **inline** במאמר — צבע אדום מסביב ל-"12 סוללות" + tooltip עם evidence.

### Cost
- Claim extraction: ~$0.0003
- Per-claim verification (avg 5 claims · 50% need LLM): ~$0.0008
- **Total per request: ~$0.0011**

---

## 🎭 J12 · Tone Analyzer

### תפקיד
זיהוי tone של ה**מקור** (לא של ה-output). למה?
- **Promotional press release** → הכותב צריך **לנטרל** את הbias ולהפוך ל-news neutral
- **Breaking news from agency** → tone דחוף — הכותב צריך פשטות + מהירות
- **Government statement** → tone formal — הכותב צריך לשמור על דרגת רשמיות

### Output schema
```json
{
  "primary_tone": "promotional",
  "tone_scores": {
    "formal": 0.45,
    "casual": 0.05,
    "promotional": 0.78,
    "alarmist": 0.12,
    "academic": 0.08,
    "journalistic": 0.32
  },
  "register": "high",
  "neutralization_recommended": true,
  "writer_instructions": "Convert promotional language to neutral reporting. Strip superlatives. Remove direct quotes from PR-speak."
}
```

ה-`writer_instructions` נכלל ב-system prompt של J13-J16 (Writers). זה הreasoning של ה-Tone — לא רק זיהוי, אלא הוראות הפעולה.

### Implementation
**Single LLM call** (Gemini 2.5 Flash) עם few-shot examples של 6 tones.

### A2UI emission
```json
{"version":"v0.9","updateComponents":{"surfaceId":"...","components":[
  {"id":"tone-indicator","component":"BrandVoiceIndicator","sourceTone":"promotional","targetTone":"journalistic","gapScore":0.46,"warning":true}
]}}
```

ה-`BrandVoiceIndicator` מציג gauge — איך ה-source tone מתאים ל-tenant brand voice (Maariv = journalistic, BBC = formal, Tikun Olam = activist).

### Cost
- 1 Gemini Flash call: ~$0.0002

---

## 📊 J10-J12 · Total

| Station | Latency | Cost | Notes |
|---|---|---|---|
| J10 Vector | <800ms | ~$0 | self-hosted |
| J11 FactCheck | <2s | $0.0011 | most expensive |
| J12 Tone | <500ms | $0.0002 | |
| **Sum** | **<3.3s** | **$0.0013** | |

הclaim הוא ש-J10 ו-J12 רצים **במקביל** עם 5W (J05-J09 ב-Round 12). J11 רץ **אחרי** 5W כי הוא צריך claims structured.

```
[J04 Desk] ─┬─→ [J05-J09 5W]  ─┐
            ├─→ [J10 Vector]   ├─→ [J11 FactCheck] → [J13 Writer]
            └─→ [J12 Tone]      ─┘
```

---

## 🎯 ההכרעות הקריטיות

### 1. למה J11 (FactCheck) רק אחרי 5W?

הclaims מובנים יותר אחרי שיש 5W structured. למשל "12 סוללות" — ה-J06 (What) כבר חילץ "object: 12 סוללות". J11 לא צריך לחפש את זה שוב.

### 2. למה J10 (Vector) רץ במקביל ל-5W?

הembedding לחיפוש vector לא חייב לחכות ל-5W. הוא יכול להשתמש בvector של הtext המקורי (raw). אם 5W מצא משהו מעניין — שלב second pass עם 5w_concat embedding.

### 3. למה J12 (Tone) ולא tenant_voice?

J12 מנתח את ה**source**, לא את ה**target**. ה-target (tenant brand voice) הוא L3 token override + writer prompt template. שני שלבים נפרדים.

---

## ✅ Closure
✅ **Round 13 closed. 12/54 floors documented.**

---

## 🛣️ Next: Round 14 — J13-J17 (4 Writers + Editor)
