• #refactoring
  • #composable
  • #sqlite
  • #financial-data
  • #handoff
  • #balance-adjustment
開発完了

Composable自動生成リファクタリング - 引き継ぎドキュメント

データ再生成コマンド

cd apps/web
node scripts/generate-financial-data.mjs

重要: SQLiteにデータを追加した後は、上記コマンドでfinancial-data.tsを再生成してください。

作成したファイル

ファイル説明
app/types/financial.ts型定義ファイル
scripts/generate-financial-data.mjsSQLite → TypeScript変換スクリプト
app/composables/financial-data.ts自動生成された財務データ(7社分)
app/pages/financial-quiz/proportional-animation-qqq.vue新しいVueページ

更新したファイル

ファイル変更内容
ProportionalFinancialStatementsAnimated.vue型定義を~/types/financialからimport

目的

SQLite (koyfin.db) から composable ファイルを自動生成するスクリプトを作成する。 その際、以下の問題を解決する。

現状の問題点

1. データ構造の不一致

現在のcomposable(useMicrosoftData.tsなど)の構造:

// 現在の BSData(粒度が粗い)
interface BSData {
  currentAssets: BalanceSheetItem[]   // { label, value }[]
  fixedAssets: BalanceSheetItem[]
  currentLiabilities: BalanceSheetItem[]
  fixedLiabilities: BalanceSheetItem[]
  equity: BalanceSheetItem[]
}

// 現在の PLData(数値のみ)
interface PLData {
  revenue: number
  grossProfit?: number
  operatingIncome?: number
  profit: number
  expenses: number
}

プロンプトで定義されている最新の構造(より詳細):

// プロンプトの BSData(検証用Total値を含む)
bs: {
  currentAssets: [
    { label: '【総資産】', value: 174472 },           // Total Assets(検証用)
    { label: '【流動資産合計】', value: 122797 },     // Total Current Assets(検証用)
    { label: '現預金・短期投資', value: 96391 },
    { label: '売掛金', value: 17908 },
    { label: '棚卸資産', value: 2902 },
    { label: 'その他流動資産', value: 5596 },
  ],
  // ... 他のセクションも同様に検証用Total値を含む
}

// プロンプトの PLData(配列形式でより詳細)
pl: {
  revenue: [
    { label: '【売上高】', value: 93580 },
    { label: '【売上総利益】', value: 60542 },
    { label: '売上原価', value: 33038 },
  ],
  operatingExpenses: [
    { label: '【営業利益】', value: 28172 },
    { label: '販管費', value: 20324 },
    { label: '研究開発費', value: 12046 },
  ],
  nonOperating: [...],
  netIncome: [...],
}

問題:

  • 現在のVueコンポーネントは粗い粒度の構造を期待している
  • プロンプトの最新構造はより詳細で検証用データを含む
  • 両者が一致していない

2. Vueページの冗長なimport

現在の proportional-animation.vue:

import { useMicrosoftData } from '~/composables/useMicrosoftData'
import { useNvidiaData } from '~/composables/useNvidiaData'
import { useAlphabetData } from '~/composables/useGoogleData'
import { useBroadcomData } from '~/composables/useBroadcomData'
import { useMetaPlatformsData } from '~/composables/useMetaData'
// ... 15個のimport

const microsoftData = useMicrosoftData()
const nvidiaData = useNvidiaData()
// ... 15個の呼び出し

問題:

  • 企業が増えるたびにimport文を手動で追加する必要がある
  • 100社になると100個のimportが必要
  • メンテナンス性が悪い

3. SQLiteデータの集約が必要

SQLite(koyfin.db)のデータ形式:

section: balance_sheet
metric_name: Total Assets
metric_value: "364,980.0M"

section: balance_sheet
metric_name: Total Cash And Short Term Investments
metric_value: "65,171.0M"

section: balance_sheet
metric_name: Total Receivables
metric_value: "66,243.0M"

... (50項目以上)

