0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GPT-6 AstraにPowerShellで集めたカクヨム統計を派生CSV化・全件検証させた

0
Posted at

以前、カクヨムのPV、通知、フォロワーなどをPowerShellで定期取得し、AIで分析できるようにする仕組みを作った。

前回の記事はこちら。

PowerShellでカクヨムのPV・通知・フォロワーなどを定期取得して、AIで分析できるようにした

その後、この仕組みでデータを蓄積していたところ、自作の週間ランキングが大きく動いた。

2026年9月7日にエッセイ・ノンフィクション週間ランキング531位でランクインし、翌9月8日には530位から157位まで上昇した。

そこで、

  • なぜ順位が上がったのか
  • PVは本当に増えているのか
  • レビューや応援、フォローの増加と時期が重なっているのか
  • どの話がいつ読まれたのか

を分析したくなった。

ただ、既存の統計CSVは「保存するためのデータ」としては十分でも、こうした分析を毎回行うには少し扱いにくい。

そこで今回は、既存データから分析用の派生統計を作る処理をGPT-6 Astraに任せてみた。

結果としてAstraは、単にCSVを作っただけではなく、

元データ確認
↓
派生統計の生成
↓
独立した検証スクリプトの作成
↓
全件再計算
↓
PASS / WARN / FAIL判定
↓
FAILの隔離
↓
仕様書・README・ログ作成
↓
Google Driveへ保存
↓
保存内容の再確認

までを約21分で行った。

この記事では、その実際の構成をまとめる。

既存データはすでにPowerShellで一次加工していた

まず重要なのは、今回AstraがカクヨムのHTMLを一から解析したわけではないという点である。

前回作った kakuyomu_stats.ps1 がすでに、

  1. カクヨムの各ページを取得する
  2. HTML原本を保存する
  3. HTMLから必要項目を抽出する
  4. CSVとして履歴を蓄積する

ところまで処理している。

全体はおおむね次のようになっている。

カクヨム
    ↓
kakuyomu_stats.ps1
    ↓
00_common/analytics/kakuyomu/
├─ raw/
├─ latest/
├─ history/
├─ state/
└─ logs/

それぞれの役割は次のとおり。

フォルダ 役割
raw 取得時点のHTML原本
latest 最新取得時点の構造化データ
history 時系列分析に必要な履歴
state 最終成功時刻などの実行状態
logs 取得・解析処理のログ

latesthistory は、raw HTMLをそのまま保存したものではない。

kakuyomu_stats.ps1 がHTMLを解析し、すでにCSVへ一次加工している。

たとえば history には次のようなデータがある。

history/
├─ work_stats.csv
├─ episode_stats_202608.csv
├─ episode_stats_202609.csv
├─ daily_pv.csv
├─ notifications.csv
├─ reactions.csv
├─ work_reviews.csv
├─ followers.csv
├─ work_followers.csv
├─ relationship_events.csv
└─ runs.csv

なお、すべてのデータを毎回フルスナップショットとして保存しているわけではない。

フォロー関係などは、最新状態に加えて relationship_events.csv に増減イベントを記録する方式へ変更している。

つまり今回Astraへ渡した時点で、

raw HTML
    ↓ PowerShellによる一次加工
latest / history

まではすでに終わっていた。

それでも分析にはもう一段加工が必要だった

たとえば各話PVは累積値として保存されている。

第1話  53PV
第2話  41PV
第3話  39PV

これは現在の状態を見るには十分である。

しかし知りたいのが、

前回取得してから今回取得するまでに、どの話が何PV増えたか

なら、取得時点ごとの差を毎回計算する必要がある。

ランキングも同様だった。

ランキング通知自体は notifications.csv に入っているが、応援、レビュー、フォローなどの通知と混在している。

順位だけ分析するなら、

発生日時
作品
ジャンル
ランキング種別
変更前順位
変更後順位

のような形へ正規化した方が扱いやすい。

