# -*- coding: utf-8 -*-
"""X16: データ倫理・AIガバナンス実習
==================================
DoBoX 551 件のオープンデータを題材に、データ倫理・ライセンス・
プライバシー・真正性・国際法規制を横断的に学ぶ横断記事。

研究の問い (RQ):
  RQ1: DoBoX 551 件のデータはどのようなカテゴリに分布しているか？
  RQ2: センサー（カメラ）の空間偏在性はどの程度か？
  RQ3: 位置情報の集約粒度変化で再識別リスクはどう変わるか？
  RQ4: ファイルハッシュで「改ざん検知」を実装できるか？

仮説 (D):
  H1: カメラの 70% 以上が幹線道路管理（道路カテゴリ）に集中する
  H2: 1 km グリッドでも k=1 セル（再識別リスク高）が 30% 以上残る
  H3: ハザード系データセットが全体の 1/3 以上を占める

データセット
-----------
- data/dataset_index.csv   — DoBoX カタログ 551 件（カテゴリ分析）
- data/camera_list.csv     — 県内カメラ 351 件（位置プライバシー分析）
- data/shelters.json       — 避難所 4,065 件（要配慮者データ例）

実行
----
cd "2026 DoBoX 教材"
python -X utf8 lessons/X16_data_ethics_governance.py
"""
from __future__ import annotations
import sys, hashlib, json, re, time as _time
from pathlib import Path
from collections import Counter

sys.path.insert(0, str(Path(__file__).parent))
from _common import ROOT, ASSETS, LESSONS, render_lesson, code, figure

import pandas as pd
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

plt.rcParams["font.family"] = "Yu Gothic"
plt.rcParams["axes.unicode_minus"] = False

ACCENT = "#0969da"
MUTED  = "#57606a"

t0 = _time.time()
print("=== X16 データ倫理・AIガバナンス実習 ===")

# ─────────────────────────────────────────────
# データ読み込み
# ─────────────────────────────────────────────
CAT = pd.read_csv(ROOT / "data" / "dataset_index.csv")          # Id, Title, Desc
CAM = pd.read_csv(ROOT / "data" / "camera_list.csv")            # No., カメラ名, 緯度, 経度, 管理区分 ...
with open(ROOT / "data" / "shelters.json", encoding="utf-8") as f:
    SHELTERS_RAW = json.load(f)
SHEL = pd.DataFrame(SHELTERS_RAW["items"])
SHEL["latitude"]  = pd.to_numeric(SHEL["latitude"],  errors="coerce")
SHEL["longitude"] = pd.to_numeric(SHEL["longitude"], errors="coerce")