必要な処理:

  1. 文字列値を数値にパース("364,980.0M"364980
  2. 複数の項目を適切なカテゴリに集約
  3. 差額計算(例: その他流動資産 = Total Current Assets - 現預金 - 売掛金 - 棚卸資産
  4. BSバランスの検証・調整

決定事項

以下の方針で進める:

項目決定
データ形式TypeScriptオブジェクト(型チェックが効くため)
型定義の場所types/financial.ts に分離
composableファイル構成1ファイル (composables/financial-data.ts) に全企業を格納
既存composable(15ファイル)残しておく(新旧並行)
新ページパス/financial-quiz/proportional-animation-qqq
Vueコンポーネント変更OK(expensesplの中に移動)

ファイルサイズ試算

企業数TypeScriptJSONgzip後(TS)
1社30KB20KB4KB
100社3MB2MB400KB
300社9MB6MB1.2MB

→ 100社程度なら1ファイルで問題なし。ページネーションで一度に全社表示しなければOK。

解決方針

新しいデータ構造(expensesplに統合)

// types/financial.ts に定義

export interface BalanceSheetItem {
  label: string
  value: number
  color?: string
}

export interface BSData {
  currentAssets: BalanceSheetItem[]
  fixedAssets: BalanceSheetItem[]
  currentLiabilities: BalanceSheetItem[]
  fixedLiabilities: BalanceSheetItem[]
  equity: BalanceSheetItem[]
}

export interface PLData {
  revenue: number
  costOfRevenue: number        // 売上原価
  grossProfit: number          // 売上総利益
  sga: number                  // 販管費
  rd: number                   // 研究開発費
  operatingIncome: number      // 営業利益
  interestExpense?: number     // 支払利息
  interestIncome?: number      // 受取利息
  ebt: number                  // 税引前利益
  incomeTax: number            // 法人税
  netIncome: number            // 純利益
}

export interface CashFlowData {
  operatingCF: number
  capex: number
  fcf: number
}

export interface PerShareData {
  eps: number
}

export interface FinancialData {
  bs: BSData
  pl: PLData
  cashFlow: CashFlowData
  perShare: PerShareData
}

export interface PeriodData {
  label: string  // '2015', '2024', 'LTM'
  data: FinancialData
}

export interface CompanyData {
  ticker: string
  name: string
  sector?: string
  industry?: string
  periods: PeriodData[]
}

// 全企業データの型
export type FinancialDataStore = Record<string, CompanyData>

composableの形式

// composables/financial-data.ts

import type { FinancialDataStore } from '~/types/financial'

export const financialData: FinancialDataStore = {
  AAPL: {
    ticker: 'AAPL',
    name: 'Apple Inc.',
    sector: 'Information Technology',
    periods: [
      {
        label: '2015',
        data: {
          bs: { ... },
          pl: { ... },
          cashFlow: { ... },
          perShare: { ... }
        }
      },
      // ... 他の期間
    ]
  },
  MSFT: { ... },
  // ... 100社分
}

// ヘルパー関数
export const getCompanyData = (ticker: string): CompanyData | undefined => {
  return financialData[ticker]
}

export const getAllTickers = (): string[] => {
  return Object.keys(financialData)
}

export const getCompaniesBySector = (sector: string): CompanyData[] => {
  return Object.values(financialData).filter(c => c.sector === sector)
}

SQLiteデータベース構造

テーブル構造

-- 使用するビュー
CREATE VIEW v_annual_data AS
SELECT
    c.ticker,
    c.name,
    fp.period_label,    -- 'FY 2015', 'FY 2024', 'Current/LTM'
    fp.fiscal_year,
    fda.section,        -- 'balance_sheet', 'income_statement', 'cash_flow'
    fda.metric_name,    -- 項目名(下記参照)
    fda.metric_value,   -- '364,980.0M', '(9,447.0)M' など
    fda.fetched_at
FROM financial_data_annual fda
JOIN companies c ON fda.company_id = c.id
JOIN financial_periods fp ON fda.period_id = fp.id;

利用可能なticker

AAPL, AMZN, AVGO, GOOGL, META, MSFT, NVDA

利用可能な期間(period_label)

period_typeperiod_label用途
annualFY 2015 ~ FY 2025年次データ
ltmCurrent/LTM直近12ヶ月

取得対象期間の仕様

  • 取得対象: FY 2015FY 2025 + Current/LTM
  • 変換後のlabel: '2015', '2016', ..., '2025', 'LTM'

SQLite → CompanyData 変換ロジック

値のパース関数

function parseValue(value: string | null): number {
  if (!value || value === 'None' || value === '') return 0

  // カッコ付きの負の値を処理: "(9,447.0)M" -> -9447
  const isNegative = value.startsWith('(') && value.includes(')')

  // 単位とカッコを除去
  let cleaned = value
    .replace(/[()MBK%x,]/g, '')
    .trim()

  const num = parseFloat(cleaned)
  return isNegative ? -num : num
}

BS metric_name 完全リスト(section: balance_sheet)

Accounts Payable                        | 買掛金
Accounts Receivable                     | 売掛金
Accrued Expenses                        | 未払費用
Accumulated Depreciation                | 減価償却累計額
Additional Paid In Capital              | 資本剰余金
Book Value / Share                      | 1株当たり純資産
Cash And Equivalents                    | 現預金
Common Equity                           | 普通株式資本
Common Stock                            | 普通株式
Comprehensive Income and Other          | その他包括利益
Current Income Taxes Payable            | 未払法人税
Current Portion of Leases               | 短期リース
Current Portion of Long-Term Debt       | 短期借入金
Deferred Charges Long-Term              | 長期繰延費用
Deferred Tax Assets Long-Term           | 繰延税金資産
Deferred Tax Liability Non Current      | 繰延税金負債
ECS Total Common Shares Outstanding     | 発行済株式数
ECS Total Shares Outstanding on Filing Date | 届出日発行済株式数
Equity Method Investments               | 持分法投資
Goodwill                                | のれん
Gross Property Plant And Equipment      | 有形固定資産(総額)
Inventory                               | 棚卸資産
Loans Receivable Long-Term              | 長期貸付金
Long-Term Debt                          | 長期借入金
Long-Term Leases                        | 長期リース
Long-term Investments                   | 長期投資
Net Debt                                | 純有利子負債
Net Property Plant And Equipment        | 有形固定資産(純額)
Other Current Assets                    | その他流動資産
Other Current Liabilities               | その他流動負債
Other Intangibles                       | その他無形資産
Other Long-Term Assets                  | その他固定資産
Other Non Current Liabilities           | その他固定負債
Other Receivables                       | その他売掛金
Prepaid Expenses                        | 前払費用
Restricted Cash                         | 拘束性預金
Retained Earnings                       | 利益剰余金
Short Term Investments                  | 短期投資
Tangible Book Value                     | 有形純資産
Tangible Book Value Per Share           | 1株当たり有形純資産
Total Assets                            | 総資産(検証用)
Total Cash And Short Term Investments   | 現預金・短期投資
Total Current Assets                    | 流動資産合計(検証用)
Total Current Liabilities               | 流動負債合計(検証用)
Total Debt                              | 有利子負債合計
Total Equity                            | 純資産合計(検証用)
Total Liabilities                       | 負債合計(検証用)
Total Liabilities And Equity            | 負債純資産合計(検証用)
Total Receivables                       | 売掛金合計
Trading Asset Securities                | 売買目的有価証券
Treasury Stock                          | 自己株式
Unearned Revenue Current, Total         | 前受収益(流動)
Unearned Revenue Non Current            | 前受収益(固定)

BSマッピング定義

const bsMapping = {
  currentAssets: [
    { sqlite: 'Total Cash And Short Term Investments', label: '現預金・短期投資' },
    { sqlite: 'Total Receivables', label: '売掛金' },
    { sqlite: 'Inventory', label: '棚卸資産' },
    // 'その他流動資産' は差額計算: Total Current Assets - 上記合計
  ],
  fixedAssets: [
    { sqlite: 'Net Property Plant And Equipment', label: '有形固定資産' },
    { sqlite: 'Long-term Investments', label: '長期投資' },
    { sqlite: 'Goodwill', label: 'のれん' },
    { sqlite: 'Other Intangibles', label: '無形資産' },
    // 'その他固定資産' は差額計算: Total Assets - Total Current Assets - 上記合計
  ],
  currentLiabilities: [
    { sqlite: 'Accounts Payable', label: '買掛金' },
    { sqlite: 'Current Portion of Long-Term Debt', label: '短期借入金' },
    { sqlite: 'Current Portion of Leases', label: '短期リース' },
    { sqlite: 'Unearned Revenue Current, Total', label: '前受収益(流動)' },
    // 'その他流動負債' は差額計算: Total Current Liabilities - 上記合計
  ],
  fixedLiabilities: [
    { sqlite: 'Long-Term Debt', label: '長期借入金' },
    { sqlite: 'Long-Term Leases', label: '長期リース' },
    { sqlite: 'Unearned Revenue Non Current', label: '長期前受収益' },
    // 'その他固定負債' は差額計算: Total Liabilities - Total Current Liabilities - 上記合計
  ],
  equity: [
    { sqlite: 'Common Stock', label: '資本金' },
    { sqlite: 'Additional Paid In Capital', label: '資本剰余金' },
    { sqlite: 'Retained Earnings', label: '利益剰余金' },
    { sqlite: 'Treasury Stock', label: '自己株式' },
    { sqlite: 'Comprehensive Income and Other', label: 'その他包括利益' },
  ],
}

PL metric_name 完全リスト(section: income_statement)

Basic EPS - Continuing Operations       | 基本EPS(継続事業)
Basic Weighted Average Shares Outstanding | 基本加重平均株式数
Cost Of Revenues                        | 売上原価
D&A for EBITDA                          | EBITDA用D&A
Depreciation & Amortization             | 減価償却費
Diluted EPS - Continuing Operations     | 希薄化後EPS(継続事業)
Diluted Weighted Average Shares Outstanding | 希薄化後加重平均株式数
Dividend Per Share                      | 1株当たり配当
EBIT                                    | EBIT
EBITA                                   | EBITA
EBITDA                                  | EBITDA
EBT, Excl. Unusual Items                | 税引前利益(特別項目除く)
EBT, Incl. Unusual Items                | 税引前利益
Earnings From Continuing Operations     | 継続事業利益
Effective Tax Rate - (Ratio)            | 実効税率
Gain (Loss) On Sale Of Assets           | 資産売却損益
Gain (Loss) On Sale Of Investments      | 投資売却損益
General and Administrative Expenses     | 一般管理費
Gross Profit (Loss)                     | 売上総利益
Impairment of Goodwill                  | のれん減損
Income (Loss) On Equity Affiliates      | 持分法損益
Income Tax Expense                      | 法人税等
Interest And Investment Income          | 受取利息・投資収益
Interest Expense                        | 支払利息
Minority Interest                       | 少数株主損益
Net EPS - Basic                         | 基本EPS
Net EPS - Diluted                       | 希薄化後EPS
Net Income                              | 純利益
Net Income to Common Excl. Extra Items  | 普通株主帰属純利益(特別項目除く)
Net Income to Common Incl Extra Items   | 普通株主帰属純利益
Net Interest Expenses                   | 純支払利息
Normalized Basic EPS                    | 調整後基本EPS
Normalized Diluted EPS                  | 調整後希薄化後EPS
Normalized Net Income                   | 調整後純利益
Operating Income                        | 営業利益
Other Non Operating Income (Expenses)   | 営業外損益
Other Operating Expenses                | その他営業費用
Other Revenues                          | その他収益
Other Unusual Items, Total              | 特別損益
Payout Ratio                            | 配当性向
R&D Expenses                            | 研究開発費
Restructuring Charges                   | リストラ費用
Revenues                                | 売上高
Selling General & Admin Expenses        | 販管費
Selling and Marketing Expenses          | 販売費
Stock-Based Comp., Other (Total)        | 株式報酬(その他)
Total Revenues                          | 売上高合計
Total Stock-Based Compensation          | 株式報酬合計
YoY Growth %                            | 前年比成長率

PLマッピング定義

const plMapping = {
  revenue: { sqlite: 'Revenues', label: '売上高' },
  costOfRevenue: { sqlite: 'Cost Of Revenues', label: '売上原価' },
  grossProfit: { sqlite: 'Gross Profit (Loss)', label: '売上総利益' },
  sga: { sqlite: 'Selling General & Admin Expenses', label: '販管費' },
  rd: { sqlite: 'R&D Expenses', label: '研究開発費' },
  operatingIncome: { sqlite: 'Operating Income', label: '営業利益' },
  interestExpense: { sqlite: 'Interest Expense', label: '支払利息' },
  interestIncome: { sqlite: 'Interest And Investment Income', label: '受取利息' },
  ebt: { sqlite: 'EBT, Incl. Unusual Items', label: '税引前利益' },
  incomeTax: { sqlite: 'Income Tax Expense', label: '法人税等' },
  netIncome: { sqlite: 'Net Income', label: '純利益' },
}

// expenses計算: revenue - netIncome
// profit = netIncome

CashFlow metric_name 完全リスト(section: cash_flow)

(Gain) Loss From Sale Of Asset          | 資産売却(損益)
(Gain) Loss on Sale of Investments      | 投資売却(損益)
Amortization of Deferred Charges, Total | 繰延費用償却
Amortization of Goodwill and Intangible Assets | のれん・無形資産償却
Asset Writedown & Restructuring Costs   | 減損・リストラ費用
Capital Expenditure                     | 設備投資
Cash Acquisitions                       | 現金買収
Cash Income Tax Paid (Refund)           | 法人税支払(還付)
Cash Interest Paid                      | 利息支払
Cash from Financing                     | 財務CF
Cash from Investing                     | 投資CF
Cash from Operations                    | 営業CF
Change In Accounts Payable              | 買掛金増減
Change In Accounts Receivable           | 売掛金増減
Change In Income Taxes                  | 法人税増減
Change In Inventories                   | 棚卸資産増減
Change In Net Working Capital           | 運転資本増減
Change in Other Net Operating Assets    | その他営業資産増減
Change in Unearned Revenues             | 前受収益増減
Common & Preferred Stock Dividends Paid | 配当金支払
Common Dividends Paid                   | 普通株配当金支払
Depreciation & Amortization             | 減価償却費
Depreciation & Amortization, Total      | 減価償却費合計
Divestitures                            | 事業売却
Foreign Exchange Rate Adjustments       | 為替換算調整
Free Cash Flow                          | フリーキャッシュフロー
Free Cash Flow per Share                | 1株当たりFCF
Investment in Mkt and Equity Securities | 有価証券投資
Issuance of Common Stock                | 株式発行
Long-Term Debt Issued, Total            | 長期借入
Long-Term Debt Repaid, Total            | 長期借入返済
Miscellaneous Cash Flow Adjustments     | その他CF調整
Net Change in Cash                      | 現金純増減
Net Debt Issued / Repaid                | 純借入/返済
Net Income                              | 純利益
Other Financing Activities              | その他財務活動
Other Investing Activities, Total       | その他投資活動
Other Operating Activities, Total       | その他営業活動
Preferred Dividends Paid                | 優先株配当金支払
Repurchase of Common Stock              | 自社株買い
Sale of Property, Plant, and Equipment  | 固定資産売却
Short Term Debt Issued, Total           | 短期借入
Short Term Debt Repaid, Total           | 短期借入返済
Special Dividends Paid                  | 特別配当支払
Stock-Based Compensation                | 株式報酬
Total Debt Issued                       | 借入合計
Total Debt Repaid                       | 借入返済合計

CashFlowマッピング定義

const cashFlowMapping = {
  operatingCF: { sqlite: 'Cash from Operations', label: '営業CF' },
  capex: { sqlite: 'Capital Expenditure', label: '設備投資' },  // 負の値
  fcf: { sqlite: 'Free Cash Flow', label: 'FCF' },
}

PerShareマッピング定義

const perShareMapping = {
  eps: { sqlite: 'Diluted EPS - Continuing Operations', label: 'EPS' },
  // または 'Net EPS - Diluted'
}

差額計算

// その他流動資産 = Total Current Assets - (現預金 + 売掛金 + 棚卸資産)
const otherCurrentAssets = totalCurrentAssets - (cash + receivables + inventory)

// その他流動負債 = Total Current Liabilities - (買掛金 + 短期借入金 + 短期リース + 前受収益)
const otherCurrentLiabilities = totalCurrentLiabilities - (ap + shortTermDebt + currentLeases + unearnedRevenue)

// その他固定負債 = (Total Liabilities - Total Current Liabilities) - (長期借入金 + 長期リース + 長期前受収益)
const totalFixedLiabilities = totalLiabilities - totalCurrentLiabilities
const otherFixedLiabilities = totalFixedLiabilities - (longTermDebt + longTermLeases + longTermUnearned)

BSバランス調整

// 資産合計
const totalAssets = sumCurrentAssets + sumFixedAssets

// 負債・純資産合計
const totalLiabilitiesAndEquity = sumCurrentLiabilities + sumFixedLiabilities + sumEquity

// 差額があれば「その他長期資産」または「その他固定負債」で調整
if (totalAssets !== totalLiabilitiesAndEquity) {
  const diff = totalLiabilitiesAndEquity - totalAssets
  // 資産側を調整
  otherLongTermAssets += diff
}

// 許容誤差: ±1M以内
// 検証失敗時: 警告ログを出力

Vueコンポーネントの変更箇所

現在の ProportionalFinancialStatementsAnimated.vue の型定義(該当箇所)

// 現在の PLData(248-254行目)
export interface PLData {
  revenue: number
  grossProfit?: number
  operatingIncome?: number
  profit: number            // 純利益(Net Income)
  expenses: number          // ← これが使われている
}

// 現在の ExpenseData(257-260行目)
export interface ExpenseData {
  sga: number   // SG&A
  rd: number    // R&D
}

// 現在の FinancialData(274-280行目)
export interface FinancialData {
  bs: BSData
  pl: PLData
  expenses?: ExpenseData    // ← 別プロパティ
  cashFlow?: CashFlowData
  perShare?: PerShareData
}

PL検証ロジック(426-429行目)

const isPlBalanced = (data: FinancialData): boolean => {
  // 現在: expenses + profit === revenue を検証
  return Math.abs((data.pl.expenses + data.pl.profit) - data.pl.revenue) < 0.1
}

変更が必要な箇所

変更不要: 現在のコンポーネントはそのまま使える。

  • pl.expensespl.profit を使っており、これは生成スクリプトで計算して設定すればよい
  • expenses?: ExpenseData は別プロパティとして存在し、表示には使用されていない(データ参照用)

生成スクリプトで設定する値:

// PLData
pl: {
  revenue: parseValue(metrics['Revenues']),
  grossProfit: parseValue(metrics['Gross Profit (Loss)']),
  operatingIncome: parseValue(metrics['Operating Income']),
  profit: parseValue(metrics['Net Income']),
  expenses: parseValue(metrics['Revenues']) - parseValue(metrics['Net Income']),  // 計算
}

// ExpenseData(オプション)
expenses: {
  sga: parseValue(metrics['Selling General & Admin Expenses']),
  rd: parseValue(metrics['R&D Expenses']),
}

実装タスク

必須タスク

  1. 型定義ファイルの作成
    • app/types/financial.ts
    • 現在のコンポーネントの型定義をそのまま移動(変更なし)
  2. 変換スクリプトの作成
    • scripts/generate-financial-data.mjs
    • SQLiteからデータを読み込み、CompanyData形式に変換
    • composables/financial-data.ts を自動生成
    • pl.expenses = revenue - netIncome で計算
  3. Vueコンポーネントの更新
    • 型定義のimport元を ~/types/financial に変更するのみ
    • ロジックの変更は不要
  4. 新しいVueページの作成
    • pages/financial-quiz/proportional-animation-qqq.vue
    • financial-data.tsからデータを読み込み
  5. 生成されたファイルの検証
    • BSバランスのチェック
    • 画面表示の確認

ファイル構成(予定)

apps/web/
├── scripts/
│   └── generate-financial-data.mjs       ← 新規作成(変換スクリプト)
├── data/
│   └── koyfin.db                         ← 既存(SQLiteデータ)
└── app/
    ├── types/
    │   └── financial.ts                  ← 新規作成(型定義)
    ├── composables/
    │   ├── financial-data.ts             ← 新規作成(全企業データ)
    │   ├── useMicrosoftData.ts           ← 既存(残す)
    │   ├── useNvidiaData.ts              ← 既存(残す)
    │   └── ...                           ← 既存(残す)
    ├── components/
    │   └── financial-quiz/
    │       └── ProportionalFinancialStatementsAnimated.vue  ← 更新
    └── pages/
        └── financial-quiz/
            ├── proportional-animation.vue      ← 既存(残す)
            └── proportional-animation-qqq.vue  ← 新規作成

実装時の変更点と経緯

1. SQLiteライブラリの変更

当初の計画: better-sqlite3 を使用

変更後: sql.js を使用

変更理由:

  • better-sqlite3 はネイティブモジュールであり、Windows環境で node-gyp によるビルドが必要
  • ビルドには Visual Studio の C++ ツールセット(Desktop development with C++)が必要
  • 環境に C++ ツールセットがインストールされていなかったため、ビルドに失敗
  • sql.js は純粋JavaScript実装のため、ネイティブビルド不要

影響:

  • APIが同期から非同期に変更(db.prepare().all()db.exec()
  • 結果の取得方法が変更(rowsresult[0]?.values

2. 型定義の分離方針

当初の計画: 型定義を完全に新しい構造に変更

変更後: 既存の型定義を維持し、~/types/financial.ts に移動

変更理由:

  • 既存の ProportionalFinancialStatementsAnimated.vuepl.expensespl.profit を使用
  • コンポーネントの変更を最小限に抑えるため、データ構造は既存に合わせる
  • 新しい詳細なPL構造(costOfRevenue, sga, rd等を直接持つ)は採用せず、expenses オプションプロパティとして保持

3. PLDataの構造

当初の計画:

interface PLData {
  revenue: number
  costOfRevenue: number
  grossProfit: number
  sga: number
  rd: number
  operatingIncome: number
  netIncome: number
}

実装後:

interface PLData {
  revenue: number
  grossProfit?: number
  operatingIncome?: number
  profit: number       // = netIncome
  expenses: number     // = revenue - profit(計算値)
}

interface ExpenseData {
  sga: number
  rd: number
}

変更理由:

  • コンポーネントの検証ロジック expenses + profit === revenue を変更せずに済む
  • expensesrevenue - netIncome で計算して設定
  • SG&A、R&D は別途 ExpenseData として保持(チャート表示用)

4. BSの純資産(equity)の集約

当初の計画: Common Stock, Additional Paid In Capital, Retained Earnings, Treasury Stock, Comprehensive Income を個別に表示

実装後: 2項目に集約

  • 資本金等 = Common Stock + Additional Paid In Capital
  • 剰余金 = Retained Earnings + Treasury Stock + Comprehensive Income(内訳をbreakdownで保持)

変更理由:

  • 表示がシンプルになる
  • ホバーで内訳を確認できる(breakdown機能)
  • Appleのように Retained Earnings がマイナス(累積赤字)の場合も適切に表示

5. 期間ラベルの変換

SQLiteのperiod_label: 'FY 2015', 'FY 2024', 'Current/LTM'

変換後のlabel: '2015', '2024', 'LTM'

実装:

function convertPeriodLabel(periodLabel) {
  if (periodLabel === 'Current/LTM') return 'LTM'
  const match = periodLabel.match(/FY (\d{4})/)
  if (match) return match[1]
  return periodLabel
}

6. バランス検証と自動調整

実装した検証:

  1. BSバランス: 資産合計 = 負債合計 + 純資産合計(差額10M以上で警告)
  2. PLバランス: expenses + profit = revenue(差額1M以上で警告)

検証結果: 全19社・全期間でバランスOK

7. 貸借バランス調整ロジック(2025-12-12追加)

問題: SQLiteから取得した財務データで、個別項目の合計と公表されたTotal値に差異が生じることがある。 これは以下の原因による:

  • 小数点の丸め誤差(SQLiteデータは小数点第1位まで保持: 45,097.1M
  • カテゴリ分類の違い(SQLiteの項目分類と会計基準の差異)

調整ロジックの実装場所: apps/web/scripts/generate-financial-data.mjs

調整の流れ:

1. カテゴリ別「その他」調整(lines 143-195)
   ├── その他流動資産 = Total Current Assets - (現預金 + 売掛金 + 棚卸資産)
   ├── その他固定資産 = Total Fixed Assets - (有形固定 + 長期投資 + のれん + 無形)
   ├── その他流動負債 = Total Current Liab - (買掛金 + 短期借入 + ...)
   └── その他固定負債 = Total Fixed Liab - (長期借入 + 長期リース + ...)

2. 純資産の剰余金調整(lines 211-218)
   └── 剰余金 = Total Equity - 資本金等(差額がある場合)

3. 最終貸借バランス調整(lines 245-267)
   ├── assetsSum = 全資産項目の合計
   ├── liabAndEquitySum = 全負債・純資産項目の合計
   ├── balanceAdjustment = assetsSum - liabAndEquitySum
   └── 差額を「その他固定負債」で調整(小数点第1位で丸め)

コード(最終貸借バランス調整部分):

// ========== 最終貸借バランス調整 ==========
// 資産合計と(負債+純資産)合計を計算
const assetsSum = currentAssets.reduce((s, i) => s + i.value, 0) +
                  fixedAssets.reduce((s, i) => s + i.value, 0)
const liabilitiesSum = currentLiabilities.reduce((s, i) => s + i.value, 0) +
                       fixedLiabilities.reduce((s, i) => s + i.value, 0)
const equitySum = equity.reduce((s, i) => s + i.value, 0)
const liabAndEquitySum = liabilitiesSum + equitySum

// 差額があれば「その他固定負債」で調整(小数点を含む差額にも対応)
const balanceAdjustment = assetsSum - liabAndEquitySum
// 小数点第1位で丸めて比較(浮動小数点誤差対策)
if (Math.abs(balanceAdjustment) > 0.01) {
  // 既存の「その他固定負債」を探す
  const otherFixedLiabIndex = fixedLiabilities.findIndex(i => i.label === 'その他固定負債')
  if (otherFixedLiabIndex >= 0) {
    // 既存の項目に加算(小数点第1位で丸め)
    fixedLiabilities[otherFixedLiabIndex].value =
      Math.round((fixedLiabilities[otherFixedLiabIndex].value + balanceAdjustment) * 10) / 10
  } else {
    // 新規に作成(小数点第1位で丸め)
    fixedLiabilities.push({
      label: 'その他固定負債',
      value: Math.round(balanceAdjustment * 10) / 10
    })
  }
}

なぜ「その他固定負債」で調整するのか:

  • 「その他」項目は元々差額計算で算出されるため、多少の調整は会計的に許容される
  • 固定負債は流動負債より目立ちにくく、ユーザーへの影響が小さい
  • 純資産の調整は剰余金で既に行われているため、負債側で調整するのが妥当

対象企業例(調整が適用された):

  • ASML: 0.1Mの調整
  • NFLX: 0.4Mの調整
  • PLTR: 0.3Mの調整

参考ファイル

  • 既存composable例: apps/web/app/composables/useMicrosoftData.ts
  • Vueコンポーネント: apps/web/app/components/financial-quiz/ProportionalFinancialStatementsAnimated.vue
  • SQLiteスキーマ: apps/web/data/schema.sql
  • データ変換プロンプト: apps/web/content/2025-12-11/qqq-financial-data-pipeline.md

今後の作業

  1. 企業追加: SQLiteにデータを追加後、node scripts/generate-financial-data.mjs を実行
  2. 既存composable削除: 新システムが安定したら、個別のcomposable(useMicrosoftData.ts等)を削除可能
  3. 既存ページ統合: proportional-animation.vueproportional-animation-qqq.vue に統合することも検討可能