そこで、既存統計とは別に derived/ を作り、元データから機械的に再生成できる派生統計だけを置くことにした。

derivedにはAIの推測を入れない

ここは意図的に分離した。

たとえば、

このPVは新規読者だろう

このレビューがランキング上昇の原因だろう

この話は人気回だろう

といったAIによる解釈は derived には入れない。

保存するのは、元データから同じ計算をすれば同じ結果になるものだけにした。

今回作ることにしたのは6種類。

ファイル 内容
episode_pv_delta.csv 各話PVの取得時点間差分
work_daily_summary.csv 日別PVと7日集計
ranking_history.csv ランキング通知の正規化
follower_growth.csv 作者・作品フォローの増減
reaction_daily_summary.csv 日ごとの応援・レビューなど
episode_performance.csv 最新各話PVの作品内比較

データの流れはこうなる。

カクヨム
    ↓
kakuyomu_stats.ps1
    ↓
raw
latest / history
state / logs
    ↓
GPT-6 Astra
    ↓
derived
    ↓
ChatGPTによる分析・仮説

実際にはAstraはrawを使わなかった

当初は、raw HTMLを保存してきたこともあり、必要ならrawまで戻って新しい項目を取り出せばよいと考えていた。

そこでAstraへの依頼にも、

不明点や例外を見つけた場合は、まず latesthistorystatelogs、必要に応じて raw を調べる

と書いた。

ところが実際には、Astraはrawを直接使わなかった。

今回必要だった各話PVの差分、日別PV、ランキング通知、フォロー変化、レビュー・コメント、最新各話PVは、すでに historylatest に存在していたからである。

実際に生成された generate_derived.py の入力定義を見ると分かりやすい。

SOURCES = {
    'episode_pv_delta': [
        'history/episode_stats_*.csv',
        'history/work_stats.csv'
    ],
    'work_daily_summary': [
        'history/daily_pv.csv',
        'latest/daily_pv_latest.csv'
    ],
    'ranking_history': [
        'history/notifications.csv',
        'latest/notifications_latest.csv',
        'history/work_stats.csv',
        'latest/my_works_latest.csv'
    ],
    'follower_growth': [
        'history/followers.csv',
        'latest/followers_latest.csv',
        'history/work_followers.csv',
        'latest/work_followers_latest.csv',
        'history/relationship_events.csv',
        'latest/relationship_events_latest.csv'
    ],
    'reaction_daily_summary': [
        'history/notifications.csv',
        'latest/notifications_latest.csv',
        'history/reactions.csv',
        'latest/reactions_latest.csv',
        'history/work_reviews.csv',
        'latest/work_reviews_latest.csv'
    ],
    'episode_performance': [
        'latest/episode_stats_latest.csv',
        'latest/work_stats_latest.csv'
    ]
}

カクヨム掲載版ではPythonコードのインデントを全角空白へ置き換えていたが、Qiitaでは通常の半角スペースによるインデントへ戻している。

rawは、既存CSVに存在しない項目が必要になった場合や、CSVへの抽出結果がおかしい場合に戻るための原本として残った。

実際にGPT-6 Astraへ渡したプロンプト

今回の処理では、単に「統計を作って」と依頼したわけではない。

何を派生統計として保存するか、どこまでをAIの解釈から切り離すか、どのように検証するか、FAILをどう扱うかまで指定した。

長いが、再現性という意味では実際に使用したプロンプトをそのまま載せておく。

実際に使用したプロンプト全文
# カクヨム derived 統計作成・検証プロンプト

Google Drive の `00_common/analytics/kakuyomu` を対象としてください。

最初に以下を確認してください。

- README.md
- カクヨム統計分析ルール_v1.0
- latest
- history
- state
- logs
- derived

今回の目的は、既存のカクヨム統計データから、AIによる解釈を含まない機械的な派生統計データを作成し、元データとの整合性を検証したうえで `derived` に保存することです。

## Work / Astra 実行方針

