"""目録の集計(`tiasdata stats`.SPEC_03「CLI」).
- 種類・所見の値ごとの件数を返す
- 対応表を開かない(SPEC_02「対応表」: 対応表を開くのは取り込みのときだけ)
"""
import sqlite3
from contextlib import closing
from conftest import ImportEnv
from tiasdata.cli import main
from tiasdata.config import load_config
from tiasdata.importers import run_import
from tiasdata.stats import collect_stats
def _open(env: ImportEnv) -> sqlite3.Connection:
return sqlite3.connect(env.catalog_root / "catalog.sqlite")
def test_empty_catalog(import_env: ImportEnv) -> None:
assert main(["init"]) == 0
with closing(_open(import_env)) as conn:
stats = collect_stats(conn)
assert stats.subjects == 0
assert stats.captures == 0
assert stats.subjects_with_multiple_captures == 0
assert set(stats.assets_by_kind.values()) == {0}
assert "tongue_crop_masked" in stats.assets_by_kind
assert stats.annotations == {}
assert stats.superseded_annotations == 0
def test_counts_after_import(import_env: ImportEnv) -> None:
assert main(["init"]) == 0
run_import(load_config(import_env.config_path), import_env.source_name)
with closing(_open(import_env)) as conn:
stats = collect_stats(conn)
assert stats.subjects == 7
assert stats.captures == 18
# ダミーの 7 人はみな 2 枚以上
assert stats.subjects_with_multiple_captures == 7
assert stats.assets_by_kind == {
"color_chart": 0,
"tongue_crop_masked": 18,
"tongue_mask": 0,
"tongue_raw": 0,
}
tongue = stats.annotations["tongue_color"]
assert tongue.total == 18
assert tongue.by_field["label"] == {
"dark_red": 4,
"light_red": 2,
"pale": 4,
"purple": 4,
"red": 4,
}
assert tongue.by_field["severity"] == {"mild": 8, "normal": 2, "strong": 8}
# 9 フォルダ(ラベル×程度の組み合わせ)ごとに 2 枚
assert len(tongue.by_value) == 9
assert set(tongue.by_value.values()) == {2}
assert stats.superseded_annotations == 0
def test_stats_does_not_need_identity_db(import_env: ImportEnv) -> None:
assert main(["init"]) == 0
run_import(load_config(import_env.config_path), import_env.source_name)
import_env.identity_path.unlink()
assert main(["stats"]) == 0
assert not import_env.identity_path.exists()
def test_reviewed_images_are_counted_with_marks(import_env: ImportEnv) -> None:
# 2026-10-08 の仕様変更: 要確認の番号も登録し,撮影に要確認の印を付ける
from conftest import write_reviews
write_reviews(import_env.reviews_dir, "dummy_tongue", [1, 2]) # light_red の 2 枚
assert main(["init"]) == 0
run_import(load_config(import_env.config_path), import_env.source_name)
with closing(_open(import_env)) as conn:
stats = collect_stats(conn)
assert stats.captures == 18
assert stats.review_marks_on_captures == 2
assert stats.review_marks_on_files == 0
assert stats.assets_without_capture == 0
def _share_first_image(env: ImportEnv, number: int) -> None:
# 番号 1 と同じ中身を同じフォルダ(同じラベル)に置き,対応表に行を足す
from conftest import write_patient_map
first = env.source.images[0]
(first.path.parent / f"{number}.png").write_bytes(first.path.read_bytes())
rows = [(n, 1, key) for n, key in sorted(env.patient_map.patient_keys.items())]
write_patient_map(env.patient_map.path, [*rows, (number, 1, "990088")])
def test_unlinked_files_and_review_marks_are_counted(import_env: ImportEnv) -> None:
# 2026-10-08: 撮影にひも付かないファイルの件数,review の印の件数(撮影・ファイル).
# review は所見の種類ごとの内訳に入れない
from conftest import write_reviews
_share_first_image(import_env, 19)
write_reviews(import_env.reviews_dir, "dummy_tongue", [1, 5, 19])
assert main(["init"]) == 0
run_import(load_config(import_env.config_path), import_env.source_name)
with closing(_open(import_env)) as conn:
stats = collect_stats(conn)
assert stats.captures == 17
assert stats.assets_by_kind["tongue_crop_masked"] == 18
assert stats.assets_without_capture == 1
assert stats.review_marks_on_captures == 1
assert stats.review_marks_on_files == 1
assert set(stats.annotations) == {"tongue_color"}
# 撮影 17 件と撮影にひも付かないファイル 1 件の舌色
assert stats.annotations["tongue_color"].total == 18
def test_superseded_review_mark_is_not_counted(import_env: ImportEnv) -> None:
from conftest import write_reviews
write_reviews(import_env.reviews_dir, "dummy_tongue", [1, 2])
assert main(["init"]) == 0
run_import(load_config(import_env.config_path), import_env.source_name)
with closing(_open(import_env)) as conn:
ids = [
i
for (i,) in conn.execute(
"SELECT annotation_id FROM annotations WHERE kind = 'review' ORDER BY annotation_id"
)
]
conn.execute(
"UPDATE annotations SET superseded_by = ? WHERE annotation_id = ?", (ids[1], ids[0])
)
conn.commit()
stats = collect_stats(conn)
assert stats.review_marks_on_captures == 1
assert stats.review_marks_on_files == 0
assert stats.superseded_annotations == 1
def test_empty_catalog_has_no_unlinked_or_marks(import_env: ImportEnv) -> None:
assert main(["init"]) == 0
with closing(_open(import_env)) as conn:
stats = collect_stats(conn)
assert stats.assets_without_capture == 0
assert stats.review_marks_on_captures == 0
assert stats.review_marks_on_files == 0