経済指標カレンダーの作成と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ファイルを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

となる

SQLite のデータのバックアップと復元

SQLite のデータのバックアップと復元
#18 データのバックアップ・復元
http://dotinstall.com/lessons/basic_sqlite/6418
を参考に
テーブル構造、データを外部ファイルに出力し
それを取り込む方法を学習
今回使用するのは
.dump
これはDBの内容をSQL文で出力する
バックアップを取るのに便利
テーブルだけバックアップするなら
.dump テーブル名
すべてのテーブルをバックアップするなら
テーブル名を指定せず
.dump
とすればOK
これらを直接ファイルに出力するには
出力先を変更する
出力先の設定確認は
.show
で行える
現在の設定は
echo: off
explain: off
headers: on
mode: list
nullvalue: “”
output: stdout
separator: “,”
stats: off
width:

出力先の設定部分は
output: stdout
になり、意味は画面表示
これを変更することで
ファイルに出力できるようになる
変更は
.output 出力するファイル名
今回は
.output data.dump
として
.dump data
と実行すると
data テーブルの中身が
SQL文でファイルに記述される
data.dump ファイルの中身を
cat data.dump で確認すると
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE data(name,score,team_text);
INSERT INTO “data” VALUES(‘hebereke’,1000,’A’);
INSERT INTO “data” VALUES(‘hiroshi’,400,’C’);
INSERT INTO “data” VALUES(‘tomonaga’,400,’B’);
COMMIT;
となっているのが確認できる
ちなみに、
.output の出力先を ファイルにした場合
.show などの結果もファイルに出力されてしまう
この変更は、一度
.exit で sqlite を抜けるか
もしくは
.output stdout
とすれば、元に戻る
ちなみに、ファイルに出力されたSQL文は
vim などで編集することが可能
変更後、再度読み込みしたいときには
.read
を使う
今回なら
.read data.dump
でOK
ただし、元のデータがあると、すでに存在していますという
エラーがでてしまうので、一度
drop table data;
.read data.dump
で読み込みする
SQL文にする使い道で一番大きいのは
カラムの編集
SQLite にはカラムを削除できないので
.dump で一度出力して
vim などでSQL文を修正し
失敗したテーブルを
drop table テーブル名
で削除
そして、テーブル作成のSQLを修正した dump ファイルを
.read で読み込んで修正というやりかたが使える

SQLite で外部ファイルからデータ読み込み

SQLite で外部ファイルからデータ読み込み
#17 外部ファイルからデータを取り込む
http://dotinstall.com/lessons/basic_sqlite/6417
を参考に、外部ファイルからデータの読み込みを学習
これにより、mysql で .sql ファイルからデータを
読み込んで実行するようなことができる
今回は、users.txt というテキストファイルに
データを記述
まず、vim でも gedit でもOKなので
データを書いて
users.txt というファイルで保存
中身は
taguchi,200
sasaki,400
yamashita,120
hebereke,800
また、新しいデータをとりこむため
練習テーブルも作成する
create table infile(name,score);
で infileテーブルと
name
score というカラムを作成
このテーブルに
users.txt の中身をいれるには
.import を使う
ただし、その前に区切り文字を指定しておく
デフォルトだと
データの区切り文字は | となっているけど
users.txt の中身のデータは , で区切ってあるので
これを変更する
現在の設定は
.show
でみることができる
ちなみに、現在は
echo: off
explain: off
headers: off
mode: list
nullvalue: “”
output: stdout
separator: “|”
stats: off
width:
これをみると、区切り文字を意味する
separator: “|”
となっているので、これを変更する
| から , に変更するには
.separator ,
とすればOK
.show
で確認すると
echo: off
explain: off
headers: off
mode: list
nullvalue: “”
output: stdout
separator: “,”
stats: off
width:
となり
separator: “,” に変更されている
この状態で
.import 読み込むファイル テーブル名
とすると
データを入れることができる
今回は users.txt を infile テーブルに入れるので
.import users.txt infile
select* from infile;
で確認すると
taguchi,200
sasaki,400
yamashita,120
hebereke,800
というように、反映されているのが確認できる
あと、設定で変更しておくと便利なのが
.header on
としておくと
デフォルトでは
select* from infile;
を実行すると
taguchi,200
sasaki,400
yamashita,120
hebereke,800
だけど
select* from infile;
name,score
taguchi,200
sasaki,400
yamashita,120
hebereke,800
というように、一番上にカラム名が表示されるようになる

