---
document_type: "spec"
service: "MOMENTRY_CORE"
title: "Story Parent-Child Chunk Rules V1.0"
date: "2026-05-05"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
  - "momentry"
  - "core"
  - "chunk"
  - "story"
  - "parent-child"
  - "v1.0"
ai_query_hints:
  - "Story parent-child chunk generation rules"
  - "CUT scene → parent chunk, ASR sentence → child chunk"
  - "boundary overlap: partial match enriches child context"
  - "parent_summary template + child_summary template"
  - "children per parent distribution"
related_documents:
  - "../CHUNK_DEFINITION_V1.0.0.md"
  - "../DUAL_EMBEDDING_PIPELINE_V1.0.0.md"
  - "../PROCESSORS/ASR_V1.0.0.md"
  - "../PROCESSORS/CUT_V1.0.0.md"
---

# Story Parent-Child Chunk Rules V1.0

## 核心概念

- **Parent chunk** = CUT 場景邊界內的所有對話 → 一個場景敘述
- **Child chunk** = 單一 ASR sentence → 一句對白
- **Boundary overlap** = 場景邊界重疊的句子 → 同時歸屬前後 parent

## 匹配規則

### Rule 1: Fully-Contained Matching

```
ASR sentence 完全在 CUT 場景時間範圍內
  → seg.start >= scene.start_time AND seg.end <= scene.end_time
  → 加入該 scene 的 children 列表
```

### Rule 2: Boundary Overlap (所有 parent)

```
對於每個 parent chunk（即使只有 1 child）:
  → 找出與 scene 時間範圍有 partial overlap 的 ASR sentence
  → seg.start < scene.end_time AND seg.end > scene.start_time
  → AND 未被 Rule 1 匹配（不是 fully-contained）
  → 加入該 scene 的 children 列表
```

邊界 overlap 讓 child chunk 可以同時歸屬前後兩個 parent，提供更多上下文。

### Rule 3: Scene Filter

```
CUT scene duration < 1s → 跳過（場景太短無意義）
```

## Parent Summary 模板

```
[{start}s-{end}s, {duration}s]
Cast: {character_list}
Total dialogue: N lines, W words
Speakers: {name} (N lines): "sample text..."
```

## Child Summary 模板

```
[{start}s-{end}s] {speaker_name}: "{asr_text}"
```

### Embedding Target

Child summary text → Ollama nomic-embed-text-v2-moe → 768D vector → pgvector

## 數據實例：Charade (1963) — 長片 113min

### 輸入

| 來源 | 數量 | 說明 |
|------|------|------|
| ASR segments | **1,629** | Whisper small 英文字幕 |
| ASR with text | 1,629 | 全部有文字 |
| ASR total duration | 6,760s (113 min) | |
| CUT scenes | **1,331** | PySceneDetect 場景切割 |
| CUT scenes ≥ 1s | 1,200 | 過濾後有效場景 |
| CUT mean duration | 5.2s | 平均場景長度 |
| CUT scene gap (unmatched) | 131 | < 1s 場景被過濾 |

### 輸出 (V2.1 — boundary overlap for ALL scenes, duration filter removed)

| 指標 | 數值 |
|------|------|
| **Parent chunks** | **1,313** (all CUT scenes ≥ 0s) |
| **Child chunks** (total in DB) | **2,927** (1,629 unique + 1,298 overlaps) |
| **Unique children** | **1,629** (100% ASR coverage) |
| DB duplicates (shared) | 1,298 (ON CONFLICT merge) |
| Children per parent | 1 ~ 43, avg **2.2** |
| Unmatched | **0** |

### 分佈

```
Children per parent:
    1:  128 parents  (獨白/短場景)
    2:   58 parents
    3:    0 parents  ← 邊界 overlap 後 3 被 2/4 吸收
    4-9:  64 parents  (中等對話場景)
   10-27: 50 parents  (多人對話場景)
```

### 已匹配率

| 指標 | 數值 |
|------|------|
| ASR unmatched | **0** (V2.1: boundary overlap for ALL scenes) |
| 已匹配率 | **100%** |

## 輸入/輸出範例

### Big Parent（多子女）

**輸入原始數據**：
```
CUT scene [2783s-2847s, 65s]
27 ASR sentences, all spoken by Audrey Hepburn + Cary Grant + SPEAKER_2
```

**輸出 Parent Summary**：
```
[2783s-2847s, 65s] Cast: Audrey Hepburn, Cary Grant, SPEAKER_2.
Total dialogue: 27 lines, 143 words.
```

**輸出 Child Summaries**（embedding target）：
```
[2784s-2786s] Audrey Hepburn: "they stole it"
[2786s-2788s] Audrey Hepburn: "by burying it"
[2788s-2790s] Audrey Hepburn: "then reporting the Germans had captured it"
... (27 total)
```

**Metadata 信度**（隨 parent/child 傳遞）：

