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?

【日本株分析プラットフォーム】3社止まりだった列を全社展開、Before/Afterはハッシュ値で証明した話(2026-08-22)

0
Last updated at Posted at 2026-08-21

このプロジェクトについて

EDINET(有価証券報告書等)の開示データをもとに、日本株の財務指標・投資判断スコアを日次で算出・公開しているサービスです。

公開サイトはこちら →
https://stocks.happy-life-design.net

前回の記事はこちら →
https://qiita.com/rightcross-ae86/items/db94259dd72ffef4dd22

見つかった放置

決算データには、公表後に訂正が入ることがあります。財務テーブルには、この訂正後の自己資本を保持するequity_edinet_restated列があります。この列は数週間前に新設したばかりで、動作確認のために特定の3社だけに対象を絞って実行したところまでは記録が残っていましたが、残り約3,800社への展開作業そのものは着手されないまま残っていました。

全社展開

コード変更は不要でした。列を書き込む関数自体はすでに全社共通ロジックとして実装済みで、対象を絞る技術的な制約は元々ありません。既存のバックフィル処理を、対象企業を指定せずに実行するだけです。

念のためバックアップを取得したうえで、まず全3,835社に対してdry-runを実行し、3,833社が成功(残り2社は別の既知の理由で元から対象外)。続けて本番実行し、14,868行を更新しました。

項目 Before After
equity_edinet_restated登録社数 3社 3,827社
対象行のカバレッジ 12行 14,791行
dry-run結果 - 3,833社成功・2社失敗(既知)
本番実行での更新行数 - 14,868行

Before/Afterの証明: 行数ではなくハッシュ値で比較する

今回の展開で書き換わったのは14,868行で、バックアップ全55,860行の残り40,992行は変化しない想定でした。この不変性を確認する必要がありましたが、「更新前後で行数が同じ」ことは、中身が変わっていないことの証明にはなりません。行数だけを見て「変化なし」と判断すると、たとえばAとBの2行の値が入れ替わっていても検知できません。

そこで、バックアップ側・現在のDB側それぞれで、対象40,992行の各行の内容を独立にハッシュ値化し、それらをid順に連結してさらに1つの代表ハッシュ値へ集約しました。1行1列でも差異があれば代表ハッシュ値は一致しません。両側で計算した結果、この代表ハッシュ値が完全に一致することを確認しました。

(技術的な補足)実コードで見る、ガードの非対称性

動作確認として、下流の計算処理(compute())を実データに対して実行し、現在DB格納済みの検証済みequityと比較したところ、17行のうち14行が新たに一致する状態へ改善し、悪化(退行)は0件でした。

この確認の過程で、今回全社展開した関数(cmd_backfill_prior())には、別の関数(cmd_backfill_equity_gaps())が持つ安全装置が入っていないことに気づきました。過去の個別調査で「この期間は値が空欄であることが正しい」と人間が確定させた期間を管理する除外リストです。

cmd_backfill_equity_gaps()側(既存のガードあり)

if (ticker, target_period_end.isoformat()) in _CONFIRMED_NULL_PRIOR_PERIODS:
    log.info(f"  [SKIP] period_end={target_period_end}: "
             f"個別調査済みでNULLが正しいと確定済み(除外リスト)")
    continue

cmd_backfill_prior()側(今回全社展開した関数、ガードなし)

for prior_n, vals in prior_data.items():
    idx = prior_n - 1
    if idx >= len(prior_period_ends):
        continue
    target_period_end = prior_period_ends[idx]
    vals_to_write = dict(vals)
    # ここに _CONFIRMED_NULL_PRIOR_PERIODS のチェックが無い
    cols = list(_FIELD_TO_COL.values())
    set_clause = ", ".join(f"{col} = %s" for col in cols)
    ...

実際に確認したところ、除外リストに載っている2期で、本来空欄であるべき値がequity_edinet_restatedに書き込まれていました。ただし、この2期は外部の参考サイト側の値も元々空欄のままだったため、下流の計算処理側のフォールバック条件(参考値がある場合のみ_restated列を参照する)を満たさず、現時点では計算結果に一切影響していないことも確認できました。将来、外部の参考サイト側にこの期間のデータが入るようになった場合にのみ顕在化しうる潜在的な不整合として、次の対応候補に残しています。

まとめ

検証用に3社だけで動かした処理を、コード変更なしで全社へ展開しました。変化していないはずのデータの不変性は、行数の一致ではなくハッシュ値でBefore/Afterを直接比較することで証明しています。あわせて、動作確認の過程で見つかった「片方の関数にだけ安全装置がない」という非対称は、現状は実害がないと確認できましたが、コードの構造上の不整合として引き続き記録しています。気になる点・ご質問があればコメントでお知らせください。

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?