0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ZENITH Codeディープ解析④ 主要な販売先から取引の依存度を読む――部品会社5社の売上に占める自動車会社の割合を並べる

0
Last updated at Posted at 2026-11-09

部品会社や取引先が、売上のどれだけを特定の顧客に頼っているかを調べるとき、有価証券報告書の「主要な顧客」の欄を1社ずつ開いていないでしょうか。顧客の名前と金額は載っていても、売上高に対する割合は書かれていないことが多く、顧客の範囲(その会社だけか、グループ会社まで含めるか)も会社ごとに違います。

前回のディープ解析③では、政策保有株式の明細から、保有先の増減・売り切った保有先・持ち合いを並べました。今回は同じ「開示リーダー」の「監査と販売先」の画面で、売上の10%以上を占める販売先と、売上高に対する割合を、自動車部品の5社で並べて読みます。

開示リーダーの「主要な販売先」。デンソー・アイシン・トヨタ紡織・武蔵精密工業・エフ・シー・シーの販売先と、売上高に対する割合

画面の内容は、2026年11月5日にZENITH Codeの実データを取得したデモです。デンソー(6902)・アイシン(7259)・トヨタ紡織(3116)・武蔵精密工業(7220)・エフ・シー・シー(7296)の、2026年3月期の有価証券報告書を並べています。

私たち株式会社Crabgrassは、企業の財務データなどをAPIで提供するZENITH Codeを開発しています。本記事は自社サービスのデータを使うためのテンプレートを題材にした実装例です。

この記事のコードは、公開サンプル集のdisclosure-readerテンプレート(執筆時点の版)に対応しています。画面の仕組みの基本(ローカルのPythonサーバーとブラウザの役割分担)は、実践編で詳しく扱いました。

1. この回で読む画面と操作

画面の部分 分かること
主要な販売先(「監査と販売先」の下半分) 売上の10%以上を占める販売先の名前・関連するセグメント・当期と前期の売上・売上高に対する割合・どの表から読んだか
事業等のリスク(「本文の読み比べ」) 特定の顧客への依存を、会社がリスクとしてどう書いているか

2. 登録不要で試す

必要なものはPython 3.9以上とブラウザです。追加のパッケージはありません。①〜③の自動車会社5社とは別の保存データを使うので、--demo suppliers を付けて起動します。

git clone https://github.com/Crab-grass/zenith-code-examples.git
cd zenith-code-examples
python3 templates/disclosure-reader/server.py --demo suppliers

ブラウザで http://127.0.0.1:8790 を開き、左の「監査と販売先」(上から4番目)を押します。デモではAPIを呼ばないので、会員登録もAPIキーも要りません。終了するときは端末で Ctrl+C を押します。--demo を付けなければ、①〜③の5社で開きます。

3. 販売先のデータは2つのAPIから受け取る

主要な販売先の表は、1社につき /stable/main-customers を1回呼びます。有報の注記(「主要な顧客ごとの情報」、IFRSの会社は「主要な顧客に関する情報」)から、売上の10%以上を占める顧客を、顧客名・その顧客への売上(円)・関連するセグメントとともに、当期と前期で返すAPIです。注記に顧客の表がない会社は、「主な相手先別の販売実績」の表から読みます。どちらから読んだかは sourceTag で分かります。

割合を出すには売上高が要るので、販売先がある会社だけ /stable/income-statement(年次・3期)を1回足します。

    company["mainCustomers"] = clean_customers(optional(requester, "/main-customers", {"symbol": symbol}, api_key), symbol)
    company["revenue"] = []
    if company["mainCustomers"] and company["mainCustomers"]["customers"]:
        # Only needed to express each customer as a share of annual revenue.
        company["revenue"] = clean_revenue(requester("/income-statement", {
            "symbol": symbol, "period": "annual", "limit": 3}, api_key), symbol)

ブラウザに渡すのは、販売先の行の period・customer・revenue・segment・notes と、損益計算書の売上高と期、それに読んだ表の種類だけです。clean_customers は sourceTag を customerSource(notes か sales)に置き換えて渡します。

    out["customerSource"] = CUSTOMER_SOURCES.get(row.get("sourceTag"))  # "notes", "sales" or None

顧客名を書いていない会社もあります。「売上の10%以上を占める顧客グループが1グループ」と金額だけ書く会社や、2つの顧客グループの合計額だけを書く会社です。この場合も、APIは金額・期・セグメントの行を返し、customer を null、理由を notes に入れます(例:TDK「顧客名の記載なし(原文: 1グループ)」466,405百万円)。テンプレートは名前の欄に「顧客名の記載なし」か「複数の顧客グループの合計」と理由を出します。

