経済指標カレンダーの作成とsqlite へ取り込み

経済指標カレンダーの作成とsqlite へ取り込み

メルマガから

■■■7月21日(月)■■■
・日本は祝日で休場
・日本の参議院選挙明け
・週明け
・米国の主要企業の決算発表(本格化)
07:45 NZ)第2四半期消費者物価指数
23:00 ★米)景気先行指数

■■■7月22日(火)■■■
・米国の主要企業の決算発表(本格化)
07:45 NZ)貿易収支
10:30 豪)RBA議事録公表(7月8日開催分)
18:15 ★米)ベイリーBOE総裁の発言   
21:30 ★米)パウエルFRB議長の発言(ブラックアウト期間中で金融政策に関する発言はない予定)
23:00 ★米)リッチモンド連銀製造業指数
26:00 ★米)ボウマンFRB副議長の発言(ブラックアウト期間中で金融政策に関する発言はない予定)
-     ★米)注目度の高い経済指標の発表はない

■■■7月23日(水)■■■
・米国の主要企業の決算発表(本格化)→テスラ、アルファベット、その他
10:30 日)内田日銀副総裁の発言(あいさつ)
未定 日)40年利付国債入札
未定 日)内田日銀副総裁の発言(記者会見)
20:00 ★米)MBA住宅ローン申請指数
23:00 ★米)中古住宅販売件数
23:30 ★米)週間原油在庫
26:00 ★米)20年債入札
米株引後 ★米)テスラ決算
米株引後 ★米)アルファベット決算

■■■7月24日(木)■■■
・米国の主要企業の決算発表(本格化)     
12:05 豪)ブロックRBA総裁の発言   
16:15 仏)製造業PMI【速報値】
16:15 仏)非製造業PMI【速報値】
16:30 独)製造業PMI【速報値】
16:30 独)非製造業PMI【速報値】
17:00 欧)製造業PMI【速報値】
17:00 欧)非製造業PMI【速報値】
17:30 英)製造業PMI【速報値】
17:30 英)非製造業PMI【速報値】
20:00 ト)TCMB政策金利&声明発表
21:15 欧)ECB政策金利&声明発表
21:30 加)小売売上高&【除自動車】
21:45 欧)ラガルドECB総裁の記者会見
21:30 ★米)新規失業保険申請件数     
21:30 ★米)シカゴ連銀全米活動指数
22:45 ★米)製造業PMI【速報値】
22:45 ★米)非製造業PMI【速報値】
23:00 ★米)新築住宅販売件数
23:30 ★米)週間天然ガス貯蔵量
24:00 ★米)カンザスシティ連銀製造業活動指数

■■■7月25日(金)■■■
・週末
・五十日(25日)
・米国の主要企業の決算発表(本格化)
08:30 日)東京都区部消費者物価指数&【除生鮮】
15:00 英)小売売上高&【除自動車】
17:00 独)IFO景況指数
21:30 ★米)耐久財受注&【除輸送用機器】

の部分をコピペして
chatGPTで

これらをcsvファイルにして

とするとcsvが作成できる

これを既に作成ずみのDBへ追加する

✅ ポイント
引数 内容
if_exists=’append’ 既存テーブルがあれば追記(削除せず)
index=False DataFrameの行番号をDBに書き込まないようにする

とすればOKらしい

✅ ステップ概要
1. SQLiteテーブルに一意制約(UNIQUE)を追加
2. to_sql() ではなく 手動でUPSERT処理 を行う(INSERT OR IGNORE)

SQLiteに CSV を追加する際に重複をチェックし、すでに登録済みのレコードはスキップ(UPSERT) する処理を組み込む

✅ 特徴
処理内容 説明
CREATE TABLE IF NOT EXISTS 初回だけテーブルを作成
UNIQUE(date, time, country, event) 主な識別要素による重複防止
ON CONFLICT IGNORE 重複レコードがある場合は無視してスキップ
INSERT OR IGNORE 既存レコードと重複するものは挿入されない

これらを元に

vim add_calendarDB.py

内容を

import pandas as pd
import sqlite3

# 新しいCSVを読み込み
csv_path = "economic_events_20250721_25.csv"
df_new = pd.read_csv(csv_path)

# SQLiteに接続
conn = sqlite3.connect("economic_events.db")
cursor = conn.cursor()

# ① テーブルがまだ存在しない場合は一意制約付きで作成
cursor.execute("""
CREATE TABLE IF NOT EXISTS events (
    date TEXT,
    time TEXT,
    country TEXT,
    event TEXT,
    importance TEXT,
    type TEXT,
    UNIQUE(date, time, country, event) ON CONFLICT IGNORE
)
""")

# ② 各レコードを1件ずつINSERT OR IGNOREで追加
for _, row in df_new.iterrows():
    cursor.execute("""
        INSERT OR IGNORE INTO events (date, time, country, event, importance, type)
        VALUES (?, ?, ?, ?, ?, ?)
    """, (
        row["date"],
        row["time"],
        row["country"],
        row["event"],
        row["importance"],
        row["type"]
    ))

# コミット & 終了
conn.commit()

# レコード数を確認
cursor.execute("SELECT COUNT(*) FROM events")
print(f"現在のeventsテーブルの件数: {cursor.fetchone()[0]}")

conn.close()

として保存

実行すると

python add_calendarDB.py 
現在のeventsテーブルの件数: 90

となる

これで再度、今日のイベント情報を取得してみる

 python today_events.py                       
         date   time country               event importance  type
0  2025-07-25  08:30       日  東京都区部消費者物価指数&【除生鮮】       None  経済指標
1  2025-07-25  15:00       英        小売売上高&【除自動車】       None  経済指標
2  2025-07-25  17:00       独             IFO景況指数       None  経済指標
3  2025-07-25  21:30       米      耐久財受注&【除輸送用機器】          ★  経済指標

問題なく実行できた

次に金の価格などの過去データだが
これは investing.com へ無料登録すれば過去データの取得が可能
一気にダウンロードしようとしたけど
20年ぐらいが限界なので分けて取得する

gmailから予定の抽出とDB格納

経済指標カレンダーがgmailで購読しているメルマガに掲載されているので、そこから抽出したい。 この経済指標カレンダーを自動売買に組み込みたい

🧩 全体構成
① Gmail APIでメールを取得
* google-api-python-client でGmail APIを使って、メルマガ(特定の送信者や件名)を定期的に取得
② 経済指標カレンダー部分の抽出
* HTML構造をパースして、経済指標情報(日付・時間・国・指標名・前回・予想・結果など)を抽出
* BeautifulSoup や正規表現を併用
③ データベース or CSVに保存(SQLiteなど)
* 抽出したデータを SQLite に保存
* 過去データの参照やトリガー管理が容易
④ 自動売買スクリプトに連携
* 経済指標発表前後のトリガー条件をPythonで定義
* 例: 「米国雇用統計の5分前にポジションをクローズする」など

実験的にメルマガの内容をテキストファイルにまとめた これから抽出してほしい
として
Gmail.txtで実験したらできた

まずはメルマガから抽出する実験をする
ダメなら
同じようにGPTでcsvファイルを作成する
これでも対処はできる

cp ~/Downloads/economic_events_20250714.* .

で作成した
Csv、JSONファイルを移動

とりあえず経済指標はGPTで抽出する

date,time,country,event,importance,type
2025-07-15,11:00,中,第2四半期GDP,,経済指標
2025-07-15,11:00,中,鉱工業生産,,経済指標
2025-07-15,11:00,中,小売売上高,,経済指標
2025-07-15,11:00,中,固定資産投資,,経済指標
2025-07-15,18:00,独,ZEW景況感調査,,経済指標
2025-07-15,21:30,加,消費者物価指数,,経済指標
2025-07-15,29:00,英,ベイリーBOE総裁の発言,,発言
2025-07-15,20時頃,米,JPモルガン・チェース決算,★,決算
2025-07-15,21:30,米,消費者物価指数&【コア】,★,経済指標
2025-07-15,21:30,米,NY連銀製造業景気指数,★,経済指標
2025-07-15,22:15,米,ボウマンFRB副議長の発言(投票権あり),★,発言
2025-07-15,23時頃,米,シティグループ決算,★,決算
2025-07-15,25:45,米,バーFRB理事の発言(投票権あり),★,発言
2025-07-15,26:00,米,バーキン:リッチモンド連銀総裁の発言(投票権なし),★,発言
2025-07-15,27:45,米,コリンズ:ボストン連銀総裁の発言(投票権あり),★,発言
2025-07-16,15:00,英,消費者物価指数&【コア】,,経済指標
2025-07-16,15:00,英,小売物価指数&【コア】,,経済指標
2025-07-16,21:15,加,住宅着工件数,,経済指標
2025-07-16,08:45,米,ローガン:ダラス連銀総裁の発言(投票権なし),★,発言
2025-07-16,20:00,米,MBA住宅ローン申請指数,★,経済指標
2025-07-16,21:00,米,バーキン:リッチモンド連銀総裁の発言(投票権なし),★,発言
2025-07-16,21時頃,米,ゴールドマン・サックス決算,★,決算
2025-07-16,21時頃,米,モルガン・スタンレー決算,★,決算
2025-07-16,21:30,米,生産者物価指数&【コア】,★,経済指標
2025-07-16,22:15,米,鉱工業生産/設備稼働率,★,経済指標
2025-07-16,22:15,米,ハマック:クリーブランド連銀総裁の発言(投票権なし),★,発言
2025-07-16,23:00,米,バーFRB理事の発言(投票権あり),★,発言
2025-07-16,23:30,米,週間原油在庫,★,経済指標
2025-07-16,27:00,米,地区連銀経済報告(ベージュブック),★,経済指標
2025-07-17,10:30,豪,失業率&新規雇用者数,,経済指標
2025-07-17,15:00,英,失業率&失業保険申請件数,,経済指標
2025-07-17,06:30,米,ウィリアムズ:NY連銀総裁の発言(投票権あり),★,発言
2025-07-17,21:30,米,新規失業保険申請件数,★,経済指標
2025-07-17,21:30,米,小売売上高&【除自動車】,★,経済指標
2025-07-17,21:30,米,フィラデルフィア連銀景況指数,★,経済指標
2025-07-17,21:30,米,輸入物価指数,★,経済指標
2025-07-17,23:00,米,企業在庫,★,経済指標
2025-07-17,23:00,米,NAHB住宅市場指数,★,経済指標
2025-07-17,23:00,米,クーグラーFRB理事の発言(投票権あり),★,発言
2025-07-17,23:30,米,週間天然ガス貯蔵量,★,経済指標
2025-07-17,25:45,米,デイリー:サンフランシスコ連銀総裁の発言(投票権なし),★,発言
2025-07-17,26:30,米,クックFRB理事の発言(投票権あり),★,発言
2025-07-17,29:00,米,対米証券投資【ネット長期フロー】&【ネットフロー合計】,★,経済指標
2025-07-17,米株引後,米,ネットフリックス決算,★,決算
2025-07-18,08:30,日,全国消費者物価指数&【除生鮮】,,経済指標
2025-07-18,07:30,米,ウォラーFRB理事の発言(投票権あり),★,発言
2025-07-18,21:30,米,住宅着工件数/建設許可件数,★,経済指標
2025-07-18,23:00,米,ミシガン大学消費者信頼感指数【速報値】,★,経済指標

