# 帳票設計書 2-Year in Review Location Shareable

## 概要

本ドキュメントは、Wikipedia iOSアプリケーションにおける「Year in Review Location Shareable」帳票の設計仕様を定義する。この帳票は位置情報ベースの読書活動を地図上に表示した共有可能な画像を生成する機能を提供する。

### 本帳票の処理概要

本帳票は、ユーザーが一年間で閲覧したWikipedia記事のうち、位置情報を持つ記事（地名、観光地、歴史的建造物等）を地図上にマッピングし、視覚的なサマリーとして共有可能な画像を生成する機能である。

**業務上の目的・背景**：ユーザーの知的好奇心の地理的広がりを可視化することで、一年間の学習活動をより直感的に把握できるようにする。地図という視覚的要素を加えることで、SNS共有時のエンゲージメント向上を図り、他のユーザーの興味を引く魅力的なコンテンツを提供する。

**帳票の利用シーン**：Year in Review機能において、位置情報スライドの内容をソーシャルメディアで共有したい場合に使用される。ユーザーが「どの国/地域の記事を多く読んだか」を地図と共に友人やフォロワーと共有できる。

**主要な出力内容**：
1. Wikipediaロゴ（W-share-logo）とヘッダー部
2. MapKitスナップショット（位置情報をプロットした地図画像）
3. タイトルテキスト（最も読んだ国/地域名を含む）
4. サブタイトルテキスト（読んだ記事名のリスト）
5. フッター部（globeアイコン、ハッシュタグ）

**帳票の出力タイミング**：Year in Review画面のLocationスライド表示中に、ユーザーが「Share」ボタンをタップした時点で画像が生成される。地図スナップショットの読み込み完了後のみ共有が可能。

**帳票の利用者**：Wikipediaアプリのログインユーザー（位置情報付き記事を2件以上閲覧したユーザー）

## 帳票種別

共有画像（地図付きソーシャルメディア共有用カード画像）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | Year in Review Location Slide View | WMFYearInReviewSlideLocationView | Shareボタンタップ |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | PNG（UIImage） |
| 用紙サイズ | 402 x 847ポイント（最小高さ） |
| 向き | 縦 |
| ファイル名 | システム生成（UIActivityViewController経由） |
| 出力方法 | UIActivityViewController（共有シート） |
| 文字コード | UTF-8 |

### 画像固有設定

| 項目 | 内容 |
|-----|------|
| スケール | UIScreen.main.scale（デバイス解像度対応） |
| 背景色 | theme.paperBackground |
| レンダリング | SwiftUI ImageRenderer |
| 地図スナップショット | MKMapSnapshotter経由のUIImage |

## 帳票レイアウト

### レイアウト概要

縦長のカード形式で、ヘッダー、地図、コンテンツ、フッターの4部構成となっている。