SQLite での複数テーブルの取り扱い
#16 複数のテーブルを扱ってみよう
http://dotinstall.com/lessons/basic_sqlite/6416
を参考に
複数のテーブルからデータを抽出、集計する方法について学習
まずは、必要なテーブルなどを作成
create table games(user_id,score);
create table player(id integer primary key autoincrement,name,team);
次に
insert into player(name,team) values(‘taguchi’,’A’);
insert into player(name,team) values(‘sasaki’,’B’);
insert into player(name,team) values(‘himura’,’B’);
insert into player(name,team) values(‘yoshino’,’B’);
でデータ入力
確認は
select * from player;
次に、ゲームデータも挿入しておく
insert into games(user_id,score) values(1,200);
insert into games(user_id,score) values(1,300);
insert into games(user_id,score) values(1,20);
insert into games(user_id,score) values(1,220);
insert into games(user_id,score) values(1,120);
insert into games(user_id,score) values(2,50);
insert into games(user_id,score) values(2,40);
insert into games(user_id,score) values(3,40);
insert into games(user_id,score) values(3,120);
insert into games(user_id,score) values(3,140);
insert into games(user_id,score) values(4,140);
insert into games(user_id,score) values(4,10);
insert into games(user_id,score) values(4,100);
最後に、select * from games;
で内容を確認
これで準備OK
これで、ユーザごとに合計点数を集計する
複数テーブルにまたがる場合
select id, name, team, sum(score) from player, games where player.id = games.user_id group by player.id;
というように実行する
構文としては
select 選択するカラム名を,で区切って複数指定
from テーブル名を,で区切って複数指定
where 検索条件
というかんじになる
複数テーブルにまたがるときには
まずカラム名を指定するけど
重複する場合は
テーブル名.カラム名というように書いて指定する
今回の場合なら
games.user_id
とか
games.name
というように、
テーブル名とカラムを . でつなげて書く
そして、今回の where の条件は
player テーブルのid

games テーブルの user_id
が同じものを集計したいので
where
player.id = games.user_id
今回は user_id で集計をしたいので
group by player.id;
となる
group by は ~でという意味
select id, name, team, sum(score) from player, games where player.id = games.user_id group by player.id;

実行結果は
1|taguchi|A|860
2|sasaki|B|90
3|himura|B|300
4|yoshino|B|250
となる
これは、player のIDが人の番号で
games の user_id も人の番号となっているんで
連携しているので簡単に集計ができている

SQLite のデータ削除

SQLite のデータ削除
#15 データを削除してみよう
http://dotinstall.com/lessons/basic_sqlite/6415
を参考に
SQLite でのデータ削除方法を学習
データ削除には
delete を使う
delete from data where score <=100; というように score が 100 以下のものを削除するように実行すると hebereke|1000|A sasaki|100|B hiroshi|400|C hiyano|500|A tomonaga|400|B が hebereke|1000|A hiroshi|400|C hiyano|500|A tomonaga|400|B というように 消えているのが確認できます 書式にすると delete from テーブル名 where 消す条件 where をつけずに実行すると テーブルの中身がすべて消えるので注意 例えば select * from user; で taguchi|200||| sasaki|100||| hiroshi|400||| hiyano|500||| ||A|| ||B|| higuchi||||2013-06-03 10:48:19 となっているのを delete from user ; とすると すべて消えます select * from user; を実行しても、空なので何もでてきません あと、SQLite で覚えておくと便利なのが ROWID これは、中のデータを連番で管理している番号を表示するもの select ROWID, * from テーブル名; とすると、番号つきで表示される select ROWID, * from data; 1|hebereke|1000|A 3|hiroshi|400|C 4|hiyano|500|A 5|tomonaga|400|B というかんじ この ROWID の使い道は、where で消したいものが うまく指定できないときに使う delete from data where ROWID=4; とすると 1|hebereke|1000|A 3|hiroshi|400|C 5|tomonaga|400|B となり ROWID=4 のところだけ消える

SQLite のデータ更新方法

SQLite のデータ更新方法
#14 データを更新してみよう
http://dotinstall.com/lessons/basic_sqlite/6414
を参考に
SQLite のデータ更新方法を学習
更新には update を使う
update data set name =’dotinstall_taguchi’ where name=’taguchi’;
というようにすれば
name=’taguchi’
の部分だけ
name =’dotinstall_taguchi’
となる
このときに、where で条件を指定しないと
名前がみなdotinstall_taguchi になってしまうので注意
構文にすると
update テーブル名 set カラム名 =新しい値 where 対象のカラムとデータ
となる
これにより
taguchi|200|A
sasaki|100|B
hiroshi|400|C
hiyano|500|A
tomonaga|400|B
から
dotinstalltaguchi|200|A
sasaki|100|B
hiroshi|400|C
hiyano|500|A
tomonaga|400|B
に変更される
name だけでなく
name と score というように、複数のカラムの変更も可能
その場合には
カラムを , で区切って実行する
update data set name=’hebereke’,score=1000 where name=’dotinstalltaguchi’;
というように、
dotinstalltaguchi のところを
name=hebereke
score=1000
に修正してみた
select * from data;
で結果を確認してみると
hebereke|1000|A
sasaki|100|B
hiroshi|400|C
hiyano|500|A
tomonaga|400|B
となっていて、修正できているのが確認できる
これにより、値の修正が可能なので
間違えて入力したり
ゲームに使う時に値を変更しながらDBへ格納することが
可能になる