という内容で

economic_events_20250714.csv	

を作成

これをsqliteに入れてDBにする

 vim calendar_csv_in_sql.py

でファイルを作成

import pandas as pd
import sqlite3

# CSVファイルの読み込み
csv_path = "economic_events_20250714.csv"  # ファイルパスは必要に応じて変更
df = pd.read_csv(csv_path)

# SQLiteデータベースに接続(なければ作成)
conn = sqlite3.connect("economic_events.db")  # 出力ファイル名も変更可能

# データをeventsテーブルに保存(既存なら上書き)
df.to_sql("events", conn, if_exists="replace", index=False)

# テーブル構成確認(任意)
cursor = conn.cursor()
cursor.execute("PRAGMA table_info(events)")
columns = cursor.fetchall()
for col in columns:
    print(f"{col[1]} ({col[2]})")

# 接続を閉じる
conn.close()

で保存

実行すると

date (TEXT)
time (TEXT)
country (TEXT)
event (TEXT)
importance (TEXT)
type (TEXT)

となる

次に経済指標で
DBの中身で今日の経済指標一覧を表示

しかし、これだと先週のデータのため表示されないので
今週のものを試す

経済指標をcsvへ保存

経済指標をcsvへ保存

import os
import pandas as pd
import matplotlib.pyplot as plt
from full_fred.fred import Fred
from datetime import datetime

# フォント設定(任意)
plt.rcParams['font.family'] = 'Times New Roman'

# --- FRED APIキー設定 ---
# 環境変数から取得(事前に設定しておくか、以下のように直接記述しても可)
# os.environ['FRED_API_KEY'] = 'あなたのAPIキー'
fred = Fred()
print(f"FRED APIキーが環境変数に設定されている:{fred.env_api_key_found()}")

# --- データ取得関数 ---
def get_fred_data(name, start="2013-01-01", end=""):
    df = fred.get_series_df(name)[["date", "value"]].copy()
    df["date"] = pd.to_datetime(df["date"])
    df["value"] = pd.to_numeric(df["value"], errors="coerce")
    df = df.set_index("date")

    if end == "":
        df = df.loc[f"{start}":]
    else:
        df = df.loc[f"{start}":f"{end}"]
    return df

# --- グラフ表示関数 ---
def MakeGraphFromFred(name, start):
    if type(name) != list:
        df = get_fred_data(name, start=start)
        data1 = df.reset_index()
        fig, axes = plt.subplots(1, 1, figsize=(6, 4), tight_layout=False, facecolor="whitesmoke")
        axes.plot(data1['date'], data1['value'])
        axes.set_xlabel('date')
        axes.set_ylabel(name)
        axes.set_title(name)
    else:
        num = len(name)
        fig, axes = plt.subplots(num, 1, figsize=(10, 4 * num / 2), tight_layout=False, sharex=True, facecolor="whitesmoke")
        for i in range(num):
            df = get_fred_data(name[i], start=start)
            data1 = df.reset_index()
            axes[i].plot(data1['date'], data1['value'])
            axes[i].set_xlabel('date')
            axes[i].set_ylabel(name[i])
    plt.show()

# --- CSV保存関数 ---
def save_fred_data_to_csv(series_name, start="1980-01-01", end=""):
    df = get_fred_data(series_name, start=start, end=end)

    # 保存ディレクトリ作成
    save_dir = "./fred_data"
    os.makedirs(save_dir, exist_ok=True)

    # 日付範囲取得(YYYY)
    start_date = df.index.min().strftime("%Y")
    end_date = df.index.max().strftime("%Y")

    # ファイル名作成
    filename = f"{series_name}_{start_date}_{end_date}.csv"
    filepath = os.path.join(save_dir, filename)

    # CSV保存
    df.reset_index().to_csv(filepath, index=False, encoding="utf-8-sig")
    print(f"保存完了: {filepath}")

# --- 実行例(UNRATE) ---
if __name__ == "__main__":
    # データ取得とグラフ表示
    MakeGraphFromFred("UNRATE", start="1980-01-01")

    # CSV保存
    save_fred_data_to_csv("UNRATE", start="1980-01-01")

として保存

これを実行するとグラフが表示され

 python unrate_csv.py 
FRED APIキーが環境変数に設定されている:True
保存完了: ./fred_data/UNRATE_1980_2025.csv

となって
fred_data の中に失業率のcsvファイルが作成される

他に取得できるものを探す

✅ よく使われるFREDの経済指標一覧(コード付き)
分類 指標名 シリーズID(コード) 内容
✅ 雇用関連 失業率 UNRATE 米国の失業率(月次)
非農業部門雇用者数 PAYEMS 米国の雇用者数(非農業)
労働参加率 CIVPART 労働人口比率
✅ 物価・インフレ 消費者物価指数 CPIAUCSL インフレの代表的指標(都市部)
コアCPI(食料・エネルギー除く) CPILFESL より安定したインフレ指標
PCEデフレーター PCEPI FRBが重視する物価指標
コアPCE PCEPILFE 食料とエネルギー除いたPCE
✅ 金融・金利 FFレート(政策金利) FEDFUNDS FOMCが決定する金利
米国10年国債利回り GS10 長期金利の指標
2年国債利回り GS2 短期金利
金利差(10年−2年) 自作可能 景気後退の予兆(逆イールド)
✅ 経済成長 実質GDP(四半期) GDPC1 インフレ調整済みGDP
名目GDP GDP 実額ベースのGDP
景気先行指数 USSLIND 将来の景気動向予測に使用
✅ 消費・支出 小売売上高 RSAFS 米国小売業の売上(月次)
個人消費支出 PCE GDPの約7割を占める項目
✅ 住宅関連 住宅着工件数 HOUST 新築住宅の開始件数
住宅価格指数(FHFA) USSTHPI 米国住宅価格の推移
建設支出 TTLCONS 総建設支出(月次)
✅ 信用・金融 家計債務残高 TOTALSL 個人の借入総額
クレジットカード貸出残高 REVOLSL リボ払い・カード債務
✅ その他 マネーサプライM2 M2SL 通貨供給量
ビットコイン価格(Coinbase) CBBTCUSD 仮想通貨指標(参考値)

✅ FXで重要とされる経済指標【総合ランキング】
ランク 経済指標 内容 市場への影響
⭐️1位 米国雇用統計(Nonfarm Payrolls: NFP) 毎月第一金曜、米国の雇用者増減 ドル、株、金利全てに影響
⭐️2位 消費者物価指数(CPI) 物価上昇率。インフレ判断基準 利上げ/利下げの判断材料
⭐️3位 FOMC政策金利(FEDFUNDS) 米国金利の発表(8回/年) ドルと世界中の金利に影響
⭐️4位 GDP成長率 経済成長の総合評価 景気の健全性判断
⭐️5位 小売売上高 個人消費の動向 GDPと景気の先行指標
⭐️6位 ISM製造業指数 景況感(PMI) 50を境に好不況判断
⭐️7位 PCEデフレーター FRBが重視するインフレ指標 CPIより重視される場合あり
⭐️8位 新規失業保険申請件数 週間ベースの雇用動向 NFPの先行指標
⭐️9位 貿易収支 輸出入のバランス 通貨需給に直接影響
⭐️10位 住宅着工件数/中古住宅販売 住宅市場の健全性 米経済の構造に深く関与

✅ FREDで取得できる代表的なFX関連指標とID
指標カテゴリ 指標名 シリーズID 内容
雇用 非農業部門雇用者数 PAYEMS 米国雇用統計(NFP)
雇用 失業率 UNRATE NFPと並ぶ重要指標
雇用 初回失業保険申請件数 ICSA 毎週発表、先行性あり
物価 CPI CPIAUCSL インフレ判断基準
物価 コアCPI CPILFESL 食品・エネルギー除く
物価 PCEデフレーター PCEPI FRBの注目指標
金利 FFレート(政策金利) FEDFUNDS FOMC発表後更新
金利 2年国債利回り GS2 市場予想の反映が早い
金利 10年国債利回り GS10 長期金利の代表格
金利 利回りスプレッド (GS10 – GS2) 景気後退の兆候(逆イールド)
景況感 ISM製造業指数 NAPM または外部API PMIとして注目
景気 実質GDP GDPC1 経済成長を確認
貿易 経常収支 NETEXP 通貨需給に影響
消費 小売売上高 RSAFS 景気動向の先行指標
住宅 新築住宅着工件数 HOUST 金利の影響を受けやすい

✅ 補足:為替相場への影響の仕組み(簡易図解)

コピーする編集する
CPI上昇 → インフレ懸念 → 利上げ期待↑ → 金利上昇 → 通貨価値↑ → 通貨買い(USD/JPY↑)
GDP減速 → 景気不安 → 利下げ期待↑ → 金利低下 → 通貨価値↓ → 通貨売り(USD/JPY↓)

✅ 使い方の例(Python × FRED)

# 雇用統計データ(NFP)取得してCSV保存
save_fred_data_to_csv("PAYEMS", start="2010-01-01")

# CPIデータ取得
save_fred_data_to_csv("CPIAUCSL", start="2010-01-01")

# 政策金利データ取得
save_fred_data_to_csv("FEDFUNDS", start="2010-01-01")

🔧 応用アイデア
* 経済指標発表カレンダーと自動連動
* 発表前後のUSD/JPYの値動きを自動分析
* SQLiteやNeo4jに格納して分析基盤を構築

ご希望あれば、指標発表日とFXチャートを自動で重ねる可視化スクリプトや、Neo4jでの因果関係分析のテンプレートも提供可能です!

これらを取得するように

import os
import pandas as pd
from full_fred.fred import Fred
from datetime import datetime

# --- FRED APIキー設定(環境変数が必要) ---
fred = Fred()
assert fred.env_api_key_found(), "FRED_API_KEYが環境変数に設定されていません。"

# --- データ取得関数 ---
def get_fred_data(series_id, start="1980-01-01", end=""):
    df = fred.get_series_df(series_id)[["date", "value"]].copy()
    df["date"] = pd.to_datetime(df["date"])
    df["value"] = pd.to_numeric(df["value"], errors="coerce")
    df = df.set_index("date")

    if end:
        df = df.loc[start:end]
    else:
        df = df.loc[start:]
    return df

# --- CSV保存関数 ---
def save_fred_data_to_csv(series_id, label, start="1980-01-01", end=""):
    df = get_fred_data(series_id, start=start, end=end)

    # 保存ディレクトリ作成
    save_dir = "./fred_data_fx"
    os.makedirs(save_dir, exist_ok=True)

    # 実際の取得範囲の年月
    start_date = df.index.min().strftime("%Y")
    end_date = df.index.max().strftime("%Y")

    # ファイル名(シリーズID_年範囲.csv)
    filename = f"{series_id}_{start_date}_{end_date}.csv"
    filepath = os.path.join(save_dir, filename)

    # CSVとして保存
    df.reset_index().to_csv(filepath, index=False, encoding="utf-8-sig")
    print(f"✅ {label} を保存しました → {filepath}")