この作業は、複数ファイルを横断して生成・検証・記録まで行う一連の実行作業です。以下の方針で、途中の確認待ちをできるだけ発生させず、自律的に最後まで進めてください。

- 不明点や例外を見つけた場合は、まず `latest`、`history`、`state`、`logs`、必要に応じて `raw` を調べ、元データから判断可能か確認してください。
- 判断が必要な場合は、既存データを破壊せず、再現可能性と検証可能性を優先した処理方針を選び、その判断根拠を `DERIVED_SPEC.md` または検証ログに記録してください。
- 作業途中で一部の生成・検証が失敗しても、そこで全体を終了せず、原因を切り分けて修正を試み、影響を受けない作業は継続してください。
- 必要であれば derived 生成・検証のための補助スクリプトを作成して構いません。その場合は保存場所、役割、入力元、出力先、再実行方法を `DERIVED_SPEC.md` に記録してください。元データ取得処理の既存スクリプトは、必要性が明確でない限り変更しないでください。
- 既存ファイルを上書きする場合は、同じ元データから再生成できる derived、仕様書、検証ログなど、この作業の管理対象に限定してください。`latest`、`history`、`raw` の元データは変更しないでください。
- データ不足や仕様上の限界で完全な検証が不可能な場合は、推測で埋めず WARN として扱い、検証できなかった範囲と理由を明記してください。
- ユーザーへの途中確認がなくても安全に進められる範囲では、そのまま作業を継続してください。権限不足や元データ破壊につながる操作など、安全に解決できない場合だけ未解決事項として残してください。
- 最終報告では、作成物、保存先、各検証結果、WARN / FAIL、未解決事項、仕様上確定した事項、次回の再生成方法をまとめてください。

## 基本原則

- derived に保存する値は、原則として `latest` または `history` のCSVから同じ計算手順で再生成できる値だけにしてください。
- 新規読者・継続読者などの推定分類、流入元の推定、告知効果の推定、読者心理、因果関係の推定、その他AIによる解釈は derived に含めないでください。
- 元データそのものは変更・削除しないでください。
- derived の値に異常があった場合は derived 側を修正し、元の正常な履歴データを書き換えないでください。
- 生成ロジックと検証ロジックは、誰が再実行しても同じ結果になるよう明文化してください。

## derived の仕様文書

`derived` 配下に以下を用意してください。

- `README.md`
- `DERIVED_SPEC.md`
- `validation/`

README.md には次を記録してください。

- derived の目的
- history / latest / raw との関係
- derived は元データから再生成可能であること
- AI推測を含まないこと
- 分析時の使用順序 `latest → derived → history → raw`

DERIVED_SPEC.md には、各CSVごとに次を記録してください。

- 使用元ファイル
- 列定義
- 主キーまたは一意性判定方法
- 計算式
- 重複排除方法
- 欠損値の扱い
- 日時の扱い
- 検証方法
- 既知の制約
- 生成ロジックを実装しているスクリプト名とバージョン(存在する場合)

生成ロジックを変更した場合は DERIVED_SPEC.md も同時に更新してください。

## 作成する派生統計

### 1. episode_pv_delta.csv

各話について、連続する取得時点間のPV差分を保存してください。

最低限の列:

- RetrievedAt
- PreviousRetrievedAt
- WorkId
- WorkKey
- WorkName
- EpisodeOrder
- EpisodeId
- Title
- PVBefore
- PVAfter
- PVDelta

計算式:

`PVDelta = PVAfter - PVBefore`

EpisodeId を同一話判定の主キーとして使用してください。話数やタイトルが変更された場合でも、EpisodeId が同じなら同一話として扱ってください。

### 2. work_daily_summary.csv

`daily_pv` を元に、作品ごとの日次統計を作成してください。

最低限の列:

- Date
- WorkId
- WorkKey
- WorkName
- PV
- PV7Days
- PVPrevious7Days
- PV7DayAverage
- WoWDelta
- WoWRate
- DaysInCurrentWindow
- DaysInPreviousWindow