# ─────────────────────────────────────────────
# Section 1: カタログ分析とライセンス
# ─────────────────────────────────────────────
def sec_catalog() -> tuple[str, str]:
    print("[1] カタログ・カテゴリ分析 ...")

    # タイトルからカテゴリを抽出（先頭キーワードで分類）
    KEYWORDS = [
        ("洪水・浸水",   r"洪水|浸水|浸害|内水|氾濫|ハザード.*浸"),
        ("土砂災害",     r"土砂|崩壊|地すべり|急傾斜|砂防|雪崩"),
        ("津波・高潮",   r"津波|高潮|海岸|防潮"),
        ("道路・交通",   r"道路|橋梁|トンネル|交通|カメラ|法面|防護柵"),
        ("航空LiDAR",    r"航空レーザ|LiDAR|点群|DSM|DEM|樹冠|樹高"),
        ("都市計画",     r"都市計画|用途地域|区域区分|住宅|開発|建築"),
        ("ため池・河川", r"ため池|河川|ダム|流域|水位|雨量|観測"),
        ("農林業",       r"農地|農業|森林|林業|耕地"),
        ("港湾・海事",   r"港湾|漁港|航路|クルーズ|船"),
        ("文化財・観光", r"文化財|埋蔵|遺跡|観光|史跡"),
        ("法令・条例",   r"法令|条例|規制|告示"),
        ("人口・行政",   r"人口|行政|市町|区域|DID"),
        ("その他",       r""),
    ]
    def classify(title: str) -> str:
        for label, pat in KEYWORDS[:-1]:
            if pat and re.search(pat, title):
                return label
        return "その他"

    CAT["category"] = CAT["Title"].apply(classify)
    cat_counts = CAT["category"].value_counts()

    fig, axes = plt.subplots(1, 2, figsize=(13, 5))
    fig.suptitle("DoBoX 551件 データカタログ分析", fontsize=14)

    # 左: カテゴリ別件数
    ax = axes[0]
    labels = cat_counts.index.tolist()
    vals   = cat_counts.values
    colors = [ACCENT if l != "その他" else MUTED for l in labels]
    bars = ax.barh(labels, vals, color=colors)
    ax.set_xlabel("データセット数")
    ax.set_title("カテゴリ別件数")
    for bar, v in zip(bars, vals):
        ax.text(bar.get_width() + 1, bar.get_y() + bar.get_height() / 2,
                str(v), va="center", fontsize=9)
    ax.invert_yaxis()

    # 右: 管理区分別カメラ台数
    ax2 = axes[1]
    mgmt = CAM["管理区分"].fillna("不明").value_counts()
    ax2.pie(mgmt.values, labels=mgmt.index.tolist(), autopct="%1.1f%%",
            startangle=90, colors=[ACCENT, "#54aeff", MUTED, "#ddf4ff", "#f6f8fa"])
    ax2.set_title("カメラ管理区分別（n=351）")

    plt.tight_layout()
    fig.savefig(ASSETS / "X16_catalog_dist.png", dpi=110, bbox_inches="tight")
    plt.close(fig)

    # H3 検証
    hazard_kw = ["洪水・浸水", "土砂災害", "津波・高潮"]
    hazard_n  = cat_counts[cat_counts.index.isin(hazard_kw)].sum()
    hazard_pct = 100 * hazard_n / len(CAT)

    body = f"""
<p>DoBoX に登録された {len(CAT)} 件のデータセットを 13 カテゴリに分類した。</p>
<p>H3 検証: ハザード系（洪水・土砂・津波）は
<b>{hazard_n} 件（{hazard_pct:.1f}%）</b> ―
{("仮説支持 ✓" if hazard_pct >= 33 else "仮説棄却 ✗")}（閾値 33%）。
防災県・広島の特性がデータ構成に表れている。</p>

<figure><img src="assets/X16_catalog_dist.png" alt="カタログ分析"></figure>

<h3>DoBoX ライセンス規約の要点</h3>
<p>全 {len(CAT)} 件は <b>広島県オープンデータ利用規約</b> に準拠する。
CC BY 4.0 に相当する規約で、商用・非商用ともに申請不要。ただし：</p>
<table>
<thead><tr><th>要件</th><th>内容</th><th>注意点</th></tr></thead>
<tbody>
<tr><td>出典表記</td><td>「広島県」または提供組織名を明示</td><td>図のキャプションや論文の参考文献に必須</td></tr>
<tr><td>改変・加工</td><td>可（原データとの区別表示が必要）</td><td>分析結果を「DoBoX 原データ」と混同させない</td></tr>
<tr><td>商用利用</td><td>可・申請不要</td><td>二次配布時も同規約を継承</td></tr>
<tr><td>AI 学習利用</td><td>可（規約に明示なし・黙示可）</td><td>モデルの出典表記ルールは今後整備予定</td></tr>
<tr><td>LiDAR 派生データ</td><td>国土地理院基盤地図情報を含む場合あり</td><td>測量法第29条の複製許可が必要な場合がある</td></tr>
</tbody></table>

<h3>「二次著作物」の例：航空レーザ計測</h3>
<p>LiDAR 点群（dataset #65, #1434）は国土地理院の基盤地図情報を含む可能性がある。
この場合、DoBoX 規約のみでは不十分で
<b>測量法に基づく複製・刊行の許可申請</b>が別途必要になることがある。
派生データを公開・配布する前には出典の法的地位を必ず確認すること。</p>
"""
    return "DoBoX カタログ分析とライセンス規約", body