# --- 重要指標リスト(シリーズIDと名前) ---
indicators = [
    ("PAYEMS", "非農業部門雇用者数 (NFP)"),
    ("UNRATE", "失業率"),
    ("ICSA", "新規失業保険申請件数"),
    ("CPIAUCSL", "消費者物価指数 (CPI)"),
    ("CPILFESL", "コアCPI"),
    ("PCEPI", "PCEデフレーター"),
    ("FEDFUNDS", "FFレート(政策金利)"),
    ("GS2", "2年国債利回り"),
    ("GS10", "10年国債利回り"),
    ("GDPC1", "実質GDP"),
    ("RSAFS", "小売売上高"),
    ("HOUST", "住宅着工件数"),
    ("NETEXP", "経常収支"),
]

# --- 一括実行 ---
if __name__ == "__main__":
    for series_id, label in indicators:
        try:
            save_fred_data_to_csv(series_id, label, start="1980-01-01")
        except Exception as e:
            print(f"⚠️ {label} ({series_id}) の保存に失敗しました: {e}")

として実行する

 python economic_index.py 
✅ 非農業部門雇用者数 (NFP) を保存しました → ./fred_data_fx/PAYEMS_1980_2025.csv
✅ 失業率 を保存しました → ./fred_data_fx/UNRATE_1980_2025.csv
✅ 新規失業保険申請件数 を保存しました → ./fred_data_fx/ICSA_1980_2025.csv
✅ 消費者物価指数 (CPI) を保存しました → ./fred_data_fx/CPIAUCSL_1980_2025.csv
✅ コアCPI を保存しました → ./fred_data_fx/CPILFESL_1980_2025.csv
✅ PCEデフレーター を保存しました → ./fred_data_fx/PCEPI_1980_2025.csv
✅ FFレート(政策金利) を保存しました → ./fred_data_fx/FEDFUNDS_1980_2025.csv
✅ 2年国債利回り を保存しました → ./fred_data_fx/GS2_1980_2025.csv
✅ 10年国債利回り を保存しました → ./fred_data_fx/GS10_1980_2025.csv
✅ 実質GDP を保存しました → ./fred_data_fx/GDPC1_1980_2025.csv
✅ 小売売上高 を保存しました → ./fred_data_fx/RSAFS_1992_2025.csv
✅ 住宅着工件数 を保存しました → ./fred_data_fx/HOUST_1980_2025.csv
✅ 経常収支 を保存しました → ./fred_data_fx/NETEXP_1980_2025.csv

とした
これで経済指標がある程度は保存できている

追加で
✅ よく使われるFREDの経済指標一覧(コード付き)
分類 指標名 シリーズID(コード) 内容
✅ 雇用関連 失業率 UNRATE 米国の失業率(月次)
非農業部門雇用者数 PAYEMS 米国の雇用者数(非農業)
労働参加率 CIVPART 労働人口比率
✅ 物価・インフレ 消費者物価指数 CPIAUCSL インフレの代表的指標(都市部)
コアCPI(食料・エネルギー除く) CPILFESL より安定したインフレ指標
PCEデフレーター PCEPI FRBが重視する物価指標
コアPCE PCEPILFE 食料とエネルギー除いたPCE
✅ 金融・金利 FFレート(政策金利) FEDFUNDS FOMCが決定する金利
米国10年国債利回り GS10 長期金利の指標
2年国債利回り GS2 短期金利
金利差(10年−2年) 自作可能 景気後退の予兆(逆イールド)
✅ 経済成長 実質GDP(四半期) GDPC1 インフレ調整済みGDP
名目GDP GDP 実額ベースのGDP
景気先行指数 USSLIND 将来の景気動向予測に使用
✅ 消費・支出 小売売上高 RSAFS 米国小売業の売上(月次)
個人消費支出 PCE GDPの約7割を占める項目
✅ 住宅関連 住宅着工件数 HOUST 新築住宅の開始件数
住宅価格指数(FHFA) USSTHPI 米国住宅価格の推移
建設支出 TTLCONS 総建設支出(月次)
✅ 信用・金融 家計債務残高 TOTALSL 個人の借入総額
クレジットカード貸出残高 REVOLSL リボ払い・カード債務
✅ その他 マネーサプライM2 M2SL 通貨供給量
ビットコイン価格(Coinbase) CBBTCUSD 仮想通貨指標(参考値)

も取得したい

import os
import pandas as pd
from full_fred.fred import Fred
from datetime import datetime

# --- FRED APIキー確認 ---
fred = Fred()
assert fred.env_api_key_found(), "❌ FRED_API_KEYが環境変数に設定されていません。"

# --- データ取得関数 ---
def get_fred_data(series_id, start="1980-01-01", end=""):
    df = fred.get_series_df(series_id)[["date", "value"]].copy()
    df["date"] = pd.to_datetime(df["date"])
    df["value"] = pd.to_numeric(df["value"], errors="coerce")
    df = df.set_index("date")
    if end:
        df = df.loc[start:end]
    else:
        df = df.loc[start:]
    return df

# --- CSV保存関数 ---
def save_fred_data_to_csv(series_id, label, start="1980-01-01", end=""):
    df = get_fred_data(series_id, start=start, end=end)
    save_dir = "./fred_data_all"
    os.makedirs(save_dir, exist_ok=True)
    start_date = df.index.min().strftime("%Y")
    end_date = df.index.max().strftime("%Y")
    filename = f"{series_id}_{start_date}_{end_date}.csv"
    filepath = os.path.join(save_dir, filename)
    df.reset_index().to_csv(filepath, index=False, encoding="utf-8-sig")
    print(f"✅ {label} を保存しました → {filepath}")

# --- 保存対象の指標一覧(シリーズIDとラベル) ---
indicators = [
    # ✅ 雇用関連
    ("UNRATE", "失業率"),
    ("PAYEMS", "非農業部門雇用者数"),
    ("CIVPART", "労働参加率"),

    # ✅ 物価・インフレ
    ("CPIAUCSL", "消費者物価指数 (CPI)"),
    ("CPILFESL", "コアCPI"),
    ("PCEPI", "PCEデフレーター"),
    ("PCEPILFE", "コアPCE"),

    # ✅ 金融・金利
    ("FEDFUNDS", "政策金利(FFレート)"),
    ("GS10", "10年国債利回り"),
    ("GS2", "2年国債利回り"),

    # ✅ 経済成長
    ("GDPC1", "実質GDP"),
    ("GDP", "名目GDP"),
    ("USSLIND", "景気先行指数"),

    # ✅ 消費・支出
    ("RSAFS", "小売売上高"),
    ("PCE", "個人消費支出"),

    # ✅ 住宅関連
    ("HOUST", "住宅着工件数"),
    ("USSTHPI", "住宅価格指数(FHFA)"),
    ("TTLCONS", "建設支出"),

    # ✅ 信用・金融
    ("TOTALSL", "家計債務残高"),
    ("REVOLSL", "クレジットカード貸出残高"),

    # ✅ その他
    ("M2SL", "マネーサプライM2"),
    ("CBBTCUSD", "ビットコイン価格(Coinbase)"),
]

# --- 一括取得・保存 ---
if __name__ == "__main__":
    for series_id, label in indicators:
        try:
            save_fred_data_to_csv(series_id, label, start="1980-01-01")
        except Exception as e:
            print(f"⚠️ {label} ({series_id}) の保存に失敗しました: {e}")

とすることで取得可能

次は商品関連の取得

Wtiの価格を1980から取得する

Wtiの価格を1980から取得する

pip install pandas_datareader

でインストール

Collecting pandas_datareader
  Downloading pandas_datareader-0.10.0-py3-none-any.whl.metadata (2.9 kB)
Requirement already satisfied: lxml in /Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages (from pandas_datareader) (5.4.0)
Requirement already satisfied: pandas>=0.23 in /Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages (from pandas_datareader) (2.2.3)
Requirement already satisfied: requests>=2.19.0 in /Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages (from pandas_datareader) (2.32.3)
Requirement already satisfied: numpy>=1.23.2 in /Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages (from pandas>=0.23->pandas_datareader) (1.26.4)
Requirement already satisfied: python-dateutil>=2.8.2 in /Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages (from pandas>=0.23->pandas_datareader) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in /Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages (from pandas>=0.23->pandas_datareader) (2024.2)
Requirement already satisfied: tzdata>=2022.7 in /Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages (from pandas>=0.23->pandas_datareader) (2025.1)
Requirement already satisfied: six>=1.5 in /Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages (from python-dateutil>=2.8.2->pandas>=0.23->pandas_datareader) (1.17.0)
Requirement already satisfied: charset-normalizer<4,>=2 in /Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages (from requests>=2.19.0->pandas_datareader) (3.4.1)
Requirement already satisfied: idna<4,>=2.5 in /Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages (from requests>=2.19.0->pandas_datareader) (3.10)
Requirement already satisfied: urllib3<3,>=1.21.1 in /Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages (from requests>=2.19.0->pandas_datareader) (2.3.0)
Requirement already satisfied: certifi>=2017.4.17 in /Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages (from requests>=2.19.0->pandas_datareader) (2024.12.14)
Downloading pandas_datareader-0.10.0-py3-none-any.whl (109 kB)
Installing collected packages: pandas_datareader
Successfully installed pandas_datareader-0.10.0

でインストール

import pandas_datareader.data as web
import pandas as pd
from datetime import datetime

# 開始・終了日
start = datetime(1980, 1, 1)
end = datetime.today()

# FREDからWTI価格データを取得
wti_df = web.DataReader('DCOILWTICO', 'fred', start, end)

# 欠損値を除去(必要に応じて)
wti_df.dropna(inplace=True)

# CSVに保存
wti_df.to_csv("wti_price_1980_to_present.csv")

print("✅ WTI原油価格データを保存しました:wti_price_1980_to_present.csv")

で実行

 python wti_csv.py            
✅ WTI原油価格データを保存しました:wti_price_1980_to_present.csv

となって
Csvファイルで取得ができた

金の価格や銀の価格も取得できるか試す

Python経済指標関連の取得

コモディティの価格を時系列で登録し、為替などの相関関係を知るようにする

コモディティの価格を時系列で登録し、為替などの相関関係を知りたい。
必要なコモディティの種類は?

コモディティ価格と為替(特にドル円やユーロ円など)の相関関係を把握するためには、以下のような主要なコモディティを時系列で登録するのが有効です。
特にFXや経済動向を分析する際には、以下の分類で把握しておくと良い

✅ 必要なコモディティの種類(カテゴリー別)
① エネルギー系
名称 代表指標 備考
WTI原油 WTI Crude Futures 米ドル建て。インフレや経済活動の先行指標。
ブレント原油 Brent Crude Futures 欧州圏の価格指標。WTIと並んで注視される。
天然ガス Henry Hub 欧州エネルギー危機などで為替と連動しやすい。