計算式:

- `PV7Days = 当日を含む直近7日間のPV合計`
- `PVPrevious7Days = その直前7日間のPV合計`
- `PV7DayAverage = PV7Days / DaysInCurrentWindow`
- `WoWDelta = PV7Days - PVPrevious7Days`
- `WoWRate = WoWDelta / PVPrevious7Days`

比較元が0の場合は WoWRate を空欄またはNULL相当値にしてください。7日分揃わない期間は実際の有効日数を残してください。

### 3. ranking_history.csv

`notifications` からランキング関連通知だけを抽出・正規化してください。

最低限の列:

- OccurredAt
- RetrievedAt
- WorkId または WorkKey
- WorkName
- Genre
- RankingType
- PreviousRank
- CurrentRank
- EventType
- SourceUrl

EventType は、元通知から機械的に判断できる範囲で以下などに正規化してください。

- rank_in
- rank_up
- rank_down
- other

ランキング通知が存在した時点だけの観測値であり、毎日の完全な順位履歴ではないことを仕様に明記してください。

### 4. follower_growth.csv

作者フォローおよび作品フォローについて、取得履歴、relationship_events、notifications 等から機械的に確認可能な増減を集計してください。

最低限の列:

- Date
- TargetType
- WorkId
- WorkKey
- Followers
- NewFollowers
- LostFollowers
- NetChange

TargetType は最低限 `author` と `work` を区別してください。推定によるフォロー解除判定は行わず、元データ上で確認できる場合だけ記録してください。

### 5. reaction_daily_summary.csv

日ごと・作品ごとの反応を集計してください。

最低限の列:

- Date
- WorkId
- WorkKey
- WorkName
- EpisodeCheers
- Comments
- StarReviews
- TextReviews
- WorkFollows
- AuthorFollows

同一イベントが `notifications` と `reactions` の両方に存在する場合、二重計上しないでください。イベントID、ReactionId、ReviewId、CommentId、URLなど、元データ上で利用できる一意キーを優先してください。

### 6. episode_performance.csv

最新の各話統計から、話ごとの比較用データを作成してください。

最低限の列:

- WorkId
- WorkKey
- WorkName
- EpisodeOrder
- EpisodeId
- Title
- CharacterCount
- PV
- PVRank
- PreviousEpisodePV
- RatioFromPrevious
- WorkAveragePV
- VsWorkAverage

評価コメントや「人気話」「失敗話」などのAI解釈は入れないでください。

## 検証手順

### 1. 元データ取得状態の確認

生成前に以下を確認してください。

- `state/last_success.txt` の最終成功日時
- `history/runs.csv` の直近実行結果
- latest と history の取得日時の整合性
- 直近取得が失敗または部分取得の場合、そのデータを無条件に正本として使用しない

### 2. 構造検証

各 derived CSV について以下を確認してください。

- 必須列が存在する
- 主キー相当の項目が欠損していない
- 日付・日時形式が統一されている
- 数値列に不正な文字列がない
- 不正な重複行がない
- 本来存在しない負数がない
- 元データより未来の日付がない
- WorkId、WorkKey、EpisodeId の対応が元データと矛盾しない

PVDelta が負数になる場合は自動的に誤りと決めつけず、エピソード削除・統計リセット・取得仕様変更など元データ側の事情を確認し、理由を説明できない場合のみ異常として扱ってください。

### 3. 再計算検証

derived の値をそのまま信用せず、元データから別途再計算して一致を確認してください。

初回作成時は、可能な限り全件検証してください。

特に以下は全件照合してください。

- episode_pv_delta
- work_daily_summary
- ranking_history

例:episode_pv_delta は対象行ごとに、元となる2取得時点の episode_stats を直接確認し、`PVAfter - PVBefore = PVDelta` を再計算してください。

### 4. 横断検証

