1. アーキテクチャ概要
- 純粋なクライアントサイドSPA。ビルド工程・サーバーサイド処理・APIは一切無い。 静的ファイルをWebサーバー(Apache)に置くだけで動作する。
- CSVの読込・解析・集計・描画・出力はすべてブラウザ内(JavaScript)で完結。データは外部送信されない。
- 編集対象は
app.js/style.css/index.htmlの3ファイルのみ。 トランスパイル不要(ES2020相当をそのまま記述)。
ファイル構成と責務
| ファイル | 責務 |
|---|---|
| index.html | DOM骨格。アップロード画面 / ダッシュボード(6タブ)/ 出力中オーバーレイ / 明細モーダル。lib/ と app.js を読込。 |
| app.js | 全ロジック。取込・分類・索引構築・各タブ描画・ドリルダウン・出力・取込履歴。約1100行・単一ファイル。 |
| style.css | スタイル全般。 |
| lib/papaparse.min.js | CSVパーサ(PapaParse 5.4.1) |
| lib/chart.umd.min.js | グラフ描画(Chart.js 4.4.1) |
| lib/jszip.min.js | ZIP生成(JSZip 3.10.1)。PNG一括出力で使用 |
| lib/pptxgen.bundle.js | PowerPoint生成(PptxGenJS 3.12.0) |
| manual.html / 仕様書.html | 操作マニュアル(一般向け)/ 本書 |
| make_sample.py | 動作確認用サンプルCSV生成(Shift-JIS・和暦)。sample/ に出力 |
lib/ に同梱(オフライン動作・依存固定のため)。
ライブラリ更新時はファイル差し替え+動作確認。2. データフロー
ファイル選択/ドロップ
→ handleFiles() … 各ファイルを順に処理
→ readFile() … FileReader で ArrayBuffer 取得
→ decodeBuffer() … UTF-8 / Shift-JIS を自動判定しテキスト化
→ Papa.parse(header:true) … 配列<オブジェクト> 化
→ classify() … ヘッダー列名から 顧客/車両/伝票/入金 を判定
→ state[type] に格納
[分析する]
→ analyze()
→ buildIndexes() … idx.custTx / idx.vehLast / idx.years を構築
→ safe('タブ名', renderXxx) … 6タブを個別 try/catch で描画
→ saveSession() … IndexedDB に取込内容を保存(最新10件)
3. グローバル状態(app.js 冒頭)
| 変数 | 型 / 内容 |
|---|---|
| state | {customers,vehicles,invoices,payments}。各値は {name,rows,fields} または null。rows はパース済み行オブジェクト配列。 |
| idx | 取込後に buildIndexes() が構築する索引。custTx(顧客CD→{count,total,first,last})/ vehLast(車両CD→最終入庫日)/ years(伝票の年配列)。 |
| charts | Chart.js インスタンスのレジストリ。キー=canvas id。draw() が登録、analyze() 冒頭で破棄・クリア。 |
| drills | canvas id → ドリル解決関数 (datasetIndex,index) ⇒ {title,columns,rows}。 |
| drillData | 明細モーダルに現在表示中の {title,columns,rows}。CSV/印刷で参照。 |
| TODAY | 基準日(new Date() 相当のハードコード)。残日数・活性区分の計算に使用。 |
4. 入力データの扱い
4.1 文字コード判定 — decodeBuffer()
UTF-8・Shift-JIS の両方で TextDecoder デコードし、日本語文字数から不正文字(U+FFFD)数×20を引いたスコアの高い方を採用。
4.2 ファイル分類 — classify()
ヘッダー列名集合に対し、種別ごとの特徴列(SIGNATURE)の一致数を数え、最大の種別へ割当て(最低2列一致)。
4.3 列名のゆらぎ吸収 — pick(r, ...names)
業務システムの出力により列名が異なるため(例:法人区分 / 法人個人区分)、候補名を並べ最初に値のある列を返す。新たなゆらぎは pick() の候補追加で対応。
4.4 和暦日付 — parseDate()
元号記号(M/T/S/H/R)・漢字元号・西暦を解釈。元号→西暦は ERA_BASE のオフセット表(S=1925, H=1988, R=2018 など)で換算。日が無い H10-01 は1日扱い。日付列は必ず parseDate() を通すこと。
5. 集計の定義
| 関数 / 定義 | 内容 |
|---|---|
| activityOf(date) | 活性区分。最終取引からの経過 ≤730日=active / ≤1095日=alert / それ超=inactive。 しきい値変更はこの1関数(車検2年周期を考慮し2年/3年)。 |
| grossProfit(r) | 伝票1行の粗利=技術料粗利合計+部品粗利合計+諸費用粗利合計+諸費セット粗利合計。 |
| isVehicleSale(r) | 売上区分名称等に「販売/車販/新車/中古車/納車」を含むか=車販。 |
| isShaken(r) | 同様に「車検」を含むか。一般整備=!isVehicleSale && !isShaken。 |
| 車検継続率 | renderYoY内:車検伝票を車両CD別に日付昇順で並べ、2件目以降を「継続」(isRenewal Set)、初回を「新規」。継続率=継続÷(車両CD付き車検全体)。 |
| areaOf(r) | 居住エリア。地区があれば優先、無ければ 住所1 から正規表現で都道府県+市区町村郡を抽出。 |
5.1 RFM 5段階スコア(しきい値はデータから自動算出)
スコアの境界値は固定しない。業態・店舗規模により取引回数や購買額の水準が大きく異なるため、
取込んだCSVの分布から分位数(quintile)で自動算出する。
R・F・M それぞれについて、取引実績のある全顧客の値を昇順に並べ、20/40/60/80パーセンタイル点
(quintileCuts())を境界として5段階に分類する。
| スコア | R:最終取引からの経過日数 (少ない=高スコア) | F:累計取引回数 / M:累計購買額 (多い=高スコア) |
|---|---|---|
| 5 | 下位20%(最も最近) | 上位20% |
| 4 | 20〜40% | 60〜80% |
| 3 | 40〜60% | 40〜60% |
| 2 | 60〜80% | 20〜40% |
| 1 | 上位20%(最も遠い) | 下位20% |
| 0 | 取引実績なし | |
R・F・M の定義:R=idx.custTx の last と基準日(TODAY)の差日数、
F=count(伝票件数)、M=total(伝票合計の累計)。顧客⇔伝票は顧客CDで突合。
実装は renderRFM() 内の rCut/fCut/mCut(境界値)と rSc/fSc/mSc(スコア関数)。
RFM内訳グラフの「範囲」列にはこの自動算出された境界値が表示される。
5.2 顧客ランク(★0〜★5)
合計スコア sc = R + F + M(3〜15)で判定。取引実績の無い顧客は sc によらず ★0。
R・F・M が相対評価(分位数)になったため、ランク境界は固定のまま運用できる。
| 合計スコア sc | 顧客ランク |
|---|---|
| 13〜15 | ★5 |
| 11〜12 | ★4 |
| 9〜10 | ★3 |
| 7〜8 | ★2 |
| 3〜6 | ★1 |
| (取引実績なし) | ★0 |
5.3 ランク別の表示指標
| 指標 | 算出 |
|---|---|
| 人数 | そのランクに属する顧客数(顧客CSV全件が母数。括弧内は構成比) |
| LTV(累計購買額) | ランク内顧客の total の平均 |
| 客単価(1伝票あたり) | Σtotal ÷ Σcount(取引実績のある顧客のみ) |
| 利用頻度 | 「count ÷ 取引年数」の平均(回/年)。取引年数=(初回〜最終取引)/365.25日、最小0.25年) |
| 取引期間 | 初回取引〜最終取引の平均(◯年◯ヶ月) |
renderRFM() を編集する。5.4 デモデータ
CSVが無い環境での動作確認用に buildDemoData() を用意。顧客160/車両260/伝票1,600/入金1,300件の
擬似データ(2024〜2026年)を生成して state に直接投入する。アップロード画面の「デモデータで試す」ボタンから実行。
取込履歴には保存しない。
6. 描画レイヤ
主要ヘルパ
| kpiRow(parent,items) | KPIカード行 |
| chartCard(parent,id,title,sub,opt) | カード+canvas生成。PNG保存ボタン付き。canvasのidを返す |
| tableCard(parent,title,sub,head,rows,opt) | 表カード。CSV/印刷ボタンを自動付与 |
| noteCard(parent,title,msg,opt) | データ欠落時に理由を表示するカード |
| draw(id,cfg) | Chart.js描画。charts[id]へ登録、onClick/onHoverでドリル連動 |
| segBar(id,entries) | 活性区分つき積み上げ横棒。伝票未取込時は単一系列 |
| tally / sumBy / topEntries / segCounts | 集計ヘルパ(件数・合計・上位N・活性区分つき件数) |
各タブは renderOverview / renderYoY / renderCustomers / renderVehicles / renderRFM / renderRanking。
analyze() から safe() 経由で呼ばれ、1タブが例外を投げても他タブは描画される。
Chart.js のアニメーションは analyze() で Chart.defaults.animation=false に設定(非表示タブの実寸描画・出力のため)。
7. ドリルダウン
カテゴリ系グラフをクリックすると該当明細をモーダル表示する。
draw()が全チャートにonClickを設定。クリック要素の(datasetIndex,index)でdrills[id]を呼び、戻り値{title,columns,rows}をopenDrill()に渡す。- drillCat(id,items,keyFn,labels,cols,prefix):カテゴリ別グラフ用。クリックした凡例ラベルで
itemsを絞込。 - drillSeg(id,items,keyFn,actFn,labels,cols,prefix):活性区分つき積み上げ用。ラベル×系列(active/alert/inactive)で絞込。
- 表示カラムは
COL_CUST / COL_CUST_TX / COL_VEH / COL_INV / COL_PAY([見出し,取得関数]の配列)。 - モーダルは画面表示500件まで、CSV/印刷は全件。
downloadCSV()(BOM付きUTF-8)/printTable()(別窓印刷)。
8. 出力機能
- PNG一括
exportPNGzip():全チャートをchartPNG()(白背景付与)でPNG化し JSZip でZIP化。 - PowerPoint
exportPPTX():PptxGenJS で表紙+1グラフ1スライド。画像は縦横比保持で配置。 withAllVisible():出力前に全タブを一時表示+resize()し、非表示タブのチャートも実寸で取得。collectCharts():chartsを走査し{no,tab,title,dataURL,w,h}を収集。
9. 取込履歴(IndexedDB)
DB名 dreampower_graph(version 2)。ストアを2つに分離し、件数を増やしても一覧表示が重くならない設計。
| ストア | keyPath | 内容 |
|---|---|---|
| meta | id | {id, savedAt, counts}。一覧表示・間引き判定で使用(軽量) |
| data | id | {id, state}。CSV本体。「この内容を表示」時に1件のみ読込 |
saveSession() が両ストアへ put、getAllKeys() で KEEP(=10) 件超を削除。
restoreSession(id) が data から復元し再分析。id は nextId() で単調増加を保証。
10. 拡張ガイド
グラフを1つ追加する
// 該当の renderXxx() 内
chartCard(grid,'my-chart','タイトル','サブ');
draw('my-chart',{type:'bar',data:{labels,datasets},options:{...}});
// 明細ドリルを付ける場合(カテゴリ別グラフ)
drillCat('my-chart', items, r=>r['区分名'], labels, COL_INV, '区分');
変更ポイント早見表
| 活性区分のしきい値 | activityOf()(730 / 1095 の日数) |
| RFMの区分境界 | recencyBucket / freqBucket / monBucket と rScore / fScore / mScore |
| 取込履歴の保持件数 | KEEP 定数 |
| ファイル判別の特徴列 | SIGNATURE |
| 列名のゆらぎ追加 | 該当箇所の pick() 候補 |
| 明細カラム | COL_CUST / COL_VEH / COL_INV / COL_PAY |
デプロイ
app.js/style.cssを編集index.htmlの?v=とフッターver.を繰上げ(ブラウザキャッシュ対策。必須)- ファイル一式を
/var/www/html/dreampower_graph/へ配置(Apache。AllowOverride Noneのため .htaccess は無効)
11. 既知の制約
- 集計は取込CSVの記載値どおり。赤伝・マイナス伝票もそのまま合算。
- 売掛残は「請求額累計−入金額累計」の推計。
- 顧客⇔伝票は顧客CD、車両⇔伝票は車両CDで突合。コード体系不一致時は紐付かない。
- 車検継続率は車両CDのある車検伝票のみが母数。車両CD欠落分は除外。
- アンケート満足度・人時生産性・AI査定・予算(目標値)は入力データに無く対象外。
- IndexedDB不可の環境では取込履歴のみ無効(分析・出力は動作)。