副業の20万円は売上ではなく所得。売上26万円でも超えない見本を、境界3本で確かめた

副業の20万円は売上ではなく所得。売上26万円でも超えない見本を、境界3本で確かめた

売上26万円、経費7万円。2026年の副業をこの2行で表すと、「20万円を超えたか」の答えは、売上を見るか所得を見るかでひっくり返ります。売上なら超えています。所得は19万円なので、超えていません。

デジタル商品やAI関連の副業を始めると、まず気になるのがこの20万円です。ネットで調べると「副業で20万円を超えたら確定申告」と一行で書かれていて、その一行をそのままコードにすると、私は最初の版で間違えました。今回は合成した売上と経費のCSVで、その間違いと直し方を残します。実在の売上も申告内容も使っていません。

11月3日の回では、AIに渡す売上CSVの列名を決めました。ただ、その合計を何と比べるのかは決めていませんでした。10月9日の消費税の端数の回も、見ていたのは売上側の数字だけです。経費を引いた先にある判定を、今回は同じ小さなコードで固定します。

数える対象は「売上」ではなく「所得金額」でした

国税庁のタックスアンサー No.1900 は、給与所得者が確定申告をしなければならない場合を挙げています。副業に関わるのは2番です。給与を1か所から受け、その全部が源泉徴収されている人は、給与所得と退職所得を除く各種の所得金額の合計が20万円を「超える」と申告が必要になります。

ここで数えるのは所得金額です。No.1350 によると、事業所得は「総収入金額 − 必要経費 = 事業所得の金額」で計算します。売上が総収入金額、経費が必要経費に当たります。

もう1つ、言葉の細かいところがあります。条件は「20万円を超える」です。ちょうど20万円は超えていません。コードに直すと > で、>= ではありません。

この回の前提は2つだけです。給与を1か所から受けていること、副業以外に他の所得が無いこと。給与が2か所以上ある場合は条件が変わります。事業所得か雑所得かの区分、住民税の申告、確定申告をすれば還付される場合も別の話で、ここでは判定しません。

売上を足しただけの最初の版

データは、売上3行と経費3行です。

mkdir side-income-check
cd side-income-check
cat > sales_a.csv <<'E'
date,item,amount
2026-03-14,テンプレートA,98000
2026-06-02,テンプレートA,92000
2026-09-20,プロンプト集,70000
E
cat > expenses_a.csv <<'E'
date,item,amount
2026-03-01,独自ドメイン,2800
2026-04-10,AI APIの利用料,41200
2026-07-05,決済手数料,26000
E

最初に書いたのは、売上CSVの合計が20万円を超えたら終了コード10で止める17行の版です。

cat > check_v1.py <<'E'
import csv
import sys

LIMIT = 200_000


def total(path):
    with open(path, newline="", encoding="utf-8") as f:
        return sum(int(row["amount"]) for row in csv.DictReader(f))


sales = total(sys.argv[1])
print(f"売上 {sales}円")
if sales > LIMIT:
    print("20万円超 -> 確定申告が必要")
    sys.exit(10)
print("20万円以下")
E
python3 check_v1.py sales_a.csv; echo "exit=$?"
売上 260000円
20万円超 -> 確定申告が必要
exit=10

動きます。出力も自然です。だから厄介でした。

この版は、経費ファイルを開いてすらいません。経費の合計は7万円なので、所得は19万円です。

AIにコードを書かせる場合も、プロンプトに「副業が20万円を超えたら」と書けば、同じ形の判定が返ってくる気がします。試したわけではありません。ただ、一行の言い回しからは売上と所得の区別が落ちやすいと思うので、検査は人が持っておきたいです。

所得で数え直し、入力も疑う版

直した版は check.py です。45行あるので畳みました。売上CSVと経費CSVを両方読み、差を所得として20万円と比べます。入力が壊れていれば、判定せずに終了コード2で止めます。

check.py の全文(45行)
cat > check.py <<'E'
import csv
import sys

LIMIT = 200_000  # 国税庁 No.1900: 20万円を「超える」と申告が必要


def load(path):
    with open(path, newline="", encoding="utf-8") as f:
        reader = csv.DictReader(f)
        if reader.fieldnames != ["date", "item", "amount"]:
            raise ValueError(f"{path}: 列は date,item,amount の順で必要です: {reader.fieldnames}")
        rows = list(reader)
    if not rows:
        raise ValueError(f"{path}: データ行が0行です")
    years = {r["date"][:4] for r in rows}
    amounts = []
    for r in rows:
        if None in r or None in r.values():
            raise ValueError(f"{path}: 列の数が合わない行があります: {r}")
        if not (r["amount"].isascii() and r["amount"].isdigit()):
            raise ValueError(f"{path}: amount は0以上の整数です: {r['amount']!r}")
        amounts.append(int(r["amount"]))
    return years, sum(amounts)