可能な範囲で以下を照合してください。

- 同一取得区間における各話PV差分合計と作品TotalPV差分
- reaction_daily_summary と reactions / notifications
- follower_growth と followers / work_followers / relationship_events
- ranking_history の WorkName と work_stats / my_works の作品対応

完全一致しない仕様上の理由がある場合は、異常扱いせず理由を記録してください。

### 5. 件数・境界検証

- 元データの対象期間と derived の対象期間が一致しているか
- 月跨ぎで episode_stats_YYYYMM.csv が切り替わる場合に連続取得点が欠落していないか
- 同一 RetrievedAt の重複取得や再実行がある場合の扱いが一貫しているか
- 当日途中の daily_pv を確定日として扱っていないか

## 検証ログ

検証結果は `derived/validation/derived_validation_YYYYMMDD_HHMMSS.log` の形式で保存してください。

最低限、以下を記録してください。

- 検証日時
- 対象ファイル
- 使用した元データ
- 元データ取得日時
- 行数
- 構造検証結果
- 重複件数
- 再計算検証件数
- 一致件数
- 不一致件数
- 横断検証結果
- 不一致内容と原因
- 検証結果 PASS / WARN / FAIL

PASS は、仕様どおり生成され、検証上の不一致がない状態とします。

WARN は、仕様上説明可能な差異や、元データ不足により一部検証不能だが利用可能な状態とします。

FAIL は、計算不一致、重複、欠損、元データとの対応不明など、分析に使用すべきでない状態とします。

FAIL のファイルは正本として使用しないでください。

## 作業順

最初から全種類を一括で完成させず、以下の順に進めてください。

各段階は検証のために分けていますが、PASS または原因を説明できる WARN になった段階でユーザー確認を待たず次へ進んでください。今回の実行内で、可能な限り第6項目まで生成・検証・記録を完了してください。ある段階で FAIL が解消できない場合でも、元データを破壊しない範囲で原因を記録し、その FAIL に依存しない後続作業は継続してください。

第1段階:

1. `episode_pv_delta.csv` を作成
2. 全件検証
3. DERIVED_SPEC.md に仕様を記録
4. 検証ログを保存
5. PASS または説明可能な WARN になるまで生成仕様を修正

第2段階:

1. `work_daily_summary.csv` を作成
2. 全件検証
3. 仕様・検証ログを更新

第3段階:

1. `ranking_history.csv` を作成
2. 全件検証
3. 仕様・検証ログを更新

ここまでの生成・検証方式が安定した後、以下を同じ方式で追加してください。

- follower_growth.csv
- reaction_daily_summary.csv
- episode_performance.csv

## 完了時の確認

作業終了前に `derived` を再度一覧し、以下を確認してください。

- 指定したCSVが存在する
- README.md と DERIVED_SPEC.md が存在する
- validation 配下に今回の検証ログがある
- derived の各CSVが仕様書の列定義と一致する
- 検証結果が PASS または内容を説明できる WARN である
- FAIL のファイルが分析用正本として残されていない

最後に、以下を報告してください。

1. 作成したファイル
2. 各ファイルの行数
3. 使用元データ
4. 検証結果
5. WARN / FAIL があれば内容と原因
6. 仕様書に記録した生成方法
7. 次回以降の再生成方法

作成したという報告だけで完了とせず、必ず保存先を再読して、実際に指定どおりのファイルと内容が存在することまで確認してください。

特に重要視したのが「検証」である。

episode_pv_delta.csv なら、

PVDelta = PVAfter - PVBefore

という計算結果を保存する。

しかし生成プログラム自身が「正しく計算しました」と言うだけでは確認にならない。

そこで、derivedの値をそのまま信用せず、元データから別途再計算して一致を確認することを明示した。

Astraは生成処理とは別にvalidatorを作った

ここは今回最も興味深かった部分だった。

Astraは generate_derived.py だけではなく、validate_derived.py も作った。