```
┌─────────────────────────────────────┐
│         W-share-logo (50pt)         │
│              ヘッダー部               │
├─────────────────────────────────────┤
│                                     │
│         MapKitスナップショット          │
│        （位置情報プロット済み）          │
│                                     │
├─────────────────────────────────────┤
│         タイトル（boldTitle1）        │
│      "You read about [国名]"         │
│         サブタイトル（HTML）           │
│              明細部                  │
├─────────────────────────────────────┤
│  [globe]  #WikipediaYearInReview    │
│              フッター部               │
└─────────────────────────────────────┘
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | Wikipediaロゴ | W-share-logoアセット | Bundle.module | Image 50pt高さ |

### 地図部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | 地図スナップショット | 位置情報プロット済み地図 | viewModel.mapViewSnapshotForSharing | UIImage、アスペクト比維持 |

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | タイトル | 国/地域名を含むタイトル | viewModel.title | boldTitle1フォント | 全幅-56pt |
| 2 | サブタイトル | 記事名リスト | viewModel.subtitle | WMFHtmlText | 全幅-56pt |

### フッター部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | Globeアイコン | 地球儀アイコン | Bundle.module | 50x50pt |
| 2 | ハッシュタグ | キャンペーンタグ | hashtag | boldTitle3、リンク色 |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| viewModel | WMFYearInReviewSlideLocationViewModel | Yes |
| mapViewSnapshotForSharing | 地図スナップショット画像 | Yes |
| isLoading | ロード完了（false）であること | Yes |
| hashtag | 共有用ハッシュタグ文字列 | Yes |
| legacyPageViews | 2件以上の位置情報付き閲覧履歴 | Yes |

### ソート順

該当なし（単一画像出力のため）

### 改ページ条件

該当なし（単一画像出力のため）

## データベース参照仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| CDYearInReviewReport | 年間レポートデータ | year = 対象年 |
| CDYearInReviewSlide | スライドデータ（location） | report.year = 対象年, id = "location" |

### テーブル別参照項目詳細

#### CDYearInReviewSlide (location)

| 参照項目（カラム名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| data | WMFLegacyPageView配列 | Binary | JSON形式、位置情報付き |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| 画像スケール | UIScreen.main.scale | - | デバイス解像度に応じた値 |
| 提案サイズ幅 | 402ポイント固定 | - | レンダラー設定 |
| 国/地域名 | CLGeocoder.reverseGeocodeLocation | - | 非同期処理 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[Shareボタンタップ] --> B[tappedShare呼び出し]
    B --> C{isLoading確認}
    C -->|true| D[処理中断]
    C -->|false| E{mapViewSnapshot確認}
    E -->|nil| D
    E -->|存在| F[WMFYearInReviewSlideLocationShareableView生成]
    F --> G[ImageRenderer初期化]
    G --> H[proposedSize設定 402x nil]
    H --> I[scale設定 UIScreen.main.scale]
    I --> J[renderer.uiImage取得]
    J --> K{画像生成成功?}
    K -->|Yes| L[coordinatorDelegate.share呼び出し]
    K -->|No| M[処理終了]
    L --> N[UIActivityViewController表示]
    N --> O[終了]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| 地図読み込み中 | isLoading = true | なし | Shareボタン無効化または処理中断 |
| スナップショットなし | mapViewSnapshotForSharing = nil | なし | 処理を中断 |
| 逆ジオコーディング失敗 | CLGeocoder失敗 | なし | 空の国名で継続 |
| 画像生成失敗 | renderer.uiImageがnil | なし | 処理を中断 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 1件/回 |
| 目標出力時間 | 2秒以内（地図スナップショット含む） |
| 同時出力数上限 | 1件 |

## セキュリティ考慮事項

- 位置情報：ユーザーが閲覧した記事の位置情報を含むが、ユーザー自身の位置ではない
- 個人情報：読書履歴に基づく地理的傾向が含まれる
- アクセス制御：ユーザーの明示的な操作（Shareボタンタップ）が必要
- 出力ログ：loggingDelegateを通じてlogYearInReviewDidTapShareで記録

## 備考

- MapKitスナップショットは事前に生成され、viewModel.mapViewSnapshotForSharingに保持
- CLGeocoderによる逆ジオコーディングで国名/海洋名を取得
- 位置情報付き記事が2件以上ある場合のみこのスライドが表示される
- クラスタリング機能で複数の近接マーカーをグループ化

---

## コードリーディングガイド

本帳票を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、プログラム間で受け渡されるデータ構造を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | WMFYearInReviewLocationViewModel.swift | `WMFComponents/Sources/WMFComponents/Components/Year in Review/View Models/WMFYearInReviewLocationViewModel.swift` | ViewModelのプロパティ構成（legacyPageViews, mapViewSnapshotForSharing, countryOrOceanName）を理解 |

**読解のコツ**: ObservableObjectプロトコルによるリアクティブな状態管理、isLoadingによる非同期処理の状態管理を把握する。

#### Step 2: エントリーポイントを理解する

処理の起点となるファイル・関数を特定。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | WMFYearInReviewViewModel.swift | `WMFComponents/Sources/WMFComponents/Components/Year in Review/View Models/WMFYearInReviewViewModel.swift` | tappedShare()関数内のlocationケース（行980-990）が共有処理 |

**主要処理フロー**:
1. **行980-983**: locationスライドの場合、isLoadingとmapViewSnapshot確認
2. **行984**: WMFYearInReviewSlideLocationShareableViewを生成
3. **行985-990**: ImageRendererで画像化、coordinatorDelegate.share呼び出し

#### Step 3: 共有可能ビューの構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | WMFYearInReviewSlideLocationShareableView.swift | `WMFComponents/Sources/WMFComponents/Components/Year in Review/Views/Shareable Slide Views/WMFYearInReviewSlideLocationShareableView.swift` | SwiftUIビューの構造、mapView計算プロパティを理解 |

**主要処理フロー**:
- **行24-30**: mapView計算プロパティ - スナップショット画像をImage型に変換
- **行32-89**: body定義 - VStackによる縦並びレイアウト
- **行42-49**: 地図画像の表示（resizable、scaledToFit）
- **行53-59**: コンテンツ部（タイトル、WMFHtmlText）
- **行66-83**: フッター部（globe、ハッシュタグ）

#### Step 4: 逆ジオコーディング処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | WMFYearInReviewLocationViewModel.swift | `WMFComponents/Sources/WMFComponents/Components/Year in Review/View Models/WMFYearInReviewLocationViewModel.swift` 行45-69 | reverseGeocode関数の実装 |

**主要処理フロー**:
- **行45-50**: Task内でCLGeocoderを使用
- **行56-60**: placemark.countryまたはplacemark.oceanを取得
- **行62-63**: タイトル・サブタイトルを更新
- **行65-67**: isLoading = falseに設定

### プログラム呼び出し階層図

```
WMFYearInReviewView (UI)
    │
    ├─ WMFYearInReviewViewModel.tappedShare()
    │      │
    │      ├─ isLoading / mapViewSnapshot チェック
    │      │
    │      ├─ WMFYearInReviewSlideLocationShareableView(viewModel:hashtag:)
    │      │      │
    │      │      └─ body: View
    │      │             ├─ Image("W-share-logo")
    │      │             ├─ mapView (UIImage -> Image変換)
    │      │             ├─ Text(viewModel.title)
    │      │             ├─ WMFHtmlText(viewModel.subtitle)
    │      │             └─ HStack [globe + hashtag]
    │      │
    │      ├─ ImageRenderer(content: view)
    │      │      └─ renderer.uiImage
    │      │
    │      └─ coordinatorDelegate.handleYearInReviewAction(.share(image:))
    │
    └─ UIActivityViewController (システム共有シート)
