[Python]アマチュア無線のログをCSVやADIFに変換してみた（2）ログCSVからAirHamLog形式CSVへ

## はじめに

&emsp;本稿は以下記事の続きになります。

https://qiita.com/Hagian/items/5fde6bbe525bd71569ce

## 概要

&emsp;本稿では、前回作成したアマチュア無線のログCSV（独自形式）から、ロギングツール「AirHamLog」にインポートするためのCSVを出力するプログラムを紹介します。

### 開発環境

* macOS Ventura 13.4
* Python 3.11.4
* Visual Studio Code 1.80.1

### AirHamLogとは

&emsp;AirHamLogはブラウザで動作するアマチュア無線/ライセンスフリー無線のロギングツールです。ブラウザベースのためOSに依存せず、ネットワークさえあればどこでもログにアクセスすることができるため、Macユーザーの私も使っています。Android/iOSアプリもリリースされており、スマートフォンやタブレットからも利用することができます。

https://air-hamlog.com/

&emsp;このAirHamLogですが、ログを手動で追加するだけでなく、CSVやADIFでのインポート/エクスポートに対応しています。数件の交信なら手動で追加してもいいのですが、まとめて追加したいときに便利です。このインポート用のCSVを今回は変換生成してみようと思います。

## ログCSVの形式
&emsp;元のログCSVはこのような形式です。[前回の記事](https://qiita.com/Hagian/items/5fde6bbe525bd71569ce)でExcelブックからCSVへ変換出力しています。

```log.csv
id,DATE,TIME(JST),frequency,mode,callsign,sent_rst,received_rst,received_qth,received_qra,is_qsl_sent,card_remarks,remarks_1,sent_qth,remarks_2,
1,2023-01-01,12:34,430MHz,FM,JA1RL,59,51,東京都豊島区,鈴木さん,false,Bureau,QSOパーティ,東京都足立区,,
2,2023-04-05,20:16,144MHz,FM,JA7YAB/7,59,59+,山形県天童市,佐藤さん,True,Direct,山形県山形市,偶然,
```

## AirHamLog形式CSVについて
&emsp;AirHamLogにインポートするCSVにはいくつか要件があります。[公式ページ](https://air-hamlog.com/qso_logs/import_csv)に記載があり、各自参照いただきたいのですが、ちょっと手間のかかる作業として、 <ins>**"交信時刻をISO8601記法に直す"**</ins> というものがあります。この作業の煩雑さを改善するためにも、自動化してみました。他にも、移動局の場合のコールサインとスラッシュ以下を分割したりしています。

## 出力したAirHamLog形式CSV

&emsp;交信年月日・時刻をISO8601記法に直すなど必要な処理を施したものが以下のCSVになります。

```202310161534_airhamlog_formatted.csv
id,callsign,portable,qso_at,sent_rst,received_rst,sent_qth,received_qth,received_qra,frequency,mode,card,remarks
1,JA1RL,,20230101T123400+0900,59,51,東京都豊島区,東京都足立区,鈴木さん,430MHz,FM,JARL,QSOパーティ
2,JA7YAB,7,20230405T201600+0900,59,59,山形県天童市,山形県山形市,佐藤さん,144MHz,FM,Direct,偶然
```

## 実装例

```python3:log2AHL.py
# log_convert.pyで加工・出力したcsvをAirHamlog用に再加工・出力するプログラム

import csv
import datetime
import glob
import os
import pathlib
import re


# 作業フォルダ内のログCSV一覧取得
logs = glob.glob("../convert_log/prep/*.csv")
logs.sort(reverse=True)
log_path = logs[0]

def rst_convert(mode,rst_num):
    # コンテストナンバー等が含まれている場合にRSTレポートのみにする
    if mode == "AM" or "FM" or "SSB":
        if len(rst_num) > 2:
            rst_num = rst_num[:2]
        else:
            pass
    else:
        if len(rst_num) > 3:
            rst_num = rst_num[:3]
        else:
            pass
    return rst_num

def qth_convert(qth):
    if qth[-1] == ")":
        hash_mark = re.search("#",qth).start()
        qth = qth[:hash_mark-1]
    else:
        pass
    return qth


# ログ読み込み
with open(f"{log_path}", mode="r", encoding="utf-8") as f:
    reader = csv.reader(f)
    output = []
    for i,row in enumerate(reader):
        if i != 0:

            # QSOの通し番号
            id_ = row[0]

            # 日付と時刻を結合
            date_time = f"{row[1]} {row[2]}" + ":00"
            # 日時の文字列を時刻に変換
            jst_time = datetime.datetime.strptime(date_time, "%Y-%m-%d %H:%M:%S")
            # 書式変更
            jst_time = jst_time.strftime("%Y%m%d%H%M%S")
            qso_at = jst_time[0:8] + "T" + jst_time[8:] + "+0900"

            # 周波数に単位を追加
            frequency = row[3]
            frequency = frequency + "MHz"

            # 電波型式
            mode = row[4]

            # コールサイン
            callsign = row[5]
            # ポータブル局ならコールサインとエリアを分割
            if callsign[-2] == "/":
                portable = callsign[-1]
                callsign = callsign[:-2]
            else:
                portable = ""

            # 送信したRSTレポート
            sent_rst = rst_convert(mode,row[6])
            
            # 受信したRSTレポート
            received_rst = rst_convert(mode,row[7])

            # 送信したQTH
            if len(row[13]) == 0:
                sent_qth = ""
            else:
                sent_qth = qth_convert(row[13])

            # 受信したQTH
            if len(row[8]) == 0:
                received_qth = ""
            else:
                received_qth = qth_convert(row[8])
            
            # 受信したQRA
            if len(row[9]) == 0:
                received_qra = ""
            else:
                received_qra = row[9]
            
            # カードは未発送ステータスにしておく
            if row[10] == "no QSL":
                card = "No Card"
            else:
                card = "JARL"

            # 備考欄
            if len(row[12]) == 0:
                if len(row[14]) == 0:
                    remarks = ""
                else:
                    remarks = row[14]
            else:
                if len(row[14]) == 0:
                    remarks = row[12]
                else:
                    remarks = row[12] + "、" + row[14]

            data_l = [id_,callsign,portable,qso_at,sent_rst,received_rst,sent_qth,received_qth,received_qra,frequency,mode,card,remarks]
            output.append(data_l)
# 作成日時を付加したCSVを新たに作成
datetime_now = datetime.datetime.now()
now_str = datetime_now.strftime("%Y%m%d%H%M")
# ディレクトリがなかったら新規作成、すでに存在していてもエラーは出ない
pathlib.Path("../convert_log/AHL/").mkdir(exist_ok=True)
conv_file = f"../convert_log/AHL/{now_str}_airhamlog_formatted.csv"

with open(conv_file, mode="w", encoding="utf-8") as p:
    # 変更したheaderを書き込み
    log_header = "id,callsign,portable,qso_at,sent_rst,received_rst,sent_qth,received_qth,received_qra,frequency,mode,card,remarks\n"
    p.write(log_header)
    writer = csv.writer(p)
    # 元のheaderは書き込まない
    writer.writerows(output)
```

## おわりに

&emsp;次回は最終回の予定、ログCSVからPOTA用ADIFへの変換について執筆する予定です