そして生成スクリプトから、別プロセスとしてvalidatorを呼んでいる。

概要を抜き出すと次のようになっている。

proc = subprocess.run(
    [
        sys.executable,
        str(Path(__file__).with_name('validate_derived.py')),
        '--root', str(root),
        '--out', str(out),
        '--name', name,
        '--file', str(candidate)
    ],
    capture_output=True,
    text=True,
    encoding='utf-8'
)

result = json.loads(proc.stdout)

if result['result'] == 'FAIL':
    failed = out / 'validation' / 'failed'
    failed.mkdir(exist_ok=True)
    candidate.replace(
        failed / (log.stem + '_' + name + '.csv')
    )
else:
    candidate.replace(out / (name + '.csv'))

つまり、

生成
↓
candidate CSV
↓
別validatorで検証
↓
PASS / WARN
    → derivedへ採用

FAIL
    → validation/failedへ隔離

となっている。

「検証してください」という自然言語の指示を、独立した検証プログラムを作り、FAILなら正本へ入れないという運用まで具体化していた。

元データ自体の状態もチェックする

派生CSVの計算だけ正しくても、入力元がおかしければ意味がない。

そこで generate_derived.py には source_gate() も作られた。

一部を抜粋する。

def source_gate(root):
    runs = load(root, 'history/runs.csv')

    success = max(
        (r for r in runs if r['Result'] == 'SUCCESS'),
        key=lambda r: stamp(r['FinishedAt'])
    )

    last = stamp(
        (root / 'state/last_success.txt')
        .read_text(encoding='utf-8-sig')
        .strip()
    )

    if abs(
        (last - stamp(success['FinishedAt'])).total_seconds()
    ) >= 1:
        raise ValueError('last_success/runs mismatch')

さらに latest/work_stats_latest.csv が最新成功実行の範囲内で取得されたものか確認し、history/work_stats.csv に同じ行が存在するかも照合している。

各話データについても latesthistory の一致を確認する。

派生統計を作る前に、そもそも今回使うlatestは正常な取得結果なのかを確認する仕組みになった。

6種類、合計5,812行を再計算した

最終的に生成されたCSVは次のとおりだった。

CSV 行数 結果
episode_pv_delta.csv 5,377 WARN
work_daily_summary.csv 176 WARN
ranking_history.csv 7 WARN
follower_growth.csv 46 WARN
reaction_daily_summary.csv 36 WARN
episode_performance.csv 170 PASS

合計は5,812行。

これらを元CSVから再計算し、

計算不一致  0
出力重複    0
FAIL        0

となった。

さらに、

月を跨ぐ各話PV差分 164行
作品TotalPVとの差分照合 130区間

についても一致した。

WARNは「失敗」という意味ではない

6種類中5種類がWARNになっている。

ただし、これは計算が間違っているという意味ではない。

今回の仕様では、

PASS
仕様上の不一致がない

WARN
元データ上の制約などはあるが、
理由が説明でき利用可能

FAIL
計算不一致や対応不能があり
分析用正本として使用しない

としている。

たとえば ranking_history.csv はWARNになった。

理由は単純で、元データがカクヨムから届いた「ランキング通知」だからである。

531位でランクイン
530位 → 157位

のような通知は保存されている。

しかし通知が来なかった日の順位までは分からない。

そのためこれは「ランキング通知が存在した時点だけの観測値」であり、「完全な日次順位履歴」ではない。

計算ミスではないためFAILにはしないが、データ上の制約としてWARNを付ける。

日別PVにも似た問題がある。

取得当日のPVはまだ1日が終わっていないため途中値である。

そこで IsPartialDayDaysInCurrentWindowDaysInPreviousWindow などを持たせ、途中値や7日分揃っていない期間を区別できるようにした。

予約中の話を0PVの公開話として扱わない

episode_performance.csv でも少し工夫が入った。

元データには、公開済みの話だけではなく予約投稿中の話も含まれていた。