Henry Hub 欧州エネルギー危機などで為替と連動しやすい。
② 金属系
名称 代表指標 備考
金(ゴールド) Gold Spot Price 安全資産。ドルと逆相関の傾向あり。
銀(シルバー) Silver Spot Price 金と類似の動き。産業需要も関与。
銅(カッパー) Copper Futures 景気敏感資産。中国・新興国と関係が深い。
プラチナ・パラジウム Platinum/Palladium 産業用・自動車触媒用途で価格変動大。
③ 農産物系(インフレ関連)
名称 代表指標 備考
小麦 Wheat Futures 戦争・天候で価格大きく変動。
トウモロコシ Corn Futures 飼料用として需給に影響。
大豆 Soybean Futures 食料・バイオ燃料で重要。
④ その他注目資源
名称 代表指標 備考
リチウム Lithium Price Index EV市場と密接に関連。中国・豪州に依存。
コバルト・ニッケル Cobalt/Nickel Prices バッテリー需要と連動。資源ナショナリズムの影響も。

✅ 登録しておきたい為替通貨ペア(相関分析用)
通貨ペア 注目理由
USD/JPY ドル建てのコモディティ価格に直結。
EUR/USD 欧州経済とエネルギー需要の指標。
AUD/USD 豪州=資源国。銅・鉄鉱石と連動性あり。
CAD/USD カナダも資源国(特に原油)。
USD/CNH 中国との資源輸入の関係性(特に銅・石炭)。

✅ おすすめの時系列データソース
データ種類 ソース例
原油・金属・農産物先物 Investing.com, TradingEconomics, Quandl
通貨為替レート Yahoo Finance API, ECB, FRED, 為替API
IMMポジション(投機筋) CFTCのCoTレポート
金利・CPIとの関連 各国中央銀行、BLS、BOJなど

✅ SQLiteやNeo4jへの登録用途
* SQLite:時系列データの保存・分析に最適(例:WTI原油価格とドル円の過去5年分の変動)
* Neo4j:コモディティ間、通貨間、インフレ・金利・景気指標との**関係性可視化(ナレッジグラフ)**に有効

✅ 補足:相関関係の例
* WTI価格上昇 ⇒ 米長期金利上昇 ⇒ ドル高 ⇒ 円安
* 金価格上昇 ⇒ リスクオフ(ドル売り) ⇒ 円高

Python経済指標関連の取得

去年WTIの価格を取得できたがライブラリの変更などで取得ができない
今後に備えてcsvにしてDBに格納しておく

経済指標関連は過去は invetpyでできたけど
内部API変更により使えなくなった

Python×APIを活用して株価を取得してみた

を参考に行う

インストール関しては

PythonでFRED、Alpha Vantage、Binanceからデータを取得する方法

pip install requests pandas python-dotenv ccxt

でインストール

APIキーが必要になる

FREDの米国経済指標をpythonでグラフ化
を参考に

https://fred.stlouisfed.org/docs/api/fred/
へアクセス
API Keys をクリックし
Request or view your API keys
をクリック

これでアカウントがなければ登録し
APIキーを発行する

登録する時に用途を聞かれる

I am developing a personal economic analysis application that stores FRED data (e.g., oil prices, CPI, interest rates) into a local SQLite and Neo4j database. The application is intended for tracking trends, visualizing correlations, and supporting financial decision-making.

SQLiteとNeo4jを使って経済指標(WTIなど)の時系列分析や相関分析を行いたいと考えているので

意味は
私は、FREDのデータ(例:原油価格、CPI、金利など)をSQLiteやNeo4jに格納し、経済指標のトレンドや相関関係を可視化・分析する個人用のアプリケーションを開発しています。これは主に家計管理や金融判断のサポートを目的としています

となる

 I have read and agree to the St. Louis Fed’s Terms of Use, Privacy Notice & Policy, and Legal Notices, Information and Disclaimers.

「私は、セントルイス連邦準備銀行(St. Louis Fed)の 利用規約、プライバシー通知とポリシー、および法的通知・情報・免責事項を読んだ上で、これらに同意します。」
という意味

これにチェックを入れる

これでキーが取得できる

ModuleNotFoundError: No module named 'fredapi'

となるので
https://pypi.org/project/fredapi/
を参考に

pip install fredapi
import pandas as pd     # データ処理用ライブラり
import os              # フォルダを作成したりするのに活用するライブラリ  
import yfinance as yf # Yahoo Financeデータ取得用ライブラリ
from fredapi import Fred # FRED API操作用ライブラリ

# –FREDからデータ取得情報に設定——————————————-
# FRED APIキーを設定
fred_api_key = ''
fred = Fred(api_key=fred_api_key)
# 取得する経済指標シリーズIDをリストを定義する、リストの名称はseries_ids
series_ids = ['CBBTCUSD', 'CPIAUCSL', 'FEDFUNDS', 'DGS10', 'DEXJPUS']
# 取得したい期間を指定
start_date = '2020-01-01'
end_date = '2023-12-31'
# ——————————————————————————-
# ★★★取得結果を格納する箱の作成
fred_data = {}
# ★★重要ループ処理の実行—————————————————

for series in series_ids:
    data = fred.get_series(series, observation_start=start_date, observation_end=end_date)
    # PandasのDataFrameに変換し、列名をシリーズIDに設定
    df = data.to_frame().reset_index()
    df.columns = ['Date', series]
    # 辞書に保存
    fred_data[series] = df
    
    print(series)

を実行すると

 python test.py
CBBTCUSD
CPIAUCSL
FEDFUNDS
DGS10
DEXJPUS

となる

まずは実験成功

次に環境変数に代入する

export FRED_API_KEY="" 

pip install full_fred

も必要

これで

import os
import pandas as pd
import seaborn as sns
from full_fred.fred import Fred
import matplotlib.pyplot as plt

#pandas:1.3.5で本プログラムは動作確認しています。
print('pandas:', pd.__version__)

を実行した時に

pandas: 2.2.3

となる

Pipでインストールしていないと

Traceback (most recent call last):
  File "/Users/snowpool/aw10s/FRED/gdp.py", line 4, in <module>
    from full_fred.fred import Fred
ModuleNotFoundError: No module named 'full_fred'

となるので注意

次に

import os
import pandas as pd
import seaborn as sns
from full_fred.fred import Fred
import matplotlib.pyplot as plt

#pandas:1.3.5で本プログラムは動作確認しています。
#print('pandas:', pd.__version__)
# FRED_API_KEY = os.getenv('FRED_API_KEY')
fred = Fred()
print(f"FRED APIキーが環境変数に設定されている:{fred.env_api_key_found()}")


def get_fred_data(name, start="2013-01-01", end=""):
    df = fred.get_series_df(name)[["date", "value"]].copy()
    df["date"] = pd.to_datetime(df["date"])
    df["value"] = pd.to_numeric(df["value"], errors="coerce")
    df = df.set_index("date")

    if end == "":
        df = df.loc[f"{start}":]
    else:
        df = df.loc[f"{start}":f"{end}"]
    return df

def MakeGraphFromFred(name, start):
    if(type(name)!=list):
        df_UNRATE = get_fred_data(name, start=start)
        data1 = df_UNRATE
        data1 = data1.reset_index()
        fig, axes = plt.subplots(1, 1, figsize=(6,4), tight_layout=False, facecolor="whitesmoke")
        axes.plot(data1['date'], data1['value'])
        axes.set_xlabel('date')
        axes.set_ylabel(name)
        axes.set_title(name)
    else:
        num = len(name)
        fig, axes = plt.subplots(num, 1, figsize=(10,4*num/2), tight_layout=False,sharex=True, facecolor="whitesmoke")
        for i in range(num):
            df_UNRATE = get_fred_data(name[i], start=start)
            data1 = df_UNRATE
            data1 = data1.reset_index()
            axes[i].plot(data1['date'], data1['value'])
            axes[i].set_xlabel('date')
            axes[i].set_ylabel(name[i])
#             axes[i].set_title(name[i])
        
    plt.show()
    return

plt.rcParams['font.family'] = 'Times new roman' #
#[ 'serif' | 'sans-serif' | 'cursive' | 'fantasy' | 'monospace' ]

MakeGraphFromFred("UNRATE", start="1980-01-01")

とすることで
1980年からの失業率をグラフで表示

次にこれをcsvに保存する

購入頻度と消費間隔の予測

購入頻度と消費間隔の予測

まず過去の購入データを全てcsvにする
これをSqlite にインポート
その後に
SQLite の購入データを Neo4j に同期させる

ここまでできたら情報が整うので
購入頻度
消費感覚
価格の推移
最安値店舗の把握と最高値店舗の把握
ができるので在庫がどれくらいの期間で消費されているかを予測できる

店舗での購入物を管理できるので
家計の把握とコントロールが可能

家で作成しているレシピを登録することで
1品ごとのコストが把握できる

これなら惣菜を買った方が得という意見の反論の証拠にもなる

あと購入物をカテゴライズし
生鮮食品なら3日
調味料1日
というようにすると良いかもしれないがバターなどはカテゴリが面倒

購入管理なら
日用品ならまとめ買いで頻繁に購入する必要がないので使えそう
あとは調味料関連
とりあえず年間の購入の履歴から購入頻度を割り出す
その後にカテゴリ分けと期間の設定を割り出した方が良さそう

あとはレシピの登録

mkdir recipes
ここにレシピを登録して neo4jに一括登録する

購入管理のCypherクエリ

購入管理のCypherクエリ

購入頻度(日付順)と数量確認

MATCH (:Store)-[r:PURCHASED_ON]->(i:Item {name: "たい"})
RETURN r.date AS date, r.quantity AS quantity
ORDER BY r.date

Pythonで r.date を日付に変換して、間隔(差分)を出せば
「何日おきに買っているか」がわかる

店舗別の購入回数

MATCH (s:Store)-[r:PURCHASED_ON]->(i:Item {name: "たい"})
RETURN s.name AS store, COUNT(*) AS times
ORDER BY times DESC

店舗依存度が見える(1店舗に集中?複数利用?)

価格と日付の傾向(最安値・平均)

MATCH (:Store)-[r:PURCHASED_ON]->(i:Item {name: "たい"})
RETURN r.date AS date, r.unit_price AS unit_price
ORDER BY r.date

折れ線グラフにして価格のトレンドを可視化

Neo4jから価格履歴をクエリで取得

MATCH (s:Store {name: "COOP ユーコープ 袋井町店"})-[r:PURCHASED_ON]->(i:Item {name: "たい"})
RETURN r.date AS date, r.unit_price AS unit_price
ORDER BY r.date

現在のNeo4jへの登録処理を確認するには
「現在の登録処理(ノード・リレーション構造)」を確認するには、Neo4j Browser または Cypher シェルでグラフ全体や構造を可視化するクエリを実行するのが基本

MATCH (n)-[r]->(m) RETURN n, r, m LIMIT 100

これで
ノード(n, m)とリレーション(r)を100件まで表示
実際のノードの種類(Store、Item)と関係(PURCHASED_ON)が図で確認できる

次に
✅ 目的
「同じ商品を複数店舗で購入した履歴がある場合に、最も安かった店舗と価格を一覧表示」する。

MATCH (s:Store)-[r:PURCHASED_ON]->(i:Item)
WITH i.name AS item, r.unit_price AS price, s.name AS store
ORDER BY item, price ASC
WITH item, collect({store: store, price: price}) AS store_prices
RETURN item, store_prices[0].store AS cheapest_store, store_prices[0].price AS min_price
ORDER BY item

商品ごとの最安値店舗一覧
が表示可能

🧠 解説
* MATCH で Store → PURCHASED_ON → Item の関係を取得
* WITH + ORDER BY で商品ごとに価格の安い順に並べる
* collect(…)[0] で最も安かった店舗と価格を取得

