bizgate-mcp-server 0.3.9 → 0.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +203 -14
- package/dist/install-skill.js +128 -3
- package/dist/jpx-cache.d.ts +10 -0
- package/dist/jpx-cache.js +39 -19
- package/dist/seed-cache.d.ts +5 -0
- package/dist/seed-cache.js +10 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -39,7 +39,7 @@ if (authMode === "ip" && !process.env.BIZGATE_APP) {
|
|
|
39
39
|
console.error("Error: BIZGATE_APP is required when BIZGATE_AUTH_MODE=ip");
|
|
40
40
|
process.exit(1);
|
|
41
41
|
}
|
|
42
|
-
const dailyLimit = Number(process.env.BIZGATE_DAILY_LIMIT ?? "
|
|
42
|
+
const dailyLimit = Number(process.env.BIZGATE_DAILY_LIMIT ?? "1000");
|
|
43
43
|
const seedCsvUrl = process.env.SEED_CSV_URL ?? "https://pub-3952dc5d8b37475196bab871e4e93bc1.r2.dev/latest/seed-list.csv.gz";
|
|
44
44
|
const sharedCacheUrl = process.env.SHARED_CACHE_URL ?? "https://bizgate-cache-worker.a-adachi.workers.dev/download";
|
|
45
45
|
const usageFile = process.env.BIZGATE_USAGE_FILE ??
|
|
@@ -430,20 +430,27 @@ server.tool("bizgate__usage_status", "本日のBizGate API残り利用回数を
|
|
|
430
430
|
});
|
|
431
431
|
// ---------- Tool 7: プロスペクトリスト(ローカルSQLite) ----------
|
|
432
432
|
const MAX_API_CALLS_HARD_LIMIT = 150;
|
|
433
|
+
/** 全角数字を半角に変換 */
|
|
434
|
+
function zenToHan(s) {
|
|
435
|
+
return s.replace(/[0-9]/g, (ch) => String.fromCharCode(ch.charCodeAt(0) - 0xFEE0));
|
|
436
|
+
}
|
|
433
437
|
/** 売上カテゴリ文字列から数値を抽出して最低ラインと比較 */
|
|
434
438
|
function revenueMatchesMin(revenue, minLabel) {
|
|
435
439
|
if (!revenue)
|
|
436
440
|
return false;
|
|
437
|
-
// revenue例: "
|
|
441
|
+
// revenue例: "5.500億以上" (全角数字+全角ピリオド)
|
|
442
|
+
// カテゴリ: 1=5億未満, 2=5-20億, 3=20-100億, 4=100-500億, 5=500億以上
|
|
438
443
|
const thresholds = {
|
|
439
|
-
"50億以上":
|
|
440
|
-
"300億以上":
|
|
441
|
-
"1000億以上":
|
|
444
|
+
"50億以上": 3, // カテゴリ3(20億-100億) 以上 — 50億含む可能性
|
|
445
|
+
"300億以上": 4, // カテゴリ4(100億-500億) 以上
|
|
446
|
+
"1000億以上": 5, // カテゴリ5(500億以上)
|
|
442
447
|
};
|
|
443
448
|
const minCat = thresholds[minLabel];
|
|
444
449
|
if (!minCat)
|
|
445
|
-
return true;
|
|
446
|
-
|
|
450
|
+
return true;
|
|
451
|
+
// 全角数字・全角ピリオドを半角に正規化してからマッチ
|
|
452
|
+
const normalized = zenToHan(revenue).replace(/./g, ".");
|
|
453
|
+
const match = normalized.match(/^(\d+)\./);
|
|
447
454
|
if (!match)
|
|
448
455
|
return false;
|
|
449
456
|
return Number(match[1]) >= minCat;
|
|
@@ -465,10 +472,10 @@ server.tool("bizgate__prospect_list", "シードリスト(国税庁572万法
|
|
|
465
472
|
city: z.string().optional().describe("市区町村名(例: 千代田区)"),
|
|
466
473
|
industry: z.string().optional().describe("BizGate業種分類キーワード(例: 製造業, サービス, IT)"),
|
|
467
474
|
revenue_min: z.string().optional().describe("最低売上(50億以上 / 300億以上 / 1000億以上)"),
|
|
468
|
-
|
|
475
|
+
emp_min: z.string().optional().describe("最低従業員数(100人以上 / 300人以上 / 1000人以上)"),
|
|
469
476
|
proposal_axis: z.union([z.string(), z.array(z.string())]).optional().describe("提案軸(DOMO / 営業DX/CRM)。部署検索のキーワードに変換される"),
|
|
470
477
|
count: z.number().optional().describe("取得したい企業数(デフォルト: 10、最大: 30)"),
|
|
471
|
-
}, async ({ pref, city, industry, revenue_min,
|
|
478
|
+
}, async ({ pref, city, industry, revenue_min, emp_min, proposal_axis, count: rawCount }) => {
|
|
472
479
|
if (!seedCache.isReady()) {
|
|
473
480
|
return {
|
|
474
481
|
content: [{ type: "text", text: "エラー: シードリストが利用できません。SEED_CSV_URL を設定し、Claude Code を再起動してください。" }],
|
|
@@ -509,10 +516,19 @@ server.tool("bizgate__prospect_list", "シードリスト(国税庁572万法
|
|
|
509
516
|
if (!industry.split(",").some((k) => ind.includes(k.trim())))
|
|
510
517
|
return false;
|
|
511
518
|
}
|
|
512
|
-
if (
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
519
|
+
if (emp_min && empVal) {
|
|
520
|
+
const empThresholds = {
|
|
521
|
+
"100人以上": 4, // カテゴリ4(100-300人) 以上
|
|
522
|
+
"300人以上": 5, // カテゴリ5(300-1000人) 以上
|
|
523
|
+
"1000人以上": 6, // カテゴリ6(1000人以上)
|
|
524
|
+
};
|
|
525
|
+
const minEmpCat = empThresholds[emp_min];
|
|
526
|
+
if (minEmpCat) {
|
|
527
|
+
const empNorm = zenToHan(empVal).replace(/./g, ".");
|
|
528
|
+
const empCat = Number(empNorm.match(/^(\d+)\./)?.[1] ?? 0);
|
|
529
|
+
if (empCat < minEmpCat)
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
516
532
|
}
|
|
517
533
|
return true;
|
|
518
534
|
}
|
|
@@ -740,7 +756,14 @@ server.tool("bizgate__enterprise_search", "上場企業(JPX約3,800社)か
|
|
|
740
756
|
: { shogo: listed.name };
|
|
741
757
|
const { docs } = await client.searchCompany(searchParams);
|
|
742
758
|
apiCalls++;
|
|
743
|
-
|
|
759
|
+
let doc = docs[0] ?? null;
|
|
760
|
+
// shogo検索ではrevenueが返らないため、compnoで再検索
|
|
761
|
+
if (doc && !doc.revenue && !listed.corporate_number && doc.compno) {
|
|
762
|
+
const { docs: docs2 } = await client.searchCompany({ compno: doc.compno });
|
|
763
|
+
apiCalls++;
|
|
764
|
+
doc = docs2[0] ?? doc;
|
|
765
|
+
}
|
|
766
|
+
return { listed, doc };
|
|
744
767
|
}
|
|
745
768
|
catch {
|
|
746
769
|
apiCalls++;
|
|
@@ -833,6 +856,172 @@ server.tool("bizgate__enterprise_search", "上場企業(JPX約3,800社)か
|
|
|
833
856
|
content: [{ type: "text", text: header + filterInfo + table + stats + usageFooter() }],
|
|
834
857
|
};
|
|
835
858
|
});
|
|
859
|
+
// ---------- Tool 9: prospect_suggest(ローカルDB候補+Claude判断 → API節約) ----------
|
|
860
|
+
server.tool("bizgate__prospect_suggest", "BizGate APIを使わずにローカルDB(上場企業JPX約3,800社 or シード約570万社)から候補企業リストを返す。sourceを省略すると利用可能な検索条件のガイドを返す(条件を知らないユーザー向け)。Claudeがリストから提案軸に合う企業を判断し、選んだ企業だけをbizgate__department_searchで照会する2段階ワークフロー用。API消費: 0回。", {
|
|
861
|
+
source: z.enum(["jpx", "seed"]).optional().describe("検索ソース。jpx=上場企業(市場・業種フィルタ可)、seed=全企業(都道府県・市区町村フィルタ可)。省略時は検索条件ガイドを表示"),
|
|
862
|
+
pref: z.string().optional().describe("都道府県(例: 東京都)。sourceがseedの場合のみ有効"),
|
|
863
|
+
city: z.string().optional().describe("市区町村(例: 渋谷区)。sourceがseedの場合のみ有効"),
|
|
864
|
+
keyword: z.string().optional().describe("企業名キーワード(部分一致)"),
|
|
865
|
+
market: z.string().optional().describe("市場区分(プライム / スタンダード / グロース)。sourceがjpxの場合のみ有効"),
|
|
866
|
+
industry: z.string().optional().describe("業種キーワード(例: 情報・通信業, 電気機器)。sourceがjpxの場合のみ有効"),
|
|
867
|
+
count: z.number().optional().describe("取得件数(デフォルト: 50、最大: 200)"),
|
|
868
|
+
shuffle: z.boolean().optional().describe("ランダム順にするか(デフォルト: false)"),
|
|
869
|
+
}, async ({ source, pref, city, keyword, market, industry, count: rawCount, shuffle }) => {
|
|
870
|
+
// ---- ガイドモード: sourceが省略された場合 ----
|
|
871
|
+
if (!source) {
|
|
872
|
+
const lines = [
|
|
873
|
+
"## 🔍 プロスペクト検索 — 条件ガイド",
|
|
874
|
+
"",
|
|
875
|
+
"このツールはローカルDBから候補企業を抽出します(**API消費 0回**)。",
|
|
876
|
+
"以下の条件を組み合わせて検索できます。",
|
|
877
|
+
"",
|
|
878
|
+
"---",
|
|
879
|
+
"",
|
|
880
|
+
"### データソース(`source` — 必須)",
|
|
881
|
+
"",
|
|
882
|
+
"| source | 対象 | 件数 | 使えるフィルタ |",
|
|
883
|
+
"|--------|------|------|--------------|",
|
|
884
|
+
"| `jpx` | 上場企業 | 約3,800社 | 市場区分, 業種, 企業名 |",
|
|
885
|
+
"| `seed` | 全法人(国税庁) | 約572万社 | 都道府県, 市区町村, 企業名 |",
|
|
886
|
+
"",
|
|
887
|
+
];
|
|
888
|
+
// JPX: 市場別
|
|
889
|
+
if (jpxCache.isReady()) {
|
|
890
|
+
const markets = jpxCache.listMarkets();
|
|
891
|
+
lines.push("### 市場区分(`market` — jpxのみ)");
|
|
892
|
+
lines.push("");
|
|
893
|
+
lines.push("| 市場 | 企業数 |");
|
|
894
|
+
lines.push("|------|--------|");
|
|
895
|
+
for (const m of markets) {
|
|
896
|
+
lines.push(`| ${m.market} | ${m.count.toLocaleString()}社 |`);
|
|
897
|
+
}
|
|
898
|
+
// JPX: 業種
|
|
899
|
+
const industries = jpxCache.listIndustries();
|
|
900
|
+
lines.push("");
|
|
901
|
+
lines.push("### 業種(`industry` — jpxのみ、部分一致)");
|
|
902
|
+
lines.push("");
|
|
903
|
+
lines.push("| 業種 | 企業数 |");
|
|
904
|
+
lines.push("|------|--------|");
|
|
905
|
+
for (const ind of industries) {
|
|
906
|
+
lines.push(`| ${ind.industry} | ${ind.count.toLocaleString()}社 |`);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
// Seed: 都道府県
|
|
910
|
+
if (seedCache.isReady()) {
|
|
911
|
+
const prefs = seedCache.listPrefectures();
|
|
912
|
+
lines.push("");
|
|
913
|
+
lines.push("### 都道府県(`pref` — seedのみ、完全一致)");
|
|
914
|
+
lines.push("");
|
|
915
|
+
lines.push("| 都道府県 | 法人数 |");
|
|
916
|
+
lines.push("|---------|--------|");
|
|
917
|
+
for (const p of prefs) {
|
|
918
|
+
lines.push(`| ${p.prefecture} | ${p.count.toLocaleString()}社 |`);
|
|
919
|
+
}
|
|
920
|
+
lines.push("");
|
|
921
|
+
lines.push("### 市区町村(`city` — seedのみ、完全一致)");
|
|
922
|
+
lines.push("都道府県と組み合わせて使用。例: `pref: \"東京都\", city: \"渋谷区\"`");
|
|
923
|
+
}
|
|
924
|
+
// 共通オプション
|
|
925
|
+
lines.push("");
|
|
926
|
+
lines.push("### 共通オプション");
|
|
927
|
+
lines.push("");
|
|
928
|
+
lines.push("| パラメータ | 説明 |");
|
|
929
|
+
lines.push("|-----------|------|");
|
|
930
|
+
lines.push("| `keyword` | 企業名キーワード(部分一致) |");
|
|
931
|
+
lines.push("| `count` | 取得件数(デフォルト50、最大200) |");
|
|
932
|
+
lines.push("| `shuffle` | ランダム順にするか(デフォルトfalse) |");
|
|
933
|
+
// BizGate API フィルタ(prospect_list / enterprise_search で使用)
|
|
934
|
+
lines.push("");
|
|
935
|
+
lines.push("---");
|
|
936
|
+
lines.push("");
|
|
937
|
+
lines.push("### 参考: BizGate APIフィルタ(`prospect_list` / `enterprise_search` で使用)");
|
|
938
|
+
lines.push("");
|
|
939
|
+
lines.push("以下はAPI消費ありのツールで使える追加フィルタです。");
|
|
940
|
+
lines.push("");
|
|
941
|
+
lines.push("**売上規模(`revenue_min`)**");
|
|
942
|
+
lines.push("| ラベル | BizGateカテゴリ |");
|
|
943
|
+
lines.push("|--------|---------------|");
|
|
944
|
+
lines.push("| `50億以上` | カテゴリ3~(20億-100億以上) |");
|
|
945
|
+
lines.push("| `300億以上` | カテゴリ4~(100億-500億以上) |");
|
|
946
|
+
lines.push("| `1000億以上` | カテゴリ5(500億以上) |");
|
|
947
|
+
lines.push("");
|
|
948
|
+
lines.push("**従業員数(`emp_min`)**");
|
|
949
|
+
lines.push("| ラベル | BizGateカテゴリ |");
|
|
950
|
+
lines.push("|--------|---------------|");
|
|
951
|
+
lines.push("| `100人以上` | カテゴリ4~(100-300人以上) |");
|
|
952
|
+
lines.push("| `300人以上` | カテゴリ5~(300-1000人以上) |");
|
|
953
|
+
lines.push("| `1000人以上` | カテゴリ6(1000人以上) |");
|
|
954
|
+
lines.push("");
|
|
955
|
+
lines.push("**提案軸(`proposal_axis`)**");
|
|
956
|
+
lines.push("| 軸 | 自動変換される部署キーワード |");
|
|
957
|
+
lines.push("|-----|---------------------------|");
|
|
958
|
+
lines.push("| `DOMO` | 経営企画, DX, システム, 情報, データ, 分析 |");
|
|
959
|
+
lines.push("| `営業DX/CRM` | 営業, マーケティング, 販売, 顧客, CRM, 販促 |");
|
|
960
|
+
return {
|
|
961
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
// ---- JPXソース ----
|
|
965
|
+
const count = Math.min(rawCount ?? 50, 200);
|
|
966
|
+
if (source === "jpx") {
|
|
967
|
+
if (!jpxCache.isReady()) {
|
|
968
|
+
return {
|
|
969
|
+
content: [{ type: "text", text: "エラー: 上場企業データベースが利用できません。サーバーを再起動してください。" }],
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
const { total, results } = jpxCache.search({
|
|
973
|
+
market,
|
|
974
|
+
industry,
|
|
975
|
+
nameKeyword: keyword,
|
|
976
|
+
limit: count,
|
|
977
|
+
});
|
|
978
|
+
if (results.length === 0) {
|
|
979
|
+
return {
|
|
980
|
+
content: [{ type: "text", text: `条件に合う上場企業が見つかりませんでした。(検索条件: 市場=${market ?? "全て"}, 業種=${industry ?? "全て"}, キーワード=${keyword ?? "なし"})` }],
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
const table = "| # | 証券コード | 会社名 | 市場 | 業種 | 法人番号 |\n" +
|
|
984
|
+
"|---|-----------|--------|------|------|----------|\n" +
|
|
985
|
+
results
|
|
986
|
+
.map((r, i) => `| ${i + 1} | ${r.securities_code} | ${r.name} | ${r.market} | ${r.industry_33} | ${r.corporate_number ?? "-"} |`)
|
|
987
|
+
.join("\n");
|
|
988
|
+
return {
|
|
989
|
+
content: [{
|
|
990
|
+
type: "text",
|
|
991
|
+
text: `## 上場企業候補(${results.length}社 / 全${total}社)\n\n${table}\n\n---\nAPI消費: 0回\n\n> この中から提案軸に合う企業をClaudeが選び、選んだ企業のみ bizgate__department_search で部署・電話番号を取得してください。法人番号(compno)を使うと精度が高くなります。`,
|
|
992
|
+
}],
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
// ---- シードソース ----
|
|
996
|
+
if (!seedCache.isReady()) {
|
|
997
|
+
return {
|
|
998
|
+
content: [{ type: "text", text: "エラー: シードデータベースが利用できません。サーバーを再起動してください。" }],
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
const { total, results } = seedCache.search({
|
|
1002
|
+
pref,
|
|
1003
|
+
city,
|
|
1004
|
+
keyword,
|
|
1005
|
+
limit: count,
|
|
1006
|
+
shuffle: shuffle ?? false,
|
|
1007
|
+
});
|
|
1008
|
+
if (results.length === 0) {
|
|
1009
|
+
return {
|
|
1010
|
+
content: [{ type: "text", text: `条件に合う企業が見つかりませんでした。(検索条件: 都道府県=${pref ?? "全て"}, 市区町村=${city ?? "全て"}, キーワード=${keyword ?? "なし"})` }],
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
const table = "| # | 会社名 | 都道府県 | 市区町村 | 法人番号 |\n" +
|
|
1014
|
+
"|---|--------|---------|---------|----------|\n" +
|
|
1015
|
+
results
|
|
1016
|
+
.map((r, i) => `| ${i + 1} | ${r.name} | ${r.prefecture} | ${r.city} | ${r.corporate_number} |`)
|
|
1017
|
+
.join("\n");
|
|
1018
|
+
return {
|
|
1019
|
+
content: [{
|
|
1020
|
+
type: "text",
|
|
1021
|
+
text: `## シード企業候補(${results.length}社 / 全${total}社)\n\n${table}\n\n---\nAPI消費: 0回\n\n> この中から提案軸に合う企業をClaudeが選び、選んだ企業のみ bizgate__department_search で部署・電話番号を取得してください。法人番号(compno)を使うと精度が高くなります。`,
|
|
1022
|
+
}],
|
|
1023
|
+
};
|
|
1024
|
+
});
|
|
836
1025
|
// ---------- 起動 ----------
|
|
837
1026
|
async function main() {
|
|
838
1027
|
// シードキャッシュ + 共有BizGateキャッシュを非同期で初期化
|
package/dist/install-skill.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
import { mkdirSync, writeFileSync, existsSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
|
-
const
|
|
5
|
+
const SKILLS_BASE = join(homedir(), ".claude", "skills");
|
|
6
|
+
const SKILL_DIR = join(SKILLS_BASE, "prospect-match");
|
|
7
|
+
const SKILL_FIND_DIR = join(SKILLS_BASE, "prospect-find");
|
|
6
8
|
const SKILL_MD = `---
|
|
7
9
|
name: prospect-match
|
|
8
10
|
description: |
|
|
@@ -90,9 +92,120 @@ bKwd: "戦略,成長,事業創造,イノベーション,企画,開発,推進,DX,
|
|
|
90
92
|
|
|
91
93
|
---
|
|
92
94
|
|
|
95
|
+
$ARGUMENTS
|
|
96
|
+
`;
|
|
97
|
+
const SKILL_FIND_MD = `---
|
|
98
|
+
name: prospect-find
|
|
99
|
+
description: |
|
|
100
|
+
条件を対話的に選択し、ローカルDBから候補企業を抽出 → Claude判断 → 部署検索の低API消費ワークフローを実行する。
|
|
101
|
+
"企業を探して", "プロスペクトを探して", "prospect find", "リスト作って",
|
|
102
|
+
"DOMOに合う企業", "営業先を探して", "ターゲット企業", "候補企業",
|
|
103
|
+
"기업을 찾아줘", "리스트 만들어줘", "영업 대상 찾아줘"
|
|
104
|
+
user-invocable: true
|
|
105
|
+
argument-hint: "[提案サービス名 or 自由条件]"
|
|
106
|
+
allowed-tools: mcp__bizgate__bizgate__prospect_suggest, mcp__bizgate__bizgate__department_search, mcp__bizgate__bizgate__company_search, AskUserQuestion, WebSearch
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
# Prospect Find: 条件ガイド付きプロスペクト探索
|
|
110
|
+
|
|
111
|
+
条件を対話的に確認し、ローカルDBから候補を抽出 → Claude判断 → BizGate部署検索、の3段階で営業ターゲットを発見するスキル。API消費を最小限に抑える。
|
|
112
|
+
|
|
113
|
+
## プロセス
|
|
114
|
+
|
|
115
|
+
### Step 1: 提案軸を確認
|
|
116
|
+
|
|
117
|
+
ユーザーの入力から **何を売りたいか(提案サービス/製品)** を特定する。
|
|
118
|
+
明示されていない場合は \`AskUserQuestion\` で確認する。
|
|
119
|
+
|
|
120
|
+
\\\`\\\`\\\`
|
|
121
|
+
何を提案したいですか?
|
|
122
|
+
|
|
123
|
+
1. DOMO(BI/データ分析ダッシュボード)
|
|
124
|
+
2. 営業代行(営業KPI管理・架電管理)
|
|
125
|
+
3. Solution(内部管理システム・DB構築)
|
|
126
|
+
4. AI(Claude × BizGate MCP連携)
|
|
127
|
+
5. その他(自由入力)
|
|
128
|
+
|
|
129
|
+
番号で選んでください。
|
|
130
|
+
\\\`\\\`\\\`
|
|
131
|
+
|
|
132
|
+
**提案軸 → 部署検索キーワード変換表:**
|
|
133
|
+
|
|
134
|
+
| 提案軸 | bKwd |
|
|
135
|
+
|--------|------|
|
|
136
|
+
| DOMO | 経営企画,DX,システム,情報,データ,分析,デジタル |
|
|
137
|
+
| 営業代行 | 営業,販売,販促,マーケティング,事業開発,顧客 |
|
|
138
|
+
| Solution | ICT,システム,DX,情報,企画,総務,イノベーション,デジタル |
|
|
139
|
+
| AI | 戦略,成長,事業創造,イノベーション,企画,開発,推進,DX,AI,ICT |
|
|
140
|
+
|
|
141
|
+
### Step 2: 検索条件を選択
|
|
142
|
+
|
|
143
|
+
\`AskUserQuestion\` で以下の条件を**1つのメッセージで**提示する。
|
|
144
|
+
不要な条件はスキップ可能であることを明記する。
|
|
145
|
+
|
|
146
|
+
\\\`\\\`\\\`
|
|
147
|
+
検索条件を選んでください(不要なものはスキップOK)。
|
|
148
|
+
|
|
149
|
+
■ データソース
|
|
150
|
+
a. 上場企業のみ(約3,800社 — 市場・業種で絞り込み可)
|
|
151
|
+
b. 全法人(約572万社 — 地域で絞り込み可)
|
|
152
|
+
|
|
153
|
+
■ 地域(全法人の場合)
|
|
154
|
+
→ 都道府県を入力(例: 東京都) — スキップ可
|
|
155
|
+
|
|
156
|
+
■ 市場区分(上場企業の場合)
|
|
157
|
+
1. 全市場 2. プライム 3. スタンダード 4. グロース
|
|
158
|
+
|
|
159
|
+
■ 業種(上場企業の場合)
|
|
160
|
+
→ キーワード入力(例: 情報・通信業) — スキップ可
|
|
161
|
+
|
|
162
|
+
■ 取得件数
|
|
163
|
+
→ デフォルト50社(最大200)
|
|
164
|
+
|
|
165
|
+
例: 「a, プライム, 情報, 100社」のように回答してください。
|
|
166
|
+
\\\`\\\`\\\`
|
|
167
|
+
|
|
168
|
+
### Step 3: prospect_suggest 実行(API 0回)
|
|
169
|
+
|
|
170
|
+
Step 2 の回答をパースして \`bizgate__prospect_suggest\` を呼ぶ。
|
|
171
|
+
|
|
172
|
+
- データソース a → \`source: "jpx"\` + market/industry
|
|
173
|
+
- データソース b → \`source: "seed"\` + pref/city
|
|
174
|
+
|
|
175
|
+
### Step 4: Claude が候補を評価(API 0回)
|
|
176
|
+
|
|
177
|
+
返ってきた候補リストを見て、**提案軸に合う企業を5〜10社選ぶ**。
|
|
178
|
+
選定基準:
|
|
179
|
+
- Claudeの知識ベースで企業の規模・業種・事業内容を判断
|
|
180
|
+
- 提案軸のサービスを導入しそうな企業を優先
|
|
181
|
+
- 選定理由を簡潔に付記する
|
|
182
|
+
|
|
183
|
+
### Step 5: department_search 実行(API N回)
|
|
184
|
+
|
|
185
|
+
選定した企業ごとに \`bizgate__department_search\` を実行する。
|
|
186
|
+
- \`compno\`: 法人番号(候補リストから取得)
|
|
187
|
+
- \`bKwd\`: Step 1 で決めた提案軸のキーワード
|
|
188
|
+
- \`bKOpr\`: "0"(OR検索)
|
|
189
|
+
|
|
190
|
+
**並列実行**で効率化する(最大5社同時)。
|
|
191
|
+
|
|
192
|
+
### Step 6: 結果出力
|
|
193
|
+
|
|
194
|
+
企業ごとにターゲット部署・電話番号・マッチ理由を表形式で出力する。
|
|
195
|
+
|
|
196
|
+
## 注意事項
|
|
197
|
+
|
|
198
|
+
- 電話番号がない場合は「-」と表示
|
|
199
|
+
- 各企業につき最大5部署まで提案
|
|
200
|
+
- マッチ理由は1行で簡潔に
|
|
201
|
+
- ユーザーの言語(日本語 or 韓国語)に合わせて出力
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
93
205
|
$ARGUMENTS
|
|
94
206
|
`;
|
|
95
207
|
export function main() {
|
|
208
|
+
// prospect-match
|
|
96
209
|
const existed = existsSync(join(SKILL_DIR, "SKILL.md"));
|
|
97
210
|
mkdirSync(SKILL_DIR, { recursive: true });
|
|
98
211
|
writeFileSync(join(SKILL_DIR, "SKILL.md"), SKILL_MD, "utf-8");
|
|
@@ -103,10 +216,22 @@ export function main() {
|
|
|
103
216
|
console.log("✔ prospect-match スキルをインストールしました");
|
|
104
217
|
}
|
|
105
218
|
console.log(` 場所: ${SKILL_DIR}/SKILL.md`);
|
|
219
|
+
// prospect-find
|
|
220
|
+
const existed2 = existsSync(join(SKILL_FIND_DIR, "SKILL.md"));
|
|
221
|
+
mkdirSync(SKILL_FIND_DIR, { recursive: true });
|
|
222
|
+
writeFileSync(join(SKILL_FIND_DIR, "SKILL.md"), SKILL_FIND_MD, "utf-8");
|
|
223
|
+
if (existed2) {
|
|
224
|
+
console.log("✔ prospect-find スキルを更新しました");
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
console.log("✔ prospect-find スキルをインストールしました");
|
|
228
|
+
}
|
|
229
|
+
console.log(` 場所: ${SKILL_FIND_DIR}/SKILL.md`);
|
|
106
230
|
console.log("");
|
|
107
231
|
console.log("使い方:");
|
|
108
|
-
console.log(" /prospect-match 会社名");
|
|
109
|
-
console.log(
|
|
232
|
+
console.log(" /prospect-match 会社名 — 特定企業の部署マッチング");
|
|
233
|
+
console.log(" /prospect-find DOMO — 条件ガイド付きプロスペクト探索");
|
|
234
|
+
console.log(' または「DOMOに合う企業を探して」「営業先を探して」');
|
|
110
235
|
}
|
|
111
236
|
// 直接実行された場合のみ実行
|
|
112
237
|
const isDirectRun = process.argv[1]?.endsWith("install-skill.js");
|
package/dist/jpx-cache.d.ts
CHANGED
|
@@ -12,6 +12,16 @@ export declare class JpxCache {
|
|
|
12
12
|
constructor(jpxUrl?: string);
|
|
13
13
|
init(): Promise<void>;
|
|
14
14
|
isReady(): boolean;
|
|
15
|
+
/** 利用可能な業種一覧と件数を返す */
|
|
16
|
+
listIndustries(): {
|
|
17
|
+
industry: string;
|
|
18
|
+
count: number;
|
|
19
|
+
}[];
|
|
20
|
+
/** 市場別の企業数を返す */
|
|
21
|
+
listMarkets(): {
|
|
22
|
+
market: string;
|
|
23
|
+
count: number;
|
|
24
|
+
}[];
|
|
15
25
|
search(params: {
|
|
16
26
|
market?: string;
|
|
17
27
|
industry?: string;
|
package/dist/jpx-cache.js
CHANGED
|
@@ -58,6 +58,22 @@ export class JpxCache {
|
|
|
58
58
|
isReady() {
|
|
59
59
|
return this.ready && this.db !== null;
|
|
60
60
|
}
|
|
61
|
+
/** 利用可能な業種一覧と件数を返す */
|
|
62
|
+
listIndustries() {
|
|
63
|
+
if (!this.db)
|
|
64
|
+
return [];
|
|
65
|
+
return this.db
|
|
66
|
+
.prepare("SELECT industry_33 as industry, COUNT(*) as count FROM listed_companies GROUP BY industry_33 ORDER BY count DESC")
|
|
67
|
+
.all();
|
|
68
|
+
}
|
|
69
|
+
/** 市場別の企業数を返す */
|
|
70
|
+
listMarkets() {
|
|
71
|
+
if (!this.db)
|
|
72
|
+
return [];
|
|
73
|
+
return this.db
|
|
74
|
+
.prepare("SELECT market, COUNT(*) as count FROM listed_companies GROUP BY market ORDER BY count DESC")
|
|
75
|
+
.all();
|
|
76
|
+
}
|
|
61
77
|
search(params) {
|
|
62
78
|
if (!this.db)
|
|
63
79
|
return { total: 0, results: [] };
|
|
@@ -122,7 +138,10 @@ export class JpxCache {
|
|
|
122
138
|
const codeCol = colKeys.find((k) => k.includes("コード")) ?? colKeys[0];
|
|
123
139
|
const nameCol = colKeys.find((k) => k.includes("銘柄名")) ?? colKeys[1];
|
|
124
140
|
const marketCol = colKeys.find((k) => k.includes("市場・商品区分")) ?? colKeys[2];
|
|
125
|
-
|
|
141
|
+
// 33業種区分(ラベル)を優先、なければ33業種コードを使用
|
|
142
|
+
const industry33LabelCol = colKeys.find((k) => k.includes("33業種区分"));
|
|
143
|
+
const industry33CodeCol = colKeys.find((k) => k.includes("33業種コード")) ?? colKeys[4];
|
|
144
|
+
const industry33Col = industry33LabelCol ?? industry33CodeCol;
|
|
126
145
|
console.error(`Column mapping: code=${codeCol}, name=${nameCol}, market=${marketCol}, industry=${industry33Col}`);
|
|
127
146
|
// seed.db を読み書きモードで開く
|
|
128
147
|
const db = new Database(DB_PATH);
|
|
@@ -175,24 +194,25 @@ export class JpxCache {
|
|
|
175
194
|
.get();
|
|
176
195
|
if (hasSeedTable) {
|
|
177
196
|
console.error("Matching with seed_companies...");
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
197
|
+
// 高速マッチング: 「株式会社+銘柄名」「銘柄名+株式会社」「銘柄名そのまま」で完全一致
|
|
198
|
+
const updateSql = `
|
|
199
|
+
UPDATE listed_companies SET corporate_number = (
|
|
200
|
+
SELECT s.corporate_number FROM seed_companies s
|
|
201
|
+
WHERE s.name = '株式会社' || listed_companies.name
|
|
202
|
+
OR s.name = listed_companies.name || '株式会社'
|
|
203
|
+
OR s.name = listed_companies.name
|
|
204
|
+
LIMIT 1
|
|
205
|
+
)
|
|
206
|
+
WHERE corporate_number IS NULL
|
|
207
|
+
`;
|
|
208
|
+
db.prepare(updateSql).run();
|
|
209
|
+
const matched = db
|
|
210
|
+
.prepare("SELECT COUNT(*) as cnt FROM listed_companies WHERE corporate_number IS NOT NULL")
|
|
211
|
+
.get().cnt;
|
|
212
|
+
const total = db
|
|
213
|
+
.prepare("SELECT COUNT(*) as cnt FROM listed_companies")
|
|
214
|
+
.get().cnt;
|
|
215
|
+
console.error(`Matched ${matched}/${total} companies with seed data`);
|
|
196
216
|
}
|
|
197
217
|
// インデックス作成
|
|
198
218
|
db.exec(`
|
package/dist/seed-cache.d.ts
CHANGED
|
@@ -12,6 +12,11 @@ export declare class SeedCache {
|
|
|
12
12
|
/** Initialize: check cache, download if needed, open DB */
|
|
13
13
|
init(): Promise<void>;
|
|
14
14
|
isReady(): boolean;
|
|
15
|
+
/** 利用可能な都道府県一覧と件数を返す */
|
|
16
|
+
listPrefectures(): {
|
|
17
|
+
prefecture: string;
|
|
18
|
+
count: number;
|
|
19
|
+
}[];
|
|
15
20
|
/** Search seed companies */
|
|
16
21
|
search(params: {
|
|
17
22
|
pref?: string;
|
package/dist/seed-cache.js
CHANGED
|
@@ -43,6 +43,14 @@ export class SeedCache {
|
|
|
43
43
|
isReady() {
|
|
44
44
|
return this.ready && this.db !== null;
|
|
45
45
|
}
|
|
46
|
+
/** 利用可能な都道府県一覧と件数を返す */
|
|
47
|
+
listPrefectures() {
|
|
48
|
+
if (!this.db)
|
|
49
|
+
return [];
|
|
50
|
+
return this.db
|
|
51
|
+
.prepare("SELECT prefecture, COUNT(*) as count FROM seed_companies WHERE prefecture IS NOT NULL AND prefecture != '' GROUP BY prefecture ORDER BY count DESC")
|
|
52
|
+
.all();
|
|
53
|
+
}
|
|
46
54
|
/** Search seed companies */
|
|
47
55
|
search(params) {
|
|
48
56
|
if (!this.db)
|
|
@@ -122,8 +130,8 @@ export class SeedCache {
|
|
|
122
130
|
const lines = csv.split("\n");
|
|
123
131
|
const batchInsert = db.transaction((rows) => {
|
|
124
132
|
for (const row of rows) {
|
|
125
|
-
if (row.length >=
|
|
126
|
-
insert.run(row[0], row[1], row[
|
|
133
|
+
if (row.length >= 6) {
|
|
134
|
+
insert.run(row[0], row[1], row[4], row[5]);
|
|
127
135
|
}
|
|
128
136
|
}
|
|
129
137
|
});
|