そのため全170話をそのまま平均すると、予約中の0PVが大量に入ってしまう。

Astraは、

public = sorted(
    [r for r in group if r['Status'] == '公開済'],
    key=lambda r: (
        num(r['EpisodeOrder']),
        r['EpisodeId']
    )
)

として、比較対象だけを公開済みに限定した。

結果として、

全データ      170話
予約中         32話
比較対象      138話

となった。

予約話自体は削除しない。

CSVには残すが、PVRankPreviousEpisodePVRatioFromPreviousWorkAveragePVVsWorkAverage などの比較項目を空欄にする。

「データを捨てる」のではなく、元データは保持しつつ、比較母集団だけを適切に限定するという処理になっている。

反応データの重複も処理した

カクヨムでは、同じ出来事が複数種類の取得データへ現れることがある。

たとえばレビューについて、notifications にも通知があり、reactions にも詳細データがある。

そのまま両方数えると二重計上になる。

そこで、CommentIdReviewIdReactionIdURL など、利用できる一意キーを優先した。

詳細なID付きデータが存在する場合には、対応する要約通知を加算しない。

実際に今回、対応する要約通知11件について重複加算を防いでいる。

一方、応援やフォロー通知には、複数人をまとめた表示が存在する。

この場合に人数を推定してはいない。

reaction_daily_summary.csv では通知レコード1件として扱う。

つまり、EpisodeCheers = 応援人数 ではなく、EpisodeCheers = 観測できた応援通知レコード数 である。

データから分からない部分を推測で埋めなかった。

途中で保存に失敗したが、自分で処理を変更した

実行途中には問題も起きている。

Astraの作業ログには、

補助スクリプトは保存先のファイル形式の扱いで本文が空表示になった

という記録がある。

つまり一度は保存に失敗している。

しかしそこでユーザーへ「ファイルが空になりました。どうしますか?」とは聞いてこなかった。

ファイル形式の問題だと判断し、

別形式で保存
↓
再読
↓
内容が読めることを確認

まで自分で行った。

最終的には、CSV、README.md、DERIVED_SPEC.md、generate_derived.py、validate_derived.py、検証ログを保存し、Google Drive上の保存物を再度読み直して一致を確認している。

作業時間は20分55秒だった。

次回はAstraを使わなくても再生成できる

今回作られたものは、一回限りのCSVではない。

再生成用スクリプトも残っている。

python derived/generate_derived.py --root .

Python 3.10以上で、追加ライブラリは不要。

同じ入力データなら、同じ派生CSVを生成する。

さらに、README.md、DERIVED_SPEC.md、validation/ も残した。

生成時には入力ファイルについて、SHA256、バイト数、行数、取得日時範囲をmanifestとして検証ログへ記録する。

つまりAstraが作ったのは「今回の統計結果」だけではなく、「次回以降も同じ処理を再実行できる小さなデータ処理システム」だった。

派生統計ができた後の分析にはAstraを使わなかった

ここでもう1つ試した。

これだけAstraで仕組みを作ったので、出来上がった統計の分析もAstraにさせた方がよいのかと通常のChatGPTに聞いた。

すると、派生統計がすでに整理・検証済みなら、この程度の分析なら通常Chatで十分という回答だった。

そこで実際に通常のChatGPTへ、何が起こったか、仮説、今後の施策を分析させた。

対象は530位から157位へ上昇した時期である。

7日PVはむしろ前週より減っていた

派生統計を使って確認すると、少し意外な結果になった。

ランキングは、

9月7日  531位で再ランクイン
9月8日  530位 → 157位

と急上昇した。

ところが日別PVは、

9月5日   5PV
9月6日  29PV
9月7日  14PV
9月8日   2PV(取得時点の途中値)

だった。

9月7日時点で、

直近7日       79PV
その前7日    104PV
差           -25PV

となっていた。

約24%減である。

つまり今回については、PVが大幅に増えたためランキングが157位まで上昇したとは説明しにくい。