✅ 特定の商品だけ調べたい場合
例えば “たい” という商品に絞りたい場合

MATCH (s:Store)-[r:PURCHASED_ON]->(i:Item {name: "たい"})
RETURN s.name AS store, r.unit_price AS price
ORDER BY price ASC
LIMIT 1

これを pythonコードにすると

 vim cheapest_store.py

from neo4j import GraphDatabase
import pandas as pd

# Neo4jの接続情報を入力
uri = "bolt://localhost:7687"
auth = ("neo4j", "あなたのパスワード")  # ここを実際のパスワードに書き換えてください

driver = GraphDatabase.driver(uri, auth=auth)

def fetch_cheapest_store_per_item(tx):
    query = """
    MATCH (s:Store)-[r:PURCHASED_ON]->(i:Item)
    WITH i.name AS item, r.unit_price AS price, s.name AS store
    ORDER BY item, price ASC
    WITH item, collect({store: store, price: price}) AS store_prices
    RETURN item, store_prices[0].store AS cheapest_store, store_prices[0].price AS min_price
    ORDER BY item
    """
    result = tx.run(query)
    return [record.data() for record in result]

with driver.session() as session:
    records = session.execute_read(fetch_cheapest_store_per_item)

driver.close()

# 表形式で出力
df = pd.DataFrame(records)
print(df.to_string(index=False))

で実行

            item      cheapest_store  min_price
  COジョウエラミックスナッツ     COOP ユーコープ 袋井町店        298
      COゼリーエネルギー     COOP ユーコープ 袋井町店        118
   COゼリーエネルギー60F     COOP ユーコープ 袋井町店        178
          CO冷凍茶豆     COOP ユーコープ 袋井町店        158
   おかめ納豆 まろやか旨味ミ             ピアゴ 袋井店         97
          かつおたたき     COOP ユーコープ 袋井町店        398
       きゅうり/丸数農園             とれたて食楽部        150
      きゅうり/河井 恵美             とれたて食楽部        100
     ごぼう/とれたて食楽部             とれたて食楽部        280
            ししゃも     COOP ユーコープ 袋井町店        298
       じゃが芋/澤口正則             とれたて食楽部        250
      その他切り花/竹原秀             とれたて食楽部        210
              たい     COOP ユーコープ 袋井町店        398
    だしのきいたなめらか玉子             ピアゴ 袋井店        128
        なす/大石真智子             とれたて食楽部        260
            なす/袋     COOP ユーコープ 袋井町店        138
    はごろも シーチキンLフ           遠鉄ストア 浅羽店        338
           ぶなしめじ ドラッグストア クリエイト 袋井山名店         98
            まいたけ     COOP ユーコープ 袋井町店         88
         アーリーレッド             とれたて食楽部        230
        イチゴ/鈴木康友             とれたて食楽部        230
          イチジク習慣           遠鉄ストア 浅羽店        398
       コーヒー/濱小路徳             とれたて食楽部       1090
      コーヒー/満り路仁徳             とれたて食楽部       1090
      サンテビオ 15mL           杏林堂 袋井旭町店        968
         シュガーコーン     COOP ユーコープ 袋井町店        278
     タイレノールA 30錠             ピアゴ 袋井店       1790
      バジルMIXピッツァ     COOP ユーコープ 袋井町店        428
        バラ/大場安輝子             とれたて食楽部        300
  ビオヨーグルト プレーン加糖 ドラッグストア クリエイト 袋井山名店        178
       ピーマン/小林宗作             とれたて食楽部        140
        フィリピンバナナ     COOP ユーコープ 袋井町店        138
    フルーツフェスタアップル ドラッグストア クリエイト 袋井山名店        198
    フルーツフェスタフルーツ ドラッグストア クリエイト 袋井山名店        198
フルーツフェスタフルーツミックス ドラッグストア クリエイト 袋井山名店        198
  ブルボン デザートマルシェ白 ドラッグストア クリエイト 袋井山名店         93
     ベビーリーフ/南グロー             とれたて食楽部        120
    ボスコEVオリーブオイル     COOP ユーコープ 袋井町店        898
      ルッコラ/Gファーム             とれたて食楽部        180
       レタス/山田さち子             とれたて食楽部        140
       レタス/山田さち子             とれたて食楽部        150
   ロイヤルブレッド山型(5) ドラッグストア クリエイト 袋井山名店        139
     ロイヤルブレッド(6) ドラッグストア クリエイト 袋井山名店        139
           塩銀鮭切身     COOP ユーコープ 袋井町店        398
         大根/石原義浩             とれたて食楽部        150
          天ぷら用えび     COOP ユーコープ 袋井町店        398
      家族の潤いマスカット ドラッグストア クリエイト 袋井山名店        108
     家族の潤い白桃ミックス ドラッグストア クリエイト 袋井山名店        108
    日興フーズ 王様のチーズ ドラッグストア クリエイト 袋井山名店        298
    日興フーズ 王様のデーツ ドラッグストア クリエイト 袋井山名店        298
      明和水産 切身・丸魚             ピアゴ 袋井店        478
   明治おいしい牛乳900ml             ピアゴ 袋井店        247
      森永バター 200g ドラッグストア クリエイト 袋井山名店        458
            生豚挽肉     COOP ユーコープ 袋井町店        522
        白ねぎ/鈴木正孝             とれたて食楽部        170
           真あじ開き     COOP ユーコープ 袋井町店        398
           若鶏ムネ肉     COOP ユーコープ 袋井町店        594
       菌床椎茸/永井晃太             とれたて食楽部        260
      豚ヒレかたまりカナダ     COOP ユーコープ 袋井町店        630
             豚小間     COOP ユーコープ 袋井町店        535
            豚小間切             とれたて食楽部        336
           豚小間切れ     COOP ユーコープ 袋井町店        190
           赤魚粕漬け     COOP ユーコープ 袋井町店        398
         雑貨/小路仁徳             とれたて食楽部        390
  雪印メグミルク 牧場の朝ヨー ドラッグストア クリエイト 袋井山名店         99
     静岡そだち牛肉入りコロ             とれたて食楽部        390
           鮭・くず桜     COOP ユーコープ 袋井町店        238
    鶏卵パック MS 10個 ドラッグストア クリエイト 袋井山名店        228
   鶏卵卵パック MS 10個 ドラッグストア クリエイト 袋井山名店        228
          黄金しょうが     COOP ユーコープ 袋井町店        178

となる

SQLite と Neo4j を組み合わせgraphDB作成

Neo4j

GraphDB はノード(商品・店舗)とエッジ(購入履歴)で関係を管理できるため、例えば「特定の商品をどの店舗で買うべきか」の分析が容易になります。

データの構造(ノードとリレーション)
* ノード
* (:Item {name: “牛乳”})
* (:Store {name: “スーパーA”})
* リレーション
* (:Item)-[:SOLD_AT {price: 198, date: “2025-02-01”}]->(:Store)

Neo4j でのデータ登録

from neo4j import GraphDatabase

uri = "bolt://localhost:7687"
driver = GraphDatabase.driver(uri, auth=("neo4j", "password"))

def add_purchase(tx, item, store, price, date):
    query = """
    MERGE (i:Item {name: $item})
    MERGE (s:Store {name: $store})
    CREATE (i)-[:SOLD_AT {price: $price, date: $date}]->(s)
    """
    tx.run(query, item=item, store=store, price=price, date=date)

with driver.session() as session:
    session.write_transaction(add_purchase, "牛乳", "スーパーA", 198, "2025-02-01")

購入履歴から最適な店舗を検索

MATCH (i:Item {name: "牛乳"})-[r:SOLD_AT]->(s:Store)
RETURN s.name AS store, MIN(r.price) AS lowest_price

SQLite と GraphDB の使い分け

| 機能          | SQLite          | GraphDB              |
| ----------- | --------------- | -------------------- |
| データの保存      | 高速・軽量           | 遅め(構造化に向いている)        |
| 最安値検索       | シンプルな SQL で実装可能 | 関係性を活かした分析が得意        |
| データの可視化     | 表形式が得意          | ネットワーク分析が可能          |
| セールや価格変動の分析 | やや複雑            | 履歴をノード間リレーションで管理しやすい |

* SQLite: 基本的な購入データ管理(最安値検索・履歴保存)に向いている。

* GraphDB: 「どの店舗でどの商品を買うべきか?」を関係性で分析するのに適している。
まずは SQLite をメインに使用し、分析が必要な部分を GraphDB に移行するのが良さそう

SQLite と Neo4j を組み合わせることは可能
それぞれの役割を明確にし、データの流れを整理すれば、最適な購買管理システムを構築できる

| データ               | SQLite | Neo4j          |
| ----------------- | ------ | -------------- |
| 購入履歴(商品・価格・店舗・日付) | ✅      | ✅              |
| 最安値の検索            | ✅(SQL) | ✅(Graph Query) |
| 価格の履歴管理           | ✅      | ✅(関係性を活用)      |
| 商品と店舗の関係分析        | ❌      | ✅(ネットワーク解析)    |
| セール・特売情報との連携      | ❌      | ✅(リレーション活用)    |

SQLite は「履歴管理」「最安値検索」に適している
Neo4j は「店舗と商品の関係分析」「価格変動の視覚化」に向いている

SQLite のデータを Neo4j に同期させることで、購入履歴と分析機能を両方活用できる

現在のディレクトリは
ls
の結果

csv_files				receipts.db
import_receipts_with_dedup_and_log.py	skipped.csv
medication_summary.py

となっている

次に SqliteDBに
purchases テーブルがあることを確認

sqlite3 receipts.db 

を実行し

.tables

を実行

import_log  purchases 

とテーブルが表示されればOK

さらに構造を確認するには

 .schema purchases

とすれば

CREATE TABLE purchases (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        store TEXT,
        date TEXT,
        item TEXT,
        unit_price INTEGER,
        total_price INTEGER,
        quantity INTEGER,
        self_med_amount INTEGER,
        UNIQUE(date, store, item)
    );

というように表示される

次に
SQLite のデータを Neo4j に同期させる

現在SQLite DBで

Enter ".help" for usage hints.
sqlite> .tables
import_log  purchases 
sqlite> .schema purchases
CREATE TABLE purchases (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        store TEXT,
        date TEXT,
        item TEXT,
        unit_price INTEGER,
        total_price INTEGER,
        quantity INTEGER,
        self_med_amount INTEGER,
        UNIQUE(date, store, item)
    );

という構造になっている

これは

購入店舗名,購入年月日,商品名,単品税抜価格,価格,個数,セルフメディケーション対象金額
杏林堂 袋井旭町店,2025/06/18,サンテビオ 15mL,968,871,1,871
ピアゴ 袋井店,2025/04/18,タイレノールA 30錠,1790,1772,1,1772
COOP ユーコープ 袋井町店,2025/06/19,COゼリーエネルギー,118,354,3,0
COOP ユーコープ 袋井町店,2025/06/19,鮭・くず桜,238,190,1,0
COOP ユーコープ 袋井町店,2025/06/19,天ぷら用えび,398,238,1,0
COOP ユーコープ 袋井町店,2025/06/19,たい,398,398,1,0
COOP ユーコープ 袋井町店,2025/06/19,塩銀鮭切身,398,398,1,0
COOP ユーコープ 袋井町店,2025/06/19,真あじ開き,398,796,2,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,日興フーズ 王様のチーズ,298,298,1,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,ロイヤルブレッド山型(5),139,139,1,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,家族の潤い白桃ミックス,108,216,2,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,鶏卵卵パック MS 10個,228,228,1,0