```

### データフロー図

```
[入力]                     [処理]                           [出力]

legacyPageViews ────────▶ WMFYearInReview                 ユーザー共有先
  │                      SlideLocationViewModel ────────▶ (Twitter/Facebook等)
  ├─ coordinates                │
  └─ articleTitles              ▼
                         CLGeocoder.reverseGeocode
                               │
                               ▼
                    countryOrOceanName, title, subtitle
                               │
                               ▼
mapViewSnapshot ──────▶ WMFYearInReviewSlide
                       LocationShareableView
                               │
                               ▼
hashtag ──────────────────────▶│
                               ▼
                          ImageRenderer
                               │
                               ▼
                          UIImage
                               │
                               ▼
                    UIActivityViewController
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| WMFYearInReviewSlideLocationShareableView.swift | `WMFComponents/Sources/WMFComponents/Components/Year in Review/Views/Shareable Slide Views/` | ソース | 共有用ビュー定義 |
| WMFYearInReviewLocationViewModel.swift | `WMFComponents/Sources/WMFComponents/Components/Year in Review/View Models/` | ソース | 位置情報スライドViewModel |
| WMFYearInReviewViewModel.swift | `WMFComponents/Sources/WMFComponents/Components/Year in Review/View Models/` | ソース | 共有処理ロジック |
| WMFYearInReviewSlideLocationView.swift | `WMFComponents/Sources/WMFComponents/Components/Year in Review/Views/Slide Views/` | ソース | 表示用ビュー |
| WMFYearInReviewSlide.swift | `WMFData/Sources/WMFData/Models/Year In Review/` | ソース | スライドモデル |
| WMFData 5.xcdatamodel | `WMFData/Sources/WMFData/Resources/WMFData.xcdatamodeld/` | 設定 | Core Dataモデル定義 |