404 は、エラーではなく「販売先の行がない」として扱います。売上の10%以上の販売先の記載がない会社のほか、有価証券報告書がまだない会社(上場したばかりの会社・TOKYO PRO Market など)や、金額が幅でしか書かれていない会社(例:「4,701~9,403百万円」)も 404 です。404 の本文の reason に、幅だけの会社は amount_range_only が入ります。依存度を判断する前に、原文の「主要な顧客」の欄を確かめます。

4. 売上高に対する割合を計算する

割合は model.mjs の customerRows で計算します。

  const target = { current: report.fiscalPeriod, prior: addMonths(report.fiscalPeriod, -12) };
  const rows = report.customers.map((c) => {
    const period = target[c.period] ?? null;
    const revenueRow = period ? (revenueRows ?? []).find((r) => r.period === "FY" && r.fiscalPeriod === period) : null;
    const revenue = finite(revenueRow?.revenue);
    const amount = finite(c.revenue);
    const share = amount !== null && revenue > 0 ? amount / revenue : null;
  • 当期の行は、同じ決算期(2026年3月期)の年間の売上高で割ります。
  • 前期の行は、12か月前の期(2025年3月期)の売上高で割ります。決算期を変えた会社などで、その期の売上高がなければ割りません。
  • 割合が100%を超える行は、単位が原文と合っていない可能性があるので、金額も割合も出しません。

5. 5社の販売先と割合

画面のカードの数字を表にまとめます。売上は百万円、割合は売上高(IFRSの会社は売上収益)に対する割合です。

会社 販売先(原文の表記) 前期 当期 割合(前期→当期)
デンソー トヨタグループ 3,903,121 4,134,582 54.5% → 54.8%
アイシン トヨタ自動車㈱及びそのグループ会社 2,870,025 3,112,449 58.6% → 60.8%
トヨタ紡織 トヨタ自動車(株)グループ 1,422,366 1,489,792 72.8% → 73.1%
武蔵精密工業 本田技研工業株式会社 — 21,931 — → 6.3%
エフ・シー・シー 本田技研工業㈱及び同グループ 94,933 92,676 37.0% → 35.5%
エフ・シー・シー Ford及び同グループ 51,659 52,574 20.1% → 20.2%
エフ・シー・シー ゼネラルモーターズ 23,747 24,089 9.3% → 9.2%

5社とも、読んだ表は注記(カードの「出どころ:注記の主要な顧客の情報」)です。武蔵精密工業は当期の行だけが開示されています。エフ・シー・シーのゼネラルモーターズは、売上高で割ると10%を下回りますが、注記には載っています。APIは注記にある行をそのまま返し、10%で切りません。

エフ・シー・シーのホンダは、原文では関連するセグメントごとに2行(二輪事業 57,273・四輪事業 35,403)です。APIは同じ顧客の行を足して1行にし、セグメントを「二輪事業、四輪事業」とつないで返します。

この表だけを見ると、武蔵精密工業はホンダへの依存が小さい会社に見えます。次の節で、そう読めない理由を見ます。

6. 販売先の範囲は、会社と表で違う

販売先の名前を見比べると、範囲が3通りあります。

  • グループ会社を含める:デンソー「トヨタグループ」、アイシン「…及びそのグループ会社」、トヨタ紡織「…グループ」、エフ・シー・シー「…及び同グループ」。
  • その会社だけ:武蔵精密工業「本田技研工業株式会社」(関連するセグメントは「日本」)。グループ会社を含めた割合は、7節のとおり事業等のリスクに書かれています。
  • 同じ会社でも、読む表によって範囲が変わる:有報の「主な相手先別の販売実績」の表は、会社ごとに1行です。

3つめは、同じデンソーの有報で確かめられます。①〜③の保存データ(--demo なしで起動、2026年11月4日取得)のデンソーのカードは、販売実績の表から読んだ「トヨタ自動車㈱ 1,991,604百万円」で、売上高の26.4%です。今回の保存データは注記の「トヨタグループ 4,134,582百万円」で、54.8%です。同じ有報、同じ期で、割合がほぼ倍違います。

ZENITH Codeは、注記に顧客の情報があればそちらを先に返します(IFRSの会社も同じ)。テンプレートは、カードの上に読んだ表を出します。

  • notes:「出どころ:注記の主要な顧客の情報(グループ会社を含めて1行に書く会社があります)」
  • sales:「出どころ:主な相手先別の販売実績の表(会社ごとの行)」

比べる会社の出どころがそろっていないときは、割合をそのまま並べないでください。

名前の照合では、同じ顧客にまとまらない

③で使った companyKey(㈱・株式会社・空白の違いをそろえる関数)にかけても、トヨタ側の4つの表記は別のままです。

トヨタグループ                         → トヨタグループ
トヨタ自動車㈱及びそのグループ会社      → トヨタ自動車及びそのグループ会社
トヨタ自動車(株)グループ             → トヨタ自動車グループ
トヨタ自動車株式会社                   → トヨタ自動車

APIは顧客名を提出者の書いたとおりに返します(「(株)」もそのままです)。比べる会社の販売先を顧客ごとに集計するときは、名前の自動照合ではなく、自分で決めた対応表を使います。

7. 事業等のリスクで、会社自身の書き方を読む

販売先の範囲を確かめるには、同じ有報の「事業等のリスク」が役に立ちます。「本文の読み比べ」で節を「事業等のリスク」にし、キーワードに「依存」と入れると、5社とも「依存」を含む見出しが出ます(デンソー「顧客企業の業績への依存」、アイシン「得意先への依存」、トヨタ紡織と武蔵精密工業「特定の取引先への依存」、エフ・シー・シー「特定の産業や取引先への依存について」)。トヨタ紡織と武蔵精密工業は、仕入先への依存の見出し(「原材料、部品供給元への依存」「特定のサプライヤーへの依存」)にも当たります。

見出しの下の文に、会社自身が書いた割合があります。注記の割合と並べます。

会社 注記(API)の割合 事業等のリスクの記載
デンソー 54.8%(トヨタグループ) トヨタグループ向けが売上の約半分
アイシン 60.8%(トヨタ自動車㈱及びそのグループ会社) トヨタグループの割合 69.7%
トヨタ紡織 73.1%(トヨタ自動車(株)グループ) トヨタ自動車㈱への割合 23.5%
武蔵精密工業 6.3%(本田技研工業株式会社) ホンダグループへの割合 約51%
エフ・シー・シー 35.5%(本田技研工業㈱及び同グループ) ホンダグループの割合 約36%
  • トヨタ紡織は、注記がグループ(73.1%)、リスクの記載がトヨタ自動車1社(23.5%)です。
  • 武蔵精密工業は逆で、注記が1社(6.3%)、リスクの記載がグループ(約51%)です。
  • アイシンは、どちらも「グループ」ですが、割合が違います(60.8%と69.7%)。グループに入れる会社の範囲が、欄ごとに同じとは限りません。
  • エフ・シー・シーは、2つの欄がほぼ同じです。

依存度を比べるときは、注記の数字だけで決めず、事業等のリスクの記載と範囲をそろえてから読みます。 画面の割合は入口で、どの範囲の数字かは原文で確かめます。

8. 大株主の表とつなぐ

②の「株主」の画面で、同じ5社の大株主の上位を見ると、5社とも販売先の自動車会社が1位です。

会社 大株主の1位 割合
デンソー トヨタ自動車株式会社 22.24%
アイシン トヨタ自動車株式会社 22.34%
トヨタ紡織 トヨタ自動車株式会社 32.43%
武蔵精密工業 本田技研工業株式会社 24.96%
エフ・シー・シー 本田技研工業株式会社 22.47%

トヨタ紡織は、事業等のリスクの同じ段落に、トヨタ自動車による議決権の所有割合(直接所有32.4%)も書いています。販売先と大株主が同じ会社であることは、取引関係を読むときの手がかりになりますが、この表から取引の条件や方針は分かりません。

9. AIに頼むときは、出典と原文だけを渡す

範囲の違う数字を並べたまま要約させると、AIは「依存度が高い・低い」と数字だけで決めがちです。出典(書類ID)と欄の原文を渡し、範囲を書き出させます。

次の文は、有価証券報告書の「主要な顧客」の注記と、「事業等のリスク」の取引先への依存の段落の原文です。出典は各社の書類IDです。

- 会社ごとに、2つの欄それぞれについて「販売先の範囲(その会社だけ/グループ会社を含む)」「割合または金額」を表にしてください。
- 範囲が欄で違う会社には印を付けてください。
- 書かれていない割合を計算したり推測したりしないでください。欄にないことは「原文で確認が必要」と書いてください。
- 取引関係の良し悪しや今後についての意見は書かないでください。

## 武蔵精密工業(7220) 2026-03期 書類ID S100YGX6
(主要な顧客ごとの情報の原文)
(事業等のリスクの「特定の取引先への依存」の段落の原文)

(以下、同じ形で各社を続ける)

10. 自社のコードで動かす

会員登録をしてAPIキーを作成し、--live を付けて起動し直します。

python3 templates/disclosure-reader/server.py --live

キーは端末の非表示入力で渡し、ローカルのPythonプロセスだけが持ちます。主要な販売先の分は、1社あたり1〜2リクエスト(/main-customers と、販売先がある会社だけ /income-statement)です。ほかの画面の分も含めると1社あたり12〜13リクエスト、5社なら最大65リクエストです。

保存データを自分の会社の組み合わせで作り直すときは、build_demo.py に証券コードを渡します。

python3 templates/disclosure-reader/build_demo.py --set suppliers               # 今回の5社を取り直す
python3 templates/disclosure-reader/build_demo.py --set suppliers 7282 6995     # 別の組み合わせ

11. 数字の検算

記事の割合はテンプレートの関数で計算したものです。テンプレートを使わずに、保存データから計算し直せます。リポジトリの直下で実行します。

python3 - <<'EOF'
import json
d = json.load(open("templates/disclosure-reader/demo-suppliers.json", encoding="utf-8"))
for c in d["companies"]:
    m = c["mainCustomers"]
    fy = m["fiscalPeriod"]
    prior = str(int(fy[:4]) - 1) + fy[4:]
    rev = {r["fiscalPeriod"]: r["revenue"] for r in c["revenue"] if r["period"] == "FY"}
    for x in m["customers"]:
        base = rev.get(fy if x["period"] == "current" else prior)
        share = x["revenue"] / base * 100 if base else None
        print(c["symbol"], x["period"], x["customer"], round(x["revenue"] / 1e6), "—" if share is None else f"{share:.1f}%")
EOF
6902 prior トヨタグループ 3903121 54.5%
6902 current トヨタグループ 4134582 54.8%
7259 prior トヨタ自動車㈱及びそのグループ会社 2870025 58.6%
7259 current トヨタ自動車㈱及びそのグループ会社 3112449 60.8%
3116 prior トヨタ自動車(株)グループ 1422366 72.8%
3116 current トヨタ自動車(株)グループ 1489792 73.1%
7220 current 本田技研工業株式会社 21931 6.3%
7296 prior 本田技研工業㈱及び同グループ 94933 37.0%
7296 current 本田技研工業㈱及び同グループ 92676 35.5%
7296 prior Ford及び同グループ 51659 20.1%
7296 current Ford及び同グループ 52574 20.2%
7296 prior ゼネラルモーターズ 23747 9.3%
7296 current ゼネラルモーターズ 24089 9.2%

画面と5節の表と一致します。筆者は次も確かめました。

  • 原文と照合する。 画面の下の「出典」の書類ID(デンソー S100Y9T1・アイシン S100YAWO・トヨタ紡織 S100Y9AH・武蔵精密工業 S100YGX6・エフ・シー・シー S100YFKQ)で有報を開き、「主要な顧客」の注記の金額と、事業等のリスクの割合(7節)を見比べました。エフ・シー・シーのホンダは、注記の2行の和(57,273+35,403=92,676)です。
  • ほかの会社でも確かめる。 執筆の前に、上場会社から選んだ1,000社で /main-customers を取り、売上高を超える行と、同じ顧客・同じ金額の重複がないことを確かめました。

12. 小さな変更:いちばん大きい販売先を1つの表に並べる

カードは会社ごとに分かれているので、比べる会社の当期でいちばん大きい販売先を1つの表に並べます。

変更後の「監査と販売先」。5社のいちばん大きい販売先と、売上高に対する割合と、出どころを並べた表

計算(model.mjs)とサーバー、APIの呼び出し回数は変えません。変えるのは app.mjs と i18n.mjs の2つです。

1. 表を足す(app.mjs の renderAudit()、const blocks = … の行の前)。

  // This year's largest customer of each company, side by side (display only).
  const tops = companies.map((c) => {
    const result = M.customerRows(c.mainCustomers, c.revenue);
    const top = result.rows.filter((r) => r.period === "current" && r.share !== null).sort((a, b) => b.share - a.share)[0] ?? null;
    return { company: c, top, source: result.source, none: result.status === "none" };
  });
  host.append(panel(t("topTitle"), t("topNote"), table([
    { label: "", render: (r) => companyTitle(r.company) },
    { label: t("customer"), cls: "name", render: (r) => (r.top ? (r.top.named ? r.top.customer : t("unnamedCustomer"))
      : r.none ? t("noCustomers") : t("topNoShare")) },
    { label: t("shareOfRevenue"), render: (r) => (r.top ? ratioCell(r.top.share) : "") },
    { label: t("topSource"), cls: "nowrap", render: (r) => (r.source ? t("topSource_" + r.source) : "—") },
  ], tops)));

customerRows が返す割合(share)と出どころ(source)をそのまま使います。割合が出ない行(期が合わない・売上高が空・単位の疑い)は選びません。販売先の行はあるのに当期の割合が出ない会社(当期は10%未満で前期の行だけの会社や、売上高が空の会社)は、「記載なし」と区別して出します。いちばん大きい行が顧客名のない行なら「顧客名の記載なし」と出します(テンプレートの文言をそのまま使います)。

2. 文言を足す(i18n.mjs の ja と en)。

    topTitle: "いちばん大きい販売先(当期)", topNote: "販売先の名前に「グループ」があるかと、出どころを見てから比べます。", topSource: "出どころ",
    topSource_notes: "注記", topSource_sales: "販売実績の表", topNoShare: "当期の割合を出せる行なし(表で確かめます)",
    topTitle: "Largest customer (this year)", topNote: "Check whether the customer name includes its group and where it was read before comparing.", topSource: "Source",
    topSource_notes: "Notes", topSource_sales: "Sales table", topNoShare: "No row with a share for this year (see the table)",

保存して再読み込みし、「監査と販売先」に「いちばん大きい販売先(当期)」の表が出れば完成です。トヨタ紡織 73.1%、武蔵精密工業 6.3% のように並びますが、6節と7節のとおり、範囲の違う数字です。表の「販売先」の列で範囲を確かめてから読みます。

テストはそのまま通ります(node --test tests/disclosure-reader.test.mjs)。文言を日本語と英語の片方だけに足すと失敗します。

13. 確認箇所

症状 確認箇所
主要な販売先が404になる 売上の10%以上の販売先の記載がない会社のほか、有価証券報告書がない会社・金額が幅でしか書かれていない会社(reason が amount_range_only)も 404 になる。「依存先なし」と決めず、原文の「主要な顧客」の欄を確かめる
販売先の名前が「顧客名の記載なし」「複数の顧客グループの合計」 原文が顧客名を書かず金額だけを書いた会社、2つ以上の顧客グループの合計額だけを書いた会社。APIは customer を null にし、理由を notes に入れる。金額と割合は原文どおり
割合が出ない(「同じ期の売上高がない」) 上場前の期の行(前期の売上高がない)、損益計算書の売上高が空の会社(売上を事業収益などで開示する会社)。金額は原文どおり出る
当期の行がなく、前期の行だけある 当期は10%未満になった会社。原文の当期の欄は「-」や「該当なし」。「いちばん大きい販売先」の表には「当期の割合を出せる行なし」と出る
銀行の販売先の金額が「経常収益」 銀行は売上高の代わりに経常収益で書く。割合は損益計算書の経常収益で割る
同じ会社で、以前の数字と割合が大きく違う 読んだ表が違う。注記はグループ会社を含めることがあり、販売実績の表は会社ごと。カードの「出どころ」を見る
割合が10%を下回る販売先がある 注記に載っている行はそのまま返す。会社が10%未満の顧客も載せることがある
原文では2行の顧客が1行になっている 同じ顧客のセグメント別の行は、APIが足して1行にし、セグメントを「、」でつなぐ
顧客名の「(株)」「㈱」「株式会社」がそろっていない 提出者の書いたまま返す。顧客ごとに集計するときは自分の対応表を使う
前期の行に割合が出ない 12か月前の期の売上高がない(決算期の変更など)。割らずに金額だけ出す

14. 次回は監査報酬を並べる

今回は、主要な販売先の割合を5社で並べ、販売先の範囲が会社と表で違うこと、事業等のリスクの記載と並べて読むことを見ました。画面は入口で、判断は原文で行います。

次回のディープ解析⑤では、同じ「監査と販売先」の画面の上半分で、監査報酬(監査証明業務と非監査業務)と、監査法人のネットワークへの報酬を並べます。

ZENITH Code — 日本企業の財務データを、APIで。サービスサイトを見る

ZENITH Codeを見る · APIドキュメント

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?