という購入履歴をDBにしたもの

これを
SQLite と Neo4j を組み合わせ
それぞれの役割を明確にし、データの流れを整理すれば、最適な購買管理システムを構築する

なお元になるcsvファイルは毎日の買い物なので毎日更新される

目的が「毎日追加されるCSVの購入データを元に、SQLiteとNeo4jを連携して購買管理を最適化すること」

✅ システム構成の概要
項目 内容
SQLiteの役割 生データの保存。CSVから読み込んだ構造化データを記録。
→ 変更・削除・追加が簡単なローカル台帳的役割。
Neo4jの役割 関係性を活用した分析(例:購入頻度・店舗との関係・カテゴリ分析)
→ 推薦、店舗の傾向把握、消費パターン予測
CSVの流れ 日次で新しいCSVが追加 → SQLiteに取り込み → Neo4jに同期(新規 or 更新)

SQLiteスキーマ(そのままでOK)
UNIQUE(date, store, item) により同じ日・店舗・商品は1件のみ保存される(重複防止)。

CREATE TABLE purchases (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    store TEXT,
    date TEXT,
    item TEXT,
    unit_price INTEGER,
    total_price INTEGER,
    quantity INTEGER,
    self_med_amount INTEGER,
    UNIQUE(date, store, item)
);

Neo4jの構造例
以下のようにグラフ構造で格納

(:Store {name: "杏林堂 袋井旭町店"})
    -[:PURCHASED_ON {date: "2025-06-18", unit_price: 968, ...}]->
(:Item {name: "サンテビオ 15mL"})

Store ノード:店舗
Item ノード:商品
:PURCHASED_ON リレーション:購入関係と属性(日付・価格・数量など)

🔁 データフロー(毎日更新の流れ)
1. CSV読み込み → SQLiteにINSERT
2. SQLiteから新規・更新データ抽出
3. Neo4jに同期(MERGEでノードとリレーションを作成)

次に実践

まずは docker を起動する
Mac の場合

docker compose up -d

を実行しても

Cannot connect to the Docker daemon at unix:///Users/snowpool/.docker/run/docker.sock. Is the docker daemon running?

となるため
あらかじめ docker desktop を立ち上げておく必要がある

 docker compose up -d

を再度実行

[+] Running 1/1
 ✔ Container ne4j_pg-neo4j-1  Started                                      

次に

vim link_sql_in_ne04jdb.py

from neo4j import GraphDatabase
import sqlite3

# SQLiteに接続
conn = sqlite3.connect('receipts.db')
cursor = conn.cursor()

# Neo4jに接続(適宜、ユーザー名・パスワードを置き換えてください)
neo4j_driver = GraphDatabase.driver("bolt://localhost:7687", auth=("neo4j", "your_password"))

# データ取得
cursor.execute("SELECT store, date, item, unit_price, total_price, quantity, self_med_amount FROM purchases")
rows = cursor.fetchall()

def insert_to_neo4j(tx, store, date, item, unit_price, total_price, quantity, self_med_amount):
    tx.run("""
    MERGE (s:Store {name: $store})
    MERGE (i:Item {name: $item})
    MERGE (s)-[r:PURCHASED_ON {date: $date}]->(i)
    SET r.unit_price = $unit_price,
        r.total_price = $total_price,
        r.quantity = $quantity,
        r.self_med_amount = $self_med_amount
    """, store=store, date=date, item=item,
         unit_price=unit_price, total_price=total_price,
         quantity=quantity, self_med_amount=self_med_amount)

# Neo4jに書き込み
with neo4j_driver.session() as session:
    for row in rows:
        session.write_transaction(insert_to_neo4j, *row)

# 接続終了
conn.close()
neo4j_driver.close()

これを実行すると

Traceback (most recent call last):
  File "/Users/snowpool/aw10s/purcharge_history/link_sql_in_ne04jdb.py", line 1, in <module>
    from neo4j import GraphDatabase
ModuleNotFoundError: No module named 'neo4j'

となる

Pythonバージョンをアップしたのが原因

pip install neo4j

でインストール

Collecting neo4j
  Downloading neo4j-5.28.1-py3-none-any.whl.metadata (5.9 kB)
Requirement already satisfied: pytz in /Users/snowpool/.pyenv/versions/3.11.0/lib/python3.11/site-packages (from neo4j) (2024.2)
Downloading neo4j-5.28.1-py3-none-any.whl (312 kB)
Installing collected packages: neo4j
Successfully installed neo4j-5.28.1

[notice] A new release of pip is available: 24.3.1 -> 25.1.1
[notice] To update, run: pip install --upgrade pip

今度は

/Users/snowpool/aw10s/purcharge_history/link_sql_in_ne04jdb.py:31: DeprecationWarning: write_transaction has been renamed to execute_write
  session.write_transaction(insert_to_neo4j, *row)

となる

これは
write_transaction() は 非推奨(Deprecated) になり、代わりに execute_write() を使うように変更されているため

このためコード変更

# 旧(警告が出る)
session.write_transaction(insert_to_neo4j, *row)

# 新(推奨される書き方)
session.execute_write(insert_to_neo4j, *row)

このため

with neo4j_driver.session() as session:
    for row in rows:
        session.execute_write(insert_to_neo4j, *row)

と変更

| 状態     | メソッド名                 | 備考            |
| ------ | --------------------- | ------------- |
| ✅ 推奨   | `execute_write()`     | Neo4j 5.x以降向け |
| ⚠️ 非推奨 | `write_transaction()` | 古いコードとの互換性用   |

これで再度

python link_sql_in_ne04jdb.py

を実行すると
graphDBが作成される

sqliteと neo4jでやること

sqliteと neo4jでやること

最適な店舗の提案
価格比較+購入パターン分析
* 過去の購入データを分析し、最安値の店舗を提案
* 店舗ごとのセールパターンを学習
* 例:「〇〇スーパーは毎週火曜に卵が安い」「ドラッグストアでシャンプーがポイント還元率が高い」

最適な店舗の提案
① データ分析
* 過去の購入履歴を集計
* 商品ごとの平均価格
* 最安値の店舗ランキング
* 曜日ごとの傾向分析
* 「火曜にスーパーAが安い」
* 「週末にまとめ買いするとお得」
② 店舗の最適化提案
* 次回の最適な購入場所をLINE通知
* 「牛乳はスーパーB(178円)、卵はスーパーC(99円)がお得」
* 「来週火曜にドラッグストアDで洗剤のセールあり」
* Webスクレイピングで最新セール情報取得
* 各スーパー・ドラッグストアのWebチラシをチェック
* 「チラシプラス」「Shufoo!」API活用

など
SQLite では商品ごとの最安値の店舗を検索するのも簡単

GraphDB での管理
GraphDB はノード(商品・店舗)とエッジ(購入履歴)で関係を管理できるため、例えば「特定の商品をどの店舗で買うべきか」の分析が容易になります。
① GraphDB の選択肢
* Neo4j(Python なら neo4j ライブラリを利用)

データの構造(ノードとリレーション)
* ノード
* (:Item {name: “牛乳”})
* (:Store {name: “スーパーA”})
* リレーション
* (:Item)-[:SOLD_AT {price: 198, date: “2025-02-01”}]->(:Store)

購入履歴の管理と最適な店舗の提案を行う

import sqlite3

conn = sqlite3.connect("shopping.db")
cur = conn.cursor()

cur.execute("INSERT INTO purchases (date, store, item, price) VALUES (?, ?, ?, ?)",
            ("2025-02-01", "スーパーA", "牛乳", 198))

conn.commit()
conn.close()


データの追加

購入データの検索

cur.execute("SELECT * FROM purchases WHERE item = ?", ("牛乳",))
print(cur.fetchall())

また
SQLite で商品ごとの最安値の店舗を検索

cur.execute("""
    SELECT item, store, MIN(price) as min_price
    FROM purchases
    GROUP BY item
""")
print(cur.fetchall())

Neo4j でのデータ登録なら

from neo4j import GraphDatabase

uri = "bolt://localhost:7687"
driver = GraphDatabase.driver(uri, auth=("neo4j", "password"))

def add_purchase(tx, item, store, price, date):
    query = """
    MERGE (i:Item {name: $item})
    MERGE (s:Store {name: $store})
    CREATE (i)-[:SOLD_AT {price: $price, date: $date}]->(s)
    """
    tx.run(query, item=item, store=store, price=price, date=date)

with driver.session() as session:
    session.write_transaction(add_purchase, "牛乳", "スーパーA", 198, "2025-02-01")

購入履歴から最適な店舗を検索なら

MATCH (i:Item {name: "牛乳"})-[r:SOLD_AT]->(s:Store)
RETURN s.name AS store, MIN(r.price) AS lowest_price

SQLite と GraphDB の使い分け

| 機能          | SQLite          | GraphDB              |
| ----------- | --------------- | -------------------- |
| データの保存      | 高速・軽量           | 遅め(構造化に向いている)        |
| 最安値検索       | シンプルな SQL で実装可能 | 関係性を活かした分析が得意        |
| データの可視化     | 表形式が得意          | ネットワーク分析が可能          |
| セールや価格変動の分析 | やや複雑            | 履歴をノード間リレーションで管理しやすい |

結論
* SQLite: 基本的な購入データ管理(最安値検索・履歴保存)に向いている。
* GraphDB: 「どの店舗でどの商品を買うべきか?」を関係性で分析するのに適している。
まずは SQLite をメインに使用し、分析が必要な部分を GraphDB に移行するのが良さそう

sqlite と neo4jを組み合わせることはできるか?
SQLite と Neo4j を組み合わせることは可能
れぞれの役割を明確にし、データの流れを整理すれば、最適な購買管理システムを構築できる

SQLite と Neo4j の役割分担

| データ               | SQLite | Neo4j          |
| ----------------- | ------ | -------------- |
| 購入履歴(商品・価格・店舗・日付) | ✅      | ✅              |
| 最安値の検索            | ✅(SQL) | ✅(Graph Query) |
| 価格の履歴管理           | ✅      | ✅(関係性を活用)      |
| 商品と店舗の関係分析        | ❌      | ✅(ネットワーク解析)    |
| セール・特売情報との連携      | ❌      | ✅(リレーション活用)    |

SQLite は「履歴管理」「最安値検索」に適している
Neo4j は「店舗と商品の関係分析」「価格変動の視覚化」に向いている

SQLite のデータを Neo4j に同期させることで、購入履歴と分析機能を両方活用できる

SQLite から Neo4j にデータを移行
Neo4j は APOC(ライブラリ)を使って外部データをインポートできる

まずSQLite のデータを取得

import sqlite3
import pandas as pd

# SQLiteに接続
conn = sqlite3.connect("shopping.db")
df = pd.read_sql("SELECT date, store, item, price FROM purchases", conn)
conn.close()