```json
// Parent metadata
{
  "speaker_confidence": { "Audrey Hepburn": 0.85, "Cary Grant": 0.64 },
  "face_confidence": { "Audrey Hepburn": 0.60, "Cary Grant": 0.64 },
  "yolo_objects": { "car": 0.72, "bottle": 0.55, "chair": 0.68 }
}

// Child metadata
{
  "speaker_name": "Audrey Hepburn",
  "speaker_confidence": 0.85,   // MAR lip: 57% events during SPEAKER_1
  "face_confidence": 0.60,       // clustering composite
  "asr_confidence": 0.92         // Whisper confidence
}
```

### 1:1 Parent（單子女）

**輸入原始數據**：
```
CUT scene [304s-318s, 14s]
1 ASR sentence, spoken by Cary Grant alone
```

**輸出 Parent Summary**：
```
[304s-318s, 14s] Cast: Cary Grant.
Total dialogue: 1 lines, 13 words.
```

**輸出 Child Summary**（embedding target）：
```
[309s-317s] Cary Grant: "Sylvia I'm getting a divorce what from Charles he's the only husband I"
```

## 與 LLM Pipeline 的關係

```
Pipeline 1 (Story):  template summary → DB + embedding
Pipeline 2 (LLM):    LLM summary → DB + embedding (future)

chunk_type:
  story_parent / story_child   ← Pipeline 1
  llm_parent   / llm_child     ← Pipeline 2 (future)
```

## 版本歷史

| 版本 | 日期 | 變更 |
|------|------|------|
| V1.0 | 2026-05-05 | 初始規則：fully-contained + boundary overlap |
| V2.1 | 2026-05-05 | 移除 duration filter，boundary overlap 對所有場景（含空場景）。100% ASR coverage。Speaker mapping 從 DB 動態讀取。 |

## Charade 1963 統計分析記錄

### 影片資料

| 指標 | 值 |
|------|-----|
| 片長 | 113 分鐘 |
| 總幀數 | 412,343 |
| FPS | 59.94 |
| 解析度 | 1920×1080 |

### 處理器產出

| Processor | 輸出行數 | 說明 |
|-----------|---------|------|
| CUT | 1,331 scenes | 平均 5.2s/scene，min 0.2s，max 64.5s |
| ASR | 1,629 segments | Whisper small，113 min total |
| ASRX | 10 speakers | SPEAKER_0/1 為主要角色 |
| Face | 4,008 frames, 6,182 faces | sample=60, Vision+CoreML ANE |
| Face Trace | 6,182 detections, 2,347 traces | IoU+embedding tracking |
| Identity | 677 traces → 7 identities | 99.4% coverage, MAR lip speaker binding |
| YOLO | 328,800 frames, 57 object classes | CoreML ANE |

### Matching 迭代記錄

#### Iteration 1: Fully-contained only, >= 1s scene filter

```
Rule: seg.start >= scene.start AND seg.end <= scene.end
Scene filter: duration >= 1s (131 scenes filtered out)

Result: 990/1629 (61%) matched
       454 unmatched, 74 in filtered scenes
       Only scenes with children got boundary overlaps
```

#### Iteration 2: Add boundary overlap for scenes with >= 3 children

```
Rule: For scenes with >= 3 children, add partial overlaps

Result: 1,210 children (+220 partial)
       Still 454 unmatched (boundary overlap only for rich scenes)
```

#### Iteration 3: Remove duration filter

```
Rule: Remove >=1s scene filter

Result: 1,496 unique children (92% coverage)
       133 unmatched
       Root cause: boundary overlap still gated by "if children:"
```

#### Iteration 4: Boundary overlap for ALL scenes (regardless of children)

```
Rule: Move boundary overlap code outside "if children:" guard
       All 1,331 scenes participate

Result: 1,629 unique children (100% coverage)
       1,313 parents (all scenes)
       2,927 total children (1,629 unique + 1,298 overlaps)
```

### 關鍵決策

| 決策 | 理由 | 影響 |
|------|------|------|
| 移除 duration filter | 131 scenes <1s 會漏掉句子 | +24 parents, +321 children |
| 移除 children guard | 空場景也要加 boundary children | +133 children (100%) |
| 用 overlap 而非 fully-contained | ASR/CUT 時間邊界不對齊 | 避免 565 sentences orphan |
| Partial overlaps 存兩次 | 邊界句可歸屬兩個 parent | 1,298 duplicates via ON CONFLICT |
| Speaker map 從 DB 讀 | 不再 hardcode 演員名 | 通用化任何影片 |

### 效能指標

| 指標 | 值 |
|------|-----|
| Story 生成時間 | < 1s (template, instant) |
| Embedding 時間 (Ollama) | ~2 min for 1,629 chunks |
| Qdrant sync 時間 | ~3 min for rule1, ~1 min for story |
| BM25 search 時間 | < 10ms per query |

### 教學要點

1. **時間邊界不對齊是常態**：ASR（語音邊界）與 CUT（視覺邊界）用不同演算法，永遠不會完美對齊。overlap matching 是必要設計。
2. **Boundary overlap 需對所有場景生效**：不能只限有 children 的場景，否則會產生 orphan sentences。
3. **ON CONFLICT merge**：同一 sentence 出現在兩個 parent 時，DB 層面用最後一個 parent。如需多對多關係，需 junction table。
4. **Hardcoded 到 Dynamic**：speaker map 從 hardcode → DB-driven 是通用化的關鍵一步。
