asia-filings
Access financial filings, statements, and XBRL data from 7,700+ companies in Japan (EDINET) and South Korea (DART) to retrieve, analyze, and search company information.
Instructions
Unified tool for Asian financial filings: access company filings, financial statements, and XBRL data from Japan (EDINET) and South Korea (DART). Provides comprehensive access to financial reports from 7,700+ Asian companies.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| method | Yes | The operation to perform: JAPAN (EDINET): - search_japan_companies: Search Japanese companies by name - get_japan_company_by_code: Get company by EDINET code - get_japan_company_filings: Get filing history for Japanese company - get_japan_filing_document: Download specific filing document - get_japan_documents_by_date: Get all filings for a specific date - get_japan_filing_facts: Extract XBRL facts from filing (J-GAAP) - get_japan_dimensional_facts: Get dimensional facts with breakdowns KOREA (DART): - search_korea_companies: Search Korean companies by name - get_korea_company_by_code: Get company by corporate code - get_korea_company_filings: Get filing history for Korean company - get_korea_financial_statements: Get financial statements (XBRL) - get_korea_major_shareholders: Get major shareholder information - get_korea_executive_info: Get executive/officer information - get_korea_dividend_info: Get dividend allocation information - get_korea_dimensional_facts: Get dimensional facts with breakdowns ADVANCED ANALYSIS (Phase 2): - build_fact_table: Build comprehensive fact table around target value with BI summaries - search_facts_by_value: Search for facts within value range (alias for build_fact_table) - time_series_analysis: Analyze financial metrics across multiple periods with growth rates UTILITIES: - filter_filings: Filter filing arrays by criteria | |
| query | No | For search methods: Company name to search (Japanese, Korean, or English) | |
| edinet_code | No | For Japan methods: EDINET code (E-number) | |
| corp_code | No | For Korea methods: Corporate code | |
| document_id | No | For get_japan_filing_document: Document ID from EDINET | |
| document_type | No | For get_japan_filing_document: Document type (1: submission, 2: PDF, 3: attachments, 4: XBRL) | |
| date | No | For get_japan_documents_by_date: Date in YYYY-MM-DD format | |
| start_date | No | For filing methods: Start date in YYYY-MM-DD format | |
| end_date | No | For filing methods: End date in YYYY-MM-DD format | |
| business_year | No | For get_korea_financial_statements, get_korea_dividend_info: Business year (YYYY) | |
| report_code | No | For get_korea_financial_statements: Report code (11011: Annual, 11013: Q1, 11012: Q2, 11014: Q3) | |
| report_type | No | For get_korea_company_filings: Report type filter | |
| limit | No | Maximum number of results to return | |
| filings | No | For filter_filings: Array of filing objects to filter | |
| filters | No | For filter_filings: Filter criteria (startDate, endDate, reportType) | |
| search_criteria | No | For dimensional_facts methods: Search criteria (concept, valueRange, period, hasDimensions) | |
| country | No | For advanced analysis methods: Country code (JP for Japan, KR for Korea) | |
| company_id | No | For advanced analysis methods: EDINET code (JP) or corp code (KR) | |
| target_value | No | For build_fact_table/search_facts_by_value: Target value to search around | |
| tolerance | No | For build_fact_table/search_facts_by_value: Tolerance range (±) | |
| options | No | For advanced analysis methods: Analysis options (maxRows, showDimensions, sortBy, concept, periods, includeGeography, includeSegments, showGrowthRates) |
Implementation Reference
- src/index.js:28-198 (schema)Registration of ListToolsRequestHandler defining the schema for the 'asia-filings' tool, including all supported methods, parameters, and descriptions.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'asia-filings', description: 'Unified tool for Asian financial filings: access company filings, financial statements, and XBRL data from Japan (EDINET) and South Korea (DART). Provides comprehensive access to financial reports from 7,700+ Asian companies.', inputSchema: { type: 'object', properties: { method: { type: 'string', enum: [ // Japan EDINET methods 'search_japan_companies', 'get_japan_company_by_code', 'get_japan_company_filings', 'get_japan_filing_document', 'get_japan_documents_by_date', 'get_japan_filing_facts', 'get_japan_dimensional_facts', // Korea DART methods 'search_korea_companies', 'get_korea_company_by_code', 'get_korea_company_filings', 'get_korea_financial_statements', 'get_korea_major_shareholders', 'get_korea_executive_info', 'get_korea_dividend_info', 'get_korea_dimensional_facts', // Advanced Analysis Methods (Phase 2) 'build_fact_table', 'search_facts_by_value', 'time_series_analysis', // Utility methods 'filter_filings' ], description: `The operation to perform: JAPAN (EDINET): - search_japan_companies: Search Japanese companies by name - get_japan_company_by_code: Get company by EDINET code - get_japan_company_filings: Get filing history for Japanese company - get_japan_filing_document: Download specific filing document - get_japan_documents_by_date: Get all filings for a specific date - get_japan_filing_facts: Extract XBRL facts from filing (J-GAAP) - get_japan_dimensional_facts: Get dimensional facts with breakdowns KOREA (DART): - search_korea_companies: Search Korean companies by name - get_korea_company_by_code: Get company by corporate code - get_korea_company_filings: Get filing history for Korean company - get_korea_financial_statements: Get financial statements (XBRL) - get_korea_major_shareholders: Get major shareholder information - get_korea_executive_info: Get executive/officer information - get_korea_dividend_info: Get dividend allocation information - get_korea_dimensional_facts: Get dimensional facts with breakdowns ADVANCED ANALYSIS (Phase 2): - build_fact_table: Build comprehensive fact table around target value with BI summaries - search_facts_by_value: Search for facts within value range (alias for build_fact_table) - time_series_analysis: Analyze financial metrics across multiple periods with growth rates UTILITIES: - filter_filings: Filter filing arrays by criteria`, examples: ['search_japan_companies', 'get_korea_financial_statements'] }, query: { type: 'string', description: 'For search methods: Company name to search (Japanese, Korean, or English)', examples: ['Toyota', 'Samsung', 'ソニー', '삼성전자'] }, edinet_code: { type: 'string', description: 'For Japan methods: EDINET code (E-number)', examples: ['E01225', 'E02166', 'E05080'] }, corp_code: { type: 'string', description: 'For Korea methods: Corporate code', examples: ['00126380', '00164742'] }, document_id: { type: 'string', description: 'For get_japan_filing_document: Document ID from EDINET', examples: ['S100XXXX'] }, document_type: { type: 'string', description: 'For get_japan_filing_document: Document type (1: submission, 2: PDF, 3: attachments, 4: XBRL)', examples: ['1', '2', '4'] }, date: { type: 'string', description: 'For get_japan_documents_by_date: Date in YYYY-MM-DD format', examples: ['2024-12-01', '2024-11-15'] }, start_date: { type: 'string', description: 'For filing methods: Start date in YYYY-MM-DD format', examples: ['2023-01-01', '2024-01-01'] }, end_date: { type: 'string', description: 'For filing methods: End date in YYYY-MM-DD format', examples: ['2024-12-31', '2024-06-30'] }, business_year: { type: 'string', description: 'For get_korea_financial_statements, get_korea_dividend_info: Business year (YYYY)', examples: ['2023', '2024'] }, report_code: { type: 'string', description: 'For get_korea_financial_statements: Report code (11011: Annual, 11013: Q1, 11012: Q2, 11014: Q3)', examples: ['11011', '11013'] }, report_type: { type: 'string', description: 'For get_korea_company_filings: Report type filter', examples: ['A', 'Q'] }, limit: { type: 'integer', description: 'Maximum number of results to return', examples: [10, 25, 50, 100] }, filings: { type: 'array', description: 'For filter_filings: Array of filing objects to filter', items: { type: 'object' } }, filters: { type: 'object', description: 'For filter_filings: Filter criteria (startDate, endDate, reportType)' }, search_criteria: { type: 'object', description: 'For dimensional_facts methods: Search criteria (concept, valueRange, period, hasDimensions)' }, country: { type: 'string', description: 'For advanced analysis methods: Country code (JP for Japan, KR for Korea)', examples: ['JP', 'KR'] }, company_id: { type: 'string', description: 'For advanced analysis methods: EDINET code (JP) or corp code (KR)', examples: ['E01225', '00126380'] }, target_value: { type: 'number', description: 'For build_fact_table/search_facts_by_value: Target value to search around', examples: [1000000000, 500000000000] }, tolerance: { type: 'number', description: 'For build_fact_table/search_facts_by_value: Tolerance range (±)', examples: [50000000, 100000000] }, options: { type: 'object', description: 'For advanced analysis methods: Analysis options (maxRows, showDimensions, sortBy, concept, periods, includeGeography, includeSegments, showGrowthRates)' } }, required: ['method'], additionalProperties: false } } ] }; });
- src/index.js:201-588 (handler)Main handler for CallTool requests to 'asia-filings', dispatching based on 'method' parameter to specific API implementations in imported modules.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; if (name !== 'asia-filings') { throw new Error(`Unknown tool: ${name}`); } try { const { method, ...params } = args; switch (method) { // ============= JAPAN EDINET METHODS ============= case 'search_japan_companies': { const { query, limit, date } = params; if (!query) { throw new Error('query parameter is required for search_japan_companies'); } const results = await edinetApi.searchCompanies(query, { limit, date }); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2) } ] }; } case 'get_japan_company_by_code': { const { edinet_code } = params; if (!edinet_code) { throw new Error('edinet_code parameter is required for get_japan_company_by_code'); } const result = await edinetApi.getCompanyByEdinetCode(edinet_code); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2) } ] }; } case 'get_japan_company_filings': { const { edinet_code, start_date, end_date, limit } = params; if (!edinet_code) { throw new Error('edinet_code parameter is required for get_japan_company_filings'); } const results = await edinetApi.getCompanyFilings(edinet_code, { startDate: start_date, endDate: end_date, limit }); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2) } ] }; } case 'get_japan_filing_document': { const { document_id, document_type } = params; if (!document_id) { throw new Error('document_id parameter is required for get_japan_filing_document'); } const result = await edinetApi.getFilingDocument(document_id, document_type || '1'); // For binary data, return metadata only return { content: [ { type: 'text', text: JSON.stringify({ document_id: result.document_id, type: result.type, content_type: result.content_type, note: 'Document downloaded successfully. Binary data not displayed.' }, null, 2) } ] }; } case 'get_japan_documents_by_date': { const { date } = params; if (!date) { throw new Error('date parameter is required for get_japan_documents_by_date'); } const results = await edinetApi.getDocumentsByDate(date); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2) } ] }; } case 'get_japan_filing_facts': { const { document_id } = params; if (!document_id) { throw new Error('document_id parameter is required for get_japan_filing_facts'); } const results = await edinetApi.getFilingFacts(document_id); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2) } ] }; } case 'get_japan_dimensional_facts': { const { document_id, search_criteria } = params; if (!document_id) { throw new Error('document_id parameter is required for get_japan_dimensional_facts'); } const results = await edinetApi.getDimensionalFacts(document_id, search_criteria || {}); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2) } ] }; } // ============= KOREA DART METHODS ============= case 'search_korea_companies': { const { query, limit } = params; if (!query) { throw new Error('query parameter is required for search_korea_companies'); } const results = await dartApi.searchCompanies(query, { limit }); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2) } ] }; } case 'get_korea_company_by_code': { const { corp_code } = params; if (!corp_code) { throw new Error('corp_code parameter is required for get_korea_company_by_code'); } const result = await dartApi.getCompanyByCorpCode(corp_code); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2) } ] }; } case 'get_korea_company_filings': { const { corp_code, start_date, end_date, report_type, limit } = params; if (!corp_code) { throw new Error('corp_code parameter is required for get_korea_company_filings'); } const results = await dartApi.getCompanyFilings(corp_code, { startDate: start_date, endDate: end_date, reportType: report_type, limit }); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2) } ] }; } case 'get_korea_financial_statements': { const { corp_code, business_year, report_code } = params; if (!corp_code || !business_year) { throw new Error('corp_code and business_year parameters are required for get_korea_financial_statements'); } const results = await dartApi.getFinancialStatements(corp_code, business_year, report_code); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2) } ] }; } case 'get_korea_major_shareholders': { const { corp_code } = params; if (!corp_code) { throw new Error('corp_code parameter is required for get_korea_major_shareholders'); } const results = await dartApi.getMajorShareholders(corp_code); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2) } ] }; } case 'get_korea_executive_info': { const { corp_code } = params; if (!corp_code) { throw new Error('corp_code parameter is required for get_korea_executive_info'); } const results = await dartApi.getExecutiveInfo(corp_code); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2) } ] }; } case 'get_korea_dividend_info': { const { corp_code, business_year } = params; if (!corp_code || !business_year) { throw new Error('corp_code and business_year parameters are required for get_korea_dividend_info'); } const results = await dartApi.getDividendInfo(corp_code, business_year); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2) } ] }; } case 'get_korea_dimensional_facts': { const { corp_code, business_year, report_code, search_criteria } = params; if (!corp_code || !business_year) { throw new Error('corp_code and business_year parameters are required for get_korea_dimensional_facts'); } const results = await dartApi.getDimensionalFacts( corp_code, business_year, report_code || '11011', search_criteria || {} ); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2) } ] }; } // ============= ADVANCED ANALYSIS METHODS (Phase 2) ============= case 'build_fact_table': case 'search_facts_by_value': { const { country, company_id, target_value, tolerance, document_id, options } = params; if (!country || !company_id || target_value === undefined) { throw new Error('country, company_id, and target_value parameters are required for build_fact_table/search_facts_by_value'); } const results = await factTableBuilder.buildFactTable({ country, companyId: company_id, targetValue: target_value, tolerance: tolerance || 50000000, documentId: document_id, options: options || {} }); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2) } ] }; } case 'time_series_analysis': { const { country, company_id, options } = params; if (!country || !company_id) { throw new Error('country and company_id parameters are required for time_series_analysis'); } const results = await timeSeriesAnalyzer.timeSeriesAnalysis({ country, companyId: company_id, options: options || {} }); return { content: [ { type: 'text', text: JSON.stringify(results, null, 2) } ] }; } // ============= UTILITY METHODS ============= case 'filter_filings': { const { filings, filters } = params; if (!filings || !Array.isArray(filings)) { throw new Error('filings array parameter is required for filter_filings'); } const results = dartApi.filterFilings(filings, filters || {}); const response = { originalCount: filings.length, filteredCount: results.length, filters: filters || {}, filings: results, source: 'Asia Filings Filter' }; return { content: [ { type: 'text', text: JSON.stringify(response, null, 2) } ] }; } default: throw new Error(`Unknown method: ${method}`); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: 'text', text: JSON.stringify({ error: errorMessage }, null, 2) } ], isError: true }; } });
- src/edinet-api.js:1-367 (helper)Helper module implementing Japan EDINET API calls for company search, filings retrieval, document download, and XBRL fact extraction/parsing.import axios from 'axios'; import * as xbrlParser from './xbrl-parser.js'; const EDINET_API_BASE = 'https://disclosure.edinet-fsa.go.jp/api/v2'; // API key should be set via environment variable or passed to functions const EDINET_API_KEY = process.env.EDINET_API_KEY || ''; /** * Search for Japanese companies by name * @param {string} query - Company name to search (Japanese or English) * @param {Object} options - Search options * @returns {Promise<Object>} Search results */ export async function searchCompanies(query, options = {}) { const { limit = 10, date } = options; try { // Get recent filings and search within them for company name const searchDate = date || new Date().toISOString().split('T')[0]; const response = await axios.get(`${EDINET_API_BASE}/documents.json`, { params: { date: searchDate.replace(/-/g, ''), type: 2, // Type 2: Metadata only }, headers: { 'Subscription-Key': EDINET_API_KEY }, timeout: 15000 }); if (!response.data || !response.data.results) { return { query, companies: [], total_found: 0, country: 'JP', source: 'EDINET API', note: 'No results found for the specified date' }; } // Filter results by company name const queryLower = query.toLowerCase(); const matchingCompanies = response.data.results .filter(doc => { const name = (doc.filerName || '').toLowerCase(); return name.includes(queryLower); }) .slice(0, limit) .map(doc => ({ name: doc.filerName, edinet_code: doc.edinetCode, sec_code: doc.secCode || null, jcn: doc.JCN || null, recent_filing: { document_id: doc.docID, document_type: doc.docTypeCode, document_description: doc.docDescription, period_start: doc.periodStart, period_end: doc.periodEnd, submit_date: doc.submitDateTime } })); return { query, companies: matchingCompanies, total_found: matchingCompanies.length, country: 'JP', source: 'EDINET API', date_searched: searchDate }; } catch (error) { if (error.response?.status === 401) { throw new Error('EDINET API key is required. Please set EDINET_API_KEY environment variable.'); } throw new Error(`EDINET company search failed: ${error.message}`); } } /** * Get company information by EDINET code * @param {string} edinetCode - EDINET code (E-number) * @returns {Promise<Object>} Company information */ export async function getCompanyByEdinetCode(edinetCode) { try { // Get recent filings for this company const today = new Date(); const startDate = new Date(today.getFullYear() - 1, today.getMonth(), today.getDate()); const filings = await getCompanyFilings(edinetCode, { startDate: startDate.toISOString().split('T')[0], endDate: today.toISOString().split('T')[0], limit: 1 }); if (!filings.filings || filings.filings.length === 0) { throw new Error(`No filings found for EDINET code: ${edinetCode}`); } const latestFiling = filings.filings[0]; return { edinet_code: edinetCode, name: latestFiling.filer_name, sec_code: latestFiling.sec_code, jcn: latestFiling.jcn, latest_filing: { document_id: latestFiling.document_id, submit_date: latestFiling.submit_date, document_type: latestFiling.document_type }, country: 'JP', source: 'EDINET API' }; } catch (error) { throw new Error(`Failed to get company by EDINET code: ${error.message}`); } } /** * Get company filings by EDINET code * @param {string} edinetCode - EDINET code * @param {Object} options - Options (startDate, endDate, limit) * @returns {Promise<Object>} Filings list */ export async function getCompanyFilings(edinetCode, options = {}) { const { startDate, endDate, limit = 100 } = options; try { const start = startDate ? new Date(startDate) : new Date(Date.now() - 365 * 24 * 60 * 60 * 1000); const end = endDate ? new Date(endDate) : new Date(); const allFilings = []; // Iterate through dates to find filings for this company const currentDate = new Date(start); while (currentDate <= end && allFilings.length < limit) { const dateStr = currentDate.toISOString().split('T')[0].replace(/-/g, ''); try { const response = await axios.get(`${EDINET_API_BASE}/documents.json`, { params: { date: dateStr, type: 2 }, headers: { 'Subscription-Key': EDINET_API_KEY }, timeout: 15000 }); if (response.data?.results) { const companyFilings = response.data.results .filter(doc => doc.edinetCode === edinetCode) .map(doc => ({ document_id: doc.docID, edinet_code: doc.edinetCode, sec_code: doc.secCode, jcn: doc.JCN, filer_name: doc.filerName, document_type: doc.docTypeCode, document_description: doc.docDescription, period_start: doc.periodStart, period_end: doc.periodEnd, submit_date: doc.submitDateTime, xbrl_flag: doc.xbrlFlag === '1', pdf_flag: doc.pdfFlag === '1', urls: { document: `${EDINET_API_BASE}/documents/${doc.docID}`, viewer: `https://disclosure.edinet-fsa.go.jp/EKW0EZ0001.html?docID=${doc.docID}` } })); allFilings.push(...companyFilings); } } catch (error) { // Skip dates with errors } // Move to next date currentDate.setDate(currentDate.getDate() + 1); // Rate limiting await new Promise(resolve => setTimeout(resolve, 200)); } return { edinet_code: edinetCode, filings: allFilings.slice(0, limit), total_found: allFilings.length, date_range: { start: start.toISOString().split('T')[0], end: end.toISOString().split('T')[0] }, source: 'EDINET API' }; } catch (error) { throw new Error(`Failed to get company filings: ${error.message}`); } } /** * Get filing document (download) * @param {string} docId - Document ID * @param {string} type - Document type (1: submission, 2: PDF, 3: attachments, 4: XBRL) * @returns {Promise<Object>} Document data */ export async function getFilingDocument(docId, type = '1') { try { const response = await axios.get(`${EDINET_API_BASE}/documents/${docId}`, { params: { type }, headers: { 'Subscription-Key': EDINET_API_KEY }, responseType: type === '4' ? 'arraybuffer' : 'stream', timeout: 30000 }); return { document_id: docId, type: type === '1' ? 'submission' : type === '2' ? 'pdf' : type === '3' ? 'attachments' : 'xbrl', data: response.data, content_type: response.headers['content-type'] }; } catch (error) { throw new Error(`Failed to download document: ${error.message}`); } } /** * Get documents list for a specific date * @param {string} date - Date in YYYY-MM-DD format * @returns {Promise<Object>} Documents list */ export async function getDocumentsByDate(date) { try { const dateStr = date.replace(/-/g, ''); const response = await axios.get(`${EDINET_API_BASE}/documents.json`, { params: { date: dateStr, type: 2 }, headers: { 'Subscription-Key': EDINET_API_KEY }, timeout: 15000 }); if (!response.data || !response.data.results) { return { date, documents: [], total_count: 0, source: 'EDINET API' }; } return { date, documents: response.data.results.map(doc => ({ document_id: doc.docID, edinet_code: doc.edinetCode, sec_code: doc.secCode, filer_name: doc.filerName, document_type: doc.docTypeCode, document_description: doc.docDescription, period_start: doc.periodStart, period_end: doc.periodEnd, submit_date: doc.submitDateTime, xbrl_flag: doc.xbrlFlag === '1' })), total_count: response.data.metadata?.resultset?.count || response.data.results.length, source: 'EDINET API' }; } catch (error) { if (error.response?.status === 401) { throw new Error('EDINET API key is required. Please set EDINET_API_KEY environment variable.'); } throw new Error(`Failed to get documents by date: ${error.message}`); } } /** * Get and parse XBRL facts from a filing * @param {string} docId - Document ID * @returns {Promise<Object>} Parsed XBRL facts */ export async function getFilingFacts(docId) { try { // Download XBRL document (type 4) const response = await axios.get(`${EDINET_API_BASE}/documents/${docId}`, { params: { type: '4' }, headers: { 'Subscription-Key': EDINET_API_KEY }, responseType: 'text', timeout: 30000 }); // Parse iXBRL HTML const parsed = xbrlParser.parseIXBRL(response.data); return { document_id: docId, ...parsed, summary: xbrlParser.buildSummary(parsed.facts) }; } catch (error) { throw new Error(`Failed to get filing facts: ${error.message}`); } } /** * Get dimensional facts from a filing * @param {string} docId - Document ID * @param {Object} searchCriteria - Search criteria * @returns {Promise<Object>} Dimensional facts */ export async function getDimensionalFacts(docId, searchCriteria = {}) { try { const { facts, ...metadata } = await getFilingFacts(docId); // Filter facts based on criteria let filteredFacts = facts; if (Object.keys(searchCriteria).length > 0) { filteredFacts = xbrlParser.filterFacts(facts, searchCriteria); } // Extract dimensional breakdowns const dimensions = xbrlParser.extractDimensions(filteredFacts); return { document_id: docId, search_criteria: searchCriteria, facts: filteredFacts, total_found: filteredFacts.length, dimensions, ...metadata }; } catch (error) { throw new Error(`Failed to get dimensional facts: ${error.message}`); } } export default { searchCompanies, getCompanyByEdinetCode, getCompanyFilings, getFilingDocument, getDocumentsByDate, getFilingFacts, getDimensionalFacts };
- src/dart-api.js:1-398 (helper)Helper module implementing South Korea DART API calls for company search, filings, financial statements, shareholder/executive info, and XBRL facts.import axios from 'axios'; import * as xbrlParser from './xbrl-parser.js'; const DART_API_BASE = 'https://opendart.fss.or.kr/api'; // API key should be set via environment variable const DART_API_KEY = process.env.DART_API_KEY || ''; /** * Search Korean companies by name * @param {string} query - Company name to search * @param {Object} options - Search options * @returns {Promise<Object>} Search results */ export async function searchCompanies(query, options = {}) { const { limit = 10 } = options; try { // Get company list (corp_code.xml contains all companies) // For now, we'll search through recent disclosures const response = await axios.get(`${DART_API_BASE}/list.json`, { params: { crtfc_key: DART_API_KEY, corp_name: query, bgn_de: new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString().split('T')[0].replace(/-/g, ''), end_de: new Date().toISOString().split('T')[0].replace(/-/g, ''), page_count: limit }, timeout: 15000 }); if (response.data.status !== '000') { throw new Error(`DART API error: ${response.data.message}`); } const companies = []; const seenCodes = new Set(); if (response.data.list) { for (const item of response.data.list) { if (!seenCodes.has(item.corp_code)) { seenCodes.add(item.corp_code); companies.push({ name: item.corp_name, corp_code: item.corp_code, stock_code: item.stock_code || null, recent_filing: { report_name: item.report_nm, receipt_number: item.rcept_no, report_date: item.rcept_dt, remarks: item.rm } }); if (companies.length >= limit) break; } } } return { query, companies, total_found: companies.length, country: 'KR', source: 'DART Open API' }; } catch (error) { if (error.response?.data?.status === '020') { throw new Error('DART API key is required or invalid. Please set DART_API_KEY environment variable.'); } throw new Error(`DART company search failed: ${error.message}`); } } /** * Get company information by corporate code * @param {string} corpCode - Corporate code * @returns {Promise<Object>} Company information */ export async function getCompanyByCorpCode(corpCode) { try { const response = await axios.get(`${DART_API_BASE}/company.json`, { params: { crtfc_key: DART_API_KEY, corp_code: corpCode }, timeout: 15000 }); if (response.data.status !== '000') { throw new Error(`DART API error: ${response.data.message}`); } return { corp_code: corpCode, name: response.data.corp_name, name_eng: response.data.corp_name_eng, stock_code: response.data.stock_code, ceo_name: response.data.ceo_nm, corporation_number: response.data.corp_cls, legal_form: response.data.corp_cls, business_registration_number: response.data.bizr_no, address: response.data.adres, homepage: response.data.hm_url, phone: response.data.phn_no, establishment_date: response.data.est_dt, accounting_month: response.data.acc_mt, country: 'KR', source: 'DART Open API' }; } catch (error) { throw new Error(`Failed to get company by corp code: ${error.message}`); } } /** * Get company filings/disclosures * @param {string} corpCode - Corporate code * @param {Object} options - Options (startDate, endDate, reportType, limit) * @returns {Promise<Object>} Filings list */ export async function getCompanyFilings(corpCode, options = {}) { const { startDate, endDate, reportType = '', // A: Annual, Q: Quarterly, etc. limit = 100 } = options; try { const start = startDate ? startDate.replace(/-/g, '') : new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString().split('T')[0].replace(/-/g, ''); const end = endDate ? endDate.replace(/-/g, '') : new Date().toISOString().split('T')[0].replace(/-/g, ''); const response = await axios.get(`${DART_API_BASE}/list.json`, { params: { crtfc_key: DART_API_KEY, corp_code: corpCode, bgn_de: start, end_de: end, pblntf_ty: reportType, page_count: limit }, timeout: 15000 }); if (response.data.status !== '000') { throw new Error(`DART API error: ${response.data.message}`); } const filings = (response.data.list || []).map(item => ({ corp_code: item.corp_code, corp_name: item.corp_name, stock_code: item.stock_code, report_name: item.report_nm, receipt_number: item.rcept_no, filing_date: item.flr_nm, report_date: item.rcept_dt, remarks: item.rm, urls: { viewer: `https://dart.fss.or.kr/dsaf001/main.do?rcpNo=${item.rcept_no}`, document: `${DART_API_BASE}/document.xml?crtfc_key=${DART_API_KEY}&rcept_no=${item.rcept_no}` } })); return { corp_code: corpCode, filings, total_found: filings.length, date_range: { start: startDate || start, end: endDate || end }, source: 'DART Open API' }; } catch (error) { throw new Error(`Failed to get company filings: ${error.message}`); } } /** * Get financial statements for a company * @param {string} corpCode - Corporate code * @param {string} businessYear - Business year (YYYY) * @param {string} reportCode - Report code (11013: Q1, 11012: Q2, 11014: Q3, 11011: Annual) * @returns {Promise<Object>} Financial statements */ export async function getFinancialStatements(corpCode, businessYear, reportCode = '11011') { try { // Get consolidated financial statements const response = await axios.get(`${DART_API_BASE}/fnlttSinglAcntAll.json`, { params: { crtfc_key: DART_API_KEY, corp_code: corpCode, bsns_year: businessYear, reprt_code: reportCode }, timeout: 15000 }); if (response.data.status !== '000') { throw new Error(`DART API error: ${response.data.message}`); } // Parse XBRL data const parsed = xbrlParser.parseXBRLJSON(response.data); return { corp_code: corpCode, business_year: businessYear, report_code: reportCode, report_type: reportCode === '11011' ? 'Annual' : reportCode === '11013' ? 'Q1' : reportCode === '11012' ? 'Q2' : 'Q3', statements: response.data.list || [], ...parsed, summary: xbrlParser.buildSummary(parsed.facts), source: 'DART Open API' }; } catch (error) { throw new Error(`Failed to get financial statements: ${error.message}`); } } /** * Get dimensional facts from financial statements * @param {string} corpCode - Corporate code * @param {string} businessYear - Business year (YYYY) * @param {string} reportCode - Report code * @param {Object} searchCriteria - Search criteria * @returns {Promise<Object>} Dimensional facts */ export async function getDimensionalFacts(corpCode, businessYear, reportCode, searchCriteria = {}) { try { const { facts, ...metadata } = await getFinancialStatements(corpCode, businessYear, reportCode); // Filter facts based on criteria let filteredFacts = facts; if (Object.keys(searchCriteria).length > 0) { filteredFacts = xbrlParser.filterFacts(facts, searchCriteria); } // Extract dimensional breakdowns const dimensions = xbrlParser.extractDimensions(filteredFacts); return { corp_code: corpCode, business_year: businessYear, report_code: reportCode, search_criteria: searchCriteria, facts: filteredFacts, total_found: filteredFacts.length, dimensions, ...metadata }; } catch (error) { throw new Error(`Failed to get dimensional facts: ${error.message}`); } } /** * Get major shareholder information * @param {string} corpCode - Corporate code * @returns {Promise<Object>} Major shareholder data */ export async function getMajorShareholders(corpCode) { try { const response = await axios.get(`${DART_API_BASE}/majorstock.json`, { params: { crtfc_key: DART_API_KEY, corp_code: corpCode }, timeout: 15000 }); if (response.data.status !== '000') { throw new Error(`DART API error: ${response.data.message}`); } return { corp_code: corpCode, shareholders: (response.data.list || []).map(item => ({ report_date: item.rcept_dt, shareholder_name: item.nm, relation: item.relate, shares_owned: item.stock_knd, ownership_percent: item.hold_stock_ratio, change_reason: item.change_cause })), source: 'DART Open API' }; } catch (error) { throw new Error(`Failed to get major shareholders: ${error.message}`); } } /** * Get company executive information * @param {string} corpCode - Corporate code * @returns {Promise<Object>} Executive information */ export async function getExecutiveInfo(corpCode) { try { const response = await axios.get(`${DART_API_BASE}/exctvSttus.json`, { params: { crtfc_key: DART_API_KEY, corp_code: corpCode }, timeout: 15000 }); if (response.data.status !== '000') { throw new Error(`DART API error: ${response.data.message}`); } return { corp_code: corpCode, executives: (response.data.list || []).map(item => ({ name: item.nm, position: item.sexdstn, registration_date: item.rcept_dt, birth_year: item.birth_ym, career: item.career })), source: 'DART Open API' }; } catch (error) { throw new Error(`Failed to get executive information: ${error.message}`); } } /** * Get dividend information * @param {string} corpCode - Corporate code * @param {string} businessYear - Business year (YYYY) * @returns {Promise<Object>} Dividend information */ export async function getDividendInfo(corpCode, businessYear) { try { const response = await axios.get(`${DART_API_BASE}/alotMatter.json`, { params: { crtfc_key: DART_API_KEY, corp_code: corpCode, bsns_year: businessYear }, timeout: 15000 }); if (response.data.status !== '000') { throw new Error(`DART API error: ${response.data.message}`); } return { corp_code: corpCode, business_year: businessYear, dividends: response.data.list || [], source: 'DART Open API' }; } catch (error) { throw new Error(`Failed to get dividend information: ${error.message}`); } } /** * Filter filings by criteria * @param {Array} filings - Array of filings * @param {Object} filters - Filter criteria * @returns {Array} Filtered filings */ export function filterFilings(filings, filters = {}) { const { startDate, endDate, reportType, hasXbrl } = filters; return filings.filter(filing => { if (startDate && filing.report_date < startDate.replace(/-/g, '')) return false; if (endDate && filing.report_date > endDate.replace(/-/g, '')) return false; if (reportType && !filing.report_name.includes(reportType)) return false; // Note: hasXbrl filter would require additional metadata return true; }); } export default { searchCompanies, getCompanyByCorpCode, getCompanyFilings, getFinancialStatements, getMajorShareholders, getExecutiveInfo, getDividendInfo, filterFilings, getDimensionalFacts };
- src/fact-table-builder.js:20-184 (helper)Advanced helper for building fact tables around target values with BI summaries, supporting both JP and KR filings with dimensional breakdowns.export async function buildFactTable(params) { const { country, companyId, targetValue, tolerance = 50000000, documentId = null, options = {} } = params; const defaultOptions = { maxRows: 25, showDimensions: true, sortBy: 'deviation', // 'deviation', 'value', 'concept' filters: {} }; const tableOptions = { ...defaultOptions, ...options }; try { let xbrlData; let filingInfo = {}; let currencySymbol = country === 'JP' ? '¥' : '₩'; // 1. Get XBRL data based on country if (country === 'JP') { // Japan - EDINET let targetDocId = documentId; if (!targetDocId) { // Get recent filing to find document ID const filings = await edinetApi.getCompanyFilings(companyId, { limit: 1 }); if (!filings.filings || filings.filings.length === 0) { throw new Error('No filings found for company'); } targetDocId = filings.filings[0].document_id; filingInfo = filings.filings[0]; } xbrlData = await edinetApi.getFilingFacts(targetDocId); filingInfo.document_id = targetDocId; } else if (country === 'KR') { // Korea - DART if (!documentId) { throw new Error('business_year and report_code are required for Korean filings'); } // documentId should be formatted as "businessYear:reportCode" const [businessYear, reportCode] = documentId.split(':'); xbrlData = await dartApi.getFinancialStatements(companyId, businessYear, reportCode || '11011'); filingInfo.business_year = businessYear; filingInfo.report_code = reportCode; } else { throw new Error('Unsupported country. Use JP for Japan or KR for Korea'); } // 2. Find facts in value range const searchCriteria = { valueRange: { min: targetValue - tolerance, max: targetValue + tolerance }, hasValue: true, ...tableOptions.filters }; const matchingFacts = xbrlParser.filterFacts(xbrlData.facts, searchCriteria); if (matchingFacts.length === 0) { return { country, company: companyId, filing_info: filingInfo, targetValue, tolerance, searchRange: { min: targetValue - tolerance, max: targetValue + tolerance, minFormatted: formatCurrency(targetValue - tolerance, currencySymbol), maxFormatted: formatCurrency(targetValue + tolerance, currencySymbol) }, table: [], summary: { totalFacts: 0, message: 'No facts found in the specified value range' } }; } // 3. Enrich facts with business intelligence const enrichedFacts = matchingFacts.map((fact, index) => { const deviation = fact.value - targetValue; const exactMatch = Math.abs(deviation) < 1000; return { rowNumber: index + 1, concept: fact.concept, accountName: fact.accountName || null, // Korean-specific namespace: fact.namespace || 'unknown', value: fact.value, valueFormatted: formatCurrency(fact.value, currencySymbol), exactMatch, deviationFromTarget: deviation, deviationFormatted: `${deviation >= 0 ? '+' : ''}${formatCurrency(deviation, currencySymbol)}`, deviationPercent: targetValue !== 0 ? ((deviation / targetValue) * 100).toFixed(2) + '%' : 'N/A', periodType: fact.period?.instant ? 'instant' : 'duration', periodStart: fact.period?.startDate, periodEnd: fact.period?.endDate || fact.period?.instant, dimensions: fact.dimensions || {}, dimensionCount: Object.keys(fact.dimensions || {}).length, geography: extractGeographyFromDimensions(fact.dimensions), segment: extractSegmentFromDimensions(fact.dimensions), product: extractProductFromDimensions(fact.dimensions), hasGeographicDimension: hasGeographyDimension(fact.dimensions), hasSegmentDimension: hasSegmentDimension(fact.dimensions), hasProductDimension: hasProductDimension(fact.dimensions), businessClassification: xbrlParser.classifyFact(fact.concept, country === 'JP' ? 'J-GAAP' : 'K-GAAP'), contextRef: fact.contextRef, unitRef: fact.unitRef || fact.unit, decimals: fact.decimals, scale: fact.scale }; }); // 4. Sort based on options sortFacts(enrichedFacts, tableOptions.sortBy); // 5. Limit results const limitedFacts = enrichedFacts.slice(0, tableOptions.maxRows); // 6. Generate business intelligence summary const summary = generateFactTableSummary(enrichedFacts, targetValue, tolerance, currencySymbol); return { country, company: companyId, filing_info: filingInfo, targetValue, tolerance, searchRange: { min: targetValue - tolerance, max: targetValue + tolerance, minFormatted: formatCurrency(targetValue - tolerance, currencySymbol), maxFormatted: formatCurrency(targetValue + tolerance, currencySymbol) }, table: limitedFacts, summary, totalFactsFound: enrichedFacts.length, totalFactsReturned: limitedFacts.length, source: country === 'JP' ? 'EDINET J-GAAP Analysis' : 'DART K-GAAP Analysis', taxonomy: country === 'JP' ? 'J-GAAP' : 'K-GAAP/IFRS' }; } catch (error) { throw new Error(`Failed to build fact table: ${error.message}`); } }