次にNeo4j にデータを挿入
from neo4j import GraphDatabase

# Neo4jの接続情報
uri = "bolt://localhost:7687"
driver = GraphDatabase.driver(uri, auth=("neo4j", "password"))

# データ挿入関数
def add_purchase(tx, date, store, item, price):
    query = """
    MERGE (i:Item {name: $item})
    MERGE (s:Store {name: $store})
    CREATE (i)-[:SOLD_AT {price: $price, date: $date}]->(s)
    """
    tx.run(query, item=item, store=store, price=price, date=date)

# SQLite のデータを Neo4j に登録
with driver.session() as session:
    for _, row in df.iterrows():
        session.write_transaction(add_purchase, row["date"], row["store"], row["item"], row["price"])

ここまでできたら
Neo4j でのデータ分析
Neo4j を使うことで、「この商品はどの店舗が安いか?」 などの分析が容易になる

商品ごとの最安値店舗を検索

MATCH (i:Item)-[r:SOLD_AT]->(s:Store)
WITH i, s, r
ORDER BY r.price ASC
RETURN i.name AS 商品, s.name AS 最安店舗, r.price AS 価格

ある店舗の価格変動を可視化

MATCH (i:Item)-[r:SOLD_AT]->(s:Store)
RETURN i.name AS 商品, COUNT(s) AS 店舗数, AVG(r.price) AS 平均価格
ORDER BY 平均価格 ASC

特定の商品の購入パターン分析

MATCH (i:Item)-[r:SOLD_AT]->(s:Store)
RETURN i.name AS 商品, COUNT(s) AS 店舗数, AVG(r.price) AS 平均価格
ORDER BY 平均価格 ASC

次に
SQLite と Neo4j の同期方法

CSVファイルをSqlite にインポートする

CSVファイルをSqlite にインポートする

最初に

import sqlite3
import csv

# CSVとDBファイルのパス
csv_file = 'receipts.csv'
db_file = 'receipts.db'

# データベース接続とテーブル作成
conn = sqlite3.connect(db_file)
cursor = conn.cursor()

cursor.execute('''
    CREATE TABLE IF NOT EXISTS purchases (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        date TEXT,
        store TEXT,
        item TEXT,
        price INTEGER,
        quantity INTEGER
    )
''')

# CSV読み込み&データ挿入
with open(csv_file, newline='', encoding='utf-8') as f:
    reader = csv.DictReader(f)
    for row in reader:
        cursor.execute('''
            INSERT INTO purchases (date, store, item, price, quantity)
            VALUES (?, ?, ?, ?, ?)
        ''', (row['日付'], row['店舗'], row['商品名'], int(row['単価']), int(row['数量'])))

conn.commit()
conn.close()

print("CSVファイルからSQLiteにデータを取り込みました。")

としたけど
毎日買い物するためCSVファイルは 20250618_create.csv のように ファイル名は 日付_店舗名.csv となる このファイルが複数になる

✅ 目的
すべての *.csv をスキャンし、SQLiteに取り込む。

なので

./csv_files/

から取り込むようにするので

mkdir csv_files
mv *.csv csv_files 

で移動しておく

そしてこれを取り込むようにコード変更

import sqlite3
import csv
import os
import re

# フォルダとDBファイルのパス
csv_folder = './csv_files'
db_file = 'receipts.db'

# データベース接続とテーブル作成
conn = sqlite3.connect(db_file)
cursor = conn.cursor()

cursor.execute('''
    CREATE TABLE IF NOT EXISTS purchases (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        date TEXT,
        store TEXT,
        item TEXT,
        price INTEGER,
        quantity INTEGER
    )
''')

# ファイル名のパターン: 20250618_イオン.csv
pattern = re.compile(r"(\d{8})_(.+)\.csv")