一方で、9月6〜7日には応援、★レビュー、文章付きレビュー、作品フォロー、作者フォローが集中していた。

各話PVを見ると、特定の最新話だけが大量に読まれたわけでもなく、第1話から過去の記録まで広くPVが付いていた。

そこからChatGPTは、PV量そのものより、反応を伴った読まれ方や作者への接触が増えた時期とランキング上昇が重なっているという仮説を出した。

もちろん、カクヨムのランキング算定ロジックは公開データから分からない。

したがって因果関係までは証明できない。

ここで重要なのは、derived にはこの仮説を保存していないということである。

derived
    ↓
事実として再計算可能なデータだけ

ChatGPT
    ↓
そのデータを読んで仮説を立てる

と役割を分けている。

Astraと通常ChatGPTの役割を分ける

今回試した範囲では、役割分担はかなり明確になった。

Astra / Work向き

複数ファイルを横断する
仕様を決める
コードを書く
実行する
エラーを修正する
全件検証する
ファイルへ保存する
再実行可能な形にする

通常Chat向き

出来上がった統計を読む
何が起きたか整理する
複数の仮説を出す
仮説に反するデータを確認する
次の施策を会話しながら考える

通常Chatでは、「それは違うと思う」「この仮説は弱い」「このデータも確認したい」と対話しながら修正できる。

一方でAstraには、最終的な成果物が決まっている長い作業をまとめて渡す方が向いていた。

今回Astraで一番違うと感じたところ

今回やらせたのは、高度な統計モデルの構築ではない。

やったことを分解すると、

データ確認
仕様化
Python実装
CSV生成
例外確認
独立検証
全件再計算
ファイル操作
ログ作成
仕様書作成
再実行方法の作成

である。

そのため、今回の結果だけからAstraは従来モデルより統計学的に圧倒的に賢いとは言えない。

一方で明確だったのは、長い仕事を複数工程へ分解し、途中で問題が起きても修正し、最後の成果物まで持っていくところだった。

今回の処理を一行にすると、

調査
→ 実装
→ 実行
→ エラー確認
→ 修正
→ 再実行
→ 検証
→ 保存
→ 保存物再確認

になる。

しかも「検証してください」という指示を、validatorを別に作る、元CSVを読み直す、全件再計算する、FAILなら正本から隔離するという仕組みにまで落とし込んだ。

個人的には、ここが今回Astraを使って最も違いを感じた部分だった。

まとめ

今回作った構成は最終的にこうなった。

カクヨム
    ↓
kakuyomu_stats.ps1
    ↓
raw HTML
    ↓
latest / history
    ↓
generate_derived.py
    ↓
candidate CSV
    ↓
validate_derived.py
    ↓
PASS / WARNのみ
    ↓
derived
    ↓
通常ChatGPT
    ↓
分析・仮説・施策

最初に作ったPowerShellは「データを取るための仕組み」だった。

今回Astraが追加したのは、取ったデータを、再現可能・検証可能な分析用データへ変換する仕組みになる。

そして分析用データまで整ってしまえば、その後の仮説検討には必ずしもAstraは必要なかった。

今回一番収穫だったのは、AIに統計そのものを解釈させたことより、AIによる解釈を始める前に、機械的に再生成できるデータ層と独立した検証工程を作れたことだった。

この仕組みを実際に使っている作品

今回紹介した仕組みは、以下のカクヨム作品の制作・分析で実際に使用しています。

アークリーチャーズ

頭のない異形が歩く世界で、頭部を外して戦う汎用人型兵器カイを中心に描くSF小説です。
AIを使いながら、設定管理・推敲・バックアップ・読者動向の分析まで含めて制作しています。

コンセプトアート.png

AIで小説を書くにはどうすればいい?

『アークリーチャーズ』をAIで制作する中で、実際に試した方法、失敗、ツールの変更、制作環境の改善などを記録しているドキュメンタリーです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?