SQLite での日付、時刻の取り扱い

SQLite での日付、時刻の取り扱い
#13 日付・時刻を扱ってみよう
http://dotinstall.com/lessons/basic_sqlite/6413
を参考に
日付、時刻について学習
SQLite では、いくつかの定数で時刻を得ることができる
select current_time;
で現在時刻の取得
select current_date;
で現在の日付
select current_timestamp;
で現在の日付と時刻が表示できる
試しに
create table date(name,crated);
でテーブルを作成して
insert into date(name,crated) values(‘higuchi’,current_timestamp);
でデータ挿入
select * from date;
higuchi|2013-06-03 10:52:36
というように、現在時刻が入っていることが確認できる
日付関連リファレンスは
http://www.sqlite.org/lang_datefunc.html
を参考に
この中で一番使われるのが
strftime(format, timestring, modifier, modifier, …)
というタイプ
指定できるフォーマットは
%d
day of month: 00
%f
fractional seconds: SS.SSS
%H
hour: 00-24
%j
day of year: 001-366
%J
Julian day number
%m
month: 01-12
%M
minute: 00-59
%s
seconds since 1970-01-01
%S
seconds: 00-59
%w
day of week 0-6 with Sunday==0
%W
week of year: 00-53
%Y
year: 0000-9999
%% %
これらのフォーマットを利用して
current_timestamp を日本語表記にするには
strftime(‘%Y年’,current_timestamp)
というようにする
select current_timestamp;
の場合
2013-06-03 11:01:44
となるけど
select strftime(‘%Y年’,current_timestamp);
とすると
2013年
となる
時刻の取得関連はゲーム関連で使うことが多いので
覚えておくと便利

SQLite のデータ集計

SQLite のデータ集計
#12 データを集計してみよう
http://dotinstall.com/lessons/basic_sqlite/6412
を参考に、データ集計に使う関数を学習
すでに作成してある user テーブルに
team test
というカラムを追加したいので
alter table user add column team text;
として
alter table テーブル名 add column カラム名;
としてカラムを追加できる
あとは、追加したカラムに値を追加する
データ追加は insert を使う
しかし、
insert into user(team text) values(‘A’);
とすると
Error: near “text”: syntax error
となるので、別のテーブルを作成し、データを追加
create table data(name,score,team_text);
でテーブルを作成し
insert into data(name,score,team_text) values(‘taguchi’,200,’A’);
insert into data(name,score,team_text) values(‘sasaki’,100,’B’);
insert into data(name,score,team_text) values(‘hiroshi’,400,’C’);
insert into data(name,score,team_text) values(‘hiyano’,500,’A’);
insert into data(name,score,team_text) values(‘tomonaga’,400,’B’);
でデータ挿入
確認のため
select * from data;
で中身を確認
taguchi|200|A
sasaki|100|B
hiroshi|400|C
hiyano|500|A
tomonaga|400|B
これで準備はOK
これで実践開
select distinct とすると、ユニークな値を抽出する
select distinct team_text from data;
とすると、team_text の中から
A
B
C
というように、チームをわけて出力することができる
つまり、被らないように出力する
使い道としては識別するときに使う
次にグループごとのデータの集計
集計には Excel とおなじように
sum() で行うことができる
また、グループわけして行いたいのなら
group by
を使う
今回は
select team_text ,sum(score) from data group by team_text;
内容は
select team_text ,sum(score) from data

data テーブルにある
team_text の score の値を合計する
group by team_text;
で、team_text ごとに
という意味
group by は
~ごとに
というかんじで覚えるとわかりやすい
もともとのデータが
taguchi|200|A
sasaki|100|B
hiroshi|400|C
hiyano|500|A
tomonaga|400|B
なので、
select team_text ,sum(score) from data group by team_text;
でチームごとの合計点にすると
A|700
B|500
C|400
となる
ゲームや、成績表など、いろいろと用途はあるので
覚えておくと便利