# フォルダ内のCSVファイルをループ処理
for filename in os.listdir(csv_folder):
    match = pattern.match(filename)
    if not match:
        continue  # 無効なファイル名はスキップ

    date = match.group(1)
    store = match.group(2)
    filepath = os.path.join(csv_folder, filename)

    with open(filepath, newline='', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        for row in reader:
            try:
                cursor.execute('''
                    INSERT INTO purchases (date, store, item, price, quantity)
                    VALUES (?, ?, ?, ?, ?)
                ''', (
                    date,
                    store,
                    row['商品名'],
                    int(row['単価']),
                    int(row['数量'])
                ))
            except Exception as e:
                print(f"[エラー] {filename} の {row} に問題があります: {e}")

conn.commit()
conn.close()
print("すべてのCSVファイルをSQLiteに取り込みました。")

✅ 今後追加可能な機能
* すでに取り込んだCSVファイルを記録してスキップ(import_log.dbなどで管理)
* 重複チェック(同じ日付・店舗・商品・価格が存在すればスキップ)

すでに取り込んだCSVファイルを記録してスキップ(import_log.dbなどで管理) 重複チェック(同じ日付・店舗・商品・価格が存在すればスキップ) を追加する

✅ 追加機能の詳細
1. 取り込み済みCSVファイルを記録してスキップ
 → 別テーブル import_log を用意し、同じファイルは2回処理しないようにする。
2. 同一商品の重複データ(同じ日付+店舗+商品+単価)をスキップ
 → purchases テーブルに一意制約(UNIQUE)を追加。

これで

import sqlite3
import csv
import os
import re

# フォルダとDBファイルのパス
csv_folder = './csv_files'
db_file = 'receipts.db'

# データベース接続とテーブル作成
conn = sqlite3.connect(db_file)
cursor = conn.cursor()

# 商品データ用テーブル(重複防止のためUNIQUE制約付き)
cursor.execute('''
    CREATE TABLE IF NOT EXISTS purchases (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        date TEXT,
        store TEXT,
        item TEXT,
        price INTEGER,
        quantity INTEGER,
        UNIQUE(date, store, item, price)
    )
''')

# CSVインポート済みファイルの記録テーブル
cursor.execute('''
    CREATE TABLE IF NOT EXISTS import_log (
        filename TEXT PRIMARY KEY
    )
''')

# ファイル名のパターン: 20250618_イオン.csv
pattern = re.compile(r"(\d{8})_(.+)\.csv")

# フォルダ内のCSVファイルをループ処理
for filename in os.listdir(csv_folder):
    match = pattern.match(filename)
    if not match:
        continue  # 無効なファイル名はスキップ

    # すでに取り込み済みかチェック
    cursor.execute('SELECT 1 FROM import_log WHERE filename = ?', (filename,))
    if cursor.fetchone():
        print(f"[スキップ] すでに取り込み済み: {filename}")
        continue

    date = match.group(1)
    store = match.group(2)
    filepath = os.path.join(csv_folder, filename)

    with open(filepath, newline='', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        inserted = 0
        for row in reader:
            try:
                cursor.execute('''
                    INSERT OR IGNORE INTO purchases (date, store, item, price, quantity)
                    VALUES (?, ?, ?, ?, ?)
                ''', (
                    date,
                    store,
                    row['商品名'],
                    int(row['単価']),
                    int(row['数量'])
                ))
                if cursor.rowcount > 0:
                    inserted += 1
            except Exception as e:
                print(f"[エラー] {filename} の {row} に問題があります: {e}")

    # 成功したらimport_logに記録
    cursor.execute('INSERT OR IGNORE INTO import_log (filename) VALUES (?)', (filename,))
    print(f"[完了] {filename} を取り込みました(新規 {inserted} 件)")

conn.commit()
conn.close()
print("すべてのCSVファイルの取り込みが完了しました。")

とする

そして
セルフメディケーション対応商品の商品名と金額、今年購入した合計金額を表示

✅ やりたいことまとめ
* 商品名に「セルフメディケーション対象」とわかるものをリストアップ(例:風邪薬、胃腸薬など)
* 購入履歴のうち、対象商品の2025年の購入金額合計を商品別に表示
* 合計金額も出す

DBで購入管理したい そのために 購入店舗名 購入年月日 商品名 価格 個数 をCSVファイルにしたい また食品なども同時に購入することがあるため セルフメディケーション対象商品を識別できるようにしたい

最初にGPTで提案してきたのは
対象商品のリスト(キーワードまたは手動リスト)
 たとえば以下のように事前定義します:

self_medication_keywords = [
    "風邪薬", "解熱剤", "鎮痛剤", "胃薬", "整腸剤", "花粉症", "鼻炎薬", "目薬", "かゆみ止め", "湿布", "トローチ"
]

これだとリストを手動で追加する必要がある

DBで購入管理したい そのために 購入店舗名 購入年月日 商品名 価格 個数 をCSVファイルにしたい また食品なども同時に購入することがあるため セルフメディケーション対象商品を識別できるようにしたい 現在購入している店舗ては ◆ もしくは セ] が商品名の先頭についている

この場合、◆をCSVにいれる以外の方法で何かあるか?
に対して

方法1:**商品名とは別に「分類」カラムを持つ(推奨)
方法2:対象商品のリストを別ファイル or 別テーブルで管理する
方法3:商品名はそのまま、別フラグ列 is_self_med を追加

✅ 結論:どの方法がいい?
方法 長所 短所 おすすめ度
方法1: 分類列 視認性と拡張性◎、CSVで完結 手入力ミスの可能性 ⭐⭐⭐⭐☆
方法2: マスタ マスタ更新で一括制御できる(大量商品向け) 初期セットアップが必要 ⭐⭐⭐⭐☆
方法3: フラグ 分類が2値なら簡単 他カテゴリ分類に弱い ⭐⭐⭐☆☆
方法0: 記号 手軽・一目でわかる データが汚れ、分類が混在する可能性 ⭐⭐☆☆☆

が回答

結論として

購入店舗名,購入年月日,商品名,単品税抜価格,価格,個数,セルフメディケーション対象金額
ドラッグストア クリエイト 袋井山名店,2025/06/21,ロイヤルブレッド(6),139,139,1,0
ドラッグストア クリエイト 袋井山名店,2025/06/21,日興フーズ 王様のデーツ,298,298,1,0
ドラッグストア クリエイト 袋井山名店,2025/06/21,家族の潤いマスカット,108,108,1,0
ドラッグストア クリエイト 袋井山名店,2025/06/21,家族の潤い白桃ミックス,108,216,2,0
ドラッグストア クリエイト 袋井山名店,2025/06/21,ビオヨーグルト プレーン加糖,178,356,2,0
ドラッグストア クリエイト 袋井山名店,2025/06/21,雪印メグミルク 牧場の朝ヨー,99,198,2,0
ドラッグストア クリエイト 袋井山名店,2025/06/21,ブルボン デザートマルシェ白,93,278,3,0
ドラッグストア クリエイト 袋井山名店,2025/06/21,フルーツフェスタフルーツミックス,198,396,2,0

というように
カラムの追加とその値に金額を入れることにした

CSVデータ作成を依頼すると重複データを送られることがある。
実際のデータレシートは1枚のため
同一のレシート内容がある場合、削除する機能が欲しい

ハッシュで重複検出
をGPTは提案してくるが
CSVファイル全体の内容が完全一致した場合だけ「重複」とみなしてスキップ
つまり差分のチェックはこれだとできない

基本的に購入管理なので商品名と日付の判定で十分。 同じ日付で同一店舗で複数購入することはまず無いため
これを元に再度作成する

✔ 重複判定キー:
sql
コピーする編集する
UNIQUE(date, store, item)
これにより、
* 同じ日付
* 同じ店舗
* 同じ商品名
が1回しか登録されないようになる

✅ 補足:複数買い(同じ商品を同日に2回買う)を防ぎたい場合
万が一「同日に同じ商品を複数回買う」ことが発生してしまうと、2回目は登録されません(意図どおり)。
でもその場合も quantity や total_price を工夫すれば、「まとめて登録したことにできる」ので問題ありません。

ということ

この制約で動くインポートスクリプト全体を提供し
重複スキップされた商品名を CSV記録する

✅ 要件まとめ
* csv_files/ フォルダに複数のCSVがあり、すべて処理対象
* CSV形式:

購入店舗名,購入年月日,商品名,単品税抜価格,価格,個数,セルフメディケーション対象金額

* UNIQUE制約:購入店舗名, 購入年月日, 商品名 の3つで一意
* 重複データは登録せずスキップ
* スキップされた商品情報をCSVに記録(例:skipped.csv)

 vim import_receipts_with_dedup_and_log.py

でファイルを作成

import sqlite3
import csv
import os
from datetime import datetime
import re

# 設定
csv_folder = './csv_files'
db_file = 'receipts.db'
skipped_log = 'skipped.csv'

# SQLite接続
conn = sqlite3.connect(db_file)
cursor = conn.cursor()

# purchasesテーブル(UNIQUE制約付き)
cursor.execute('''
    CREATE TABLE IF NOT EXISTS purchases (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        store TEXT,
        date TEXT,
        item TEXT,
        unit_price INTEGER,
        total_price INTEGER,
        quantity INTEGER,
        self_med_amount INTEGER,
        UNIQUE(date, store, item)
    )
''')

# import_logテーブル
cursor.execute('''
    CREATE TABLE IF NOT EXISTS import_log (
        filename TEXT PRIMARY KEY
    )
''')

# skipped.csv が存在していたら上書き、なければ新規作成
with open(skipped_log, 'w', newline='', encoding='utf-8') as skipfile:
    skip_writer = csv.writer(skipfile)
    skip_writer.writerow(['購入店舗名', '購入年月日', '商品名', '単品税抜価格', '価格', '個数', 'セルフメディケーション対象金額'])

    for filename in os.listdir(csv_folder):
        if not filename.endswith('.csv'):
            continue

        filepath = os.path.join(csv_folder, filename)

        # ファイルの重複チェック
        cursor.execute('SELECT 1 FROM import_log WHERE filename = ?', (filename,))
        if cursor.fetchone():
            print(f"[スキップ] {filename}(すでにインポート済み)")
            continue

        print(f"[処理中] {filename}")
        try:
            with open(filepath, newline='', encoding='utf-8') as f:
                reader = csv.DictReader(f)
                for row in reader:
                    store = row['購入店舗名'].strip()
                    date = datetime.strptime(row['購入年月日'], '%Y/%m/%d').strftime('%Y%m%d')
                    item = row['商品名'].strip()
                    unit_price = int(row['単品税抜価格'])
                    total_price = int(row['価格'])
                    quantity = int(row['個数'])
                    self_med_amount = int(row['セルフメディケーション対象金額'])

                    try:
                        cursor.execute('''
                            INSERT INTO purchases
                            (store, date, item, unit_price, total_price, quantity, self_med_amount)
                            VALUES (?, ?, ?, ?, ?, ?, ?)
                        ''', (store, date, item, unit_price, total_price, quantity, self_med_amount))
                    except sqlite3.IntegrityError:
                        # 重複データをskipped.csvに記録
                        skip_writer.writerow([
                            store, row['購入年月日'], item,
                            unit_price, total_price, quantity, self_med_amount
                        ])

            # ファイル名をインポート済みとして記録
            cursor.execute('INSERT INTO import_log (filename) VALUES (?)', (filename,))
            print(f"[完了] {filename} をインポートしました。")

        except Exception as e:
            print(f"[エラー] {filename}: {e}")

conn.commit()
conn.close()
print("✅ すべてのCSVの処理が完了しました。スキップされた商品は skipped.csv に保存されています。")

これを実行したら

 python import_receipts_with_dedup_and_log.py
[処理中] 20250621.csv
[エラー] 20250621.csv: time data '購入年月日' does not match format '%Y/%m/%d'
[処理中] 20240618-19.csv
[完了] 20240618-19.csv をインポートしました。
[処理中] 20250612.csv
[完了] 20250612.csv をインポートしました。
[処理中] 20250613.csv
[完了] 20250613.csv をインポートしました。
[処理中] 20250614-19.csv
[完了] 20250614-19.csv をインポートしました。
[処理中] 20250605.csv
[完了] 20250605.csv をインポートしました。
[処理中] 20250611.csv
[完了] 20250611.csv をインポートしました。
✅ すべてのCSVの処理が完了しました。スキップされた商品は skipped.csv に保存されています。

となる

cat skipped.csv 

で中身を確認

購入店舗名,購入年月日,商品名,単品税抜価格,価格,個数,セルフメディケーション対象金額
杏林堂 袋井旭町店,2025/06/18,サンテビオ 15mL,968,871,1,871
ピアゴ 袋井店,2025/04/18,タイレノールA 30錠,1790,1772,1,1772
COOP ユーコープ 袋井町店,2025/06/19,COゼリーエネルギー,118,354,3,0
COOP ユーコープ 袋井町店,2025/06/19,鮭・くず桜,238,190,1,0
COOP ユーコープ 袋井町店,2025/06/19,天ぷら用えび,398,238,1,0
COOP ユーコープ 袋井町店,2025/06/19,たい,398,398,1,0
COOP ユーコープ 袋井町店,2025/06/19,塩銀鮭切身,398,398,1,0
COOP ユーコープ 袋井町店,2025/06/19,真あじ開き,398,796,2,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,日興フーズ 王様のチーズ,298,298,1,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,ロイヤルブレッド山型(5),139,139,1,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,家族の潤い白桃ミックス,108,216,2,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,鶏卵卵パック MS 10個,228,228,1,0

で調べるとスキップされている

 cat csv_files/20240618-19.csv 
で中身を確認
購入店舗名,購入年月日,商品名,単品税抜価格,価格,個数,セルフメディケーション対象金額
杏林堂 袋井旭町店,2025/06/18,サンテビオ 15mL,968,871,1,871
ピアゴ 袋井店,2025/04/18,タイレノールA 30錠,1790,1772,1,1772
COOP ユーコープ 袋井町店,2025/06/19,COゼリーエネルギー,118,354,3,0
COOP ユーコープ 袋井町店,2025/06/19,鮭・くず桜,238,190,1,0
COOP ユーコープ 袋井町店,2025/06/19,天ぷら用えび,398,238,1,0
COOP ユーコープ 袋井町店,2025/06/19,たい,398,398,1,0
COOP ユーコープ 袋井町店,2025/06/19,塩銀鮭切身,398,398,1,0
COOP ユーコープ 袋井町店,2025/06/19,真あじ開き,398,796,2,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,日興フーズ 王様のチーズ,298,298,1,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,ロイヤルブレッド山型(5),139,139,1,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,家族の潤い白桃ミックス,108,216,2,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,鶏卵卵パック MS 10個,228,228,1,0

で調べると

これは row[‘購入年月日’] の値が本来 “2025/06/18” のような日付になるはずのところ、
誤って “購入年月日”(つまりヘッダーの文字列)が入っているため、datetime.strptime() が失敗しています。
とのこと

CSVファイル内に ヘッダー行がもう一度繰り返されている可能性が非常に高いです(コピペミス、マージ時の事故など)。

✅ 解決策:誤って混入したヘッダー行をスキップ
以下の1行を for row in reader: の直後に追加

# ヘッダー行がデータ行に混入していたらスキップ
if row['購入年月日'] == '購入年月日':
    continue

これを処理に追加して

with open(filepath, newline='', encoding='utf-8') as f:
    reader = csv.DictReader(f)
    for row in reader:
        if row['購入年月日'] == '購入年月日':
            continue  # ← ここで2回目以降のヘッダー行を除外

        store = row['購入店舗名'].strip()
        date = datetime.strptime(row['購入年月日'], '%Y/%m/%d').strftime('%Y%m%d')
        ...

とすることで
✅ 結果
* ✔ ヘッダーが再登場しても自動でスキップ
* ✔ 異常な日付データがあってもクラッシュせず処理継続

保存して再度実行

 python import_receipts_with_dedup_and_log.py
[処理中] 20250621.csv
[完了] 20250621.csv をインポートしました。
[スキップ] 20240618-19.csv(すでにインポート済み)
[スキップ] 20250612.csv(すでにインポート済み)
[スキップ] 20250613.csv(すでにインポート済み)
[スキップ] 20250614-19.csv(すでにインポート済み)
[スキップ] 20250605.csv(すでにインポート済み)
[スキップ] 20250611.csv(すでにインポート済み)
✅ すべてのCSVの処理が完了しました。スキップされた商品は skipped.csv に保存されています。

となって無事に成功

次に
セルフメディケーション対象商品の合計金額(2025年) 商品別セルフメディケーション金額ランキング を表示したい

SQLiteに保存された購入データ(テーブル名:purchases)から、以下の2つを表示

セルフメディケーション対象商品の合計金額(2025年)

SELECT SUM(self_med_amount) AS total_amount
FROM purchases
WHERE date BETWEEN '20250101' AND '20251231'
  AND self_med_amount > 0;

商品別セルフメディケーション金額ランキング(2025年)

SELECT item, SUM(self_med_amount) AS total_amount
FROM purchases
WHERE date BETWEEN '20250101' AND '20251231'
  AND self_med_amount > 0
GROUP BY item
ORDER BY total_amount DESC;

これがSQLになる

 vim medication_summary.py
import sqlite3

# SQLiteに接続
conn = sqlite3.connect("receipts.db")
cursor = conn.cursor()

print("📋 セルフメディケーション対象商品の合計金額(2025年)")
cursor.execute('''
    SELECT SUM(self_med_amount)
    FROM purchases
    WHERE date BETWEEN '20250101' AND '20251231'
      AND self_med_amount > 0
''')
total = cursor.fetchone()[0] or 0
print(f"▶ 合計金額:¥{total:,}")
print()

print("📊 商品別セルフメディケーション対象金額ランキング(2025年)")
cursor.execute('''
    SELECT item, SUM(self_med_amount) AS total
    FROM purchases
    WHERE date BETWEEN '20250101' AND '20251231'
      AND self_med_amount > 0
    GROUP BY item
    ORDER BY total DESC
''')

rows = cursor.fetchall()
if not rows:
    print("(対象商品はありません)")
else:
    print("商品名\t\t金額")
    print("-" * 30)
    for item, amount in rows:
        print(f"{item[:14]:<14} ¥{amount:,}")

conn.close()

これを実行すると

python medication_summary.py 
📋 セルフメディケーション対象商品の合計金額(2025年)
▶ 合計金額:¥2,643

📊 商品別セルフメディケーション対象金額ランキング(2025年)
商品名		金額
------------------------------
タイレノールA 30錠    ¥1,772
サンテビオ 15mL     ¥871

となる