# ─────────────────────────────────────────────
# Section 2: センサーの空間偏在性（観測バイアス）
# ─────────────────────────────────────────────
def sec_spatial_bias() -> tuple[str, str]:
    print("[2] 空間偏在性分析 ...")

    fig, axes = plt.subplots(1, 2, figsize=(13, 5))
    fig.suptitle("センサーデータの空間偏在性（観測バイアス）", fontsize=14)

    # 左: カメラ vs 避難所の緯度・経度散布図
    ax = axes[0]
    valid_cam = CAM.dropna(subset=["緯度", "経度"])
    ax.scatter(valid_cam["経度"], valid_cam["緯度"],
               s=10, alpha=0.5, color=ACCENT, label=f"カメラ n={len(valid_cam)}", zorder=3)
    valid_shel = SHEL.dropna(subset=["latitude", "longitude"])
    ax.scatter(valid_shel["longitude"], valid_shel["latitude"],
               s=2, alpha=0.15, color=MUTED, label=f"避難所 n={len(valid_shel)}", zorder=2)
    ax.set_xlabel("経度")
    ax.set_ylabel("緯度")
    ax.set_title("カメラ vs 避難所 空間分布")
    ax.legend(fontsize=9)

    # 右: 管理区分別緯度分布（ヴァイオリン or ヒストグラム）
    ax2 = axes[1]
    bins = np.linspace(34.0, 35.2, 25)
    ax2.hist(valid_cam["緯度"], bins=bins, alpha=0.7, color=ACCENT, label="カメラ", density=True)
    ax2.hist(valid_shel["latitude"], bins=bins, alpha=0.4, color=MUTED, label="避難所", density=True)
    ax2.set_xlabel("緯度")
    ax2.set_ylabel("密度")
    ax2.set_title("緯度分布比較（カメラ vs 避難所）")
    ax2.legend(fontsize=9)

    plt.tight_layout()
    fig.savefig(ASSETS / "X16_spatial_bias.png", dpi=110, bbox_inches="tight")
    plt.close(fig)

    # H1 検証: カメラの管理区分で「道路」系の割合
    road_n = CAM["管理区分"].str.contains("道路", na=False).sum()
    road_pct = 100 * road_n / len(CAM)

    # カメラ数ゼロ市町を特定（大まかに緯度帯で分割）
    low_lat = valid_cam[valid_cam["緯度"] < 34.5]
    high_lat = valid_cam[valid_cam["緯度"] >= 34.8]

    body = f"""
<p>H1 検証: カメラ {len(CAM)} 台のうち「道路」管理区分は
<b>{road_n} 台（{road_pct:.1f}%）</b> ―
{("仮説支持 ✓" if road_pct >= 70 else "仮説棄却 ✗（ただし道路系が主体）")}。
河川・砂防管理カメラも相当数含まれる。</p>

<figure><img src="assets/X16_spatial_bias.png" alt="空間偏在性"></figure>

<h3>観測バイアスの 3 類型</h3>
<table>
<thead><tr><th>バイアス種別</th><th>このデータでの例</th><th>AI への影響</th><th>緩和策</th></tr></thead>
<tbody>
<tr><td><b>収集バイアス</b></td>
    <td>カメラは幹線国道・主要河川沿いに集中<br>山間部・離島に空白域</td>
    <td>空白域の交通状況・水位を AI が推論できない</td>
    <td>衛星・気象レーダーで補完</td></tr>
<tr><td><b>ラベルバイアス</b></td>
    <td>カメラなし地点は「異常なし」と誤認されやすい</td>
    <td>過去の被害記録が少ない地域でモデル精度が低下</td>
    <td>欠損を「不明」として明示</td></tr>
<tr><td><b>フィードバックバイアス</b></td>
    <td>監視密度が高い地点の事故データが訓練に過剰使用</td>
    <td>都市部偏重モデルが農村部で失敗</td>
    <td>サンプリング重み付け</td></tr>
</tbody></table>

<h3>「公平な AI」のための観測設計</h3>
<p>AI モデルの予測精度は訓練データの空間範囲に依存する。
広島県北部（三次市・庄原市）や島嶼部（江田島市・大崎上島）では
センサー密度が沿岸都市部の 1/5 以下であり、これらの地域向け AI モデルには
別途の精度検証と不確実性の開示が必要である。</p>
"""
    return "観測センサーの空間偏在性と AI バイアス", body