def main(sales_path, expenses_path):
    try:
        y1, sales = load(sales_path)
        y2, expenses = load(expenses_path)
        if len(y1 | y2) != 1:
            raise ValueError(f"年が混ざっています: {sorted(y1 | y2)}")
    except (OSError, ValueError) as e:
        print(f"ERROR {e}")
        return 2
    income = sales - expenses
    print(f"売上 {sales}円 - 経費 {expenses}円 = 所得 {income}円")
    if income > LIMIT:
        print("20万円超 -> 所得税の確定申告が必要")
        return 10
    print("20万円以下 -> この条件では申告不要")
    return 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1], sys.argv[2]))
E

さっきのデータで実行します。

python3 check.py sales_a.csv expenses_a.csv; echo "exit=$?"
売上 260000円 - 経費 70000円 = 所得 190000円
20万円以下 -> この条件では申告不要
exit=0

出力の最後を「申告不要」と言い切るか、少し迷いました。上に書いた前提が外れれば、不要とは限りません。だから「この条件では」を付けています。

20万円ちょうどと、1円違いを固定する

一番ずれやすいのは境界です。売上30万円を固定して、経費を10万円と99,999円にした2本を足します。所得は20万円ちょうど、と20万0001円です。

printf 'date,item,amount\n2026-05-01,テンプレートB,300000\n' > sales_b.csv
printf 'date,item,amount\n2026-05-02,AI APIの利用料,100000\n' > expenses_b.csv
printf 'date,item,amount\n2026-05-02,AI APIの利用料,99999\n' > expenses_c.csv
python3 check.py sales_b.csv expenses_b.csv; echo "exit=$?"
python3 check.py sales_b.csv expenses_c.csv; echo "exit=$?"
売上 300000円 - 経費 100000円 = 所得 200000円
20万円以下 -> この条件では申告不要
exit=0
売上 300000円 - 経費 99999円 = 所得 200001円
20万円超 -> 所得税の確定申告が必要
exit=10

並べると、所得19万円は終了コード0、20万円ちょうども0、20万0001円は10です。

この3本が本当に境界を見張っているのか、判定を >= に書き換えたコピーで確かめました。

sed 's/income > LIMIT/income >= LIMIT/' check.py > check_mut.py
python3 check_mut.py sales_b.csv expenses_b.csv; echo "exit=$?"
売上 300000円 - 経費 100000円 = 所得 200000円
20万円超 -> 所得税の確定申告が必要
exit=10

20万円ちょうどの結果が、0から10に変わりました。> と >= の取り違えは、データが19万円と25万円だけでは見つかりません。境界のデータは、この1本のために置いています。

悪い入力で、一度すり抜けました

CSVを検査するコードは、正常な見本だけでは足りません。列名の違い、データ0行、年の混在を試しました。

printf 'date,item,price\n2026-05-01,テンプレートB,300000\n' > sales_badcol.csv
printf 'date,item,amount\n' > sales_empty.csv
printf 'date,item,amount\n2025-12-31,テンプレートB,300000\n' > sales_2025.csv
for f in sales_badcol.csv sales_empty.csv sales_2025.csv; do
  python3 check.py $f expenses_b.csv; echo "exit=$?"
done
ERROR sales_badcol.csv: 列は date,item,amount の順で必要です: ['date', 'item', 'price']
exit=2
ERROR sales_empty.csv: データ行が0行です
exit=2
ERROR 年が混ざっています: ['2025', '2026']
exit=2

ここまでは想定どおり。通らなかったのは、金額を 300,000 とカンマ付きで書いた行です。CSVではカンマが列の区切りになるため、300 と 000 に分かれます。

printf 'date,item,amount\n2026-05-01,テンプレートB,300,000\n' > sales_comma.csv
sed '/None in r or None in r.values()/,+1d' check.py > check_prefix.py
python3 check_prefix.py sales_comma.csv expenses_b.csv; echo "exit=$?"
売上 300円 - 経費 100000円 = 所得 -99700円
20万円以下 -> この条件では申告不要
exit=0

売上30万円が300円として読まれ、所得はマイナスになり、終了コード0で「申告不要」と返しました。申告が要る人に「要らない」と言う、一番危険な向きの間違いです。

check_prefix.py は、check.py から「列の数が合わない行」を調べる2行を抜いた、直す前の版です。

check.py の if None in r or None in r.values() は、この穴をふさぐために足した行です。直した版では、同じ入力が止まります。

python3 check.py sales_comma.csv expenses_b.csv; echo "exit=$?"
ERROR sales_comma.csv: 列の数が合わない行があります: {'date': '2026-05-01', 'item': 'テンプレートB', 'amount': '300', None: ['000']}
exit=2

全角数字の 3000000 も試しましたが、amount は0以上の整数です で止まりました。

まだ納得していないこと

この検査は、経費の線引きを判定していません。AI APIの利用料を全額経費にしてよいのか、自宅の通信費はどう按分するのか。コードは入力された経費をそのまま引くだけ。入れすぎれば所得は小さく出ます。判定が変わる20万円の手前では、ここが一番気になります。

もう1つは、事業所得と雑所得の違いです。この回は事業所得の計算式だけを根拠に書きました。どちらになるのか。国税庁の資料を読み込んでから、別に書くつもりです。

参照

この記事をシェア