0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

無償データで72時間降雨予測を組み立てる:Step 2 - GFS・IFS・AIFSを110ファイル保存して検証する

0
Posted at

無償データで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度 f001f072、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検査を継続する

処理の流れは次のとおりです。

ChatGPT Image 2026年8月15日 15_24_03.png


取得対象

対象範囲

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/rawdata/cachedata/interimdata/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

件数だけを見て完了とせず、verifysummarize-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時間

また、APCPtpの単位、stepTypestepRangeも確認する必要があります。

Step 2では、配布元のnative形式を残すため、次はnoneです。

{
  "unit_conversion": "none",
  "temporal_conversion": "none"
}

次のStep 3

次は、取得した雨量を共通形式へ整理します。

予定している内容です。

  1. GFSのAPCPとECMWFのtpの単位を確認する
  2. stepTypestepRangeを確認する
  3. 同一run内の積算差分から区間雨量を作る
  4. native intervalのままParquetへ保存する
  5. 必要に応じて1時間時系列を別データとして作る
  6. 安倍川流域界を準備する
  7. 格子と流域界を重ね、流域平均雨量を計算する
  8. AMeDASや国土交通省雨量観測所と比較する

処理イメージです。

ChatGPT Image 2026年8月15日 16_49_08.png

流出量、河川流量、水位は、降雨データの時間軸と流域平均雨量が確認できてから進めます。


参考リンク

NOAA

ECMWF

使用ツール

次段で使うデータ

0
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?