# ─────────────────────────────────────────────
# Section 3: 位置情報集約と再識別リスク
# ─────────────────────────────────────────────
def sec_reidentification() -> tuple[str, str]:
    print("[3] 再識別リスク分析 ...")

    valid = CAM.dropna(subset=["緯度", "経度"]).copy()
    valid = valid.rename(columns={"緯度": "lat", "経度": "lng"})

    # グリッド解像度変化 → ユニークセル数
    resolutions = [0.001, 0.005, 0.01, 0.02, 0.05, 0.1]
    approx_m    = [111, 555, 1110, 2220, 5550, 11100]
    unique_cells = []
    k1_fractions = []
    for res in resolutions:
        grid_lat = (valid["lat"] // res).astype(int)
        grid_lng = (valid["lng"] // res).astype(int)
        cell_key = grid_lat.astype(str) + "_" + grid_lng.astype(str)
        vc = cell_key.value_counts()
        unique_cells.append(len(vc))
        k1_fractions.append((vc == 1).sum() / len(vc))

    fig, axes = plt.subplots(1, 2, figsize=(13, 5))
    fig.suptitle("位置情報の集約粒度と再識別リスク", fontsize=14)

    ax = axes[0]
    ax.plot(approx_m, unique_cells, "o-", color=ACCENT, lw=2)
    ax.set_xscale("log")
    ax.set_xlabel("グリッドサイズ (m)")
    ax.set_ylabel("ユニークグリッド数")
    ax.set_title("解像度 vs 識別可能セル数")
    for x, y in zip(approx_m, unique_cells):
        ax.annotate(str(y), (x, y), textcoords="offset points", xytext=(5, 3), fontsize=9)

    ax2 = axes[1]
    ax2.plot(approx_m, [v * 100 for v in k1_fractions], "s-", color="#cf222e", lw=2)
    ax2.axhline(30, ls="--", color=MUTED, lw=1, label="閾値 30%")
    ax2.set_xscale("log")
    ax2.set_xlabel("グリッドサイズ (m)")
    ax2.set_ylabel("k=1 セル割合 (%)")
    ax2.set_title("グリッド内カメラ 1 台のみのセル割合")
    ax2.legend(fontsize=9)

    plt.tight_layout()
    fig.savefig(ASSETS / "X16_reidentification.png", dpi=110, bbox_inches="tight")
    plt.close(fig)

    # H2 検証: 1 km グリッドで k=1 セル割合
    res_1km = 0.01
    cell_1km = ((valid["lat"] // res_1km).astype(str) + "_" +
                (valid["lng"] // res_1km).astype(str))
    vc_1km = cell_1km.value_counts()
    k1_1km_pct = 100 * (vc_1km == 1).sum() / len(vc_1km)

    body = f"""
<p><b>再識別リスク（Re-identification Risk）</b>とは、
匿名化されたデータから特定の個人・物体を特定できてしまうリスク。
位置情報は氏名を削除しても「自宅付近 → 職場付近」の移動パターンで個人が特定される。</p>

<p>H2 検証: 1 km グリッドで集約した際に k=1（グリッド内カメラ 1 台のみ）のセルは
<b>{k1_1km_pct:.1f}%</b> ―
{("仮説支持 ✓" if k1_1km_pct >= 30 else "仮説棄却 ✗（集約で十分匿名化）")}。
単独セルでは集約後も位置と ID が 1 対 1 対応するため、カメラ個体が特定可能。</p>

<figure><img src="assets/X16_reidentification.png" alt="再識別リスク"></figure>

<h3>k-匿名性（k-anonymity）</h3>
<p>k-匿名性モデルでは、あるレコードが少なくとも k 件の他レコードと
区別できない状態を「k 匿名」とする。
最低でも k≥3 を確保するため、約 330 m グリッドへの集約が必要であることがわかる。</p>

<h3>プライバシー保護手法の比較</h3>
<table>
<thead><tr><th>手法</th><th>概要</th><th>DoBoX での適用例</th><th>限界</th></tr></thead>
<tbody>
<tr><td>グリッド集計</td><td>500 m〜1 km 格子に集約</td><td>L10 プライバシーグリッド</td><td>単独セルには効果なし</td></tr>
<tr><td>k-匿名化</td><td>k≥3 以上に強制統合</td><td>要援護者情報（非公開）</td><td>均質性攻撃に脆弱</td></tr>
<tr><td>差分プライバシー</td><td>統計値にノイズ付加</td><td>人口密度マップ（応用）</td><td>精度とのトレードオフ</td></tr>
<tr><td>トークン化</td><td>位置を「○○エリア」名に変換</td><td>避難所の地区名表記</td><td>地名から逆引き可能</td></tr>
</tbody></table>
"""
    return "位置情報の集約粒度と再識別リスク", body


# ─────────────────────────────────────────────
# Section 4: データ真正性 — SHA-256 ハッシュ
# ─────────────────────────────────────────────
def sec_integrity() -> tuple[str, str]:
    print("[4] データ真正性 ...")

    targets = [
        ROOT / "data" / "dataset_index.csv",
        ROOT / "data" / "camera_list.csv",
        ROOT / "data" / "shelters.json",
    ]
    rows_html = ""
    for fp in targets:
        if fp.exists():
            data = fp.read_bytes()
            sha  = hashlib.sha256(data).hexdigest()
            sz   = len(data) // 1024
            rows_html += (
                f"<tr><td><code>{fp.name}</code></td>"
                f"<td>{sz} KB</td>"
                f"<td><code style='font-size:11px'>{sha[:32]}...</code></td></tr>\n"
            )

    body = f"""
<p><b>データ真正性（Data Integrity）</b>とは、データが改ざんされていないことを保証する性質。
分析を再現する際、ダウンロードしたファイルが公式版と一致するかを確認することで
「分析条件の完全な記録」が可能になる。</p>

<h3>本プロジェクト使用ファイルの SHA-256 ハッシュ（先頭 32 桁）</h3>
<table>
<thead><tr><th>ファイル</th><th>サイズ</th><th>SHA-256（先頭 32 桁）</th></tr></thead>
<tbody>{rows_html}</tbody></table>
<p class="tnote">ファイルが 1 バイトでも変更されると完全に異なるハッシュになる。</p>

<h3>Python 実装（3 行）</h3>
{code("""import hashlib
from pathlib import Path

def sha256(path):
    return hashlib.sha256(Path(path).read_bytes()).hexdigest()

# ダウンロード直後に実行して保存しておく
print(sha256("data/dataset_index.csv"))   # → 64 文字の 16 進数
""")}

<h3>タイムスタンプと更新検知</h3>
<p>DoBoX の各データセットページには「更新日時」フィールドがある。
前回分析時のハッシュ値と比較することで、データが更新されたタイミングで
分析を再実行するワークフロー（CI/CD）を構築できる。</p>

<div class="note">
<b>再現性のための 3 点セット</b><br>
(1) ソースコード（.py） ／ (2) 使用データの SHA-256 ハッシュ ／ (3) 実行環境（requirements.txt）<br>
この 3 点が揃えば、誰でも・いつでも同じ結果を再現できる。
</div>
"""
    return "データ真正性とハッシュ検証", body


# ─────────────────────────────────────────────
# Section 5: 国際比較 — GDPR / PIPL / 個情法
# ─────────────────────────────────────────────
def sec_international() -> tuple[str, str]:
    print("[5] 国際法規制比較 ...")

    body = """
<p>位置情報・センサーデータを公開・AI 学習に使う際は、
<b>どの法制度が適用されるか</b>を把握する必要がある。
DoBoX は広島県の公的機関が公開したオープンデータのため「個人情報」には
直接該当しないが、二次利用で個人を特定できる加工データを作成した場合は注意が必要。</p>

<h3>主要法制度の比較</h3>
<table>
<thead><tr>
  <th>法制度</th><th>適用地域</th><th>位置情報規制</th>
  <th>AI・自動処理</th><th>違反時制裁</th>
</tr></thead>
<tbody>
<tr>
  <td><b>GDPR</b><br>EU 一般データ保護規則</td>
  <td>EU/EEA + EU 市民データを扱う全組織</td>
  <td>高リスクデータに準拠<br>同意または正当利益が必要</td>
  <td>自動的決定・プロファイリングへの異議権（22 条）</td>
  <td>最大 2,000 万 EUR または年間売上 4%</td>
</tr>
<tr>
  <td><b>PIPL</b><br>中国個人情報保護法</td>
  <td>中国国内 + 中国人データを扱う全組織</td>
  <td>「機微個人情報」<br>明示的同意必須</td>
  <td>AI 推薦への対抗権あり</td>
  <td>最大 5,000 万人民元または年間売上 5%</td>
</tr>
<tr>
  <td><b>個人情報保護法</b><br>日本</td>
  <td>日本国内</td>
  <td>単独位置情報は個人情報に非該当<br>（本人識別可能なら該当）</td>
  <td>自動処理への明示規制は限定的</td>
  <td>1 億円以下の罰金（法人）</td>
</tr>
</tbody></table>

<h3>AI 倫理ガイドライン（日本）</h3>
<table>
<thead><tr><th>文書</th><th>発行</th><th>要点</th></tr></thead>
<tbody>
<tr><td>人間中心の AI 社会原則</td><td>内閣府 2019</td><td>プライバシー確保・公平性・透明性・説明責任</td></tr>
<tr><td>AI 利活用ガイドライン</td><td>総務省 2019</td><td>利用者への説明・透明性・安全確保</td></tr>
<tr><td>EU AI 法（参考）</td><td>EU 2024 施行</td><td>リスク分類（禁止 / 高リスク / 低リスク）</td></tr>
</tbody></table>

<h3>DoBoX データ利用前チェックリスト</h3>
<ol>
<li>出典表記（広島県または提供組織名）を成果物に含めているか</li>
<li>加工データと原データの区別が明示されているか</li>
<li>位置情報を含む成果物で個人・物体が特定可能でないか（k≥3 以上を確認）</li>
<li>AI モデルの訓練データとして使う場合、空間偏在性のバイアス評価を行ったか</li>
<li>LiDAR 派生データを公開する場合、測量法上の許可要否を確認したか</li>
<li>ファイルハッシュを記録し、再現性を担保したか</li>
</ol>
"""
    return "国際法規制比較と AI 倫理ガイドライン", body


# ─────────────────────────────────────────────
# Section 6: 発展課題
# ─────────────────────────────────────────────
def sec_exercises() -> tuple[str, str]:
    body = """
<ol>
<li>カメラ位置データを 500 m グリッドで集約し、k=1 セルの割合が
    1 km グリッド時と比べてどう変化するか計算せよ。</li>
<li>差分プライバシーを実装し（ラプラスノイズ付加）、
    ノイズ量（ε）を変化させたときのカメラ密度マップの精度変化を可視化せよ。</li>
<li>DoBoX カタログ 551 件の Desc（説明文）を形態素解析し、
    頻出単語上位 30 語を WordCloud で表示せよ。</li>
<li>GDPR の「忘れられる権利（Right to Erasure）」は DoBoX オープンデータに適用可能か？
    理由とともに 200 字以内で論じよ。</li>
<li>EU AI 法のリスク分類に基づき、本プロジェクトで作成した AI モデル（例: L07 回帰、L08 PCA）
    を「禁止 / 高リスク / 低リスク / 最小リスク」に分類せよ。</li>
</ol>
"""
    return "発展課題", body


# ─────────────────────────────────────────────
# メイン
# ─────────────────────────────────────────────
sections = []
for fn in [sec_catalog, sec_spatial_bias, sec_reidentification,
           sec_integrity, sec_international, sec_exercises]:
    try:
        h2, body = fn()
        sections.append((h2, body))
    except Exception as e:
        import traceback
        sections.append((fn.__name__, f"<p>エラー: {e}</p><pre>{traceback.format_exc()}</pre>"))

html_out = render_lesson(
    num=16,
    title="X16: データ倫理・AI ガバナンス実習",
    tags=["X系", "横断研究", "データ倫理", "プライバシー", "ライセンス", "AI倫理", "GDPR", "再識別リスク"],
    time="60分",
    level="リテラシ基礎〜心得",
    data_label=(
        '<a href="https://hiroshima-dobox.jp/datasets/1279" target="_blank">カメラ情報(#1279)</a> × '
        '<a href="https://hiroshima-dobox.jp/datasets/42" target="_blank">避難所情報(#42)</a> × '
        'カタログ 551件'
    ),
    sections=sections,
    script_filename="lessons/X16_data_ethics_governance.py",
)
html_out = html_out.replace("<body>", '<body data-draft="1" data-stier="X">', 1)

out_path = LESSONS / "X16_data_ethics_governance.html"
out_path.write_text(html_out, encoding="utf-8")
print(f"\n[HTML] {out_path.name} ({len(html_out):,} bytes)")
print(f"=== DONE in {_time.time() - t0:.1f}s ===")
