UNPKG

sec-edgar-toolkit

Version:

Open source toolkit to facilitate working with the SEC EDGAR database

1,026 lines 36.6 kB
"use strict"; /** * SEC EDGAR API client with fluent interface design - TypeScript implementation * * This module provides a comprehensive toolkit for accessing and analyzing * SEC filing data through a chainable API interface. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.FinancialData = exports.FilingAnalysis = exports.FilingContentAccess = exports.Filing = exports.Company = exports.CompanyFactsBuilder = exports.FactsQueryBuilder = exports.CompanyFilingBuilder = exports.FilingQueryBuilder = exports.CompanySearchBuilder = exports.CompanyQueryBuilder = exports.EdgarClient = void 0; exports.createClient = createClient; const edgar_client_1 = require("./client/edgar-client"); const item_extractor_1 = require("./parsers/item-extractor"); const errors_1 = require("./exceptions/errors"); /** * SEC EDGAR API client with fluent interface design. * * This client provides comprehensive access to SEC filing data through * a chainable, type-safe API with intelligent caching and rate limiting. * * @example * ```typescript * const client = new EdgarClient({ userAgent: "MyApp/1.0 (contact@example.com)" }); * const company = await client.companies.lookup("AAPL"); * const filings = await company?.filings.formTypes(["10-K"]).limit(5).fetch(); * ``` */ class EdgarClient { constructor(config) { this.baseClient = new edgar_client_1.EdgarClient({ userAgent: config.userAgent }); this.companies = new CompanyQueryBuilder(this.baseClient); this.filings = new FilingQueryBuilder(this.baseClient); this.facts = new FactsQueryBuilder(this.baseClient); } /** * Configure client settings with method chaining. */ configure(_settings) { // Could update internal settings here return this; } } exports.EdgarClient = EdgarClient; /** * Fluent interface for company queries. */ class CompanyQueryBuilder { constructor(client) { this.client = client; } /** * Look up a single company by ticker or CIK. * * @param identifier - Ticker symbol or CIK number * @returns Company object if found * * @example * ```typescript * const company = await client.companies.lookup("AAPL"); * ``` */ async lookup(identifier) { try { let data = null; // Try as ticker first if (typeof identifier === 'string' && !/^\d+$/.test(identifier)) { data = await this.client.getCompanyByTicker(identifier); } // Try as CIK if ticker failed if (!data) { const cik = typeof identifier === 'number' ? identifier.toString().padStart(10, '0') : identifier.padStart(10, '0'); data = await this.client.getCompanyByCik(cik); } return data ? new Company(data, this.client) : null; } catch (error) { console.warn(`Failed to lookup company ${identifier}:`, error); return null; } } /** * Search for companies with fluent interface. * * @param query - Search query * @returns CompanySearchBuilder for further filtering * * @example * ```typescript * const results = await client.companies.search("Apple").limit(5).execute(); * ``` */ search(query) { return new CompanySearchBuilder(this.client, query); } /** * Look up multiple companies in batch. * * @param identifiers - List of ticker symbols or CIKs * @returns Array of Company objects (null for not found) * * @example * ```typescript * const companies = await client.companies.batchLookup(["AAPL", "MSFT", "GOOGL"]); * ``` */ async batchLookup(identifiers) { const promises = identifiers.map(id => this.lookup(id)); return Promise.all(promises); } } exports.CompanyQueryBuilder = CompanyQueryBuilder; /** * Builder for company search queries. */ class CompanySearchBuilder { constructor(client, query) { this.client = client; this.query = query; } /** * Limit number of results. */ limit(count) { this._limit = count; return this; } /** * Execute the search query. */ async execute() { try { const results = await this.client.searchCompanies(this.query); const companies = results.map(data => new Company(data, this.client)); return this._limit ? companies.slice(0, this._limit) : companies; } catch (error) { console.warn(`Failed to search companies for "${this.query}":`, error); return []; } } } exports.CompanySearchBuilder = CompanySearchBuilder; /** * Fluent interface for filing queries. */ class FilingQueryBuilder { constructor(client) { this.client = client; } /** * Get filings for a specific company. * * @param company - Company object, ticker, or CIK * @returns CompanyFilingBuilder for further filtering * * @example * ```typescript * const filings = await client.filings.forCompany("AAPL").formTypes(["10-K"]).recent(5).fetch(); * ``` */ forCompany(company) { if (company instanceof Company) { return new CompanyFilingBuilder(this.client, company.cik); } else { // For string/number, we'll need to resolve to CIK in the builder return new CompanyFilingBuilder(this.client, company); } } } exports.FilingQueryBuilder = FilingQueryBuilder; /** * Builder for company filing queries. */ class CompanyFilingBuilder { constructor(client, companyIdentifier) { this.client = client; this.companyIdentifier = companyIdentifier; } /** * Filter by form types. */ formTypes(forms) { this._formTypes = forms; return this; } /** * Filter filings since date (YYYY-MM-DD). */ since(date) { this._since = date; return this; } /** * Filter filings until date (YYYY-MM-DD). */ until(date) { this._until = date; return this; } /** * Limit to most recent filings. */ recent(count) { this._limit = count; return this; } /** * Execute the query and fetch filings. */ async fetch() { try { // Resolve CIK if needed let cik; if (typeof this.companyIdentifier === 'string' && this.companyIdentifier.length === 10) { cik = this.companyIdentifier; } else { const company = await new CompanyQueryBuilder(this.client).lookup(this.companyIdentifier); if (!company) { throw new errors_1.CompanyNotFoundError(this.companyIdentifier, typeof this.companyIdentifier === 'string' && this.companyIdentifier.length === 10 ? 'cik' : typeof this.companyIdentifier === 'number' ? 'cik' : 'ticker'); } cik = company.cik; } // Get submissions const submissions = await this.client.getCompanySubmissions(cik); const filings = []; const recentFilings = submissions.filings?.recent; if (recentFilings) { const { accessionNumber = [], form = [], filingDate = [] } = recentFilings; for (let i = 0; i < accessionNumber.length; i++) { if (i >= form.length || i >= filingDate.length) break; const formType = form[i]; const date = filingDate[i]; // Filter by form types if specified if (this._formTypes && !this._formTypes.includes(formType)) { continue; } const filingData = { cik, accessionNumber: accessionNumber[i], formType, filingDate: date, }; const filing = new Filing(filingData, this.client); filings.push(filing); if (this._limit && filings.length >= this._limit) { break; } } } return filings; } catch (error) { console.warn('Failed to fetch filings:', error); return []; } } } exports.CompanyFilingBuilder = CompanyFilingBuilder; /** * Fluent interface for XBRL facts queries. */ class FactsQueryBuilder { constructor(client) { this.client = client; } /** * Get facts for a specific company. * * @param company - Company object, ticker, or CIK * @returns CompanyFactsBuilder for further querying * * @example * ```typescript * const facts = await client.facts.forCompany("AAPL").concept("Assets").inUnits("USD").fetch(); * ``` */ forCompany(company) { if (company instanceof Company) { return new CompanyFactsBuilder(this.client, company.cik); } else { return new CompanyFactsBuilder(this.client, company); } } } exports.FactsQueryBuilder = FactsQueryBuilder; /** * Builder for company facts queries. */ class CompanyFactsBuilder { constructor(client, companyIdentifier) { this.client = client; this.companyIdentifier = companyIdentifier; this._taxonomy = 'us-gaap'; } /** * Filter by specific concept. */ concept(conceptName) { this._concept = conceptName; return this; } /** * Specify taxonomy (default: us-gaap). */ taxonomy(taxonomyName) { this._taxonomy = taxonomyName; return this; } /** * Filter by units (e.g., USD, shares). */ inUnits(units) { this._units = units; return this; } /** * Filter by period. */ period(periodFilter) { this._period = periodFilter; return this; } /** * Execute the query and fetch facts. */ async fetch() { try { // Resolve CIK if needed let cik; if (typeof this.companyIdentifier === 'string' && this.companyIdentifier.length === 10) { cik = this.companyIdentifier; } else { const company = await new CompanyQueryBuilder(this.client).lookup(this.companyIdentifier); if (!company) { throw new errors_1.CompanyNotFoundError(this.companyIdentifier, typeof this.companyIdentifier === 'string' && this.companyIdentifier.length === 10 ? 'cik' : typeof this.companyIdentifier === 'number' ? 'cik' : 'ticker'); } cik = company.cik; } if (this._concept) { const data = await this.client.getCompanyConcept(cik, this._taxonomy, this._concept, this._units); return this.processConceptData(data); } else { const facts = await this.client.getCompanyFacts(cik); return this.processAllFacts(facts); } } catch (error) { console.warn('Failed to fetch facts:', error); return []; } } processConceptData(data) { const results = []; const units = data.units || {}; for (const [unit, unitData] of Object.entries(units)) { if (this._units && unit !== this._units) continue; if (Array.isArray(unitData)) { for (const fact of unitData) { if (this._period) { const factPeriod = fact.fy || fact.fp || fact.frame || ''; if (!factPeriod.toString().includes(this._period)) continue; } const factRecord = { concept: this._concept, value: fact.val, unit, period: fact.frame || `FY${fact.fy || ''}${fact.fp || ''}`, fiscalYear: fact.fy, fiscalPeriod: fact.fp, filed: fact.filed, form: fact.form, }; results.push(factRecord); } } } return results; } processAllFacts(facts) { const results = []; // Process us-gaap facts if (facts['us-gaap']) { for (const [concept, conceptData] of Object.entries(facts['us-gaap'])) { if (conceptData && typeof conceptData === 'object' && 'units' in conceptData) { const units = conceptData.units; for (const [unit, unitData] of Object.entries(units)) { if (Array.isArray(unitData)) { for (const fact of unitData) { results.push({ concept, taxonomy: 'us-gaap', unit, value: fact.val, period: fact.period, form: fact.form, frame: fact.frame, accn: fact.accn, }); } } } } } } // Process dei facts if (facts.dei) { for (const [concept, conceptData] of Object.entries(facts.dei)) { if (conceptData && typeof conceptData === 'object' && 'units' in conceptData) { const units = conceptData.units; for (const [unit, unitData] of Object.entries(units)) { if (Array.isArray(unitData)) { for (const fact of unitData) { results.push({ concept, taxonomy: 'dei', unit, value: fact.val, period: fact.period, form: fact.form, frame: fact.frame, accn: fact.accn, }); } } } } } } return results; } } exports.CompanyFactsBuilder = CompanyFactsBuilder; /** * Comprehensive company representation with fluent interface design. * * This class provides rich access to company data, filings, and financial * information through an intuitive, chainable API. */ class Company { constructor(data, client) { this.data = data; this.client = client; this.cik = data.cik_str; this.ticker = data.ticker || ''; this.name = data.title; this.exchange = data.exchange || ''; } /** * Get filings builder for this company. */ get filings() { return new CompanyFilingBuilder(this.client, this.cik); } /** * Get facts builder for this company. */ get facts() { return new CompanyFactsBuilder(this.client, this.cik); } /** * Get the most recent filing of a specific type. * * @param formType - Type of form to retrieve * @returns Most recent filing or null * * @example * ```typescript * const latest10K = await company.getLatestFiling("10-K"); * ``` */ async getLatestFiling(formType = '10-K') { const filings = await this.filings.formTypes([formType]).recent(1).fetch(); return filings[0] || null; } /** * Get a summary of key financial metrics. * * @returns Object with key financial data * * @example * ```typescript * const summary = await company.getFinancialSummary(); * console.log(`Assets: $${summary.totalAssets?.toLocaleString()}`); * ``` */ async getFinancialSummary() { const keyConcepts = ['Assets', 'Liabilities', 'StockholdersEquity', 'Revenues']; const summary = {}; for (const concept of keyConcepts) { try { const facts = await this.facts.concept(concept).inUnits('USD').fetch(); if (facts.length > 0) { const latest = facts.reduce((latest, fact) => (fact.filed > latest.filed) ? fact : latest); const key = `total${concept}`; summary[key] = latest.value; } } catch (error) { console.warn(`Failed to get ${concept}:`, error); } } return summary; } toString() { return this.ticker ? `${this.ticker}: ${this.name}` : `CIK ${this.cik}: ${this.name}`; } } exports.Company = Company; /** * Comprehensive filing representation with advanced content processing. * * This class provides rich access to SEC filing content, structured data * extraction, and financial analysis capabilities. */ class Filing { constructor(data, client) { this.data = data; this.client = client; this.extractedItems = null; this.itemExtractor = new item_extractor_1.ItemExtractor(); this.cik = data.cik; this.accessionNumber = data.accessionNumber; this.formType = data.formType; this.filingDate = data.filingDate; } /** * Get content access interface. */ get content() { return new FilingContentAccess(this); } /** * Get analysis interface. */ get analysis() { return new FilingAnalysis(this); } /** * Get XBRL instance for this filing. * * @returns XBRLInstance for accessing XBRL data * * @example * ```typescript * const xbrl = await filing.xbrl(); * const assets = await xbrl.getConceptValue("Assets"); * ``` */ async xbrl() { const { XBRLInstance } = await Promise.resolve().then(() => __importStar(require('./core/xbrl'))); return new XBRLInstance(this, this.client); } /** * Get API access for internal use */ get api() { return this.client; } /** * Get a preview of the filing content. * * @param length - Number of characters to preview * @returns Preview text */ async preview(length = 500) { try { const text = await this.content.asText(); return text.length > length ? text.substring(0, length) + '...' : text; } catch (error) { return 'Content preview not available'; } } /** * Extract individual items from the filing (e.g., Item 1, Item 1A, etc.). * * @param itemNumbers - Optional list of specific item numbers to extract. * If not provided, extracts all items. * @returns Dictionary mapping item numbers to their content * * @example * const filing = await company.getFiling("10-K"); * const items = await filing.extractItems(); * console.log(items["1"]); // Business section * console.log(items["1A"]); // Risk Factors * * // Extract specific items only * const specificItems = await filing.extractItems(["1", "1A", "7"]); */ async extractItems(itemNumbers) { if (!this.extractedItems) { // Get the filing content const content = await this.content.asText(); // Extract all items try { this.extractedItems = this.itemExtractor.extractItems(content, this.formType); } catch (error) { console.warn(`Item extraction not supported for ${this.formType}: ${error}`); this.extractedItems = {}; } } if (itemNumbers) { // Return only requested items const result = {}; for (const itemNum of itemNumbers) { if (itemNum in this.extractedItems) { result[itemNum] = this.extractedItems[itemNum]; } } return result; } else { return this.extractedItems; } } /** * Get a specific item from the filing. * * @param itemNumber - The item number to retrieve (e.g., "1", "1A", "7") * @returns The item content or undefined if not found * * @example * const filing = await company.getFiling("10-K"); * const riskFactors = await filing.getItem("1A"); * const business = await filing.getItem("1"); */ async getItem(itemNumber) { const items = await this.extractItems([itemNumber]); return items[itemNumber]; } /** * Get all extracted items from the filing. * * This is a convenience property that extracts all items. * * @returns Dictionary mapping item numbers to their content * * @example * const filing = await company.getFiling("10-K"); * const allItems = await filing.items; * for (const [itemNum, content] of Object.entries(allItems)) { * console.log(`Item ${itemNum}: ${content.length} characters`); * } */ get items() { return this.extractItems(); } toString() { return `${this.formType} filing for ${this.cik} on ${this.filingDate}`; } } exports.Filing = Filing; /** * Interface for accessing filing content. */ class FilingContentAccess { constructor(filing) { this.filing = filing; } /** * Get filing as plain text. */ async asText(clean = true) { try { const api = this.filing.api; const details = await api.getFiling(this.filing.cik, this.filing.accessionNumber); // Find the main document URL const accessionClean = this.filing.accessionNumber.replace(/-/g, ''); let mainDocument = null; if (details?.directory?.item) { const items = Array.isArray(details.directory.item) ? details.directory.item : [details.directory.item]; for (const item of items) { const name = item.name || ''; if ((name.endsWith('.htm') || name.endsWith('.txt')) && !name.endsWith('-index.htm')) { if (name.includes(this.filing.formType.toLowerCase()) || name.includes('filing')) { mainDocument = name; break; } else if (!mainDocument) { mainDocument = name; } } } } if (!mainDocument) { // Fallback to common naming patterns mainDocument = `${this.filing.accessionNumber}.txt`; } // Construct document URL const documentUrl = `https://www.sec.gov/Archives/edgar/data/${this.filing.cik}/${accessionClean}/${mainDocument}`; // Fetch content const response = await fetch(documentUrl); if (!response.ok) { throw new errors_1.FilingContentError(`Failed to fetch filing content: ${response.status} ${response.statusText}`, this.filing.accessionNumber, 'text/html'); } let content = await response.text(); if (clean) { // Remove HTML/SGML tags content = content.replace(/<[^>]+>/g, ' '); // Clean up whitespace content = content.replace(/\s+/g, ' ').trim(); } return content; } catch (error) { throw new errors_1.FilingContentError(`Failed to fetch filing text: ${error}`, this.filing.accessionNumber, 'text/plain'); } } /** * Get filing as HTML. */ async asHtml() { // Get raw content without cleaning return this.asText(false); } /** * Get filing as structured data. */ async asStructuredData() { const content = await this.asText(); const structuredData = { formType: this.filing.formType, filingDate: this.filing.filingDate, accessionNumber: this.filing.accessionNumber, cik: this.filing.cik, }; // Extract basic metadata const companyMatch = content.match(/COMPANY\s+(?:CONFORMED\s+)?NAME:\s*([^\n]+)/i); if (companyMatch) { structuredData.companyName = companyMatch[1].trim(); } const periodMatch = content.match(/CONFORMED\s+PERIOD\s+OF\s+REPORT:\s*(\d{8})/i); if (periodMatch) { structuredData.periodOfReport = periodMatch[1]; } // Form-specific parsing if (this.filing.formType === '8-K') { const events = {}; const itemPatterns = [ ['1.01', /Item\s+1\.01[^a-zA-Z]*([^\n\r]+)/i], ['2.02', /Item\s+2\.02[^a-zA-Z]*([^\n\r]+)/i], ['3.02', /Item\s+3\.02[^a-zA-Z]*([^\n\r]+)/i], ['5.02', /Item\s+5\.02[^a-zA-Z]*([^\n\r]+)/i], ['7.01', /Item\s+7\.01[^a-zA-Z]*([^\n\r]+)/i], ['8.01', /Item\s+8\.01[^a-zA-Z]*([^\n\r]+)/i], ]; for (const [item, pattern] of itemPatterns) { const match = content.match(pattern); if (match) { events[item] = match[1].trim(); } } if (Object.keys(events).length > 0) { structuredData.currentEvents = events; } } return structuredData; } /** * Get direct download URL. */ getDownloadUrl() { const accessionClean = this.filing.accessionNumber.replace(/-/g, ''); return `https://www.sec.gov/Archives/edgar/data/${this.filing.cik}/${accessionClean}/${this.filing.accessionNumber}-index.htm`; } } exports.FilingContentAccess = FilingContentAccess; /** * Interface for filing analysis and extraction. */ class FilingAnalysis { constructor(filing) { this.filing = filing; } /** * Extract financial data if available. */ async extractFinancials() { if (['10-K', '10-Q'].includes(this.filing.formType)) { return new FinancialData(this.filing); } return null; } /** * Extract key business metrics. */ async extractKeyMetrics() { try { const structured = await this.filing.content.asStructuredData(); if (this.filing.formType === '8-K') { return structured.currentEvents || {}; } else if (['3', '4', '5'].includes(this.filing.formType)) { return { insiderTransactions: structured.nonDerivativeTransactions?.length || 0, holdings: structured.nonDerivativeHoldings?.length || 0, }; } return {}; } catch (error) { return {}; } } } exports.FilingAnalysis = FilingAnalysis; /** * Enhanced financial data interface. */ class FinancialData { constructor(filing) { this.filing = filing; } /** * Get balance sheet data. */ async getBalanceSheet() { try { const xbrl = await this.filing.xbrl(); const balanceSheet = { assets: { current: await xbrl.getConceptValue('AssetsCurrent'), nonCurrent: await xbrl.getConceptValue('AssetsNoncurrent'), total: await xbrl.getConceptValue('Assets'), }, liabilities: { current: await xbrl.getConceptValue('LiabilitiesCurrent'), nonCurrent: await xbrl.getConceptValue('LiabilitiesNoncurrent'), total: await xbrl.getConceptValue('Liabilities'), }, equity: { total: await xbrl.getConceptValue('StockholdersEquity'), retainedEarnings: await xbrl.getConceptValue('RetainedEarningsAccumulatedDeficit'), }, }; // Only return if we have some data if (balanceSheet.assets.total || balanceSheet.liabilities.total) { return balanceSheet; } return null; } catch (error) { console.warn('Failed to extract balance sheet:', error); return null; } } /** * Get income statement data. */ async getIncomeStatement() { try { const xbrl = await this.filing.xbrl(); const incomeStatement = { revenue: await xbrl.getConceptValue('Revenues'), costOfRevenue: await xbrl.getConceptValue('CostOfRevenue'), grossProfit: await xbrl.getConceptValue('GrossProfit'), operatingExpenses: { total: await xbrl.getConceptValue('OperatingExpenses'), rd: await xbrl.getConceptValue('ResearchAndDevelopmentExpense'), sga: await xbrl.getConceptValue('SellingGeneralAndAdministrativeExpense'), }, operatingIncome: await xbrl.getConceptValue('OperatingIncomeLoss'), netIncome: await xbrl.getConceptValue('NetIncomeLoss'), eps: { basic: await xbrl.getConceptValue('EarningsPerShareBasic'), diluted: await xbrl.getConceptValue('EarningsPerShareDiluted'), }, }; // Only return if we have some data if (incomeStatement.revenue || incomeStatement.netIncome) { return incomeStatement; } return null; } catch (error) { console.warn('Failed to extract income statement:', error); return null; } } /** * Get cash flow statement data. */ async getCashFlow() { try { const xbrl = await this.filing.xbrl(); const cashFlow = { operating: { netCashFlow: await xbrl.getConceptValue('NetCashProvidedByUsedInOperatingActivities'), netIncome: await xbrl.getConceptValue('NetIncomeLoss'), depreciation: await xbrl.getConceptValue('DepreciationDepletionAndAmortization'), }, investing: { netCashFlow: await xbrl.getConceptValue('NetCashProvidedByUsedInInvestingActivities'), capitalExpenditures: await xbrl.getConceptValue('PaymentsToAcquirePropertyPlantAndEquipment'), }, financing: { netCashFlow: await xbrl.getConceptValue('NetCashProvidedByUsedInFinancingActivities'), dividends: await xbrl.getConceptValue('PaymentsOfDividends'), stockRepurchases: await xbrl.getConceptValue('PaymentsForRepurchaseOfCommonStock'), }, netChange: await xbrl.getConceptValue('CashAndCashEquivalentsPeriodIncreaseDecrease'), endingCash: await xbrl.getConceptValue('CashAndCashEquivalentsAtCarryingValue'), }; // Only return if we have some data if (cashFlow.operating.netCashFlow || cashFlow.netChange) { return cashFlow; } return null; } catch (error) { console.warn('Failed to extract cash flow:', error); return null; } } /** * Calculate key financial ratios. */ async getKeyRatios() { const ratios = {}; try { const [balanceSheet, incomeStatement, cashFlow] = await Promise.all([ this.getBalanceSheet(), this.getIncomeStatement(), this.getCashFlow(), ]); if (balanceSheet && incomeStatement) { // Profitability ratios if (incomeStatement.netIncome && incomeStatement.revenue) { ratios.netProfitMargin = incomeStatement.netIncome / incomeStatement.revenue; } if (incomeStatement.grossProfit && incomeStatement.revenue) { ratios.grossProfitMargin = incomeStatement.grossProfit / incomeStatement.revenue; } // Return ratios if (incomeStatement.netIncome && balanceSheet.equity?.total) { ratios.returnOnEquity = incomeStatement.netIncome / balanceSheet.equity.total; } if (incomeStatement.netIncome && balanceSheet.assets?.total) { ratios.returnOnAssets = incomeStatement.netIncome / balanceSheet.assets.total; } // Liquidity ratios if (balanceSheet.assets?.current && balanceSheet.liabilities?.current) { ratios.currentRatio = balanceSheet.assets.current / balanceSheet.liabilities.current; // Quick ratio (approximate - would need inventory data) ratios.quickRatio = balanceSheet.assets.current * 0.8 / balanceSheet.liabilities.current; } // Leverage ratios if (balanceSheet.liabilities?.total && balanceSheet.equity?.total) { ratios.debtToEquity = balanceSheet.liabilities.total / balanceSheet.equity.total; } } if (cashFlow && incomeStatement) { // Cash flow ratios if (cashFlow.operating?.netCashFlow && incomeStatement.revenue) { ratios.operatingCashFlowMargin = cashFlow.operating.netCashFlow / incomeStatement.revenue; } } } catch (error) { console.warn('Failed to calculate ratios:', error); } return ratios; } } exports.FinancialData = FinancialData; // Convenience functions function createClient(config) { /** * Create an Edgar client instance. * * @param config - Client configuration * @returns EdgarClient instance * * @example * ```typescript * const client = createClient({ userAgent: "MyApp/1.0 (contact@example.com)" }); * ``` */ return new EdgarClient(config); } //# sourceMappingURL=edgar.js.map