無償データで72時間降雨予測を組み立てる:Step 2 - GFS・IFS・AIFSを110ファイル保存して検証する
はじめに
前回のStep 1では、安倍川流域周辺を対象に、RISH、気象庁、NOAA、ECMWF、国土地理院の取得先と利用条件を整理しました。
この段階では、外部データを一つも取得していません。先に対象範囲、ファイル名、予報初期時刻、予報時間、格子、変数、保存先をYAMLへ記録し、取得前の台帳を作りました。
Step 2では、そこから一歩進めて、実際の数値予報データを取得します。
今回扱うのは次の3つのデータセットです。
- NOAA GFS 0.25度
- ECMWF IFS Open Data
- ECMWF AIFS Single
最初は1ファイルずつsmoke取得し、通信経路、GRIB2、CDOによる切り出し、sidecarの保存を確認しました。その後、予報初期時刻を2026081418へ固定し、GFS 72件、IFS 25件、AIFS 13件、合計110件を取得しています。
ここで大事なのは、「ファイルが取れた」だけで終わらせないことでした。
気象データは、同じようなファイル名が並んでいても、予報初期時刻が混ざっていたり、予報時間が一つ欠けていたり、HTMLのエラーページをGRIB2として保存していたりすることがあります。後段の処理が動いてしまうだけに、気づくのが遅れます。
そこでStep 2では、次のところまでを一つの処理にしました。
最新ランの確認
↓
1件だけsmoke取得
↓
予報初期時刻を固定
↓
72時間分をデータセットごとに取得
↓
sidecar・SHA-256・GRIB2を検査
↓
予報時間の欠損を確認
↓
取得台帳をDuckDBへ作成
雨量の単位変換、積算値から区間雨量への変換、GFS・IFS・AIFSの時間軸統一は、次のStep 3で扱います。
本文中のデータセット仕様とリンクは、2026年8月15日に確認した内容です。配布仕様は変わることがあるため、実行前に公式ページも確認してください。
今回の到達点
今回の実行では、次の予報ランを使いました。
run key : 2026081418
UTC : 2026-08-14 18:00
JST : 2026-08-15 03:00
終端 : 2026-08-18 03:00 JST
取得したファイル数です。
| データセット | native lead | GRIB2 | sidecar |
|---|---|---|---|
| NOAA GFS 0.25度 |
f001~f072、1時間間隔 |
72 | 72 |
| ECMWF IFS | 0, 3, ..., 72 |
25 | 25 |
| ECMWF AIFS Single | 0, 6, ..., 72 |
13 | 13 |
| 合計 | - | 110 | 110 |
各にはrun_manifest.jsonも作成します。
GRIB2 110件
sidecar 110件
run manifest 3件
残存.part 0件
最終行が次になれば、Step 2の本取得は完了です。
ACQUISITION_72H=PASS
Step 2で作ったもの
プロジェクト名は次のとおりです。
RainfallForecast72h_FreeData_Step2_r005
参考のために、ZIPファイルを置いておきますので、ダウンロードして活用してください。
72時間本取得を正式な運用手順として追加しました。
主な追加点です。
-
scripts/run_72h_acquisition.shをプロジェクトへ組み込んだ - GFS、IFS、AIFSを並列にせず順番に取得する
- 各データセットの取得直後に完全検証する
- 最後に3データセットをまとめて検証する
- sidecarからDuckDB取得台帳を再構築する
- run単位のGRIB2、sidecar、manifestを集計し、
data/全体の.partを確認する - 期待件数をシェルへ固定せず、YAMLの
native_leadsから計算する - 本取得ログをrun別フォルダーへ保存する
- Pythonソースとテストのdocstring検査を継続する
処理の流れは次のとおりです。
取得対象
対象範囲
Step 1と同じ、安倍川流域周辺の暫定矩形です。
west = 137.75
south = 34.55
east = 138.90
north = 35.85
CRS = EPSG:4326
まだ公式な安倍川流域界ではありません。
Step 2の目的は、予報データの取得・保存・検証なので、この矩形を使います。流域平均雨量を計算する前に、DEMまたは流域界データを使って安倍川流域ポリゴンへ置き換えます。
GFS
NOAA/NCEPのGFS 0.25度データセットは、gfs.tCCz.pgrb2.0p25.fFFFというファイル名で公開されています。CCは00、06、12、18 UTC、FFFは予報時間です。
今回の最小プロファイルでは、次だけを取得します。
変数 APCP
鉛直面 surface
予報時間 f001~f072
空間 安倍川流域周辺の取得矩形
GFSはNOMADS GRIB Filterへ変数、鉛直面、矩形を渡すため、全球ファイルを一度保存する必要がありません。
参考:
ECMWF IFS
ECMWF Open Dataは、原則として0.25度のGRIB2で提供されています。
IFSの00・12 UTCは、144時間先までは3時間間隔です。06・18 UTCも144時間先までは3時間間隔なので、今回の72時間範囲は次の25時刻になります。
0, 3, 6, 9, ..., 72
取得変数はtpです。
ECMWF AIFS Single
AIFS Singleは00、06、12、18 UTCの4回、6時間間隔で提供されています。
今回の72時間範囲は次の13時刻です。
0, 6, 12, 18, ..., 72
取得変数はIFSと同じtpです。
参考:
ECMWF Open Dataは直近12ラン程度のローリング保存です。後から同じ予報を取得できるとは限らないため、必要なランは自分の環境へ保存します。
フォルダー構成
RainfallForecast72h_FreeData_Step2_r005/
├── configs/
│ ├── acquisition.yaml
│ ├── project_policy.yaml
│ ├── target_area.yaml
│ └── sources/
│ ├── rish.yaml
│ ├── jma.yaml
│ ├── noaa.yaml
│ ├── ecmwf.yaml
│ └── gsi.yaml
├── src/
│ ├── rainfall_catalog/
│ └── rainfall_acquisition/
│ ├── acquirer.py
│ ├── cli.py
│ ├── config.py
│ ├── ecmwf_open.py
│ ├── grib_tools.py
│ ├── http_client.py
│ ├── ledger.py
│ ├── models.py
│ ├── noaa_gfs.py
│ ├── planner.py
│ ├── register_local.py
│ ├── run_summary.py
│ ├── sidecar.py
│ ├── time_utils.py
│ └── verifier.py
├── scripts/
│ ├── check_docstrings.py
│ ├── check_tools.sh
│ ├── run_72h_acquisition.sh
│ └── verify_acquisition.sh
├── docs/
│ ├── full_acquisition_runbook.md
│ ├── maintenance_guide.md
│ ├── sidecar_schema.md
│ └── step2_design.md
├── data/
│ ├── raw/
│ ├── cache/
│ ├── interim/
│ └── processed/
├── catalog/
├── reports/
├── tests/
├── Dockerfile
├── compose.yaml
└── pyproject.toml
data/raw、data/cache、data/interim、data/processedのデータ本体はGitへ入れません。
まずDocker環境を確認する
.envを作ります。
cp .env.example .env
利用条件を確認した後、次を変更します。
STEP2_IMAGE_TAG=r005
ACCEPT_DATA_TERMS=YES
ACQUISITION_PROFILE=rainfall_minimal
FORECAST_RUN=latest
ALLOW_PARTIAL=YES
ACCEPT_DATA_TERMS=NOのままでは、実ファイルの取得だけでなく、最新ランを探す外部接続も止めます。
Dockerイメージを作ります。
docker compose build
必要なツールを確認します。
docker compose run --rm check-tools
正常時は、少なくとも次が表示されます。
[ok] python
[ok] cdo
[ok] grib_ls
[ok] git
[ok] python module yaml
[ok] python module requests
[ok] python module duckdb
[ok] python module ecmwf.opendata
Pythonソースとテストのdocstringも確認します。
docker compose run --rm check-docstrings
DOCSTRING_CHECK=PASS scope=src+tests
続いて、Step 1から引き継いだカタログを確認します。
docker compose run --rm validate-catalog
docker compose run --rm catalog
現在の対象範囲は暫定矩形なので、次の警告は想定内です。
TARGET_GEOMETRY_PROVISIONAL
取得計画を確認する
まずネットワークを使わずに計画を確認します。
docker compose run --rm plan
rainfall_minimalでは、合計110件になります。
GFS 72
IFS 25
AIFS 13
合計 110
次に、配布元へ問い合わせて最新ランを確認します。
docker compose run --rm plan-live
今回の確認では、3データセットとも次のrunを選べました。
2026081418
ECMWFの実行時に、同時接続数の上限やAWS、Azure、Google Cloudのミラーに関する案内が表示されることがあります。続いてJSONの取得計画が出力されていれば、エラーではありません。
まず1件ずつ取得する
最初から110件を取りに行かず、各データセットを1件ずつ確認しました。
docker compose run --rm download-gfs-smoke
docker compose run --rm download-ifs-smoke
docker compose run --rm download-aifs-smoke
今回のsmoke取得では、次を確認できました。
GFS
source_id noaa_gfs_0p25
run_time_utc 2026-08-14T18:00:00Z
lead_hour 1
valid_time_utc 2026-08-14T19:00:00Z
HTTP status 200
variable APCP
level surface
NOMADS側で対象矩形を切り出し、GRIB2、sidecar、run_manifest.jsonが作成されました。
IFS
model ifs
run_time_utc 2026-08-14T18:00:00Z
lead_hour 0
param tp
stream oper
type fc
ECMWFクライアントで選択したGRIB2を一時保存し、CDOで安倍川流域周辺へ切り出せました。
AIFS
model aifs-single
run_time_utc 2026-08-14T18:00:00Z
lead_hour 0
param tp
stream oper
type fc
AIFSもIFSと同じ経路で切り出せました。
GRIB2の先頭4byteは次で確認できます。
grib_file=$(find data/raw -type f -name '*.grib2' | head -1)
od -An -c -N4 "$grib_file"
G R I B
grib_lsでも読めることを確認します。
docker compose run --rm -T check-tools \
grib_ls "/workspace/${grib_file}"
なぜ本取得ではrunを固定するのか
smokeまではFORECAST_RUN=latestで構いません。
ただし、GFS、IFS、AIFSを順番に取得している間に、新しい予報ランが公開されることがあります。
GFS 2026081418
IFS 2026081500
AIFS 2026081500
このように初期時刻が混ざると、同じ72時間予報として比較できません。
本取得では、plan-liveで確認したrunを明示します。
FORECAST_RUN=2026081418
r005の本取得スクリプトも、引数なしでは実行できないようにしています。
./scripts/run_72h_acquisition.sh 2026081418
latestを自動で選び続けるより、少し手間でも固定した方が後で迷いません。
72時間分を一括取得する
スクリプトで実行する
プロジェクト直下で実行します。
chmod +x scripts/run_72h_acquisition.sh
./scripts/run_72h_acquisition.sh 2026081418
Makefileを使う場合です。
make acquire-72h RUN_KEY=2026081418
スクリプトは並列実行しません。
初回から並列化すると、提供元への接続数が増えるだけでなく、エラーが出たときにGFS、IFS、AIFSのどこで止まったのか分かりにくくなります。まずは順番に取得し、データセットごとに検証する構成にしました。
scripts/run_72h_acquisition.sh
#!/usr/bin/env bash
#
# Step 2 r005: GFS・ECMWF IFS・ECMWF AIFSの72時間予報を順番に取得し、
# データセット別検証、全体検証、DuckDB取得台帳、run単位の最終サマリーまで作成する。
#
# 使用例:
# cd ~/RainfallForecast72h_FreeData_Step2_r005
# ./scripts/run_72h_acquisition.sh 2026081418
#
# 前提:
# 1. 各配布元の利用条件を確認し、.envでACCEPT_DATA_TERMS=YESにしていること
# 2. plan-liveと3データセットのsmoke取得が成功していること
# 3. 引数にはplan-liveで確認した共通runをYYYYMMDDHH(UTC)で渡すこと
#
# 並列化しない理由:
# 初回運用では提供元への同時接続を増やさず、失敗したデータセットとleadを切り分けやすく
# することを優先する。正常な取得済みファイルはsidecar・サイズ・SHA-256・GRIB
# 識別子が一致した場合だけ再利用されるため、途中停止後も同じコマンドで再開できる。
set -Eeuo pipefail
readonly SCRIPT_NAME="$(basename "$0")"
readonly RUN_KEY="${1:-}"
readonly PROJECT_ROOT="${PWD}"
readonly LOG_ROOT="${PROJECT_ROOT}/reports/runtime/${RUN_KEY:-invalid}"
usage() {
# runを省略するとlatestが途中で切り替わる可能性があるため、明示指定を必須にする。
cat <<USAGE
Usage: ${SCRIPT_NAME} YYYYMMDDHH
Example:
${SCRIPT_NAME} 2026081418
USAGE
}
read_env_value() {
# シェル環境を優先し、未設定の場合だけプロジェクト直下の.envを読む。
# source .envは任意のシェルコードを実行し得るため、単純なKEY=VALUEだけを抽出する。
local key="$1"
local default_value="$2"
local current_value="${!key:-}"
if [[ -n "${current_value}" ]]; then
printf '%s\n' "${current_value}"
return
fi
if [[ -f "${PROJECT_ROOT}/.env" ]]; then
local file_value
file_value="$(awk -F= -v key="${key}" '$1 == key {sub(/^[^=]*=/, ""); print; exit}' "${PROJECT_ROOT}/.env")"
if [[ -n "${file_value}" ]]; then
printf '%s\n' "${file_value}"
return
fi
fi
printf '%s\n' "${default_value}"
}
on_error() {
# エラー箇所とログ保存先を最後に出し、長い取得ログから調査対象を探しやすくする。
local exit_code="$?"
local line_number="$1"
echo "ERROR: 72時間本取得がline ${line_number}で停止しました。" >&2
echo "logs: ${LOG_ROOT}" >&2
exit "${exit_code}"
}
trap 'on_error ${LINENO}' ERR
if [[ -z "${RUN_KEY}" ]]; then
usage >&2
exit 2
fi
# YYYYMMDDHHの10桁だけを許可する。誤った文字列をディレクトリ名やrunとして渡さない。
if [[ ! "${RUN_KEY}" =~ ^[0-9]{10}$ ]]; then
echo "ERROR: run key must be YYYYMMDDHH: ${RUN_KEY}" >&2
exit 2
fi
# 取得処理はプロジェクト直下のCompose定義とStep 2設定を前提とする。
if [[ ! -f "${PROJECT_ROOT}/compose.yaml" ]] \
|| [[ ! -f "${PROJECT_ROOT}/configs/acquisition.yaml" ]]; then
echo "ERROR: Step 2 r005のプロジェクト直下から実行してください。" >&2
exit 2
fi
# スクリプト側で利用条件へ同意したことにはしない。.envまたはシェル環境で利用者が
# 明示的にYESへ変更している場合だけ続行する。
TERMS_VALUE="$(read_env_value ACCEPT_DATA_TERMS NO)"
if [[ "${TERMS_VALUE}" != "YES" ]]; then
echo "ERROR: 利用条件確認後、.envのACCEPT_DATA_TERMS=YESへ変更してください。" >&2
exit 2
fi
mkdir -p "${LOG_ROOT}"
# r004以前の.envをコピーした場合でも、今回のソースと同じr005タグを使う。
# Docker Composeはシェル環境を.envより優先するため、古いタグの混在を防げる。
export STEP2_IMAGE_TAG="r005"
export ACCEPT_DATA_TERMS="YES"
export ACQUISITION_PROFILE="$(read_env_value ACQUISITION_PROFILE rainfall_minimal)"
export FORECAST_RUN="${RUN_KEY}"
export ALLOW_PARTIAL="NO"
run_logged() {
# コマンドの標準出力と標準エラーを画面とrun別ログの両方へ残す。
# pipefailにより、teeが成功しても元コマンドの異常終了を見落とさない。
local log_name="$1"
shift
echo
echo "======================================================================"
echo "START: ${log_name}"
echo "======================================================================"
"$@" 2>&1 | tee "${LOG_ROOT}/${log_name}.log"
echo "END: ${log_name}"
}
verify_one() {
# smokeではなく本取得なので欠損を許可しない。SHA-256やGRIB不正も同じverifyで止める。
local source_id="$1"
local log_name="$2"
export VERIFY_TARGETS="${source_id}:${RUN_KEY}"
export ALLOW_PARTIAL="NO"
run_logged "${log_name}" docker compose run --rm verify
}
# 1. r005共通イメージと、CDO・ecCodes・Git・DuckDB・ECMWFクライアントを確認する。
run_logged "00_build" docker compose build
run_logged "01_check_tools" docker compose run --rm check-tools
run_logged "02_check_docstrings" docker compose run --rm check-docstrings
# 2. 指定runで計画を再生成する。固定runなので、3データセットの途中でlatestが切り替わらない。
run_logged "03_plan_fixed_run" docker compose run --rm plan-live
# 3. GFSはNOMADS側でAPCP・surface・対象矩形を切り出してから保存する。
run_logged "10_download_gfs" docker compose run --rm download-gfs
verify_one "noaa_gfs_0p25" "11_verify_gfs"
# 4. IFSはtpの全球選択メッセージを取得し、CDOで安倍川周辺へ切り出す。
run_logged "20_download_ifs" docker compose run --rm download-ifs
verify_one "ecmwf_ifs_open" "21_verify_ifs"
# 5. AIFSもIFSと同じ保存経路を使うが、native leadは6時間間隔である。
run_logged "30_download_aifs" docker compose run --rm download-aifs
verify_one "ecmwf_aifs_open" "31_verify_aifs"
# 6. 同一runの3データセットをまとめて再検査する。データセット単独検査後にも全体検査を残すことで、
# VERIFY_TARGETSの指定漏れや別run混在を運用ログから見つけやすくする。
export VERIFY_TARGETS="noaa_gfs_0p25:${RUN_KEY},ecmwf_ifs_open:${RUN_KEY},ecmwf_aifs_open:${RUN_KEY}"
export ALLOW_PARTIAL="NO"
run_logged "40_verify_all" docker compose run --rm verify
# 7. sidecarを正本としてDuckDB取得台帳を再構築し、全runの保存状況を表示する。
run_logged "50_build_ledger" docker compose run --rm ledger
run_logged "51_status" docker compose run --rm status
# 8. 期待件数はシェルへ固定せず、acquisition.yamlのnative_leadsから算出する。
# GRIB2、sidecar、run manifest、残存.partがそろわない場合は非0で終了する。
run_logged "60_summarize_run" docker compose run --rm summarize-run
# 9. 原本はdata/rawに置いてよいが、Git追跡や保護領域外への漏出は許可しない。
run_logged "70_guard_git" docker compose run --rm guard
cat <<SUMMARY
======================================================================
72-HOUR ACQUISITION COMPLETED
======================================================================
run_key : ${RUN_KEY}
profile : ${ACQUISITION_PROFILE}
logs : ${LOG_ROOT}
verification : ${PROJECT_ROOT}/reports/acquisition_verification.md
summary : ${PROJECT_ROOT}/reports/acquisition_72h_summary_${RUN_KEY}.md
ledger : ${PROJECT_ROOT}/catalog/acquisition.duckdb
======================================================================
ACQUISITION_72H=PASS
SUMMARY
上のコードは配布版と同じ内容です。エラー時のログ案内だけでなく、固定run、逐次取得、再利用、検証順を選んだ理由もコメントへ残しています。
スクリプトが行う確認
1. ツールとdocstring
docker compose run --rm check-tools
docker compose run --rm check-docstrings
2. 固定runの取得計画
FORECAST_RUN=2026081418 docker compose run --rm plan-live
明示runの場合、配布元の最新時刻へ勝手に移動しません。
3. データセット別取得と検証
GFS取得 → GFSの72件を検証
IFS取得 → IFSの25件を検証
AIFS取得 → AIFSの13件を検証
4. 全体検証
VERIFY_TARGETS=noaa_gfs_0p25:2026081418,ecmwf_ifs_open:2026081418,ecmwf_aifs_open:2026081418 \
ALLOW_PARTIAL=NO \
docker compose run --rm verify
5. 取得台帳
docker compose run --rm ledger
docker compose run --rm status
6. run単位サマリー
FORECAST_RUN=2026081418 docker compose run --rm summarize-run
この処理では、期待件数をシェルへ直接書きません。
expected_leads = tuple(build_native_leads(source["native_leads"]))
configs/acquisition.yamlの設定を読み、データセットごとの期待件数を作ります。
予報期間を変えたときに、YAMLでは96時間、シェルでは72時間のまま、といったずれを作らないためです。
本取得の結果
最後に、次のサマリーを確認できました。
run_key : 2026081418
noaa_gfs_0p25
GRIB2 72 / 72
sidecar 72 / 72
ECMWF IFS
GRIB2 25 / 25
sidecar 25 / 25
ECMWF AIFS
GRIB2 13 / 13
sidecar 13 / 13
合計
GRIB2 110 / 110
sidecar 110 / 110
.part 0
最終判定です。
ACQUISITION_72H=PASS
run単位のサマリーは次へ保存します。
reports/acquisition_72h_summary_2026081418.json
reports/acquisition_72h_summary_2026081418.md
GRIB2の内容を確認した検証レポートは別です。
reports/acquisition_verification.json
reports/acquisition_verification.md
件数だけを見て完了とせず、verifyとsummarize-runを分けています。
-
verify: SHA-256、サイズ、GRIB識別子、GRIBメッセージ、leadの欠損を確認 -
summarize-run: GRIB2、sidecar、manifest、.partの物理配置を短く確認
保存されるファイル
GFS
data/raw/noaa/gfs/2026081418/
├── noaa_gfs_2026081418_f001_abe.grib2
├── noaa_gfs_2026081418_f001_abe.grib2.meta.json
├── ...
├── noaa_gfs_2026081418_f072_abe.grib2
├── noaa_gfs_2026081418_f072_abe.grib2.meta.json
└── run_manifest.json
IFS
data/raw/ecmwf/ifs/2026081418/
├── ecmwf_ifs_2026081418_f000_abe.grib2
├── ecmwf_ifs_2026081418_f000_abe.grib2.meta.json
├── ...
├── ecmwf_ifs_2026081418_f072_abe.grib2
├── ecmwf_ifs_2026081418_f072_abe.grib2.meta.json
└── run_manifest.json
AIFS
data/raw/ecmwf/aifs/2026081418/
├── ecmwf_aifs_2026081418_f000_abe.grib2
├── ecmwf_aifs_2026081418_f000_abe.grib2.meta.json
├── ...
├── ecmwf_aifs_2026081418_f072_abe.grib2
├── ecmwf_aifs_2026081418_f072_abe.grib2.meta.json
└── run_manifest.json
sidecarへ残す情報
GRIB2と同じ場所に.meta.jsonを作ります。
{
"schema_version": 1,
"source_id": "noaa_gfs_0p25",
"model": "gfs",
"run_time_utc": "2026-08-14T18:00:00Z",
"lead_hour": 1,
"valid_time_utc": "2026-08-14T19:00:00Z",
"received_at_utc": "2026-08-15T02:16:48Z",
"request": {
"profile": "rainfall_minimal",
"variables": ["APCP"],
"levels": ["surface"],
"bbox_wgs84": {
"west": 137.75,
"south": 34.55,
"east": 138.9,
"north": 35.85
}
},
"local_file": {
"path": "data/raw/noaa/gfs/2026081418/noaa_gfs_2026081418_f001_abe.grib2",
"size_bytes": 432,
"sha256": "..."
},
"processing": {
"spatial_subset": "provider_side_nomads_filter",
"unit_conversion": "none",
"temporal_conversion": "none"
}
}
Step 2では、次の時刻を分けて保存します。
run_time_utc 予報計算の初期時刻
lead_hour 初期時刻から何時間先か
valid_time_utc 予報値が有効になる時刻
received_at_utc ローカルへ保存した時刻
この区別は、後で過去予報と観測雨量を対応させるときに必要です。
取得後の検証
verifyでは、次を確認します。
- sidecarが読めるか
-
source_idが一致するか -
run_time_utcが対象runと一致するか -
lead_hourが重複していないか - 必要なleadが欠けていないか
- 実ファイルが存在するか
- sidecarと実ファイルのサイズが一致するか
- SHA-256が一致するか
- 先頭が
GRIBか -
grib_lsでメッセージを読めるか
ファイル数だけでは不十分です。
例えば、f024が2件あり、f025が欠けていても、件数は同じになります。そのため、件数ではなくleadの集合を比較します。
expected = set(build_native_leads(source["native_leads"]))
found = {int(sidecar["lead_hour"]) for sidecar in sidecars}
missing = sorted(expected - found)
途中で停止した場合は再実行する
通信エラーやPC停止で途中終了した場合は、同じrunで再実行します。
./scripts/run_72h_acquisition.sh 2026081418
正常な取得済みファイルは、次が一致した場合だけ再利用されます。
- sidecarに記録された保存先
- 実ファイルの存在
- ファイルサイズ
- SHA-256
- GRIB識別子
そのため、最初から取り直す必要はありません。
不整合が見つかったファイルは自動削除しません。原因を確認して、意図的に取り直す場合だけ--overwriteを使います。
このようにしたのは、壊れたファイルを黙って差し替えると、何が起きたのか分からなくなるためです。
ログをrunごとに残す
本取得スクリプトのログは、次へ保存します。
reports/runtime/2026081418/
├── 00_build.log
├── 01_check_tools.log
├── 02_check_docstrings.log
├── 03_plan_fixed_run.log
├── 10_download_gfs.log
├── 11_verify_gfs.log
├── 20_download_ifs.log
├── 21_verify_ifs.log
├── 30_download_aifs.log
├── 31_verify_aifs.log
├── 40_verify_all.log
├── 50_build_ledger.log
├── 51_status.log
├── 60_summarize_run.log
└── 70_guard_git.log
途中停止した場合は、一番新しいログから確認します。
取得台帳をDuckDBへ作る
docker compose run --rm ledger
docker compose run --rm status
生成物です。
catalog/acquisition_ledger.sql
catalog/acquisition.duckdb
DuckDBへGRIB2本体は入れません。
保存するのは、次のような取得メタデータです。
source_id
model
run_time_utc
lead_hour
valid_time_utc
received_at_utc
path
size_bytes
sha256
retrieval_method
request_json
processing_json
terms_json
sidecar_path
DuckDBを削除しても、sidecarから再構築できます。
原本はGitへ入れない
docker compose run --rm guard
data/rawにGRIB2が存在すること自体は正常です。
検査するのは次です。
-
data/rawがGit追跡されていないか -
data/cacheがGit追跡されていないか -
data/interimがGit追跡されていないか - 保護領域外へGRIB2やNetCDFが漏れていないか
記事や配布ZIPには、原本データを含めません。
Step 2でまだ行わないこと
取得できたGRIB2を、そのまま同じ雨量時系列として扱うことはできません。
現在の時間間隔は次です。
GFS 1時間
IFS 3時間
AIFS 6時間
また、APCPとtpの単位、stepType、stepRangeも確認する必要があります。
Step 2では、配布元のnative形式を残すため、次はnoneです。
{
"unit_conversion": "none",
"temporal_conversion": "none"
}
次のStep 3
次は、取得した雨量を共通形式へ整理します。
予定している内容です。
- GFSの
APCPとECMWFのtpの単位を確認する -
stepTypeとstepRangeを確認する - 同一run内の積算差分から区間雨量を作る
- native intervalのままParquetへ保存する
- 必要に応じて1時間時系列を別データとして作る
- 安倍川流域界を準備する
- 格子と流域界を重ね、流域平均雨量を計算する
- AMeDASや国土交通省雨量観測所と比較する
処理イメージです。
流出量、河川流量、水位は、降雨データの時間軸と流域平均雨量が確認できてから進めます。

