From 4087e5e3f79190e0db0ce6fd7ae520e2c15874df Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Sat, 6 Nov 2021 12:52:54 +0900 Subject: [PATCH 01/20] Add PeriodParser --- src/fiscal-periods/date-param.test.ts | 12 ++++++++ src/fiscal-periods/date-param.ts | 19 ++++++++++++ src/fiscal-periods/error.ts | 9 ++++++ src/fiscal-periods/period-parser.test.ts | 17 +++++++++++ src/fiscal-periods/period-parser.ts | 30 +++++++++++++++++++ src/fiscal-periods/year-quarter-param.test.ts | 22 +++++++++++++- src/fiscal-periods/year-quarter-param.ts | 17 ++++++++++- 7 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 src/fiscal-periods/period-parser.test.ts create mode 100644 src/fiscal-periods/period-parser.ts diff --git a/src/fiscal-periods/date-param.test.ts b/src/fiscal-periods/date-param.test.ts index 127d613..2cd7f59 100644 --- a/src/fiscal-periods/date-param.test.ts +++ b/src/fiscal-periods/date-param.test.ts @@ -1,4 +1,5 @@ import { DateParam } from '~/fiscal-periods/date-param' +import { ParseError } from '~/fiscal-periods/error' test('toString', () => { expect(new DateParam('latest').toString()).toEqual('latest') @@ -16,3 +17,14 @@ test('isLatest', () => { expect(new DateParam('latest').isLatest()).toBeTruthy() expect(new DateParam(new Date('2020-09-06')).isLatest()).toBeFalsy() }) + +test('parse', () => { + expect(DateParam.parse('2020-09-06')).toEqual( + new DateParam(new Date(2020, 9, 6)) + ) + expect(DateParam.parse('latest')).toEqual(new DateParam('latest')) + expect(DateParam.parse('Latest')).toEqual(new DateParam('latest')) + expect(() => DateParam.parse('foo')).toThrow(ParseError) + expect(() => DateParam.parse('2020-9-6')).toThrow(ParseError) + expect(() => DateParam.parse('2020/09/06')).toThrow(ParseError) +}) diff --git a/src/fiscal-periods/date-param.ts b/src/fiscal-periods/date-param.ts index 55138c8..ac5f530 100644 --- a/src/fiscal-periods/date-param.ts +++ b/src/fiscal-periods/date-param.ts @@ -1,3 +1,5 @@ +import { ParseError } from '~/fiscal-periods/error' + export class DateParam { constructor(public date: Date | 'latest') {} @@ -20,4 +22,21 @@ export class DateParam { public isLatest(): boolean { return this.date === 'latest' } + + static parse(str: string): DateParam { + str = str.toLowerCase() + if (str == 'latest') { + return new DateParam(str) + } + + const matches = str.match(/^(\d{4})-(\d{2})-(\d{2})$/) + if (matches == undefined) { + throw new ParseError(`Invalid date format: ${str}`) + } + + const year = parseInt(matches[1], 10) + const month = parseInt(matches[2], 10) + const day = parseInt(matches[3], 10) + return new DateParam(new Date(year, month, day)) + } } diff --git a/src/fiscal-periods/error.ts b/src/fiscal-periods/error.ts index 61e93f3..0166913 100644 --- a/src/fiscal-periods/error.ts +++ b/src/fiscal-periods/error.ts @@ -24,3 +24,12 @@ export class InvalidLYLQError implements Error { this.message = message } } + +export class ParseError implements Error { + public name = 'ParseError' + public message: string + + constructor(message = '') { + this.message = message + } +} diff --git a/src/fiscal-periods/period-parser.test.ts b/src/fiscal-periods/period-parser.test.ts new file mode 100644 index 0000000..fbda466 --- /dev/null +++ b/src/fiscal-periods/period-parser.test.ts @@ -0,0 +1,17 @@ +import { DateParam } from '~/fiscal-periods/date-param' +import { ParseError } from '~/fiscal-periods/error' +import { PeriodParser } from '~/fiscal-periods/period-parser' +import { YearQuarterParam } from '~/fiscal-periods/year-quarter-param' + +test('parse', () => { + expect(PeriodParser.parse('2020Q3')).toEqual(new YearQuarterParam(2020, 3)) + expect(PeriodParser.parse('LYLQ')).toEqual(new YearQuarterParam('LY', 'LQ')) + expect(PeriodParser.parse('2020-09-06')).toEqual( + new DateParam(new Date(2020, 9, 6)) + ) + expect(() => PeriodParser.parse('foo')).toThrow(ParseError) + expect(() => PeriodParser.parse('2020LQ')).toThrow(ParseError) + expect(() => PeriodParser.parse('LYQ3')).toThrow(ParseError) + expect(() => PeriodParser.parse('2020/09/06')).toThrow(ParseError) + expect(() => PeriodParser.parse('latest')).toThrow(ParseError) +}) diff --git a/src/fiscal-periods/period-parser.ts b/src/fiscal-periods/period-parser.ts new file mode 100644 index 0000000..b1a9e7f --- /dev/null +++ b/src/fiscal-periods/period-parser.ts @@ -0,0 +1,30 @@ +import { DateParam } from '~/fiscal-periods/date-param' +import { ParseError } from '~/fiscal-periods/error' +import { YearQuarterParam } from '~/fiscal-periods/year-quarter-param' + +export class PeriodParser { + constructor() { + // noop + } + + static parse(str: string): DateParam | YearQuarterParam { + str = str.toUpperCase() + + try { + return YearQuarterParam.parse(str) + } catch { + // noop + } + + try { + const date = DateParam.parse(str) + if (!date.isLatest()) { + return date + } + } catch { + // noop + } + + throw new ParseError(`Invalid period format: ${str}`) + } +} diff --git a/src/fiscal-periods/year-quarter-param.test.ts b/src/fiscal-periods/year-quarter-param.test.ts index b44a25d..d6e5ea2 100644 --- a/src/fiscal-periods/year-quarter-param.test.ts +++ b/src/fiscal-periods/year-quarter-param.test.ts @@ -1,7 +1,8 @@ import { InvalidLYLQError, InvalidYearError, - InvalidQuarterError + InvalidQuarterError, + ParseError } from '~/fiscal-periods/error' import { YearQuarter } from '~/fiscal-periods/year-quarter' import { YearQuarterParam } from '~/fiscal-periods/year-quarter-param' @@ -50,3 +51,22 @@ test('fromYearQuarter', () => { new YearQuarterParam(2018, 1) ) }) + +test('parse', () => { + expect(YearQuarterParam.parse('2020Q3')).toEqual( + new YearQuarterParam(2020, 3) + ) + expect(YearQuarterParam.parse('2020q3')).toEqual( + new YearQuarterParam(2020, 3) + ) + expect(YearQuarterParam.parse('LYLQ')).toEqual( + new YearQuarterParam('LY', 'LQ') + ) + expect(YearQuarterParam.parse('lylq')).toEqual( + new YearQuarterParam('LY', 'LQ') + ) + expect(() => YearQuarterParam.parse('20Q3')).toThrow(ParseError) + expect(() => YearQuarterParam.parse('LYQ3')).toThrow(InvalidLYLQError) + expect(() => YearQuarterParam.parse('2020LQ')).toThrow(InvalidLYLQError) + expect(() => YearQuarterParam.parse('foo')).toThrow(ParseError) +}) diff --git a/src/fiscal-periods/year-quarter-param.ts b/src/fiscal-periods/year-quarter-param.ts index ceba3d1..57489ef 100644 --- a/src/fiscal-periods/year-quarter-param.ts +++ b/src/fiscal-periods/year-quarter-param.ts @@ -1,7 +1,8 @@ import { InvalidLYLQError, InvalidYearError, - InvalidQuarterError + InvalidQuarterError, + ParseError } from '~/fiscal-periods/error' import { YearQuarter } from '~/fiscal-periods/year-quarter' @@ -44,6 +45,20 @@ export class YearQuarterParam { return this.quarter === 'LQ' } + // XXX: LYLQの同時指定や-1などの相対値指定をどうするか確認する + static parse(str: string): YearQuarterParam { + str = str.toUpperCase() + const matches = str.match(/^(?\d{4}|LY)(?Q\d|LQ)$/) + if (matches == undefined) { + throw new ParseError(`Invalid year-quarter format: ${str}`) + } + + const year = matches[1] === 'LY' ? 'LY' : parseInt(matches[1], 10) + const quarter = + matches[2] === 'LQ' ? 'LQ' : parseInt(matches[2].substring(1), 10) + return new YearQuarterParam(year, quarter) + } + static fromYearQuarter(period: YearQuarter): YearQuarterParam { return new YearQuarterParam(period.year, period.quarter) } From 62fc8679dca77ed2ac7eb04e53c5b099614599b8 Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Sat, 6 Nov 2021 12:55:49 +0900 Subject: [PATCH 02/20] Fix for node v8 --- src/fiscal-periods/year-quarter-param.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fiscal-periods/year-quarter-param.ts b/src/fiscal-periods/year-quarter-param.ts index 57489ef..d319380 100644 --- a/src/fiscal-periods/year-quarter-param.ts +++ b/src/fiscal-periods/year-quarter-param.ts @@ -48,7 +48,7 @@ export class YearQuarterParam { // XXX: LYLQの同時指定や-1などの相対値指定をどうするか確認する static parse(str: string): YearQuarterParam { str = str.toUpperCase() - const matches = str.match(/^(?\d{4}|LY)(?Q\d|LQ)$/) + const matches = str.match(/^(\d{4}|LY)(Q\d|LQ)$/) if (matches == undefined) { throw new ParseError(`Invalid year-quarter format: ${str}`) } From d60d0f9bef8d34cfb15a1831900144fac824b307 Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Wed, 10 Nov 2021 18:43:57 +0900 Subject: [PATCH 03/20] Add Daily --- src/api/v3/caching-client.test.ts | 43 ++++++++++++++++++++---- src/api/v3/caching-client.ts | 13 ++------ src/api/v3/client.test.ts | 19 ++++++++--- src/api/v3/client.ts | 13 ++++---- src/entities/v3/daily.ts | 15 +++++++++ src/services/daily-cache.test.ts | 43 +++++++++++++++++++----- src/services/daily-cache.ts | 54 ++++++++++++++++++++++++++++--- 7 files changed, 162 insertions(+), 38 deletions(-) create mode 100644 src/entities/v3/daily.ts diff --git a/src/api/v3/caching-client.test.ts b/src/api/v3/caching-client.test.ts index a832b72..9fd106c 100644 --- a/src/api/v3/caching-client.test.ts +++ b/src/api/v3/caching-client.test.ts @@ -1,10 +1,10 @@ import { CompanyCache } from '~/__mocks__/services/company-cache' +import { DailyCache } from '~/__mocks__/services/daily-cache' import { QuarterCache } from '~/__mocks__/services/quarter-cache' import { CachingBuffettCodeApiClientV3 } from '~/api/v3/caching-client' import { DateParam } from '~/fiscal-periods/date-param' import { YearQuarter } from '~/fiscal-periods/year-quarter' import { YearQuarterParam } from '~/fiscal-periods/year-quarter-param' -import { DailyCache } from '~/services/daily-cache' jest.mock('~/api/v3/client', () => jest.requireActual('~/__mocks__/api/v3/client') @@ -52,10 +52,10 @@ describe('daily', () => { expect(DailyCache.get(ticker, date)).toBeNull() const client = new CachingBuffettCodeApiClientV3('token') - const res = client.daily(ticker, date) - expect(res).not.toBeNull() + const daily = client.daily(ticker, date) + expect(daily).not.toBeNull() - expect(DailyCache.get(ticker, date)).toEqual(res) + expect(DailyCache.get(ticker, date)).toEqual(daily) }) test('(cached)', () => { @@ -64,8 +64,8 @@ describe('daily', () => { expect(cached).not.toBeNull() const client = new CachingBuffettCodeApiClientV3('token') - const res = client.daily(ticker, date) - expect(res).toEqual(cached) + const daily = client.daily(ticker, date) + expect(daily).toEqual(cached) expect(DailyCache.get(ticker, date)).toEqual(cached) }) @@ -180,3 +180,34 @@ describe('ondemandQuarter', () => { }) }) }) + +describe('ondemandDaily', () => { + const ticker = '2371' + + beforeAll(() => { + DailyCache.clearAll() + }) + + const period = new DateParam(new Date('2020-09-06')) + + test('(uncached)', () => { + expect(DailyCache.get(ticker, period)).toBeNull() + + const client = new CachingBuffettCodeApiClientV3('token') + const daily = client.ondemandDaily(ticker, period) + expect(daily).not.toBeNull() + + expect(DailyCache.get(ticker, period)).toEqual(daily) + }) + + test('(cached)', () => { + const cached = DailyCache.get(ticker, period) + expect(cached).not.toBeNull() + + const client = new CachingBuffettCodeApiClientV3('token') + const daily = client.ondemandDaily(ticker, period) + expect(daily).toEqual(cached) + + expect(DailyCache.get(ticker, period)).toEqual(cached) + }) +}) diff --git a/src/api/v3/caching-client.ts b/src/api/v3/caching-client.ts index 362a526..367d656 100644 --- a/src/api/v3/caching-client.ts +++ b/src/api/v3/caching-client.ts @@ -1,4 +1,5 @@ import { BuffettCodeApiClientV3 } from '~/api/v3/client' +import { Daily } from '~/entities/v3/daily' import { DateParam } from '~/fiscal-periods/date-param' import { YearQuarterParam } from '~/fiscal-periods/year-quarter-param' import { CompanyCache } from '~/services/company-cache' @@ -22,17 +23,13 @@ export class CachingBuffettCodeApiClientV3 extends BuffettCodeApiClientV3 { return company } - daily(ticker: string, date: DateParam): object | null { + daily(ticker: string, date: DateParam): Daily { const cached = DailyCache.get(ticker, date) if (cached) { return cached } const daily = super.daily(ticker, date) - if (!daily) { - return null - } - DailyCache.put(ticker, daily) return daily @@ -56,17 +53,13 @@ export class CachingBuffettCodeApiClientV3 extends BuffettCodeApiClientV3 { return quarter } - ondemandDaily(ticker: string, date: DateParam): object | null { + ondemandDaily(ticker: string, date: DateParam): Daily { const cached = DailyCache.get(ticker, date) if (cached) { return cached } const daily = super.ondemandDaily(ticker, date) - if (!daily) { - return null - } - DailyCache.put(ticker, daily) return daily diff --git a/src/api/v3/client.test.ts b/src/api/v3/client.test.ts index 8f65f96..139d74e 100644 --- a/src/api/v3/client.test.ts +++ b/src/api/v3/client.test.ts @@ -4,6 +4,7 @@ import * as quarter from '~/__mocks__/fixtures/v3/quarter' import { HttpError } from '~/api/http-error' import { useMockedUrlFetchApp } from '~/api/test-helper' import { BuffettCodeApiClientV3 } from '~/api/v3/client' +import { Daily } from '~/entities/v3/daily' import { DateParam } from '~/fiscal-periods/date-param' import { DateRange } from '~/fiscal-periods/date-range' import { YearQuarter } from '~/fiscal-periods/year-quarter' @@ -158,7 +159,7 @@ describe('BuffettCodeApiClientV3', () => { const client = new BuffettCodeApiClientV3('foo') const ticker = '2371' const date = new DateParam(new Date('2021-08-11')) - expect(client.daily(ticker, date)).toEqual(daily['data']) + expect(client.daily(ticker, date)).toEqual(Daily.fromResponse(daily)) expect(mockFetch.mock.calls.length).toBe(1) expect(mockFetch.mock.calls[0].length).toBe(2) expect(mockFetch.mock.calls[0][0]).toBe( @@ -171,12 +172,20 @@ describe('BuffettCodeApiClientV3', () => { }) test('bulkDaily', () => { - const mockFetch = useMockedUrlFetchApp(200, JSON.stringify(daily)) + const bulkDaily = { + data: { + '2020-09-06': daily['data'] + }, + column_description: daily['column_description'] // eslint-disable-line @typescript-eslint/camelcase + } + const mockFetch = useMockedUrlFetchApp(200, JSON.stringify(bulkDaily)) const client = new BuffettCodeApiClientV3('foo') const ticker = '2371' const range = new DateRange(new Date('2021-08-11'), new Date('2021-08-17')) - expect(client.bulkDaily(ticker, range)).toEqual(daily['data']) + expect(client.bulkDaily(ticker, range)).toEqual( + Daily.fromBulkResponse(bulkDaily) + ) expect(mockFetch.mock.calls.length).toBe(1) expect(mockFetch.mock.calls[0].length).toBe(2) expect(mockFetch.mock.calls[0][0]).toBe( @@ -194,7 +203,9 @@ describe('BuffettCodeApiClientV3', () => { const client = new BuffettCodeApiClientV3('foo') const ticker = '2371' const date = new DateParam(new Date('2021-08-11')) - expect(client.ondemandDaily(ticker, date)).toEqual(daily['data']) + expect(client.ondemandDaily(ticker, date)).toEqual( + Daily.fromResponse(daily) + ) expect(mockFetch.mock.calls.length).toBe(1) expect(mockFetch.mock.calls[0].length).toBe(2) expect(mockFetch.mock.calls[0][0]).toBe( diff --git a/src/api/v3/client.ts b/src/api/v3/client.ts index 765a587..3adea13 100644 --- a/src/api/v3/client.ts +++ b/src/api/v3/client.ts @@ -1,5 +1,6 @@ import { HttpError } from '~/api/http-error' import { UrlBuilder } from '~/api/url-builder' +import { Daily } from '~/entities/v3/daily' import { DateParam } from '~/fiscal-periods/date-param' import { DateRange } from '~/fiscal-periods/date-range' import { YearQuarterParam } from '~/fiscal-periods/year-quarter-param' @@ -99,7 +100,7 @@ export class BuffettCodeApiClientV3 { return res['data'] } - public daily(ticker: string, date: DateParam): object { + public daily(ticker: string, date: DateParam): Daily { const endpoint = BuffettCodeApiClientV3.baseUrl + '/daily' const builder = new UrlBuilder(endpoint, { ticker, @@ -109,10 +110,10 @@ export class BuffettCodeApiClientV3 { const options = this.defaultOptions() const res = BuffettCodeApiClientV3.request(url, options) - return res['data'] + return Daily.fromResponse(res) } - public bulkDaily(ticker: string, range: DateRange): object[] { + public bulkDaily(ticker: string, range: DateRange): Daily[] { const endpoint = BuffettCodeApiClientV3.baseUrl + '/bulk/daily' const builder = new UrlBuilder(endpoint, { ticker, @@ -123,10 +124,10 @@ export class BuffettCodeApiClientV3 { const options = this.defaultOptions() const res = BuffettCodeApiClientV3.request(url, options) - return res['data'] + return Daily.fromBulkResponse(res) } - public ondemandDaily(ticker: string, date: DateParam): object { + public ondemandDaily(ticker: string, date: DateParam): Daily { const endpoint = BuffettCodeApiClientV3.baseUrl + '/ondemand/daily' const builder = new UrlBuilder(endpoint, { ticker, @@ -136,6 +137,6 @@ export class BuffettCodeApiClientV3 { const options = this.defaultOptions() const res = BuffettCodeApiClientV3.request(url, options) - return res['data'] + return Daily.fromResponse(res) } } diff --git a/src/entities/v3/daily.ts b/src/entities/v3/daily.ts new file mode 100644 index 0000000..0f71817 --- /dev/null +++ b/src/entities/v3/daily.ts @@ -0,0 +1,15 @@ +export class Daily { + constructor(readonly data: object, readonly columnDescription: object) { + // noop + } + + static fromResponse(response: object): Daily { + return new Daily(response['data'], response['column_description']) + } + + static fromBulkResponse(response: object): Daily[] { + return Object.keys(response['data']).map(key => { + return new Daily(response['data'][key], response['column_description']) + }) + } +} diff --git a/src/services/daily-cache.test.ts b/src/services/daily-cache.test.ts index 6adf9cc..4b0d8c6 100644 --- a/src/services/daily-cache.test.ts +++ b/src/services/daily-cache.test.ts @@ -18,30 +18,57 @@ test('key', () => { ) }) -const daily = dailyFixture['data'] +test('columnDescriptionKey', () => { + expect(DailyCache.columnDescriptionKey()).toBe('daily-column-description') +}) + const date = new Date('2020-09-06') beforeEach(() => { jest.clearAllMocks() }) -test('get', () => { - getMock.mockReturnValueOnce(JSON.stringify(daily)) - expect(DailyCache.get('2371', date)).toEqual(daily) - expect(DailyCache.get('9999', date)).toBeNull() +test('getData', () => { + getMock.mockReturnValueOnce(JSON.stringify(dailyFixture['data'])) + expect(DailyCache.getData('2371', date)).toEqual(dailyFixture['data']) + expect(DailyCache.getData('9999', date)).toBeNull() expect(getMock).toBeCalledTimes(2) expect(getMock).nthCalledWith(1, 'daily-2371-2020-09-06') expect(getMock).nthCalledWith(2, 'daily-9999-2020-09-06') }) -test('put', () => { - DailyCache.put('2371', daily) +test('getColumnDescription', () => { + getMock.mockReturnValueOnce( + JSON.stringify(dailyFixture['column_description']) + ) + + expect(DailyCache.getColumnDescription()).toEqual( + dailyFixture['column_description'] + ) + + expect(getMock).toBeCalledTimes(1) + expect(getMock).toBeCalledWith('daily-column-description') +}) + +test('putData', () => { + DailyCache.putData('2371', dailyFixture['data']) expect(putMock).toBeCalledTimes(1) expect(putMock).toBeCalledWith( 'daily-2371-2020-09-06', - JSON.stringify(daily), + JSON.stringify(dailyFixture['data']), + 21600 + ) +}) + +test('putColumnDescription', () => { + DailyCache.putColumnDescription(dailyFixture['column_description']) + + expect(putMock).toBeCalledTimes(1) + expect(putMock).toBeCalledWith( + 'daily-column-description', + JSON.stringify(dailyFixture['column_description']), 21600 ) }) diff --git a/src/services/daily-cache.ts b/src/services/daily-cache.ts index 36cce4d..51660c3 100644 --- a/src/services/daily-cache.ts +++ b/src/services/daily-cache.ts @@ -1,3 +1,4 @@ +import { Daily } from '~/entities/v3/daily' import { DateParam } from '~/fiscal-periods/date-param' export class DailyCache { @@ -17,7 +18,11 @@ export class DailyCache { return `${this.prefix}-${ticker}-${date}` } - static get(ticker: string, date: Date | DateParam): object | null { + static columnDescriptionKey(): string { + return `${this.prefix}-column-description` + } + + static getData(ticker: string, date: Date | DateParam): object | null { const cache = CacheService.getUserCache() const key = this.key(ticker, date) const cached = cache.get(key) @@ -28,10 +33,51 @@ export class DailyCache { return JSON.parse(cached) } - static put(ticker: string, daily: object, expirationInSeconds = 21600): void { + static getColumnDescription(): object | null { + const cache = CacheService.getUserCache() + const cached = cache.get(this.columnDescriptionKey()) + if (!cached) { + return null + } + + return JSON.parse(cached) + } + + static get(ticker: string, date: Date | DateParam): Daily | null { + const cachedData = this.getData(ticker, date) + const cachedColumnDescription = this.getColumnDescription() + if (!cachedData || !cachedColumnDescription) { + return null + } + + return new Daily(cachedData, cachedColumnDescription) + } + + static putData( + ticker: string, + data: object, + expirationInSeconds = 21600 + ): void { const cache = CacheService.getUserCache() - const date = new Date(daily['day']) + const date = new Date(data['day']) const key = this.key(ticker, date) - cache.put(key, JSON.stringify(daily), expirationInSeconds) + cache.put(key, JSON.stringify(data), expirationInSeconds) + } + + static putColumnDescription( + columnDescription: object, + expirationInSeconds = 21600 + ): void { + const cache = CacheService.getUserCache() + cache.put( + this.columnDescriptionKey(), + JSON.stringify(columnDescription), + expirationInSeconds + ) + } + + static put(ticker: string, daily: Daily, expirationInSeconds = 21600): void { + this.putData(ticker, daily.data, expirationInSeconds) + this.putColumnDescription(daily.columnDescription, expirationInSeconds) } } From 98fd83962a0b2adecf1697254eba31984945acd2 Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Wed, 10 Nov 2021 18:45:03 +0900 Subject: [PATCH 04/20] Add v3-style testing apikey error suuport --- src/api/http-error.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/api/http-error.ts b/src/api/http-error.ts index 509c1df..87cf247 100644 --- a/src/api/http-error.ts +++ b/src/api/http-error.ts @@ -12,7 +12,9 @@ export class HttpError implements Error { return ( content === '{"message":"Testing Apikey is only allowed to ticker ending with \\"01\\""}' || - content === '{"message":"Testing apikey is not allowed"}' + content === '{"message":"Testing apikey is not allowed"}' || + content === + '{"message":"Trial request only supports ticker ending \'01\'"}' ) } From bfdf6b2e89a7fce8e54032ee6adae12d3fef2c9f Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Wed, 10 Nov 2021 18:48:25 +0900 Subject: [PATCH 05/20] Add 403 handling --- src/api/http-error.ts | 5 +++++ src/custom-functions/v2/bcode.ts | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/api/http-error.ts b/src/api/http-error.ts index 87cf247..6e6e637 100644 --- a/src/api/http-error.ts +++ b/src/api/http-error.ts @@ -18,6 +18,11 @@ export class HttpError implements Error { ) } + public isInvalidTokenRequest(): boolean { + const content = this.response.getContentText() + return content === '{"message":"Forbidden"}' + } + public toString(): string { return this.message } diff --git a/src/custom-functions/v2/bcode.ts b/src/custom-functions/v2/bcode.ts index 7b320dd..b91fd9a 100644 --- a/src/custom-functions/v2/bcode.ts +++ b/src/custom-functions/v2/bcode.ts @@ -95,8 +95,10 @@ export function bcode( if (e.isInvalidTestingRequest()) { throw new Error('<<テスト用のAPIキーでは取得できないデータです>>') - } else if (code === 403) { + } else if (e.isInvalidTokenRequest()) { throw new Error('<>') + } else if (code === 403) { + throw new Error('<<月間リクエスト制限に達しています>>') } else if (code === 429) { throw new Error('<>') } else if (Math.floor(code / 100) === 4) { From c385b1e93f335b813501ad049db703830381a1ef Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Sat, 13 Nov 2021 11:09:41 +0900 Subject: [PATCH 06/20] Fix mock of DailyCache --- src/__mocks__/api/v3/client.ts | 16 +++++++-------- src/__mocks__/services/daily-cache.ts | 29 +++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/__mocks__/api/v3/client.ts b/src/__mocks__/api/v3/client.ts index 7481ffc..b539237 100644 --- a/src/__mocks__/api/v3/client.ts +++ b/src/__mocks__/api/v3/client.ts @@ -1,6 +1,7 @@ import { default as company } from '~/__mocks__/fixtures/v3/company.js' import { default as daily } from '~/__mocks__/fixtures/v3/daily.js' import { default as quarter } from '~/__mocks__/fixtures/v3/quarter.js' +import { Daily } from '~/entities/v3/daily' export class BuffettCodeApiClientV3 { public mockCompany = jest.fn() @@ -13,9 +14,8 @@ export class BuffettCodeApiClientV3 { this.mockQuarter.mockReturnValue(quarter) } - company(): object | null { - const company = this.mockCompany()['data'] - return company ? company : null + company(): object { + return this.mockCompany()['data'] } quarter(): object | null { @@ -23,14 +23,12 @@ export class BuffettCodeApiClientV3 { return quarter ? quarter : null } - daily(): object | null { - const daily = this.mockDaily()['data'] - return daily ? daily : null + daily(): Daily { + return Daily.fromResponse(this.mockDaily()) } - ondemandDaily(): object | null { - const daily = this.mockDaily()['data'] - return daily ? daily : null + ondemandDaily(): Daily { + return Daily.fromResponse(this.mockDaily()) } ondemandQuarter(): object | null { diff --git a/src/__mocks__/services/daily-cache.ts b/src/__mocks__/services/daily-cache.ts index e4922a3..2b5b0c2 100644 --- a/src/__mocks__/services/daily-cache.ts +++ b/src/__mocks__/services/daily-cache.ts @@ -1,9 +1,20 @@ +import { Daily } from '~/entities/v3/daily' import { DateParam } from '~/fiscal-periods/date-param' export class DailyCache { static readonly cache = {} - static get(ticker: string, date: Date | DateParam): object | null { + static get(ticker: string, date: Date | DateParam): Daily | null { + const cachedData = this.getData(ticker, date) + const cachedColumnDescription = this.getColumnDescription() + if (cachedData == undefined || cachedColumnDescription == undefined) { + return null + } + + return new Daily(cachedData, cachedColumnDescription) + } + + static getData(ticker: string, date: Date | DateParam): object | null { if (date instanceof Date) { date = new DateParam(date) } @@ -12,10 +23,24 @@ export class DailyCache { return cached === undefined ? null : cached } - static put(ticker: string, daily: object): void { + static getColumnDescription(): object | null { + const cached = this.cache['column-description'] + return cached === undefined ? null : cached + } + + static put(ticker: string, daily: Daily): void { + this.putData(ticker, daily.data) + this.putColumnDescription(daily.columnDescription) + } + + static putData(ticker: string, daily: object): void { DailyCache.cache[`${ticker}-${daily['day']}`] = daily } + static putColumnDescription(columnDescription: object): void { + this.cache['column-description'] = columnDescription + } + // for testing static clearAll(): void { Object.keys(this.cache).forEach(key => delete this.cache[key]) From c4555003f1fcf6a3ca700139dce4d5e8241fb81a Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Sat, 13 Nov 2021 11:08:10 +0900 Subject: [PATCH 07/20] Add Quarter entity --- src/__mocks__/api/v3/client.ts | 11 ++-- src/__mocks__/services/quarter-cache.ts | 33 ++++++++++-- src/api/v2/caching-client.test.ts | 32 +++++++----- src/api/v2/caching-client.ts | 8 +-- src/api/v3/caching-client.test.ts | 16 +++--- src/api/v3/caching-client.ts | 13 ++--- src/api/v3/client.test.ts | 21 ++++++-- src/api/v3/client.ts | 13 ++--- src/custom-functions/v2/bcode-quarter.test.ts | 10 ++-- src/entities/v3/quarter.ts | 15 ++++++ src/services/csv-exporter.test.ts | 10 ++-- src/services/quarter-cache.test.ts | 30 +++++++++-- src/services/quarter-cache.ts | 50 ++++++++++++++++++- 13 files changed, 193 insertions(+), 69 deletions(-) create mode 100644 src/entities/v3/quarter.ts diff --git a/src/__mocks__/api/v3/client.ts b/src/__mocks__/api/v3/client.ts index b539237..0f86d63 100644 --- a/src/__mocks__/api/v3/client.ts +++ b/src/__mocks__/api/v3/client.ts @@ -2,6 +2,7 @@ import { default as company } from '~/__mocks__/fixtures/v3/company.js' import { default as daily } from '~/__mocks__/fixtures/v3/daily.js' import { default as quarter } from '~/__mocks__/fixtures/v3/quarter.js' import { Daily } from '~/entities/v3/daily' +import { Quarter } from '~/entities/v3/quarter' export class BuffettCodeApiClientV3 { public mockCompany = jest.fn() @@ -18,9 +19,8 @@ export class BuffettCodeApiClientV3 { return this.mockCompany()['data'] } - quarter(): object | null { - const quarter = this.mockQuarter()['data'] - return quarter ? quarter : null + quarter(): Quarter { + return Quarter.fromResponse(this.mockQuarter()) } daily(): Daily { @@ -31,8 +31,7 @@ export class BuffettCodeApiClientV3 { return Daily.fromResponse(this.mockDaily()) } - ondemandQuarter(): object | null { - const quarter = this.mockQuarter()['data'] - return quarter ? quarter : null + ondemandQuarter(): Quarter { + return Quarter.fromResponse(this.mockQuarter()) } } diff --git a/src/__mocks__/services/quarter-cache.ts b/src/__mocks__/services/quarter-cache.ts index 6e85f00..b9a4472 100644 --- a/src/__mocks__/services/quarter-cache.ts +++ b/src/__mocks__/services/quarter-cache.ts @@ -1,19 +1,44 @@ +import { Quarter } from '~/entities/v3/quarter' import { YearQuarter } from '~/fiscal-periods/year-quarter' export class QuarterCache { static readonly cache = {} - static get(ticker: string, yearQuarter: YearQuarter): object | null { - const cached = QuarterCache.cache[`${ticker}-${yearQuarter}`] + static get(ticker: string, yearQuarter: YearQuarter): Quarter | null { + const cachedData = this.getData(ticker, yearQuarter) + const cachedColumnDescription = this.getColumnDescription() + if (cachedData == undefined || cachedColumnDescription == undefined) { + return null + } + + return new Quarter(cachedData, cachedColumnDescription) + } + + static getData(ticker: string, yearQuarter: YearQuarter): object | null { + const cached = this.cache[`${ticker}-${yearQuarter}`] + return cached === undefined ? null : cached + } + + static getColumnDescription(): object | null { + const cached = this.cache['column-description'] return cached === undefined ? null : cached } - static put(ticker: string, quarter: object): void { - QuarterCache.cache[ + static put(ticker: string, quarter: Quarter): void { + this.putData(ticker, quarter.data) + this.putColumnDescription(quarter.columnDescription) + } + + static putData(ticker: string, quarter: object): void { + this.cache[ `${ticker}-${quarter['fiscal_year']}Q${quarter['fiscal_quarter']}` ] = quarter } + static putColumnDescription(columnDescription: object): void { + this.cache['column-description'] = columnDescription + } + // for testing static clearAll(): void { Object.keys(this.cache).forEach(key => delete this.cache[key]) diff --git a/src/api/v2/caching-client.test.ts b/src/api/v2/caching-client.test.ts index 63f4c1c..99bc879 100644 --- a/src/api/v2/caching-client.test.ts +++ b/src/api/v2/caching-client.test.ts @@ -79,7 +79,7 @@ describe('quarter', () => { const period = new YearQuarterParam(2018, 1) test('(uncached)', () => { - expect(QuarterCache.get(ticker, period.toYearQuarter())).toBeNull() + expect(QuarterCache.getData(ticker, period.toYearQuarter())).toBeNull() const client = new CachingBuffettCodeApiClientV2('token') const res = client.quarter(ticker, period) @@ -87,11 +87,11 @@ describe('quarter', () => { expect(res['fiscal_year']).toBe(period.year) expect(res['fiscal_quarter']).toBe(period.quarter) - expect(QuarterCache.get(ticker, period.toYearQuarter())).toEqual(res) + expect(QuarterCache.getData(ticker, period.toYearQuarter())).toEqual(res) }) test('(cached)', () => { - const cached = QuarterCache.get(ticker, period.toYearQuarter()) + const cached = QuarterCache.getData(ticker, period.toYearQuarter()) expect(cached).not.toBeNull() const client = new CachingBuffettCodeApiClientV2('token') @@ -100,7 +100,9 @@ describe('quarter', () => { expect(res['fiscal_year']).toBe(period.year) expect(res['fiscal_quarter']).toBe(period.quarter) - expect(QuarterCache.get(ticker, period.toYearQuarter())).toEqual(cached) + expect(QuarterCache.getData(ticker, period.toYearQuarter())).toEqual( + cached + ) }) }) @@ -112,7 +114,7 @@ describe('quarter', () => { const period = new YearQuarterParam('LY', 'LQ') test('(uncached)', () => { - expect(QuarterCache.get(ticker, new YearQuarter(2018, 1))).toBeNull() + expect(QuarterCache.getData(ticker, new YearQuarter(2018, 1))).toBeNull() const client = new CachingBuffettCodeApiClientV2('token') const res = client.quarter(ticker, period) @@ -120,7 +122,9 @@ describe('quarter', () => { expect(res['fiscal_year']).toBe(2018) expect(res['fiscal_quarter']).toBe(1) - expect(QuarterCache.get(ticker, new YearQuarter(2018, 1))).toEqual(res) + expect(QuarterCache.getData(ticker, new YearQuarter(2018, 1))).toEqual( + res + ) }) }) }) @@ -136,24 +140,26 @@ describe('ondemandQuarter', () => { const period = new YearQuarterParam(2018, 1) test('(uncached)', () => { - expect(QuarterCache.get(ticker, period.toYearQuarter())).toBeNull() + expect(QuarterCache.getData(ticker, period.toYearQuarter())).toBeNull() const client = new CachingBuffettCodeApiClientV2('token') const res = client.ondemandQuarter(ticker, period) expect(res).not.toBeNull() - expect(QuarterCache.get(ticker, period.toYearQuarter())).toEqual(res) + expect(QuarterCache.getData(ticker, period.toYearQuarter())).toEqual(res) }) test('(cached)', () => { - const cached = QuarterCache.get(ticker, period.toYearQuarter()) + const cached = QuarterCache.getData(ticker, period.toYearQuarter()) expect(cached).not.toBeNull() const client = new CachingBuffettCodeApiClientV2('token') const res = client.ondemandQuarter(ticker, period) expect(res).toEqual(cached) - expect(QuarterCache.get(ticker, period.toYearQuarter())).toEqual(cached) + expect(QuarterCache.getData(ticker, period.toYearQuarter())).toEqual( + cached + ) }) }) @@ -165,7 +171,7 @@ describe('ondemandQuarter', () => { const period = new YearQuarterParam('LY', 'LQ') test('(uncached)', () => { - expect(QuarterCache.get(ticker, new YearQuarter(2018, 1))).toBeNull() + expect(QuarterCache.getData(ticker, new YearQuarter(2018, 1))).toBeNull() const client = new CachingBuffettCodeApiClientV2('token') const res = client.ondemandQuarter(ticker, period) @@ -173,7 +179,9 @@ describe('ondemandQuarter', () => { expect(res['fiscal_year']).toBe(2018) expect(res['fiscal_quarter']).toBe(1) - expect(QuarterCache.get(ticker, new YearQuarter(2018, 1))).toEqual(res) + expect(QuarterCache.getData(ticker, new YearQuarter(2018, 1))).toEqual( + res + ) }) }) }) diff --git a/src/api/v2/caching-client.ts b/src/api/v2/caching-client.ts index 0e3a57f..b566ddb 100644 --- a/src/api/v2/caching-client.ts +++ b/src/api/v2/caching-client.ts @@ -39,7 +39,7 @@ export class CachingBuffettCodeApiClientV2 extends BuffettCodeApiClientV2 { quarter(ticker: string, period: YearQuarterParam): object | null { if (period.convertibleToYearQuarter()) { - const cached = QuarterCache.get(ticker, period.toYearQuarter()) + const cached = QuarterCache.getData(ticker, period.toYearQuarter()) if (cached) { return cached } @@ -50,14 +50,14 @@ export class CachingBuffettCodeApiClientV2 extends BuffettCodeApiClientV2 { return null } - QuarterCache.put(ticker, quarter) + QuarterCache.putData(ticker, quarter) return quarter } ondemandQuarter(ticker: string, period: YearQuarterParam): object | null { if (period.convertibleToYearQuarter()) { - const cached = QuarterCache.get(ticker, period.toYearQuarter()) + const cached = QuarterCache.getData(ticker, period.toYearQuarter()) if (cached) { return cached } @@ -68,7 +68,7 @@ export class CachingBuffettCodeApiClientV2 extends BuffettCodeApiClientV2 { return null } - QuarterCache.put(ticker, quarter) + QuarterCache.putData(ticker, quarter) return quarter } diff --git a/src/api/v3/caching-client.test.ts b/src/api/v3/caching-client.test.ts index 9fd106c..c9c77df 100644 --- a/src/api/v3/caching-client.test.ts +++ b/src/api/v3/caching-client.test.ts @@ -87,8 +87,8 @@ describe('quarter', () => { const client = new CachingBuffettCodeApiClientV3('token') const res = client.quarter(ticker, period) expect(res).not.toBeNull() - expect(res['fiscal_year']).toBe(period.year) - expect(res['fiscal_quarter']).toBe(period.quarter) + expect(res.data['fiscal_year']).toBe(period.year) + expect(res.data['fiscal_quarter']).toBe(period.quarter) expect(QuarterCache.get(ticker, period.toYearQuarter())).toEqual(res) }) @@ -100,8 +100,8 @@ describe('quarter', () => { const client = new CachingBuffettCodeApiClientV3('token') const res = client.quarter(ticker, period) expect(res).toEqual(cached) - expect(res['fiscal_year']).toBe(period.year) - expect(res['fiscal_quarter']).toBe(period.quarter) + expect(res.data['fiscal_year']).toBe(period.year) + expect(res.data['fiscal_quarter']).toBe(period.quarter) expect(QuarterCache.get(ticker, period.toYearQuarter())).toEqual(cached) }) @@ -120,8 +120,8 @@ describe('quarter', () => { const client = new CachingBuffettCodeApiClientV3('token') const res = client.quarter(ticker, period) expect(res).not.toBeNull() - expect(res['fiscal_year']).toBe(2018) - expect(res['fiscal_quarter']).toBe(1) + expect(res.data['fiscal_year']).toBe(2018) + expect(res.data['fiscal_quarter']).toBe(1) expect(QuarterCache.get(ticker, new YearQuarter(2018, 1))).toEqual(res) }) @@ -173,8 +173,8 @@ describe('ondemandQuarter', () => { const client = new CachingBuffettCodeApiClientV3('token') const res = client.ondemandQuarter(ticker, period) expect(res).not.toBeNull() - expect(res['fiscal_year']).toBe(2018) - expect(res['fiscal_quarter']).toBe(1) + expect(res.data['fiscal_year']).toBe(2018) + expect(res.data['fiscal_quarter']).toBe(1) expect(QuarterCache.get(ticker, new YearQuarter(2018, 1))).toEqual(res) }) diff --git a/src/api/v3/caching-client.ts b/src/api/v3/caching-client.ts index 367d656..0d38fa4 100644 --- a/src/api/v3/caching-client.ts +++ b/src/api/v3/caching-client.ts @@ -1,5 +1,6 @@ import { BuffettCodeApiClientV3 } from '~/api/v3/client' import { Daily } from '~/entities/v3/daily' +import { Quarter } from '~/entities/v3/quarter' import { DateParam } from '~/fiscal-periods/date-param' import { YearQuarterParam } from '~/fiscal-periods/year-quarter-param' import { CompanyCache } from '~/services/company-cache' @@ -35,7 +36,7 @@ export class CachingBuffettCodeApiClientV3 extends BuffettCodeApiClientV3 { return daily } - quarter(ticker: string, period: YearQuarterParam): object | null { + quarter(ticker: string, period: YearQuarterParam): Quarter { if (period.convertibleToYearQuarter()) { const cached = QuarterCache.get(ticker, period.toYearQuarter()) if (cached) { @@ -44,10 +45,6 @@ export class CachingBuffettCodeApiClientV3 extends BuffettCodeApiClientV3 { } const quarter = super.quarter(ticker, period) - if (!quarter) { - return null - } - QuarterCache.put(ticker, quarter) return quarter @@ -65,7 +62,7 @@ export class CachingBuffettCodeApiClientV3 extends BuffettCodeApiClientV3 { return daily } - ondemandQuarter(ticker: string, period: YearQuarterParam): object | null { + ondemandQuarter(ticker: string, period: YearQuarterParam): Quarter { if (period.convertibleToYearQuarter()) { const cached = QuarterCache.get(ticker, period.toYearQuarter()) if (cached) { @@ -74,10 +71,6 @@ export class CachingBuffettCodeApiClientV3 extends BuffettCodeApiClientV3 { } const quarter = super.ondemandQuarter(ticker, period) - if (!quarter) { - return null - } - QuarterCache.put(ticker, quarter) return quarter diff --git a/src/api/v3/client.test.ts b/src/api/v3/client.test.ts index 139d74e..b30c498 100644 --- a/src/api/v3/client.test.ts +++ b/src/api/v3/client.test.ts @@ -5,6 +5,7 @@ import { HttpError } from '~/api/http-error' import { useMockedUrlFetchApp } from '~/api/test-helper' import { BuffettCodeApiClientV3 } from '~/api/v3/client' import { Daily } from '~/entities/v3/daily' +import { Quarter } from '~/entities/v3/quarter' import { DateParam } from '~/fiscal-periods/date-param' import { DateRange } from '~/fiscal-periods/date-range' import { YearQuarter } from '~/fiscal-periods/year-quarter' @@ -102,7 +103,9 @@ describe('BuffettCodeApiClientV3', () => { const client = new BuffettCodeApiClientV3('foo') const ticker = '2371' const period = new YearQuarterParam(2018, 1) - expect(client.quarter(ticker, period)).toEqual(quarter['data']) + expect(client.quarter(ticker, period)).toEqual( + Quarter.fromResponse(quarter) + ) expect(mockFetch.mock.calls.length).toBe(1) expect(mockFetch.mock.calls[0].length).toBe(2) expect(mockFetch.mock.calls[0][0]).toBe( @@ -115,7 +118,13 @@ describe('BuffettCodeApiClientV3', () => { }) test('bulkQuarter', () => { - const mockFetch = useMockedUrlFetchApp(200, JSON.stringify(quarter)) + const bulkQuarter = { + data: { + '2020-09-06': quarter['data'] + }, + column_description: quarter['column_description'] // eslint-disable-line @typescript-eslint/camelcase + } + const mockFetch = useMockedUrlFetchApp(200, JSON.stringify(bulkQuarter)) const client = new BuffettCodeApiClientV3('foo') const ticker = '2371' @@ -123,7 +132,9 @@ describe('BuffettCodeApiClientV3', () => { new YearQuarter(2018, 1), new YearQuarter(2018, 4) ) - expect(client.bulkQuarter(ticker, range)).toEqual(quarter['data']) + expect(client.bulkQuarter(ticker, range)).toEqual( + Quarter.fromBulkResponse(bulkQuarter) + ) expect(mockFetch.mock.calls.length).toBe(1) expect(mockFetch.mock.calls[0].length).toBe(2) expect(mockFetch.mock.calls[0][0]).toBe( @@ -141,7 +152,9 @@ describe('BuffettCodeApiClientV3', () => { const client = new BuffettCodeApiClientV3('foo') const ticker = '2371' const period = new YearQuarterParam(2018, 1) - expect(client.ondemandQuarter(ticker, period)).toEqual(quarter['data']) + expect(client.ondemandQuarter(ticker, period)).toEqual( + Quarter.fromResponse(quarter) + ) expect(mockFetch.mock.calls.length).toBe(1) expect(mockFetch.mock.calls[0].length).toBe(2) expect(mockFetch.mock.calls[0][0]).toBe( diff --git a/src/api/v3/client.ts b/src/api/v3/client.ts index 3adea13..0e028cd 100644 --- a/src/api/v3/client.ts +++ b/src/api/v3/client.ts @@ -1,6 +1,7 @@ import { HttpError } from '~/api/http-error' import { UrlBuilder } from '~/api/url-builder' import { Daily } from '~/entities/v3/daily' +import { Quarter } from '~/entities/v3/quarter' import { DateParam } from '~/fiscal-periods/date-param' import { DateRange } from '~/fiscal-periods/date-range' import { YearQuarterParam } from '~/fiscal-periods/year-quarter-param' @@ -58,7 +59,7 @@ export class BuffettCodeApiClientV3 { return res[ticker] } - public quarter(ticker: string, period: YearQuarterParam): object { + public quarter(ticker: string, period: YearQuarterParam): Quarter { const endpoint = BuffettCodeApiClientV3.baseUrl + '/quarter' const builder = new UrlBuilder(endpoint, { ticker, @@ -69,10 +70,10 @@ export class BuffettCodeApiClientV3 { const options = this.defaultOptions() const res = BuffettCodeApiClientV3.request(url, options) - return res['data'] + return Quarter.fromResponse(res) } - public bulkQuarter(ticker: string, range: YearQuarterRange): object[] { + public bulkQuarter(ticker: string, range: YearQuarterRange): Quarter[] { const endpoint = BuffettCodeApiClientV3.baseUrl + '/bulk/quarter' const builder = new UrlBuilder(endpoint, { ticker, @@ -83,10 +84,10 @@ export class BuffettCodeApiClientV3 { const options = this.defaultOptions() const res = BuffettCodeApiClientV3.request(url, options) - return res['data'] + return Quarter.fromBulkResponse(res) } - public ondemandQuarter(ticker: string, period: YearQuarterParam): object { + public ondemandQuarter(ticker: string, period: YearQuarterParam): Quarter { const endpoint = BuffettCodeApiClientV3.baseUrl + '/ondemand/quarter' const builder = new UrlBuilder(endpoint, { ticker, @@ -97,7 +98,7 @@ export class BuffettCodeApiClientV3 { const options = this.defaultOptions() const res = BuffettCodeApiClientV3.request(url, options) - return res['data'] + return Quarter.fromResponse(res) } public daily(ticker: string, date: DateParam): Daily { diff --git a/src/custom-functions/v2/bcode-quarter.test.ts b/src/custom-functions/v2/bcode-quarter.test.ts index 8493694..cb2cae8 100644 --- a/src/custom-functions/v2/bcode-quarter.test.ts +++ b/src/custom-functions/v2/bcode-quarter.test.ts @@ -31,14 +31,14 @@ describe('bcodeQuarter', () => { test('(quarter, FY, FQ)', () => { const ticker = '2371' const yearQuarter = new YearQuarter(2018, 1) - expect(QuarterCache.get(ticker, yearQuarter)).toBeNull() + expect(QuarterCache.getData(ticker, yearQuarter)).toBeNull() expect(QuarterPropertyCache.get()).toBeNull() const client = new CachingBuffettCodeApiClientV2('token') const result = bcodeQuarter(client, ticker, 2018, 1, 'net_sales', false) expect(result).toEqual(new BcodeResult(12513000000.0, '百万円')) - expect(QuarterCache.get(ticker, yearQuarter)['net_sales']).toBe( + expect(QuarterCache.getData(ticker, yearQuarter)['net_sales']).toBe( 12513000000.0 ) expect(QuarterPropertyCache.get()).not.toBeNull() @@ -47,14 +47,16 @@ describe('bcodeQuarter', () => { test('(quarter, LY, LQ)', () => { const ticker = '2371' const period = new YearQuarter(2018, 1) - expect(QuarterCache.get(ticker, period)).toBeNull() + expect(QuarterCache.getData(ticker, period)).toBeNull() expect(QuarterPropertyCache.get()).toBeNull() const client = new CachingBuffettCodeApiClientV2('token') const result = bcodeQuarter(client, ticker, 'LY', 'LQ', 'net_sales', false) expect(result).toEqual(new BcodeResult(12513000000.0, '百万円')) - expect(QuarterCache.get(ticker, period)['net_sales']).toBe(12513000000.0) + expect(QuarterCache.getData(ticker, period)['net_sales']).toBe( + 12513000000.0 + ) expect(QuarterPropertyCache.get()).not.toBeNull() }) }) diff --git a/src/entities/v3/quarter.ts b/src/entities/v3/quarter.ts new file mode 100644 index 0000000..51f90f5 --- /dev/null +++ b/src/entities/v3/quarter.ts @@ -0,0 +1,15 @@ +export class Quarter { + constructor(readonly data: object, readonly columnDescription: object) { + // noop + } + + static fromResponse(response: object): Quarter { + return new Quarter(response['data'], response['column_description']) + } + + static fromBulkResponse(response: object): Quarter[] { + return Object.keys(response['data']).map(key => { + return new Quarter(response['data'][key], response['column_description']) + }) + } +} diff --git a/src/services/csv-exporter.test.ts b/src/services/csv-exporter.test.ts index f6219ab..fa5a316 100644 --- a/src/services/csv-exporter.test.ts +++ b/src/services/csv-exporter.test.ts @@ -30,7 +30,7 @@ describe('generateData', () => { const from = '2018Q1' const to = '2018Q1' const today = new Date('2020-09-23') - expect(QuarterCache.get(ticker, new YearQuarter(2018, 1))).toBeNull() + expect(QuarterCache.getData(ticker, new YearQuarter(2018, 1))).toBeNull() const data = CsvExporter.generateData(ticker, from, to, today) @@ -50,7 +50,7 @@ describe('generateData', () => { 1.0 ]) expect( - QuarterCache.get(ticker, new YearQuarter(2018, 1))['net_sales'] + QuarterCache.getData(ticker, new YearQuarter(2018, 1))['net_sales'] ).toBe(12513000000.0) }) @@ -59,7 +59,9 @@ describe('generateData', () => { const from = '2018Q1' const to = '2018Q1' const today = new Date('2020-09-23') - expect(QuarterCache.get(ticker, new YearQuarter(2018, 1))).not.toBeNull() + expect( + QuarterCache.getData(ticker, new YearQuarter(2018, 1)) + ).not.toBeNull() const data = CsvExporter.generateData(ticker, from, to, today) @@ -79,7 +81,7 @@ describe('generateData', () => { 1.0 ]) expect( - QuarterCache.get(ticker, new YearQuarter(2018, 1))['net_sales'] + QuarterCache.getData(ticker, new YearQuarter(2018, 1))['net_sales'] ).toBe(12513000000.0) }) }) diff --git a/src/services/quarter-cache.test.ts b/src/services/quarter-cache.test.ts index 3ebde77..3b1c609 100644 --- a/src/services/quarter-cache.test.ts +++ b/src/services/quarter-cache.test.ts @@ -10,24 +10,33 @@ test('key', () => { }) const quarter = quarterFixture['2371'][0] +const columnDescription = quarterFixture['column_description'] const yearQuarter = new YearQuarter(2018, 1) beforeEach(() => { jest.clearAllMocks() }) -test('get', () => { +test('getData', () => { getMock.mockReturnValueOnce(JSON.stringify(quarter)) - expect(QuarterCache.get('2371', yearQuarter)).toEqual(quarter) - expect(QuarterCache.get('9999', yearQuarter)).toBeNull() + expect(QuarterCache.getData('2371', yearQuarter)).toEqual(quarter) + expect(QuarterCache.getData('9999', yearQuarter)).toBeNull() expect(getMock).toBeCalledTimes(2) expect(getMock).nthCalledWith(1, 'quarter-2371-2018Q1') expect(getMock).nthCalledWith(2, 'quarter-9999-2018Q1') }) -test('put', () => { - QuarterCache.put('2371', quarter) +test('getColumnDescription', () => { + getMock.mockReturnValueOnce(JSON.stringify(columnDescription)) + expect(QuarterCache.getColumnDescription()).toEqual(columnDescription) + + expect(getMock).toBeCalledTimes(1) + expect(getMock).toBeCalledWith('quarter-column-description') +}) + +test('putData', () => { + QuarterCache.putData('2371', quarter) expect(putMock).toBeCalledTimes(1) expect(putMock).toBeCalledWith( @@ -36,3 +45,14 @@ test('put', () => { 21600 ) }) + +test('putColumnDescription', () => { + QuarterCache.putColumnDescription(columnDescription) + + expect(putMock).toBeCalledTimes(1) + expect(putMock).toBeCalledWith( + 'quarter-column-description', + JSON.stringify(columnDescription), + 21600 + ) +}) diff --git a/src/services/quarter-cache.ts b/src/services/quarter-cache.ts index d6593f7..1166e8b 100644 --- a/src/services/quarter-cache.ts +++ b/src/services/quarter-cache.ts @@ -1,3 +1,4 @@ +import { Quarter } from '~/entities/v3/quarter' import { YearQuarter } from '~/fiscal-periods/year-quarter' export class QuarterCache { @@ -11,7 +12,11 @@ export class QuarterCache { return `${this.prefix}-${ticker}-${yearQuarter}` } - static get(ticker: string, yearQuarter: YearQuarter): object[] | null { + static columnDescriptionKey(): string { + return `${this.prefix}-column-description` + } + + static getData(ticker: string, yearQuarter: YearQuarter): object | null { const cache = CacheService.getUserCache() const key = this.key(ticker, yearQuarter) const cached = cache.get(key) @@ -22,7 +27,27 @@ export class QuarterCache { return JSON.parse(cached) } - static put( + static getColumnDescription(): object | null { + const cache = CacheService.getUserCache() + const cached = cache.get(this.columnDescriptionKey()) + if (!cached) { + return null + } + + return JSON.parse(cached) + } + + static get(ticker: string, yearQuarter: YearQuarter): Quarter | null { + const cachedData = this.getData(ticker, yearQuarter) + const cachedColumnDescription = this.getColumnDescription() + if (!cachedData || !cachedColumnDescription) { + return null + } + + return new Quarter(cachedData, cachedColumnDescription) + } + + static putData( ticker: string, quarter: object, expirationInSeconds = 21600 @@ -35,4 +60,25 @@ export class QuarterCache { const key = this.key(ticker, yearQuarter) cache.put(key, JSON.stringify(quarter), expirationInSeconds) } + + static putColumnDescription( + columnDescription: object, + expirationInSeconds = 21600 + ): void { + const cache = CacheService.getUserCache() + cache.put( + this.columnDescriptionKey(), + JSON.stringify(columnDescription), + expirationInSeconds + ) + } + + static put( + ticker: string, + quarter: Quarter, + expirationInSeconds = 21600 + ): void { + this.putData(ticker, quarter.data, expirationInSeconds) + this.putColumnDescription(quarter.columnDescription, expirationInSeconds) + } } From e5c4241ccc4f0e8c05f571065ed9eed57aa86b3f Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Sat, 13 Nov 2021 13:45:37 +0900 Subject: [PATCH 08/20] Update company fixtures --- src/__mocks__/fixtures/v2/company.js | 28 +++++++++++++------ src/__mocks__/fixtures/v3/company.js | 34 +++++++++++++++-------- src/api/company-service.test.ts | 13 ++++----- src/api/ondemand-api-period-range.test.ts | 32 ++++++++++----------- 4 files changed, 62 insertions(+), 45 deletions(-) diff --git a/src/__mocks__/fixtures/v2/company.js b/src/__mocks__/fixtures/v2/company.js index f926ffd..fa498cb 100644 --- a/src/__mocks__/fixtures/v2/company.js +++ b/src/__mocks__/fixtures/v2/company.js @@ -1,24 +1,26 @@ module.exports = { - '2371': [ + 2371: [ { tosyo_33category: 'サービス業', url: 'http://corporate.kakaku.com/', company_name: 'カカクコム', company_name_en: 'Kakaku.com Inc.', fiscal_month: 3.0, - established_date: '1997/12', + established_date: '1997/12/01', listing_date: '2003/10/09', priority_market: 't1', accounting_standard: 'IFRS', - latest_fiscal_year: 2020.0, - latest_fiscal_quarter: 1.0, - oldest_fiscal_year: 2008.0, + latest_fiscal_year: 2021.0, + latest_fiscal_quarter: 2.0, + oldest_fiscal_year: 2001.0, oldest_fiscal_quarter: 4.0, + oldest_date: '2003-10-10', fixed_tier_range: { - oldest_fiscal_year: 2015.0, - oldest_fiscal_quarter: 1.0, - latest_fiscal_year: 2020.0, - latest_fiscal_quarter: 1.0 + oldest_fiscal_year: 2016.0, + oldest_fiscal_quarter: 3.0, + latest_fiscal_year: 2021.0, + latest_fiscal_quarter: 2.0, + oldest_date: '2016-11-13T00:00:00.000Z' } } ], @@ -75,6 +77,10 @@ module.exports = { name_jp: '取得可能な最古の決算四半期', unit: 'なし' }, + oldest_date: { + name_jp: '取得可能な最古の日付', + unit: 'なし' + }, fixed_tier_range: { oldest_fiscal_year: { name_jp: '定額利用可能な下限の決算年度', @@ -91,6 +97,10 @@ module.exports = { latest_fiscal_quarter: { name_jp: '定額利用可能な上限の決算四半期', unit: 'なし' + }, + oldest_date: { + name_jp: '定額利用可能な下限の日付', + unit: 'なし' } } } diff --git a/src/__mocks__/fixtures/v3/company.js b/src/__mocks__/fixtures/v3/company.js index 5256734..5c177a3 100644 --- a/src/__mocks__/fixtures/v3/company.js +++ b/src/__mocks__/fixtures/v3/company.js @@ -1,23 +1,25 @@ module.exports = { - 'data': { + data: { tosyo_33category: 'サービス業', url: 'http://corporate.kakaku.com/', company_name: 'カカクコム', company_name_en: 'Kakaku.com Inc.', - fiscal_month: 3.0, - established_date: '1997/12', + fiscal_month: 3, + established_date: '1997/12/01', listing_date: '2003/10/09', priority_market: 't1', accounting_standard: 'IFRS', - latest_fiscal_year: 2020.0, - latest_fiscal_quarter: 1.0, - oldest_fiscal_year: 2008.0, - oldest_fiscal_quarter: 4.0, - fixed_tier_range: { - oldest_fiscal_year: 2015.0, - oldest_fiscal_quarter: 1.0, - latest_fiscal_year: 2020.0, - latest_fiscal_quarter: 1.0 + latest_fiscal_year: 2021, + latest_fiscal_quarter: 2, + oldest_fiscal_year: 2001, + oldest_fiscal_quarter: 4, + oldest_date: '2003-10-10', + fixed_tier_range: { + oldest_fiscal_year: 2016, + oldest_fiscal_quarter: 3, + latest_fiscal_year: 2021, + latest_fiscal_quarter: 2, + oldest_date: '2016-11-13' } }, column_description: { @@ -73,6 +75,10 @@ module.exports = { name_jp: '取得可能な最古の決算四半期', unit: 'なし' }, + oldest_date: { + name_jp: '取得可能な最古の日付', + unit: 'なし' + }, fixed_tier_range: { oldest_fiscal_year: { name_jp: '定額利用可能な下限の決算年度', @@ -89,6 +95,10 @@ module.exports = { latest_fiscal_quarter: { name_jp: '定額利用可能な上限の決算四半期', unit: 'なし' + }, + oldest_date: { + name_jp: '定額利用可能な下限の日付', + unit: 'なし' } } } diff --git a/src/api/company-service.test.ts b/src/api/company-service.test.ts index 2e67563..88407b8 100644 --- a/src/api/company-service.test.ts +++ b/src/api/company-service.test.ts @@ -20,18 +20,15 @@ test('isOndemandQuarterApiPeriod', () => { const client = new CachingBuffettCodeApiClientV2('token') const service = new CompanyService('2371', client) - expect(service.isOndemandQuarterApiPeriod(new YearQuarter(2014, 4))).toBe( + expect(service.isOndemandQuarterApiPeriod(new YearQuarter(2001, 4))).toBe( true ) - expect(service.isOndemandQuarterApiPeriod(new YearQuarter(2015, 1))).toBe( + expect(service.isOndemandQuarterApiPeriod(new YearQuarter(2016, 2))).toBe( + true + ) + expect(service.isOndemandQuarterApiPeriod(new YearQuarter(2016, 3))).toBe( false ) - expect( - service.isOndemandQuarterApiPeriod(new YearQuarterParam(2014, 4)) - ).toBe(true) - expect( - service.isOndemandQuarterApiPeriod(new YearQuarterParam(2015, 1)) - ).toBe(false) expect( service.isOndemandQuarterApiPeriod(new YearQuarterParam('LY', 'LQ')) ).toBe(false) diff --git a/src/api/ondemand-api-period-range.test.ts b/src/api/ondemand-api-period-range.test.ts index 9cde213..55b5e19 100644 --- a/src/api/ondemand-api-period-range.test.ts +++ b/src/api/ondemand-api-period-range.test.ts @@ -19,20 +19,22 @@ test('selectOndemandQuarterApiPeriod', () => { companyService ) - const from = new YearQuarter(2013, 1) - const to = new YearQuarter(2016, 4) + const from = new YearQuarter(2014, 1) + const to = new YearQuarter(2017, 4) const range = new YearQuarterRange(from, to) expect( ondemandQuarterApiPeriodRange.selectOndemandQuarterApiPeriod(ticker, range) ).toEqual([ - new YearQuarter(2013, 1), - new YearQuarter(2013, 2), - new YearQuarter(2013, 3), - new YearQuarter(2013, 4), new YearQuarter(2014, 1), new YearQuarter(2014, 2), new YearQuarter(2014, 3), - new YearQuarter(2014, 4) + new YearQuarter(2014, 4), + new YearQuarter(2015, 1), + new YearQuarter(2015, 2), + new YearQuarter(2015, 3), + new YearQuarter(2015, 4), + new YearQuarter(2016, 1), + new YearQuarter(2016, 2) ]) }) @@ -44,19 +46,17 @@ test('filterOndemandQuarterApiPeriod', () => { companyService ) - const from = new YearQuarter(2013, 1) - const to = new YearQuarter(2016, 4) + const from = new YearQuarter(2014, 1) + const to = new YearQuarter(2017, 4) const range = new YearQuarterRange(from, to) expect( ondemandQuarterApiPeriodRange.filterOndemandQuarterApiPeriod(ticker, range) ).toEqual([ - new YearQuarter(2015, 1), - new YearQuarter(2015, 2), - new YearQuarter(2015, 3), - new YearQuarter(2015, 4), - new YearQuarter(2016, 1), - new YearQuarter(2016, 2), new YearQuarter(2016, 3), - new YearQuarter(2016, 4) + new YearQuarter(2016, 4), + new YearQuarter(2017, 1), + new YearQuarter(2017, 2), + new YearQuarter(2017, 3), + new YearQuarter(2017, 4) ]) }) From 0b452a3680b3fa4952b64765a93f8422f2868ac8 Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Sat, 13 Nov 2021 13:46:12 +0900 Subject: [PATCH 09/20] Add CompanyService#isOndemandDailyApiPeriod --- src/api/company-service.test.ts | 24 +++++++++++++++ src/api/company-service.ts | 15 ++++++++- src/custom-functions/v3/bcode-daily.ts | 42 ++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/custom-functions/v3/bcode-daily.ts diff --git a/src/api/company-service.test.ts b/src/api/company-service.test.ts index 88407b8..8625d3a 100644 --- a/src/api/company-service.test.ts +++ b/src/api/company-service.test.ts @@ -1,11 +1,16 @@ import { CompanyService } from '~/api/company-service' import { CachingBuffettCodeApiClientV2 } from '~/api/v2/caching-client' +import { CachingBuffettCodeApiClientV3 } from '~/api/v3/caching-client' +import { DateParam } from '~/fiscal-periods/date-param' import { YearQuarter } from '~/fiscal-periods/year-quarter' import { YearQuarterParam } from '~/fiscal-periods/year-quarter-param' jest.mock('~/api/v2/client', () => jest.requireActual('~/__mocks__/api/v2/client') ) +jest.mock('~/api/v3/client', () => + jest.requireActual('~/__mocks__/api/v3/client') +) jest.mock('~/services/company-cache', () => jest.requireActual('~/__mocks__/services/company-cache') ) @@ -33,3 +38,22 @@ test('isOndemandQuarterApiPeriod', () => { service.isOndemandQuarterApiPeriod(new YearQuarterParam('LY', 'LQ')) ).toBe(false) }) + +test('isOndemandDailyApiPeriod', () => { + const client = new CachingBuffettCodeApiClientV3('token') + const service = new CompanyService('2371', client) + + expect( + service.isOndemandDailyApiPeriod(new DateParam(new Date('2003-10-10'))) + ).toBe(true) + expect( + service.isOndemandDailyApiPeriod(new DateParam(new Date('2016-11-12'))) + ).toBe(true) + expect( + service.isOndemandDailyApiPeriod(new DateParam(new Date('2016-11-13'))) + ).toBe(false) + expect(service.isOndemandDailyApiPeriod(new DateParam(new Date()))).toBe( + false + ) + expect(service.isOndemandDailyApiPeriod(new DateParam('latest'))).toBe(false) +}) diff --git a/src/api/company-service.ts b/src/api/company-service.ts index 3e264b7..1c5bbf6 100644 --- a/src/api/company-service.ts +++ b/src/api/company-service.ts @@ -1,4 +1,6 @@ import { BuffettCodeApiClientV2 } from '~/api/v2/client' +import { BuffettCodeApiClientV3 } from '~/api/v3/client' +import { DateParam } from '~/fiscal-periods/date-param' import { YearQuarter } from '~/fiscal-periods/year-quarter' import { YearQuarterParam } from '~/fiscal-periods/year-quarter-param' @@ -7,7 +9,7 @@ export class CompanyService { constructor( public ticker: string, - client: BuffettCodeApiClientV2, + client: BuffettCodeApiClientV2 | BuffettCodeApiClientV3, private today: Date = new Date() ) { this.company = client.company(ticker) @@ -42,4 +44,15 @@ export class CompanyService { ) return !period.isAfterOrEqual(fixedTierOldestPeriod) } + + public isOndemandDailyApiPeriod(date: DateParam): boolean { + if (date.isLatest()) { + return false + } + + const fixedTierRange = this.company['fixed_tier_range'] + const fixedTierOldestDate = new Date(fixedTierRange['oldest_date']) + + return date.toDate().valueOf() < fixedTierOldestDate.valueOf() + } } diff --git a/src/custom-functions/v3/bcode-daily.ts b/src/custom-functions/v3/bcode-daily.ts new file mode 100644 index 0000000..4b5dd32 --- /dev/null +++ b/src/custom-functions/v3/bcode-daily.ts @@ -0,0 +1,42 @@ +import { CompanyService } from '~/api/company-service' +import { BuffettCodeApiClientV3 } from '~/api/v3/client' +import { BcodeResult } from '~/custom-functions/bcode-result' +import { + ApiResponseError, + OndemandApiNotEnabledError, + UnsupportedTickerError +} from '~/custom-functions/error' +import { Daily } from '~/entities/v3/daily' +import { DateParam } from '~/fiscal-periods/date-param' + +export function bcodeDaily( + client: BuffettCodeApiClientV3, + ticker: string, + date: DateParam, + propertyName: string, + ondemandApiEnabled: boolean +): BcodeResult { + const companyService = new CompanyService(ticker, client) + if (!companyService.isSupportedTicker()) { + throw new UnsupportedTickerError() + } + + let daily: Daily + if (companyService.isOndemandDailyApiPeriod(date)) { + if (!ondemandApiEnabled) { + throw new OndemandApiNotEnabledError() + } + + daily = client.ondemandDaily(ticker, date) + } else { + daily = client.daily(ticker, date) + } + + if (daily == undefined) { + throw new ApiResponseError() + } + + const value = daily.data[propertyName] + const unit = daily.columnDescription[propertyName]['unit'] + return new BcodeResult(value, unit) +} From 419c766d8b209cd8d4c1e8675271e1ad3e5add4c Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Sat, 13 Nov 2021 14:22:18 +0900 Subject: [PATCH 10/20] Add PropertyNotFoundError --- src/custom-functions/error.test.ts | 8 +++++++- src/custom-functions/error.ts | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/custom-functions/error.test.ts b/src/custom-functions/error.test.ts index e70f5f2..90025b6 100644 --- a/src/custom-functions/error.test.ts +++ b/src/custom-functions/error.test.ts @@ -1,6 +1,7 @@ import { ApiResponseError, - OndemandApiNotEnabledError + OndemandApiNotEnabledError, + PropertyNotFoundError } from '~/custom-functions/error' test('ApiResponseError', () => { @@ -12,3 +13,8 @@ test('OndemandApiNotEnabledError', () => { const error = new OndemandApiNotEnabledError() expect(error instanceof OndemandApiNotEnabledError).toBeTruthy() }) + +test('PropertyNotFoundError', () => { + const error = new PropertyNotFoundError() + expect(error instanceof PropertyNotFoundError).toBeTruthy() +}) diff --git a/src/custom-functions/error.ts b/src/custom-functions/error.ts index 8df2221..e97b712 100644 --- a/src/custom-functions/error.ts +++ b/src/custom-functions/error.ts @@ -16,6 +16,15 @@ export class OndemandApiNotEnabledError implements Error { } } +export class PropertyNotFoundError implements Error { + public name = 'PropertyNotFoundError' + public message: string + + constructor(message = '') { + this.message = message + } +} + export class UnsupportedTickerError implements Error { public name = 'UnsupportedTickerError' public message: string From 6eccdd29d6c4cdf5ba97c3d84274c5a798f0956a Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Mon, 15 Nov 2021 10:28:42 +0900 Subject: [PATCH 11/20] Add bcodeDaily for v3 --- src/custom-functions/v3/bcode-daily.test.ts | 29 +++++++++++++++++++++ src/custom-functions/v3/bcode-daily.ts | 11 +++++++- 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 src/custom-functions/v3/bcode-daily.test.ts diff --git a/src/custom-functions/v3/bcode-daily.test.ts b/src/custom-functions/v3/bcode-daily.test.ts new file mode 100644 index 0000000..9f06c69 --- /dev/null +++ b/src/custom-functions/v3/bcode-daily.test.ts @@ -0,0 +1,29 @@ +import { CachingBuffettCodeApiClientV3 } from '~/api/v3/caching-client' +import { BcodeResult } from '~/custom-functions/bcode-result' +import { bcodeDaily } from '~/custom-functions/v3/bcode-daily' +import { DateParam } from '~/fiscal-periods/date-param' +import { DailyCache } from '~/services/daily-cache' + +jest.mock('~/api/v3/client', () => + jest.requireActual('~/__mocks__/api/v3/client') +) +jest.mock('~/services/company-cache', () => + jest.requireActual('~/__mocks__/services/company-cache') +) +jest.mock('~/services/daily-cache', () => + jest.requireActual('~/__mocks__/services/daily-cache') +) + +test('bcodeDaily', () => { + const ticker = '2371' + const date = new DateParam(new Date('2020-09-06')) + const propertyName = 'market_capital' + + expect(DailyCache.get(ticker, date)).toBeNull() + + const client = new CachingBuffettCodeApiClientV3('token') + const result = bcodeDaily(client, ticker, date, propertyName, false) + + expect(result).toEqual(new BcodeResult(587800541668.0, '百万円')) + expect(DailyCache.get(ticker, date)).not.toBeNull() +}) diff --git a/src/custom-functions/v3/bcode-daily.ts b/src/custom-functions/v3/bcode-daily.ts index 4b5dd32..47422c5 100644 --- a/src/custom-functions/v3/bcode-daily.ts +++ b/src/custom-functions/v3/bcode-daily.ts @@ -4,6 +4,7 @@ import { BcodeResult } from '~/custom-functions/bcode-result' import { ApiResponseError, OndemandApiNotEnabledError, + PropertyNotFoundError, UnsupportedTickerError } from '~/custom-functions/error' import { Daily } from '~/entities/v3/daily' @@ -36,7 +37,15 @@ export function bcodeDaily( throw new ApiResponseError() } + const property = daily.columnDescription[propertyName] + if (property == undefined) { + throw new PropertyNotFoundError( + `propetyName '${propertyName}' is not found.` + ) + } + const value = daily.data[propertyName] - const unit = daily.columnDescription[propertyName]['unit'] + const unit = property['unit'] + return new BcodeResult(value, unit) } From 784d9aba07e7b027835eb50ef7e55aacd11a5073 Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Mon, 15 Nov 2021 15:53:18 +0900 Subject: [PATCH 12/20] Update fixtures of v3 daily --- src/__mocks__/fixtures/v3/daily-property.js | 82 ------------- src/__mocks__/fixtures/v3/daily.js | 127 +++++++++++--------- src/custom-functions/v3/bcode-daily.test.ts | 2 +- 3 files changed, 69 insertions(+), 142 deletions(-) delete mode 100644 src/__mocks__/fixtures/v3/daily-property.js diff --git a/src/__mocks__/fixtures/v3/daily-property.js b/src/__mocks__/fixtures/v3/daily-property.js deleted file mode 100644 index 614382d..0000000 --- a/src/__mocks__/fixtures/v3/daily-property.js +++ /dev/null @@ -1,82 +0,0 @@ -module.exports = { - cash_market_capital_ratio: { - name_jp: 'キャッシュ時価総額比率', - unit: '%' - }, - debt_market_capital_ratio: { - name_jp: '有利子負債/時価総額比率', - unit: '倍' - }, - dividend_yield_actual: { - name_jp: '配当利回り(実績)', - unit: '%' - }, - dividend_yield_forecast: { - name_jp: '配当利回り(会社予想)', - unit: '%' - }, - ebitda_forecast: { - name_jp: 'EBITDA(会社予想)', - unit: '百万円' - }, - enterprise_value: { - name_jp: '企業価値', - unit: '百万円' - }, - eps_forecast: { - name_jp: 'EPS(会社予想)', - unit: '円' - }, - ev_ebitda_forecast: { - name_jp: 'EV/EBITDA(会社予想)', - unit: '倍' - }, - ex_dividend: { - name_jp: '配当金(会社予想)', - unit: '円' - }, - listing_years: { - name_jp: '上場年数', - unit: '年' - }, - market_capital: { - name_jp: '時価総額', - unit: '百万円' - }, - net_income_growth_rate_forecast: { - name_jp: '純利益成長率(直近年度実績→会社予想)', - unit: '%' - }, - net_sales_growth_rate_forecast: { - name_jp: '売上高成長率(直近年度実績→会社予想)', - unit: '%' - }, - operating_income_growth_rate_forecast: { - name_jp: '営業利益成長率(直近年度実績→会社予想)', - unit: '%' - }, - pbr: { - name_jp: 'PBR', - unit: '倍' - }, - pcfr_forecast: { - name_jp: 'PCFR', - unit: '倍' - }, - per_forecast: { - name_jp: 'PER(会社予想)', - unit: '倍' - }, - per_pbr: { - name_jp: 'PER×PBR', - unit: '単位無し' - }, - psr_forecast: { - name_jp: 'PSR(会社予想)', - unit: '倍' - }, - trading_volume: { - name_jp: '出来高', - unit: '株' - } -} diff --git a/src/__mocks__/fixtures/v3/daily.js b/src/__mocks__/fixtures/v3/daily.js index 66ecc5c..2f3ab5c 100644 --- a/src/__mocks__/fixtures/v3/daily.js +++ b/src/__mocks__/fixtures/v3/daily.js @@ -1,23 +1,19 @@ module.exports = { column_description: { - cash_market_capital_ratio: { - name_jp: 'キャッシュ時価総額比率', - unit: '%' - }, - debt_market_capital_ratio: { - name_jp: '有利子負債/時価総額比率', - unit: '倍' + ticker: { + name_jp: 'ティッカー', + unit: '単位無し' }, - dividend_yield_actual: { - name_jp: '配当利回り(実績)', - unit: '%' + day: { + name_jp: '日付', + unit: '単位無し' }, - dividend_yield_forecast: { - name_jp: '配当利回り(会社予想)', - unit: '%' + trading_volume: { + name_jp: '出来高', + unit: '株' }, - ebitda_forecast: { - name_jp: 'EBITDA(会社予想)', + market_capital: { + name_jp: '時価総額', unit: '百万円' }, enterprise_value: { @@ -28,24 +24,44 @@ module.exports = { name_jp: 'EPS(会社予想)', unit: '円' }, + per_forecast: { + name_jp: 'PER(会社予想)', + unit: '倍' + }, + pbr: { + name_jp: 'PBR', + unit: '倍' + }, + per_pbr: { + name_jp: 'PER×PBR', + unit: '単位無し' + }, + ebitda_forecast: { + name_jp: 'EBITDA(会社予想)', + unit: '百万円' + }, ev_ebitda_forecast: { name_jp: 'EV/EBITDA(会社予想)', unit: '倍' }, + psr_forecast: { + name_jp: 'PSR(会社予想)', + unit: '倍' + }, + pcfr_forecast: { + name_jp: 'PCFR', + unit: '倍' + }, ex_dividend: { name_jp: '配当金(会社予想)', unit: '円' }, - listing_years: { - name_jp: '上場年数', - unit: '年' - }, - market_capital: { - name_jp: '時価総額', - unit: '百万円' + dividend_yield_forecast: { + name_jp: '配当利回り(会社予想)', + unit: '%' }, - net_income_growth_rate_forecast: { - name_jp: '純利益成長率(直近年度実績→会社予想)', + dividend_yield_actual: { + name_jp: '配当利回り(実績)', unit: '%' }, net_sales_growth_rate_forecast: { @@ -56,52 +72,45 @@ module.exports = { name_jp: '営業利益成長率(直近年度実績→会社予想)', unit: '%' }, - pbr: { - name_jp: 'PBR', - unit: '倍' - }, - pcfr_forecast: { - name_jp: 'PCFR', - unit: '倍' + net_income_growth_rate_forecast: { + name_jp: '純利益成長率(直近年度実績→会社予想)', + unit: '%' }, - per_forecast: { - name_jp: 'PER(会社予想)', + debt_market_capital_ratio: { + name_jp: '有利子負債/時価総額比率', unit: '倍' }, - per_pbr: { - name_jp: 'PER×PBR', - unit: '単位無し' - }, - psr_forecast: { - name_jp: 'PSR(会社予想)', - unit: '倍' + cash_market_capital_ratio: { + name_jp: 'キャッシュ時価総額比率', + unit: '%' }, - trading_volume: { - name_jp: '出来高', - unit: '株' + listing_years: { + name_jp: '上場年数', + unit: '年' } }, data: { + ticker: '2371', day: '2020-09-06', - cash_market_capital_ratio: 4.98944759676063, - debt_market_capital_ratio: 1.48298604408628, - dividend_yield_actual: 1.40154169586545, - dividend_yield_forecast: 1.40154169586545, - ebitda_forecast: null, - enterprise_value: 567189541668.0, + trading_volume: 636600, + market_capital: 550294097166, + enterprise_value: 535358097166, eps_forecast: 0.0, - ev_ebitda_forecast: null, - ex_dividend: 40.0, - listing_years: 17.0, - market_capital: 587800541668.0, - net_income_growth_rate_forecast: -100.0, - net_sales_growth_rate_forecast: -100.0, - operating_income_growth_rate_forecast: -100.0, - pbr: 13.3959420603934, - pcfr_forecast: 182.603461220255, per_forecast: null, + pbr: 13.66, per_pbr: null, + ebitda_forecast: null, + ev_ebitda_forecast: null, psr_forecast: null, - trading_volume: 437700.0 + pcfr_forecast: 170.95, + ex_dividend: 40, + dividend_yield_forecast: 1.5, + dividend_yield_actual: 1.5, + net_sales_growth_rate_forecast: -100.0, + operating_income_growth_rate_forecast: -100.0, + net_income_growth_rate_forecast: -100.0, + debt_market_capital_ratio: 1.6, + cash_market_capital_ratio: 4.31, + listing_years: 16 } } diff --git a/src/custom-functions/v3/bcode-daily.test.ts b/src/custom-functions/v3/bcode-daily.test.ts index 9f06c69..5f2934d 100644 --- a/src/custom-functions/v3/bcode-daily.test.ts +++ b/src/custom-functions/v3/bcode-daily.test.ts @@ -24,6 +24,6 @@ test('bcodeDaily', () => { const client = new CachingBuffettCodeApiClientV3('token') const result = bcodeDaily(client, ticker, date, propertyName, false) - expect(result).toEqual(new BcodeResult(587800541668.0, '百万円')) + expect(result).toEqual(new BcodeResult(550294097166, '百万円')) expect(DailyCache.get(ticker, date)).not.toBeNull() }) From 0ab19fbb8d6d682707a1c78c5f4336464faf4bba Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Mon, 15 Nov 2021 18:17:39 +0900 Subject: [PATCH 13/20] Update fixture for v3 quarter --- src/__mocks__/fixtures/v3/quarter.js | 1042 ++++++++++++++++++++++---- 1 file changed, 915 insertions(+), 127 deletions(-) diff --git a/src/__mocks__/fixtures/v3/quarter.js b/src/__mocks__/fixtures/v3/quarter.js index 6ee4525..2ea4359 100644 --- a/src/__mocks__/fixtures/v3/quarter.js +++ b/src/__mocks__/fixtures/v3/quarter.js @@ -1,187 +1,975 @@ module.exports = { - 'data': { - issued_share_num: null, - treasury_stock_num: 0.0, - ex_net_sales: 52000000000.0, - ex_operating_income: 25200000000.0, - ex_ordinary_income: null, - ex_net_income: 17090000000.0, - ex_dividend: 36.0, - dividend: null, - fiscal_year: 2018.0, - fiscal_quarter: 1.0, + data: { + ticker: '2371', company_name: '株式会社カカクコム', ceo_name: '代表取締役社長  畑 彰之介', headquarters_address: '東京都渋谷区恵比寿南三丁目5番7号', + end_date: '2018-06-30', + updated_date: '2018-08-09', + edinet_updated_date: '2018-08-09', + tdnet_updated_date: '2018-08-02', + edinet_title: '【E05350】株式会社カカクコム 四半期報告書-第22期第1四半期(平成30年4月1日-平成30年6月30日)', + tdnet_title: '【23710】株式会社カカクコム 2019年3月期 第1四半期決算短信〔IFRS〕(連結)', accounting_standard: 'IFRS', - employee_num: null, - assets: 42143000000.0, - current_assets: 28774000000.0, - cash_and_deposits: 20517000000.0, - notes_accounts_receivable: 6857000000.0, + num_of_shares: 209899293, + issued_share_num: 210605000, + treasury_stock_num: 705707, + assets: 42143000000, + current_assets: 28774000000, + cash_and_deposits: 20517000000, + trade_receivables: 6857000000, + notes_accounts_receivable: 6857000000, notes_receivable: null, accounts_receivable: null, - inventories: null, + current_securities: null, + inventories: 0, merchandise: null, work_in_process: null, raw_materials_and_supplies: null, - non_current_assets: 13369000000.0, - tangible_fixed_assets: null, - liabilities: 7737000000.0, - current_liabilities: 7132000000.0, - notes_accounts_payable: 1986000000.0, + prepaid_expenses: null, + current_dta: null, + current_allowance_doubtful_accounts: null, + non_current_assets: 13369000000, + tangible_fixed_assets: 1094000000, + buildings: null, + machineries: null, + land: null, + construction_in_progress: null, + intangible_assets: 7126000000, + good_will: null, + investments_and_other_assets: null, + investment_securities: null, + non_current_dta: null, + non_current_allowance_doubtful_accounts: null, + lease_and_guarantee_deposits: null, + liabilities: 7737000000, + debt: 487000000, + current_liabilities: 7132000000, + trade_payables: 1986000000, + notes_accounts_payable: 1986000000, accounts_payable: null, notes_payable: null, short_term_bonds_payable: null, - short_term_loans_payable: 223000000.0, + short_term_loans_payable: 223000000, commercial_papers_liabilities: null, current_lease_obligations: null, current_portion_of_long_term_loans: null, current_portion_of_bonds: null, current_portion_of_convertible_bonds: null, current_portion_of_bonds_with_subscription_rights: null, - non_current_liabilities: 605000000.0, + advances_received: null, + corporate_tax_payable: null, + non_current_liabilities: 605000000, bonds_payable: null, convertible_bonds: null, convertible_bond_type_bonds_with_subscription_rights: null, non_current_bonds_with_subscription_right: null, - long_term_loans_payable: 264000000.0, + long_term_loans_payable: 264000000, non_current_lease_obligations: null, - net_assets: 34406000000.0, - shareholders_equity: 34049000000.0, - capital_stock: 916000000.0, - retained_earnings: 33238000000.0, - treasury_stock: -1115000000.0, + non_current_dtl: null, + net_assets: 34406000000, + shareholders_equity: 34049000000, + capital_stock: 916000000, + additional_capital_stock: null, + retained_earnings: 33238000000, + treasury_stock: -1115000000, valuation_and_translation_adjustments: null, - non_controlling_interests: 357000000.0, - subscription_rights: 172000000.0, - net_sales: 12513000000.0, - cost_of_sales: 0.0, - gross_profit: 12512000000.0, - sga: 6952000000.0, - operating_income: 5560000000.0, - non_operating_income: 3000000.0, - non_operating_expenses: 31000000.0, + non_controlling_interests: 357000000, + net_sales: 12513000000, + cost_of_sales: 0, + gross_profit: 12512000000, + gross_margin: 99.99, + sga: 6952000000, + operating_income: 5560000000, + operating_margin: 44, + non_operating_income: 3000000, + interest_and_dividends_income: null, + interest_income: null, + dividends_income: null, + equity_method_income: -29000000, + non_operating_expenses: 31000000, interest_expense: null, - ordinary_income: 5533000000.0, + equity_method_loss: null, + ordinary_income: 5533000000, extraordinary_income: null, + gain_of_sales_non_current_assets: null, + gain_of_sales_investment_securities: null, extraordinary_loss: null, + loss_of_sales_non_current_assets: null, + loss_of_valuation_investment_securities: null, impairment_loss: null, - income_before_income_taxes: 5533000000.0, - income_taxes: 1997000000.0, - net_income: 3534000000.0, - operating_cash_flow: 3110000000.0, - investment_cash_flow: -525000000.0, - financial_cash_flow: -3098000000.0, - depreciation: 452000000.0, - amortization: null + income_before_income_taxes: 5533000000, + income_taxes: 1997000000, + real_corporate_tax_rate: 36.09, + net_income: 3535000000, + non_controling_interests: 1000000, + profit_loss_attributable_to_owners_of_parent: 3534000000, + net_profit_margin: 28.24, + operating_cash_flow: 3110000000, + income_before_taxes: 5533000000, + depreciation: 452000000, + amortization: null, + decrease_trade_receivables_op_cf: 755000000, + decrease_inventories_op_cf: null, + increase_trade_payables_op_cf: 250000000, + investment_cash_flow: -525000000, + purchase_of_property: -73000000, + sale_of_property: null, + purchase_of_intangible_assets: -273000000, + sale_of_intangible_assets: null, + purchase_of_non_current_assets: null, + sale_of_non_current_assets: null, + purchase_of_securities: null, + sale_of_securities: null, + purchase_of_investment_securities: -100000000, + sale_of_investment_securities: null, + lending: null, + return_of_lending: null, + financial_cash_flow: -3098000000, + net_short_term_debt: null, + long_term_debt_issuance: 150000000, + long_term_debt_repayment: -67000000, + bonds_issuance: null, + bonds_repayment: null, + share_repurchase: null, + share_sales: null, + dividend_payment: -3328000000, + cash_translation_difference: null, + free_cash_flow: null, + ex_net_sales: 52000000000, + ex_operating_income: 25200000000, + ex_ordinary_income: null, + ex_net_income: 17090000000, + dividend: null, + eps_actual: 79, + bps: 161, + ebitda_actual: 6012000000, + roe: 49.29, + real_roe: 47.72, + total_asset_turnover: 1.3, + financial_leverage: 1.24, + roa: 39.62, + roic: 49.39, + doe: 22.31, + net_sales_operating_cash_flow_ratio: 24.85, + sga_ratio: 55.56, + depreciation_gross_profit_ratio: 3.61, + r_and_d_ratio: 0.28, + interest_op_income_ratio: 0.0, + interest_coverage_ratio: null, + net_sales_progress: 24.06, + operating_income_progress: 22.06, + net_income_progress: 20.68, + cash_assets_ratio: 48.68, + cash_monthly_sales_ratio: 4.49, + accounts_receivable_turnover: 45.64, + inventory_turnover: null, + trade_payable_turnover: null, + working_capital: 4871000000, + ccc: 45.64, + tangible_fixed_assets_turnover: 50.12, + debt_assets_ratio: 1.16, + debt_monthly_sales_ratio: 0.11, + operating_cash_flow_debt_ratio: 2.66, + net_debt: -20030000000, + adjusted_debt_ratio: 21.78, + de_ratio: 0.01, + current_ratio: 403.45, + net_debt_net_income_ratio: -1.2, + equity: 33877000000, + equity_ratio: 80.39, + accrual: 424000000, + employee_num: null, + net_sales_per_employee: null, + operating_income_per_employee: null, + segment_member: null, + increase_in_properties: 346000000, + r_and_d_expenses: 35000000, + defined_benefit_asset: null, + defined_benefit_liability: null, + asset_retirement_obligations_ncl: null, + subscription_rights: 172000000, + fiscal_year: 2018, + fiscal_quarter: 1, + dividend_payout_ratio: null, + real_corp_tax_rate: 36.09 }, column_description: { - company_name: { name_jp: '社名', unit: 'なし' }, - ceo_name: { name_jp: '代表社名', unit: 'なし' }, - headquarters_address: { name_jp: '所在地', unit: 'なし' }, - accounting_standard: { name_jp: '会計基準', unit: 'なし' }, - issued_share_num: { name_jp: '発行済株式総数', unit: '株' }, - employee_num: { name_jp: '従業員数', unit: '人' }, - assets: { name_jp: '総資産', unit: '百万円' }, - current_assets: { name_jp: '流動資産', unit: '百万円' }, - cash_and_deposits: { name_jp: '現預金', unit: '百万円' }, + ticker: { + name_jp: 'ティッカー', + unit: 'なし' + }, + company_name: { + name_jp: '社名', + unit: 'なし' + }, + ceo_name: { + name_jp: '代表者名', + unit: 'なし' + }, + headquarters_address: { + name_jp: '所在地', + unit: 'なし' + }, + end_date: { + name_jp: '期末日', + unit: '日付' + }, + updated_date: { + name_jp: '更新日', + unit: '日付' + }, + edinet_updated_date: { + name_jp: 'edinet発行日', + unit: '日付' + }, + tdnet_updated_date: { + name_jp: 'tdnet発行日', + unit: '日付' + }, + edinet_title: { + name_jp: 'edinet開示資料名', + unit: 'なし' + }, + tdnet_title: { + name_jp: 'tdnet開示資料名', + unit: 'なし' + }, + accounting_standard: { + name_jp: '会計基準', + unit: 'なし' + }, + num_of_shares: { + name_jp: '株式総数', + unit: '株' + }, + issued_share_num: { + name_jp: '発行済株式総数', + unit: '株' + }, + treasury_stock_num: { + name_jp: '自己株数', + unit: '株' + }, + assets: { + name_jp: '総資産', + unit: '円' + }, + current_assets: { + name_jp: '流動資産', + unit: '円' + }, + cash_and_deposits: { + name_jp: '現預金', + unit: '円' + }, + trade_receivables: { + name_jp: '売上債権', + unit: '円' + }, notes_accounts_receivable: { - name_jp: '受取手形及び売掛金', - unit: '百万円' - }, - notes_receivable: { name_jp: '受取手形', unit: '百万円' }, - accounts_receivable: { name_jp: '売掛金', unit: '百万円' }, - inventories: { name_jp: '在庫', unit: '百万円' }, - merchandise: { name_jp: '商品', unit: '百万円' }, - work_in_process: { name_jp: '仕掛品', unit: '百万円' }, - raw_materials_and_supplies: { name_jp: '原材料', unit: '百万円' }, - non_current_assets: { name_jp: '固定資産', unit: '百万円' }, - tangible_fixed_assets: { name_jp: '有形固定資産', unit: '百万円' }, - liabilities: { name_jp: '負債', unit: '百万円' }, - current_liabilities: { name_jp: '流動負債', unit: '百万円' }, - notes_accounts_payable: { name_jp: '支払手形及び買掛金', unit: '百万円' }, - accounts_payable: { name_jp: '買掛金', unit: '百万円' }, - notes_payable: { name_jp: '支払手形', unit: '百万円' }, - short_term_bonds_payable: { name_jp: '短期社債', unit: '百万円' }, - short_term_loans_payable: { name_jp: '短期借入金', unit: '百万円' }, + name_jp: '受取手形および売掛金', + unit: '円' + }, + notes_receivable: { + name_jp: '受取手形', + unit: '円' + }, + accounts_receivable: { + name_jp: '売掛金', + unit: '円' + }, + current_securities: { + name_jp: '有価証券', + unit: '円' + }, + inventories: { + name_jp: '棚卸資産', + unit: '円' + }, + merchandise: { + name_jp: '商品', + unit: '円' + }, + work_in_process: { + name_jp: '仕掛品', + unit: '円' + }, + raw_materials_and_supplies: { + name_jp: '原材料', + unit: '円' + }, + prepaid_expenses: { + name_jp: '前払金', + unit: '円' + }, + current_dta: { + name_jp: '繰延税金資産(流動)', + unit: '円' + }, + current_allowance_doubtful_accounts: { + name_jp: '貸倒引当金', + unit: '円' + }, + non_current_assets: { + name_jp: '固定資産', + unit: '円' + }, + tangible_fixed_assets: { + name_jp: '有形固定資産', + unit: '円' + }, + buildings: { + name_jp: '建物・構築物', + unit: '円' + }, + machineries: { + name_jp: '機械・運搬具・工具', + unit: '円' + }, + land: { + name_jp: '土地', + unit: '円' + }, + construction_in_progress: { + name_jp: '建設仮勘定', + unit: '円' + }, + intangible_assets: { + name_jp: '無形固定資産', + unit: '円' + }, + good_will: { + name_jp: 'のれん', + unit: '円' + }, + investments_and_other_assets: { + name_jp: '投資その他の資産', + unit: '円' + }, + investment_securities: { + name_jp: '投資有価証券', + unit: '円' + }, + non_current_dta: { + name_jp: '繰延税金資産(固定)', + unit: '円' + }, + non_current_allowance_doubtful_accounts: { + name_jp: '貸倒引当金', + unit: '円' + }, + lease_and_guarantee_deposits: { + name_jp: '敷金および保証金', + unit: '円' + }, + liabilities: { + name_jp: '負債', + unit: '円' + }, + debt: { + name_jp: '有利子負債', + unit: '円' + }, + current_liabilities: { + name_jp: '流動負債', + unit: '円' + }, + trade_payables: { + name_jp: '仕入債務', + unit: '円' + }, + notes_accounts_payable: { + name_jp: '支払手形および買掛金', + unit: '円' + }, + accounts_payable: { + name_jp: '買掛金', + unit: '円' + }, + notes_payable: { + name_jp: '支払手形', + unit: '円' + }, + short_term_bonds_payable: { + name_jp: '短期社債', + unit: '円' + }, + short_term_loans_payable: { + name_jp: '短期借入金', + unit: '円' + }, commercial_papers_liabilities: { name_jp: 'コマーシャルペーパー', - unit: '百万円' + unit: '円' }, current_lease_obligations: { name_jp: 'リース債務(流動負債)', - unit: '百万円' + unit: '円' }, current_portion_of_long_term_loans: { name_jp: '1年以内返済の長期借入金', - unit: '百万円' + unit: '円' + }, + current_portion_of_bonds: { + name_jp: '1年以内返済の社債', + unit: '円' }, - current_portion_of_bonds: { name_jp: '1年以内返済の社債', unit: '百万円' }, current_portion_of_convertible_bonds: { name_jp: '1年以内返済の転換社債', - unit: '百万円' + unit: '円' }, current_portion_of_bonds_with_subscription_rights: { name_jp: '1年以内返済の新株予約権付社債', - unit: '百万円' + unit: '円' + }, + advances_received: { + name_jp: '前受金', + unit: '円' + }, + corporate_tax_payable: { + name_jp: '未払法人税等', + unit: '円' + }, + non_current_liabilities: { + name_jp: '固定負債', + unit: '円' + }, + bonds_payable: { + name_jp: '社債', + unit: '円' + }, + convertible_bonds: { + name_jp: '転換社債', + unit: '円' }, - non_current_liabilities: { name_jp: '固定負債', unit: '百万円' }, - bonds_payable: { name_jp: '社債', unit: '百万円' }, - convertible_bonds: { name_jp: '転換社債', unit: '百万円' }, convertible_bond_type_bonds_with_subscription_rights: { name_jp: '新株予約権付転換社債', - unit: '百万円' + unit: '円' }, non_current_bonds_with_subscription_right: { name_jp: '新株予約権付社債', - unit: '百万円' + unit: '円' + }, + long_term_loans_payable: { + name_jp: '長期借入金', + unit: '円' }, - long_term_loans_payable: { name_jp: '長期借入金', unit: '百万円' }, non_current_lease_obligations: { name_jp: 'リース債務(固定負債)', - unit: '百万円' + unit: '円' + }, + non_current_dtl: { + name_jp: '繰延税金負債(固定)', + unit: '円' + }, + net_assets: { + name_jp: '純資産', + unit: '円' + }, + shareholders_equity: { + name_jp: '株主資本', + unit: '円' + }, + capital_stock: { + name_jp: '資本金', + unit: '円' + }, + additional_capital_stock: { + name_jp: '資本剰余金', + unit: '円' + }, + retained_earnings: { + name_jp: '利益剰余金', + unit: '円' + }, + treasury_stock: { + name_jp: '自己株式', + unit: '円' }, - net_assets: { name_jp: '純資産', unit: '百万円' }, - shareholders_equity: { name_jp: '株主資本', unit: '百万円' }, - capital_stock: { name_jp: '資本金', unit: '百万円' }, - retained_earnings: { name_jp: '利益剰余金', unit: '百万円' }, - treasury_stock: { name_jp: '自己株式', unit: '百万円' }, valuation_and_translation_adjustments: { name_jp: '評価換算差額等', - unit: '百万円' - }, - non_controlling_interests: { name_jp: '非支配持分', unit: '百万円' }, - subscription_rights: { name_jp: '新株予約権', unit: '百万円' }, - net_sales_summary: { name_jp: '売上', unit: '百万円' }, - net_sales: { name_jp: '売上', unit: '百万円' }, - cost_of_sales: { name_jp: '売上原価', unit: '百万円' }, - gross_profit: { name_jp: '売上高総利益', unit: '百万円' }, - sga: { name_jp: '販売費および一般管理費', unit: '百万円' }, - operating_income: { name_jp: '営業利益', unit: '百万円' }, - non_operating_income: { name_jp: '営業外利益', unit: '百万円' }, - non_operating_expenses: { name_jp: '営業外損失', unit: '百万円' }, - interest_expense: { name_jp: '支払利息', unit: '百万円' }, - ordinary_income: { name_jp: '経常利益', unit: '百万円' }, - extraordinary_income: { name_jp: '特別利益', unit: '百万円' }, - extraordinary_loss: { name_jp: '特別損失', unit: '百万円' }, - impairment_loss: { name_jp: '減損損失', unit: '百万円' }, - income_before_income_taxes: { name_jp: '税引前当期純利益', unit: '百万円' }, - income_taxes: { name_jp: '法人税等', unit: '百万円' }, - net_income: { name_jp: '当期純利益', unit: '百万円' }, - operating_cash_flow: { name_jp: '営業キャッシュフロー', unit: '百万円' }, - investment_cash_flow: { name_jp: '投資キャッシュフロー', unit: '百万円' }, - financial_cash_flow: { name_jp: '財務キャッシュフロー', unit: '百万円' }, - depreciation: { name_jp: '減価償却費', unit: '百万円' }, - amortization: { name_jp: 'のれん償却費', unit: '百万円' }, - treasury_stock_num: { name_jp: '自己株数', unit: '株' }, - ex_net_sales: { name_jp: '売上(会予)', unit: '百万円' }, - ex_operating_income: { name_jp: '営業利益(会予)', unit: '百万円' }, - ex_ordinary_income: { name_jp: '経常利益(会予)', unit: '百万円' }, - ex_net_income: { name_jp: '当期純利益(会予)', unit: '百万円' }, - ex_dividend: { name_jp: '配当予想', unit: '円' }, - dividend: { name_jp: '配当金', unit: '円' }, - fiscal_year: { name_jp: '会計年度', unit: '年' }, - fiscal_quarter: { name_jp: '四半期', unit: 'なし' } + unit: '円' + }, + non_controlling_interests: { + name_jp: '非支配持分', + unit: '円' + }, + net_sales: { + name_jp: '売上', + unit: '円' + }, + cost_of_sales: { + name_jp: '売上原価', + unit: '円' + }, + gross_profit: { + name_jp: '売上高総利益', + unit: '円' + }, + gross_margin: { + name_jp: '売上高総利益率', + unit: '%' + }, + sga: { + name_jp: '販売費および一般管理費', + unit: '円' + }, + operating_income: { + name_jp: '営業利益', + unit: '円' + }, + operating_margin: { + name_jp: '営業利益率', + unit: '%' + }, + non_operating_income: { + name_jp: '営業外収益', + unit: '円' + }, + interest_and_dividends_income: { + name_jp: '受取利息および受取配当金', + unit: '円' + }, + interest_income: { + name_jp: '受取利息', + unit: '円' + }, + dividends_income: { + name_jp: '受取配当金', + unit: '円' + }, + equity_method_income: { + name_jp: '持分法による投資利益', + unit: '円' + }, + non_operating_expenses: { + name_jp: '営業外費用', + unit: '円' + }, + interest_expense: { + name_jp: '支払利息', + unit: '円' + }, + equity_method_loss: { + name_jp: '持分法による投資損失', + unit: '円' + }, + ordinary_income: { + name_jp: '経常利益', + unit: '円' + }, + extraordinary_income: { + name_jp: '特別利益', + unit: '円' + }, + gain_of_sales_non_current_assets: { + name_jp: '固定資産売却益', + unit: '円' + }, + gain_of_sales_investment_securities: { + name_jp: '投資有価証券売却益', + unit: '円' + }, + extraordinary_loss: { + name_jp: '特別損失', + unit: '円' + }, + loss_of_sales_non_current_assets: { + name_jp: '固定資産売却損', + unit: '円' + }, + loss_of_valuation_investment_securities: { + name_jp: '投資有価証券売却損', + unit: '円' + }, + impairment_loss: { + name_jp: '減損損失', + unit: '円' + }, + income_before_income_taxes: { + name_jp: '税引前当期純利益', + unit: '円' + }, + income_taxes: { + name_jp: '法人税等', + unit: '円' + }, + real_corporate_tax_rate: { + name_jp: '実質法人税率', + unit: '%' + }, + net_income: { + name_jp: '非支配持分控除前四半期純利益', + unit: '円' + }, + non_controling_interests: { + name_jp: '非支配株主に帰属する当期純利益', + unit: '円' + }, + profit_loss_attributable_to_owners_of_parent: { + name_jp: '親会社株主に帰属する当期純利益', + unit: '円' + }, + net_profit_margin: { + name_jp: '当期純利益率', + unit: '%' + }, + operating_cash_flow: { + name_jp: '営業キャッシュフロー', + unit: '円' + }, + income_before_taxes: { + name_jp: '税金等調整前当期純利益', + unit: '円' + }, + depreciation: { + name_jp: '減価償却費', + unit: '円' + }, + amortization: { + name_jp: 'のれん償却費', + unit: '円' + }, + decrease_trade_receivables_op_cf: { + name_jp: '売上債権の増減額', + unit: '円' + }, + decrease_inventories_op_cf: { + name_jp: '棚卸資産の増減額', + unit: '円' + }, + increase_trade_payables_op_cf: { + name_jp: '仕入債務の増減額', + unit: '円' + }, + investment_cash_flow: { + name_jp: '投資キャッシュフロー', + unit: '円' + }, + purchase_of_property: { + name_jp: '有形固定資産の取得による支出', + unit: '円' + }, + sale_of_property: { + name_jp: '有形固定資産の売却による収入', + unit: '円' + }, + purchase_of_intangible_assets: { + name_jp: '無形固定資産の取得による支出', + unit: '円' + }, + sale_of_intangible_assets: { + name_jp: '無形固定資産の売却による収入', + unit: '円' + }, + purchase_of_non_current_assets: { + name_jp: '固定資産の取得による支出', + unit: '円' + }, + sale_of_non_current_assets: { + name_jp: '固定資産の売却による収入', + unit: '円' + }, + purchase_of_securities: { + name_jp: '有価証券の取得による支出', + unit: '円' + }, + sale_of_securities: { + name_jp: '有価証券の売却・償還による収入', + unit: '円' + }, + purchase_of_investment_securities: { + name_jp: '投資有価証券の取得による支出', + unit: '円' + }, + sale_of_investment_securities: { + name_jp: '投資有価証券の売却・償還による収入', + unit: '円' + }, + lending: { + name_jp: '貸付けによる支出', + unit: '円' + }, + return_of_lending: { + name_jp: '貸付金の回収による収入', + unit: '円' + }, + financial_cash_flow: { + name_jp: '財務キャッシュフロー', + unit: '円' + }, + net_short_term_debt: { + name_jp: '短期借入金の純増減額', + unit: '円' + }, + long_term_debt_issuance: { + name_jp: '長期借入れによる収入', + unit: '円' + }, + long_term_debt_repayment: { + name_jp: '長期借入金の返済による支出', + unit: '円' + }, + bonds_issuance: { + name_jp: '社債の発行による収入', + unit: '円' + }, + bonds_repayment: { + name_jp: '社債の償還による支出', + unit: '円' + }, + share_repurchase: { + name_jp: '自己株式の取得による支出', + unit: '円' + }, + share_sales: { + name_jp: '自己株式の売却による収入', + unit: '円' + }, + dividend_payment: { + name_jp: '配当金の支払額', + unit: '円' + }, + cash_translation_difference: { + name_jp: '現金及び現金同等物に係る換算差額', + unit: '円' + }, + free_cash_flow: { + name_jp: 'フリーキャッシュフロー', + unit: '円' + }, + ex_net_sales: { + name_jp: '売上(会社予想)', + unit: '円' + }, + ex_operating_income: { + name_jp: '営業利益(会社予想)', + unit: '円' + }, + ex_ordinary_income: { + name_jp: '経常利益(会社予想)', + unit: '円' + }, + ex_net_income: { + name_jp: '当期純利益(会社予想)', + unit: '円' + }, + dividend: { + name_jp: '配当金(実績)', + unit: '円' + }, + eps_actual: { + name_jp: 'EPS(実績)', + unit: '円' + }, + bps: { + name_jp: 'BPS', + unit: '円' + }, + ebitda_actual: { + name_jp: 'EBITDA(実績)', + unit: '円' + }, + roe: { + name_jp: 'ROE', + unit: '%' + }, + real_roe: { + name_jp: '実質ROE', + unit: '%' + }, + total_asset_turnover: { + name_jp: '総資産回転率', + unit: '倍' + }, + financial_leverage: { + name_jp: '財務レバレッジ', + unit: '倍' + }, + roa: { + name_jp: 'ROA', + unit: '%' + }, + roic: { + name_jp: 'ROIC', + unit: '%' + }, + doe: { + name_jp: '自己資本配当率', + unit: '%' + }, + net_sales_operating_cash_flow_ratio: { + name_jp: '営業キャッシュフロー/売上比率', + unit: '%' + }, + sga_ratio: { + name_jp: '販管費/売上率', + unit: '%' + }, + depreciation_gross_profit_ratio: { + name_jp: '減価償却費/粗利比率', + unit: '%' + }, + r_and_d_ratio: { + name_jp: '研究開発費/売上比率', + unit: '%' + }, + interest_op_income_ratio: { + name_jp: '支払利息/営業利益比率', + unit: '%' + }, + interest_coverage_ratio: { + name_jp: 'インタレストカバレッジレシオ ', + unit: '倍' + }, + net_sales_progress: { + name_jp: '売上進捗率', + unit: '%' + }, + operating_income_progress: { + name_jp: '営業利益進捗率', + unit: '%' + }, + net_income_progress: { + name_jp: '純利益進捗率', + unit: '%' + }, + cash_assets_ratio: { + name_jp: '現金総資産比率', + unit: '%' + }, + cash_monthly_sales_ratio: { + name_jp: '現金売上倍率', + unit: '倍' + }, + accounts_receivable_turnover: { + name_jp: '売上債権回転期間', + unit: '日' + }, + inventory_turnover: { + name_jp: '棚卸資産回転期間', + unit: '日' + }, + trade_payable_turnover: { + name_jp: '仕入債務回転期間', + unit: '日' + }, + working_capital: { + name_jp: '運転資本', + unit: '円' + }, + ccc: { + name_jp: 'CCC', + unit: '日' + }, + tangible_fixed_assets_turnover: { + name_jp: '有形固定資産回転率', + unit: '%' + }, + debt_assets_ratio: { + name_jp: '有利子負債/総資産比率', + unit: '%' + }, + debt_monthly_sales_ratio: { + name_jp: '有利子負債/月商比率', + unit: 'ヶ月' + }, + operating_cash_flow_debt_ratio: { + name_jp: '有利子負債/営業キャッシュフロー倍率', + unit: '倍' + }, + net_debt: { + name_jp: '純有利子負債', + unit: '円' + }, + adjusted_debt_ratio: { + name_jp: '自己株式調整済負債比率', + unit: '%' + }, + de_ratio: { + name_jp: 'DE比率', + unit: '%' + }, + current_ratio: { + name_jp: '流動比率', + unit: '%' + }, + net_debt_net_income_ratio: { + name_jp: 'ネットD純利益比率', + unit: '倍' + }, + equity: { + name_jp: '自己資本', + unit: '円' + }, + equity_ratio: { + name_jp: '自己資本比率', + unit: '%' + }, + accrual: { + name_jp: 'アクルーアル ', + unit: '円' + }, + employee_num: { + name_jp: '従業員数', + unit: '人' + }, + net_sales_per_employee: { + name_jp: '従業員一人あたり売上', + unit: '円' + }, + operating_income_per_employee: { + name_jp: '従業員一人あたり営業利益', + unit: '円' + }, + segment_member: { + name_jp: 'セグメント情報', + unit: 'なし' + }, + increase_in_properties: { + name_jp: '設備投資額', + unit: '円' + }, + r_and_d_expenses: { + name_jp: '研究開発費', + unit: '円' + }, + desc_business: { + name_jp: '事業概要', + unit: 'なし' + }, + defined_benefit_asset: { + name_jp: '退職給付に係る資産', + unit: 'なし' + }, + defined_benefit_liability: { + name_jp: '退職給付に係る負債', + unit: 'なし' + }, + asset_retirement_obligations_ncl: { + name_jp: '資産除去債務', + unit: 'なし' + }, + subscription_rights: { + name_jp: '新株予約権', + unit: 'なし' + }, + fiscal_year: { + name_jp: '会計年度', + unit: 'なし' + }, + fiscal_quarter: { + name_jp: '四半期', + unit: 'なし' + }, + dividend_payout_ratio: { + name_jp: '配当性向', + unit: '%' + }, + real_corp_tax_rate: { + name_jp: '実質法人税率', + unit: '%' + } } } From 2839a48a72ff66cd6b8d8b487a13604b793f76ff Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Mon, 15 Nov 2021 18:52:57 +0900 Subject: [PATCH 14/20] Add bcodeQuarter for v3 --- src/custom-functions/v3/bcode-quarter.test.ts | 36 +++++++++++++ src/custom-functions/v3/bcode-quarter.ts | 51 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/custom-functions/v3/bcode-quarter.test.ts create mode 100644 src/custom-functions/v3/bcode-quarter.ts diff --git a/src/custom-functions/v3/bcode-quarter.test.ts b/src/custom-functions/v3/bcode-quarter.test.ts new file mode 100644 index 0000000..6b61025 --- /dev/null +++ b/src/custom-functions/v3/bcode-quarter.test.ts @@ -0,0 +1,36 @@ +import { CachingBuffettCodeApiClientV3 } from '~/api/v3/caching-client' +import { BcodeResult } from '~/custom-functions/bcode-result' +import { bcodeQuarter } from '~/custom-functions/v3/bcode-quarter' +import { YearQuarter } from '~/fiscal-periods/year-quarter' +import { YearQuarterParam } from '~/fiscal-periods/year-quarter-param' +import { QuarterCache } from '~/services/quarter-cache' + +jest.mock('~/api/v3/client', () => + jest.requireActual('~/__mocks__/api/v3/client') +) +jest.mock('~/services/company-cache', () => + jest.requireActual('~/__mocks__/services/company-cache') +) +jest.mock('~/services/quarter-cache', () => + jest.requireActual('~/__mocks__/services/quarter-cache') +) + +test('bcodeQuarter', () => { + const ticker = '2371' + const period = new YearQuarter(2018, 1) + const propertyName = 'net_sales' + + expect(QuarterCache.get(ticker, period)).toBeNull() + + const client = new CachingBuffettCodeApiClientV3('token') + const result = bcodeQuarter( + client, + ticker, + YearQuarterParam.fromYearQuarter(period), + propertyName, + false + ) + + expect(result).toEqual(new BcodeResult(12513000000, '円')) + expect(QuarterCache.get(ticker, period)).not.toBeNull() +}) diff --git a/src/custom-functions/v3/bcode-quarter.ts b/src/custom-functions/v3/bcode-quarter.ts new file mode 100644 index 0000000..98a74b8 --- /dev/null +++ b/src/custom-functions/v3/bcode-quarter.ts @@ -0,0 +1,51 @@ +import { CompanyService } from '~/api/company-service' +import { BuffettCodeApiClientV3 } from '~/api/v3/client' +import { BcodeResult } from '~/custom-functions/bcode-result' +import { + ApiResponseError, + OndemandApiNotEnabledError, + PropertyNotFoundError, + UnsupportedTickerError +} from '~/custom-functions/error' +import { Quarter } from '~/entities/v3/quarter' +import { YearQuarterParam } from '~/fiscal-periods/year-quarter-param' + +export function bcodeQuarter( + client: BuffettCodeApiClientV3, + ticker: string, + period: YearQuarterParam, + propertyName: string, + ondemandApiEnabled: boolean +): BcodeResult { + const companyService = new CompanyService(ticker, client) + if (!companyService.isSupportedTicker()) { + throw new UnsupportedTickerError() + } + + let quarter: Quarter + if (companyService.isOndemandQuarterApiPeriod(period)) { + if (!ondemandApiEnabled) { + throw new OndemandApiNotEnabledError() + } + + quarter = client.ondemandQuarter(ticker, period) + } else { + quarter = client.quarter(ticker, period) + } + + if (quarter == undefined) { + throw new ApiResponseError() + } + + const property = quarter.columnDescription[propertyName] + if (property == undefined) { + throw new PropertyNotFoundError( + `propetyName '${propertyName}' is not found.` + ) + } + + const value = quarter.data[propertyName] + const unit = property['unit'] + + return new BcodeResult(value, unit) +} From 0108604fe28214bf21eb574025e1d3e309ee4fbf Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Wed, 17 Nov 2021 17:09:50 +0900 Subject: [PATCH 15/20] Add bcode v3 --- src/custom-functions/bcode.ts | 17 ++++- src/custom-functions/v3/bcode.ts | 112 +++++++++++++++++++++++++++++++ src/main.ts | 3 +- 3 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 src/custom-functions/v3/bcode.ts diff --git a/src/custom-functions/bcode.ts b/src/custom-functions/bcode.ts index 74d1594..79bd1be 100644 --- a/src/custom-functions/bcode.ts +++ b/src/custom-functions/bcode.ts @@ -1,4 +1,5 @@ import { bcode as bcodeV2 } from '~/custom-functions/v2/bcode' +import { bcode as bcodeV3 } from '~/custom-functions/v3/bcode' export function castStringAsBoolean(bool: string | boolean): boolean { return typeof bool === 'string' ? bool.toLowerCase() === 'true' : bool @@ -30,8 +31,20 @@ export function bcode( param5: string | boolean = false ): number | string { if (param1 instanceof Date || isV3Call(param1, param2)) { - throw new Error( - `<<引数が不正な形式です。param1: ${param1} (${typeof param1}), param2: ${param2} (${typeof param2})>>` + if (typeof param1 === 'number') { + param1 = param1.toString() + } + + if (typeof param2 === 'number') { + param2 = param2.toString() + } + + return bcodeV3( + ticker, + param1, + param2, + castStringAsBoolean(param3), + castStringAsBoolean(param4) ) } else { return bcodeV2( diff --git a/src/custom-functions/v3/bcode.ts b/src/custom-functions/v3/bcode.ts new file mode 100644 index 0000000..d7dbb4c --- /dev/null +++ b/src/custom-functions/v3/bcode.ts @@ -0,0 +1,112 @@ +import { HttpError } from '~/api/http-error' +import { CachingBuffettCodeApiClientV3 } from '~/api/v3/caching-client' +import { BcodeResult } from '~/custom-functions/bcode-result' +import { + ApiResponseError, + OndemandApiNotEnabledError, + UnsupportedTickerError +} from '~/custom-functions/error' +import { bcodeDaily } from '~/custom-functions/v3/bcode-daily' +import { bcodeQuarter } from '~/custom-functions/v3/bcode-quarter' +import { DateParam } from '~/fiscal-periods/date-param' +import { + InvalidLYLQError, + InvalidYearError, + InvalidQuarterError +} from '~/fiscal-periods/error' +import { PeriodParser } from '~/fiscal-periods/period-parser' +import { Setting } from '~/setting' + +function handleError(e): void { + if (e instanceof ApiResponseError) { + throw new Error('<<指定されたデータを取得できませんでした>>') + } else if (e instanceof OndemandApiNotEnabledError) { + throw new Error('<<従量課金APIが有効になっていません>>') + } else if (e instanceof UnsupportedTickerError) { + throw new Error('<<サポートされていないtickerです>>') + } else if (e instanceof HttpError) { + const code = e.response.getResponseCode() + + if (e.isInvalidTestingRequest()) { + throw new Error('<<テスト用のAPIキーでは取得できないデータです>>') + } else if (e.isInvalidTokenRequest()) { + throw new Error('<>') + } else if (code === 403) { + throw new Error('<<月間リクエスト制限に達しています>>') + } else if (code === 429) { + throw new Error('<>') + } else if (Math.floor(code / 100) === 4) { + throw new Error(`<<無効なリクエストです。${e.name}: ${e.message}>>`) + } else { + console.error('システムエラー', e.name, e.message) + throw new Error( + `<<システムエラーが発生しました。${e.name}: ${e.message}>>` + ) + } + } else if (e instanceof InvalidLYLQError) { + throw new Error('<>') + } else if (e instanceof InvalidYearError) { + throw new Error(`<<無効な決算年度が指定されています>>`) + } else if (e instanceof InvalidQuarterError) { + throw new Error(`<<無効な四半期が指定されています>>`) + } else { + console.error('未定義のエラー', e.name, e.message) + throw new Error(`<<未定義のエラーが発生しました。${e.name}: ${e.message}>>`) + } +} + +export function bcode( + ticker: string, + period: string | Date, + propertyName: string, + isRawValue = false, + isWithUnits = false +): number | string { + if (!ticker) { + throw new Error('<>') + } + + if (!period) { + throw new Error('<>') + } + + if (!propertyName) { + throw new Error('<>') + } + + if (period instanceof Date) { + period = period.toISOString().substring(0, 10) + } + + const setting = Setting.load() + if (!setting.token) { + throw new Error('<>') + } + + try { + const client = new CachingBuffettCodeApiClientV3(setting.token) + const parsedPeriod = PeriodParser.parse(period) + let result: BcodeResult + if (parsedPeriod instanceof DateParam) { + result = bcodeDaily( + client, + ticker, + parsedPeriod, + propertyName, + setting.ondemandApiEnabled + ) + } else { + result = bcodeQuarter( + client, + ticker, + parsedPeriod, + propertyName, + setting.ondemandApiEnabled + ) + } + + return result.format(isRawValue, isWithUnits) + } catch (e) { + handleError(e) + } +} diff --git a/src/main.ts b/src/main.ts index ffd919d..14d9203 100644 --- a/src/main.ts +++ b/src/main.ts @@ -38,8 +38,7 @@ global.exportCsv = exportCsv * 指定した銘柄の財務数字や指標を取得します。 * * @param {"6501"} ticker 銘柄コード - * @param {"2017"} fiscalYear 会計年度 (または"LY") - * @param {"4"} fiscalQuarter 四半期 (1~4の数字または"LQ") + * @param {"2017Q4"} period 会計期間 (例: 四半期 '2017Q4', 日付 '2020-09-06') * @param {"net_sales"} propertyName 項目名 * @param {TRUE} isRawValue (オプション) 数値をRAWデータで表示するかどうか (デフォルト値: FALSE) * @param {TRUE} isWithUnits (オプション) 単位を末尾に付加するかどうか (デフォルト値: FALSE) From f719d9898d53172445f58c7bc21e4ad06dafedb2 Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Fri, 19 Nov 2021 15:17:57 +0900 Subject: [PATCH 16/20] Update fixtures --- src/__mocks__/fixtures/v3/daily.js | 6 +- src/__mocks__/fixtures/v3/quarter-property.js | 746 ------------------ 2 files changed, 3 insertions(+), 749 deletions(-) delete mode 100644 src/__mocks__/fixtures/v3/quarter-property.js diff --git a/src/__mocks__/fixtures/v3/daily.js b/src/__mocks__/fixtures/v3/daily.js index 2f3ab5c..6b6e1e1 100644 --- a/src/__mocks__/fixtures/v3/daily.js +++ b/src/__mocks__/fixtures/v3/daily.js @@ -14,11 +14,11 @@ module.exports = { }, market_capital: { name_jp: '時価総額', - unit: '百万円' + unit: '円' }, enterprise_value: { name_jp: '企業価値', - unit: '百万円' + unit: '円' }, eps_forecast: { name_jp: 'EPS(会社予想)', @@ -38,7 +38,7 @@ module.exports = { }, ebitda_forecast: { name_jp: 'EBITDA(会社予想)', - unit: '百万円' + unit: '円' }, ev_ebitda_forecast: { name_jp: 'EV/EBITDA(会社予想)', diff --git a/src/__mocks__/fixtures/v3/quarter-property.js b/src/__mocks__/fixtures/v3/quarter-property.js deleted file mode 100644 index d284384..0000000 --- a/src/__mocks__/fixtures/v3/quarter-property.js +++ /dev/null @@ -1,746 +0,0 @@ -module.exports = { - company_name: { - name_jp: '社名', - unit: 'なし' - }, - ceo_name: { - name_jp: '代表者名', - unit: 'なし' - }, - headquarters_address: { - name_jp: '所在地', - unit: 'なし' - }, - end_date: { - name_jp: '有価証券報告書の期末日', - unit: '日付' - }, - edinet_updated_date: { - name_jp: 'edinet発行日', - unit: '日付' - }, - tdnet_updated_date: { - name_jp: 'tdnet発行日', - unit: '日付' - }, - edinet_title: { - name_jp: 'edinet開示資料名', - unit: 'なし' - }, - tdnet_title: { - name_jp: 'tdnet開示資料名', - unit: 'なし' - }, - accounting_standard: { - name_jp: '会計基準', - unit: 'なし' - }, - num_of_shares: { - name_jp: '株式総数', - unit: '株' - }, - issued_share_num: { - name_jp: '発行済株式総数', - unit: '株' - }, - treasury_stock_num: { - name_jp: '自己株数', - unit: '株' - }, - assets: { - name_jp: '総資産', - unit: '百万円' - }, - current_assets: { - name_jp: '流動資産', - unit: '百万円' - }, - cash_and_deposits: { - name_jp: '現預金', - unit: '百万円' - }, - trade_receivables: { - name_jp: '売上債権', - unit: '百万円' - }, - notes_accounts_receivable: { - name_jp: '受取手形および売掛金', - unit: '百万円' - }, - notes_receivable: { - name_jp: '受取手形', - unit: '百万円' - }, - accounts_receivable: { - name_jp: '売掛金', - unit: '百万円' - }, - current_securities: { - name_jp: '有価証券', - unit: '百万円' - }, - inventories: { - name_jp: '棚卸資産', - unit: '百万円' - }, - merchandise: { - name_jp: '商品', - unit: '百万円' - }, - work_in_process: { - name_jp: '仕掛品', - unit: '百万円' - }, - raw_materials_and_supplies: { - name_jp: '原材料', - unit: '百万円' - }, - prepaid_expenses: { - name_jp: '前払金', - unit: '百万円' - }, - current_dta: { - name_jp: '繰延税金資産(流動)', - unit: '百万円' - }, - current_allowance_doubtful_accounts: { - name_jp: '貸倒引当金', - unit: '百万円' - }, - non_current_assets: { - name_jp: '固定資産', - unit: '百万円' - }, - tangible_fixed_assets: { - name_jp: '有形固定資産', - unit: '百万円' - }, - buildings: { - name_jp: '建物・構築物', - unit: '百万円' - }, - machineries: { - name_jp: '機械・運搬具・工具', - unit: '百万円' - }, - land: { - name_jp: '土地', - unit: '百万円' - }, - construction_in_progress: { - name_jp: '建設仮勘定', - unit: '百万円' - }, - intangible_assets: { - name_jp: '無形固定資産', - unit: '百万円' - }, - good_will: { - name_jp: 'のれん', - unit: '百万円' - }, - investments_and_other_assets: { - name_jp: '投資その他の資産', - unit: '百万円' - }, - investment_securities: { - name_jp: '投資有価証券', - unit: '百万円' - }, - non_current_dta: { - name_jp: '繰延税金資産(固定)', - unit: '百万円' - }, - non_current_allowance_doubtful_accounts: { - name_jp: '貸倒引当金', - unit: '百万円' - }, - lease_and_guarantee_deposits: { - name_jp: '敷金および保証金', - unit: '百万円' - }, - liabilities: { - name_jp: '負債', - unit: '百万円' - }, - debt: { - name_jp: '有利子負債', - unit: '百万円' - }, - current_liabilities: { - name_jp: '流動負債', - unit: '百万円' - }, - trade_payables: { - name_jp: '仕入債務', - unit: '百万円' - }, - notes_accounts_payable: { - name_jp: '支払手形および買掛金', - unit: '百万円' - }, - accounts_payable: { - name_jp: '買掛金', - unit: '百万円' - }, - notes_payable: { - name_jp: '支払手形', - unit: '百万円' - }, - short_term_bonds_payable: { - name_jp: '短期社債', - unit: '百万円' - }, - short_term_loans_payable: { - name_jp: '短期借入金', - unit: '百万円' - }, - commercial_papers_liabilities: { - name_jp: 'コマーシャルペーパー', - unit: '百万円' - }, - current_lease_obligations: { - name_jp: 'リース債務(流動負債)', - unit: '百万円' - }, - current_portion_of_long_term_loans: { - name_jp: '1年以内返済の長期借入金', - unit: '百万円' - }, - current_portion_of_bonds: { - name_jp: '1年以内返済の社債', - unit: '百万円' - }, - current_portion_of_convertible_bonds: { - name_jp: '1年以内返済の転換社債', - unit: '百万円' - }, - current_portion_of_bonds_with_subscription_rights: { - name_jp: '1年以内返済の新株予約権付社債', - unit: '百万円' - }, - advances_received: { - name_jp: '前受金', - unit: '百万円' - }, - corporate_tax_payable: { - name_jp: '未払法人税等', - unit: '百万円' - }, - non_current_liabilities: { - name_jp: '固定負債', - unit: '百万円' - }, - bonds_payable: { - name_jp: '社債', - unit: '百万円' - }, - convertible_bonds: { - name_jp: '転換社債', - unit: '百万円' - }, - convertible_bond_type_bonds_with_subscription_rights: { - name_jp: '新株予約権付転換社債', - unit: '百万円' - }, - non_current_bonds_with_subscription_right: { - name_jp: '新株予約権付社債', - unit: '百万円' - }, - long_term_loans_payable: { - name_jp: '長期借入金', - unit: '百万円' - }, - non_current_lease_obligations: { - name_jp: 'リース債務(固定負債)', - unit: '百万円' - }, - non_current_dtl: { - name_jp: '繰延税金負債(固定)', - unit: '百万円' - }, - net_assets: { - name_jp: '純資産', - unit: '百万円' - }, - shareholders_equity: { - name_jp: '株主資本', - unit: '百万円' - }, - capital_stock: { - name_jp: '資本金', - unit: '百万円' - }, - additional_capital_stock: { - name_jp: '資本剰余金', - unit: '百万円' - }, - retained_earnings: { - name_jp: '利益剰余金', - unit: '百万円' - }, - treasury_stock: { - name_jp: '自己株式', - unit: '百万円' - }, - valuation_and_translation_adjustments: { - name_jp: '評価換算差額等', - unit: '百万円' - }, - non_controlling_interests: { - name_jp: '非支配持分', - unit: '百万円' - }, - net_sales: { - name_jp: '売上', - unit: '百万円' - }, - cost_of_sales: { - name_jp: '売上原価', - unit: '百万円' - }, - gross_profit: { - name_jp: '売上高総利益', - unit: '百万円' - }, - gross_margin: { - name_jp: '売上高総利益率', - unit: '%' - }, - sga: { - name_jp: '販売費および一般管理費', - unit: '百万円' - }, - operating_income: { - name_jp: '営業利益', - unit: '百万円' - }, - operating_margin: { - name_jp: '営業利益率', - unit: '%' - }, - non_operating_income: { - name_jp: '営業外収益', - unit: '百万円' - }, - interest_and_dividends_income: { - name_jp: '受取利息および受取配当金', - unit: '百万円' - }, - interest_income: { - name_jp: '受取利息', - unit: '百万円' - }, - dividends_income: { - name_jp: '受取配当金', - unit: '百万円' - }, - equity_method_income: { - name_jp: '持分法による投資利益', - unit: '百万円' - }, - non_operating_expenses: { - name_jp: '営業外費用', - unit: '百万円' - }, - interest_expense: { - name_jp: '支払利息', - unit: '百万円' - }, - equity_method_loss: { - name_jp: '持分法による投資損失', - unit: '百万円' - }, - ordinary_income: { - name_jp: '経常利益', - unit: '百万円' - }, - extraordinary_income: { - name_jp: '特別利益', - unit: '百万円' - }, - gain_of_sales_non_current_assets: { - name_jp: '固定資産売却益', - unit: '百万円' - }, - gain_of_sales_investment_securities: { - name_jp: '投資有価証券売却益', - unit: '百万円' - }, - extraordinary_loss: { - name_jp: '特別損失', - unit: '百万円' - }, - loss_of_sales_non_current_assets: { - name_jp: '固定資産売却損', - unit: '百万円' - }, - loss_of_valuation_investment_securities: { - name_jp: '投資有価証券売却損', - unit: '百万円' - }, - impairment_loss: { - name_jp: '減損損失', - unit: '百万円' - }, - income_before_income_taxes: { - name_jp: '税引前当期純利益', - unit: '百万円' - }, - income_taxes: { - name_jp: '法人税等', - unit: '百万円' - }, - real_corporate_tax_rate: { - name_jp: '実質法人税率', - unit: '%' - }, - net_income: { - name_jp: '当期純利益', - unit: '百万円' - }, - non_controling_interests: { - name_jp: '非支配株主に帰属する当期純利益', - unit: '百万円' - }, - profit_loss_attributable_to_owners_of_parent: { - name_jp: '親会社株主に帰属する当期純利益', - unit: '百万円' - }, - net_profit_margin: { - name_jp: '当期純利益率', - unit: '%' - }, - operating_cash_flow: { - name_jp: '営業キャッシュフロー', - unit: '百万円' - }, - income_before_taxes: { - name_jp: '税金等調整前当期純利益', - unit: '百万円' - }, - depreciation: { - name_jp: '減価償却費', - unit: '百万円' - }, - amortization: { - name_jp: 'のれん償却費', - unit: '百万円' - }, - decrease_trade_receivables_op_cf: { - name_jp: '売上債権の増減額', - unit: '百万円' - }, - decrease_inventories_op_cf: { - name_jp: '棚卸資産の増減額', - unit: '百万円' - }, - increase_trade_payables_op_cf: { - name_jp: '仕入債務の増減額', - unit: '百万円' - }, - investment_cash_flow: { - name_jp: '投資キャッシュフロー', - unit: '百万円' - }, - purchase_of_property: { - name_jp: '有形固定資産の取得による支出', - unit: '百万円' - }, - sale_of_property: { - name_jp: '有形固定資産の売却による収入', - unit: '百万円' - }, - purchase_of_intangible_assets: { - name_jp: '無形固定資産の取得による支出', - unit: '百万円' - }, - sale_of_intangible_assets: { - name_jp: '無形固定資産の売却による収入', - unit: '百万円' - }, - purchase_of_non_current_assets: { - name_jp: '固定資産の取得による支出', - unit: '百万円' - }, - sale_of_non_current_assets: { - name_jp: '固定資産の売却による収入', - unit: '百万円' - }, - purchase_of_securities: { - name_jp: '有価証券の取得による支出', - unit: '百万円' - }, - sale_of_securities: { - name_jp: '有価証券の売却・償還による収入', - unit: '百万円' - }, - purchase_of_investment_securities: { - name_jp: '投資有価証券の取得による支出', - unit: '百万円' - }, - sale_of_investment_securities: { - name_jp: '投資有価証券の売却・償還による収入', - unit: '百万円' - }, - lending: { - name_jp: '貸付けによる支出', - unit: '百万円' - }, - return_of_lending: { - name_jp: '貸付金の回収による収入', - unit: '百万円' - }, - financial_cash_flow: { - name_jp: '財務キャッシュフロー', - unit: '百万円' - }, - net_short_term_debt: { - name_jp: '短期借入金の純増減額', - unit: '百万円' - }, - long_term_debt_issuance: { - name_jp: '長期借入れによる収入', - unit: '百万円' - }, - long_term_debt_repayment: { - name_jp: '長期借入金の返済による支出', - unit: '百万円' - }, - bonds_issuance: { - name_jp: '社債の発行による収入', - unit: '百万円' - }, - bonds_repayment: { - name_jp: '社債の償還による支出', - unit: '百万円' - }, - share_repurchase: { - name_jp: '自己株式の取得による支出', - unit: '百万円' - }, - share_sales: { - name_jp: '自己株式の売却による収入', - unit: '百万円' - }, - dividend_payment: { - name_jp: '配当金の支払額', - unit: '百万円' - }, - cash_translation_difference: { - name_jp: '現金及び現金同等物に係る換算差額', - unit: '百万円' - }, - free_cash_flow: { - name_jp: 'フリーキャッシュフロー', - unit: '百万円' - }, - ex_net_sales: { - name_jp: '売上(会社予想)', - unit: '百万円' - }, - ex_operating_income: { - name_jp: '営業利益(会社予想)', - unit: '百万円' - }, - ex_ordinary_income: { - name_jp: '経常利益(会社予想)', - unit: '百万円' - }, - ex_net_income: { - name_jp: '当期純利益(会社予想)', - unit: '百万円' - }, - dividend: { - name_jp: '配当金(実績)', - unit: '円' - }, - eps_actual: { - name_jp: 'EPS(実績)', - unit: '円' - }, - bps: { - name_jp: 'BPS', - unit: '円' - }, - ebitda_forecast: { - name_jp: 'EBITDA(会社予想)', - unit: '百万円' - }, - ebitda_actual: { - name_jp: 'EBITDA(実績)', - unit: '百万円' - }, - roe: { - name_jp: 'ROE', - unit: '%' - }, - real_roe: { - name_jp: '実質ROE', - unit: '%' - }, - total_asset_turnover: { - name_jp: '総資産回転率', - unit: '倍' - }, - financial_leverage: { - name_jp: '財務レバレッジ', - unit: '倍' - }, - roa: { - name_jp: 'ROA', - unit: '%' - }, - roic: { - name_jp: 'ROIC', - unit: '%' - }, - doe: { - name_jp: '自己資本配当率', - unit: '%' - }, - net_sales_operating_cash_flow_ratio: { - name_jp: '営業キャッシュフロー/売上比率', - unit: '%' - }, - sga_ratio: { - name_jp: '販管費/売上率', - unit: '%' - }, - depreciation_gross_profit_ratio: { - name_jp: '減価償却費/粗利比率', - unit: '%' - }, - r_and_d_ratio: { - name_jp: '研究開発費/売上比率', - unit: '%' - }, - interest_op_income_ratio: { - name_jp: '支払利息/営業利益比率', - unit: '%' - }, - interest_coverage_ratio: { - name_jp: 'インタレストカバレッジレシオ ', - unit: '倍' - }, - net_sales_progress: { - name_jp: '売上進捗率', - unit: '%' - }, - operating_income_progress: { - name_jp: '営業利益進捗率', - unit: '%' - }, - net_income_progress: { - name_jp: '純利益進捗率', - unit: '%' - }, - net_sales_growth_rate_forecast: { - name_jp: '売上高成長率(直近年度実績→会社予想)', - unit: '%' - }, - operating_income_growth_rate_forecast: { - name_jp: '営業利益成長率(直近年度実績→会社予想)', - unit: '%' - }, - net_income_growth_rate_forecast: { - name_jp: '純利益成長率(直近年度実績→会社予想)', - unit: '%' - }, - cash_assets_ratio: { - name_jp: '現金総資産比率', - unit: '%' - }, - cash_monthly_sales_ratio: { - name_jp: '現金売上倍率', - unit: '倍' - }, - accounts_receivable_turnover: { - name_jp: '売上債権回転期間', - unit: '日' - }, - inventory_turnover: { - name_jp: '棚卸資産回転期間', - unit: '日' - }, - trade_payable_turnover: { - name_jp: '仕入債務回転期間', - unit: '日' - }, - working_capital: { - name_jp: '運転資本', - unit: '百万円' - }, - ccc: { - name_jp: 'CCC', - unit: '日' - }, - tangible_fixed_assets_turnover: { - name_jp: '有形固定資産回転率', - unit: '%' - }, - debt_assets_ratio: { - name_jp: '有利子負債/総資産比率', - unit: '%' - }, - debt_monthly_sales_ratio: { - name_jp: '有利子負債/月商比率', - unit: 'ヶ月' - }, - operating_cash_flow_debt_ratio: { - name_jp: '有利子負債/営業キャッシュフロー倍率', - unit: '倍' - }, - net_debt: { - name_jp: '純有利子負債', - unit: '百万円' - }, - adjusted_debt_ratio: { - name_jp: '自己株式調整済負債比率', - unit: '%' - }, - de_ratio: { - name_jp: 'DE比率', - unit: '%' - }, - current_ratio: { - name_jp: '流動比率', - unit: '%' - }, - net_debt_net_income_ratio: { - name_jp: 'ネットD純利益比率', - unit: '倍' - }, - equity: { - name_jp: '自己資本', - unit: '百万円' - }, - equity_ratio: { - name_jp: '自己資本比率', - unit: '%' - }, - accrual: { - name_jp: 'アクルーアル ', - unit: '百万円' - }, - employee_num: { - name_jp: '従業員数', - unit: '人' - }, - net_sales_per_employee: { - name_jp: '従業員一人あたり売上', - unit: '百万円' - }, - operating_income_per_employee: { - name_jp: '従業員一人あたり営業利益', - unit: '百万円' - }, - fiscal_year: { - name_jp: '会計年度', - unit: 'なし' - }, - fiscal_quarter: { - name_jp: '四半期', - unit: 'なし' - } -} From e09cca6f30de19b8fc254e2abd3439d3a4beb2ce Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Fri, 19 Nov 2021 15:53:17 +0900 Subject: [PATCH 17/20] Add new BcodeResult --- .../v2/bcode-indicator.test.ts | 2 +- src/custom-functions/v2/bcode-indicator.ts | 2 +- src/custom-functions/v2/bcode-quarter.test.ts | 2 +- src/custom-functions/v2/bcode-quarter.ts | 2 +- .../{ => v2}/bcode-result.test.ts | 2 +- src/custom-functions/{ => v2}/bcode-result.ts | 0 src/custom-functions/v2/bcode.ts | 2 +- src/custom-functions/v3/bcode-daily.test.ts | 4 +- src/custom-functions/v3/bcode-daily.ts | 4 +- src/custom-functions/v3/bcode-quarter.test.ts | 4 +- src/custom-functions/v3/bcode-quarter.ts | 4 +- src/custom-functions/v3/bcode-result.test.ts | 52 +++++++ src/custom-functions/v3/bcode-result.ts | 42 ++++++ src/custom-functions/v3/bcode.ts | 2 +- src/custom-functions/v3/unit-conversion.js | 138 ++++++++++++++++++ 15 files changed, 247 insertions(+), 15 deletions(-) rename src/custom-functions/{ => v2}/bcode-result.test.ts (95%) rename src/custom-functions/{ => v2}/bcode-result.ts (100%) create mode 100644 src/custom-functions/v3/bcode-result.test.ts create mode 100644 src/custom-functions/v3/bcode-result.ts create mode 100644 src/custom-functions/v3/unit-conversion.js diff --git a/src/custom-functions/v2/bcode-indicator.test.ts b/src/custom-functions/v2/bcode-indicator.test.ts index 9af9c3c..d077b1c 100644 --- a/src/custom-functions/v2/bcode-indicator.test.ts +++ b/src/custom-functions/v2/bcode-indicator.test.ts @@ -1,6 +1,6 @@ import { CachingBuffettCodeApiClientV2 } from '~/api/v2/caching-client' -import { BcodeResult } from '~/custom-functions/bcode-result' import { bcodeIndicator } from '~/custom-functions/v2/bcode-indicator' +import { BcodeResult } from '~/custom-functions/v2/bcode-result' import { IndicatorCache } from '~/services/indicator-cache' import { IndicatorPropertyCache } from '~/services/indicator-property-cache' diff --git a/src/custom-functions/v2/bcode-indicator.ts b/src/custom-functions/v2/bcode-indicator.ts index 9e3dfb3..7d4bc53 100644 --- a/src/custom-functions/v2/bcode-indicator.ts +++ b/src/custom-functions/v2/bcode-indicator.ts @@ -1,7 +1,7 @@ import { CachingBuffettCodeApiClientV2 } from '~/api/v2/caching-client' import { CachingIndicatorProperty } from '~/api/v2/caching-indicator-property' -import { BcodeResult } from '~/custom-functions/bcode-result' import { ApiResponseError } from '~/custom-functions/error' +import { BcodeResult } from '~/custom-functions/v2/bcode-result' export function bcodeIndicator( client: CachingBuffettCodeApiClientV2, diff --git a/src/custom-functions/v2/bcode-quarter.test.ts b/src/custom-functions/v2/bcode-quarter.test.ts index cb2cae8..592109f 100644 --- a/src/custom-functions/v2/bcode-quarter.test.ts +++ b/src/custom-functions/v2/bcode-quarter.test.ts @@ -1,7 +1,7 @@ import { QuarterCache } from '~/__mocks__/services/quarter-cache' import { CachingBuffettCodeApiClientV2 } from '~/api/v2/caching-client' -import { BcodeResult } from '~/custom-functions/bcode-result' import { bcodeQuarter } from '~/custom-functions/v2/bcode-quarter' +import { BcodeResult } from '~/custom-functions/v2/bcode-result' import { YearQuarter } from '~/fiscal-periods/year-quarter' import { QuarterPropertyCache } from '~/services/quarter-property-cache' diff --git a/src/custom-functions/v2/bcode-quarter.ts b/src/custom-functions/v2/bcode-quarter.ts index 580ebfc..fe37a19 100644 --- a/src/custom-functions/v2/bcode-quarter.ts +++ b/src/custom-functions/v2/bcode-quarter.ts @@ -1,12 +1,12 @@ import { CompanyService } from '~/api/company-service' import { CachingBuffettCodeApiClientV2 } from '~/api/v2/caching-client' import { CachingQuarterProperty } from '~/api/v2/caching-quarter-property' -import { BcodeResult } from '~/custom-functions/bcode-result' import { ApiResponseError, OndemandApiNotEnabledError, UnsupportedTickerError } from '~/custom-functions/error' +import { BcodeResult } from '~/custom-functions/v2/bcode-result' import { YearQuarterParam } from '~/fiscal-periods/year-quarter-param' export function bcodeQuarter( diff --git a/src/custom-functions/bcode-result.test.ts b/src/custom-functions/v2/bcode-result.test.ts similarity index 95% rename from src/custom-functions/bcode-result.test.ts rename to src/custom-functions/v2/bcode-result.test.ts index c40a584..f71cd1d 100644 --- a/src/custom-functions/bcode-result.test.ts +++ b/src/custom-functions/v2/bcode-result.test.ts @@ -1,4 +1,4 @@ -import { BcodeResult } from '~/custom-functions/bcode-result' +import { BcodeResult } from '~/custom-functions/v2/bcode-result' test('format', () => { expect(new BcodeResult(1234.5678, '日').format(false, false)).toBe(1234.6) diff --git a/src/custom-functions/bcode-result.ts b/src/custom-functions/v2/bcode-result.ts similarity index 100% rename from src/custom-functions/bcode-result.ts rename to src/custom-functions/v2/bcode-result.ts diff --git a/src/custom-functions/v2/bcode.ts b/src/custom-functions/v2/bcode.ts index b91fd9a..ccc2f40 100644 --- a/src/custom-functions/v2/bcode.ts +++ b/src/custom-functions/v2/bcode.ts @@ -2,7 +2,6 @@ import { HttpError } from '~/api/http-error' import { CachingBuffettCodeApiClientV2 } from '~/api/v2/caching-client' import { CachingIndicatorProperty } from '~/api/v2/caching-indicator-property' import { QuarterProperty } from '~/api/v2/quarter-property' -import { BcodeResult } from '~/custom-functions/bcode-result' import { ApiResponseError, OndemandApiNotEnabledError, @@ -10,6 +9,7 @@ import { } from '~/custom-functions/error' import { bcodeIndicator } from '~/custom-functions/v2/bcode-indicator' import { bcodeQuarter } from '~/custom-functions/v2/bcode-quarter' +import { BcodeResult } from '~/custom-functions/v2/bcode-result' import { InvalidLYLQError, InvalidYearError, diff --git a/src/custom-functions/v3/bcode-daily.test.ts b/src/custom-functions/v3/bcode-daily.test.ts index 5f2934d..35ddb4f 100644 --- a/src/custom-functions/v3/bcode-daily.test.ts +++ b/src/custom-functions/v3/bcode-daily.test.ts @@ -1,6 +1,6 @@ import { CachingBuffettCodeApiClientV3 } from '~/api/v3/caching-client' -import { BcodeResult } from '~/custom-functions/bcode-result' import { bcodeDaily } from '~/custom-functions/v3/bcode-daily' +import { BcodeResult } from '~/custom-functions/v3/bcode-result' import { DateParam } from '~/fiscal-periods/date-param' import { DailyCache } from '~/services/daily-cache' @@ -24,6 +24,6 @@ test('bcodeDaily', () => { const client = new CachingBuffettCodeApiClientV3('token') const result = bcodeDaily(client, ticker, date, propertyName, false) - expect(result).toEqual(new BcodeResult(550294097166, '百万円')) + expect(result).toEqual(new BcodeResult(propertyName, 550294097166, '円')) expect(DailyCache.get(ticker, date)).not.toBeNull() }) diff --git a/src/custom-functions/v3/bcode-daily.ts b/src/custom-functions/v3/bcode-daily.ts index 47422c5..e42b502 100644 --- a/src/custom-functions/v3/bcode-daily.ts +++ b/src/custom-functions/v3/bcode-daily.ts @@ -1,12 +1,12 @@ import { CompanyService } from '~/api/company-service' import { BuffettCodeApiClientV3 } from '~/api/v3/client' -import { BcodeResult } from '~/custom-functions/bcode-result' import { ApiResponseError, OndemandApiNotEnabledError, PropertyNotFoundError, UnsupportedTickerError } from '~/custom-functions/error' +import { BcodeResult } from '~/custom-functions/v3/bcode-result' import { Daily } from '~/entities/v3/daily' import { DateParam } from '~/fiscal-periods/date-param' @@ -47,5 +47,5 @@ export function bcodeDaily( const value = daily.data[propertyName] const unit = property['unit'] - return new BcodeResult(value, unit) + return new BcodeResult(propertyName, value, unit) } diff --git a/src/custom-functions/v3/bcode-quarter.test.ts b/src/custom-functions/v3/bcode-quarter.test.ts index 6b61025..b01d2d4 100644 --- a/src/custom-functions/v3/bcode-quarter.test.ts +++ b/src/custom-functions/v3/bcode-quarter.test.ts @@ -1,6 +1,6 @@ import { CachingBuffettCodeApiClientV3 } from '~/api/v3/caching-client' -import { BcodeResult } from '~/custom-functions/bcode-result' import { bcodeQuarter } from '~/custom-functions/v3/bcode-quarter' +import { BcodeResult } from '~/custom-functions/v3/bcode-result' import { YearQuarter } from '~/fiscal-periods/year-quarter' import { YearQuarterParam } from '~/fiscal-periods/year-quarter-param' import { QuarterCache } from '~/services/quarter-cache' @@ -31,6 +31,6 @@ test('bcodeQuarter', () => { false ) - expect(result).toEqual(new BcodeResult(12513000000, '円')) + expect(result).toEqual(new BcodeResult(propertyName, 12513000000, '円')) expect(QuarterCache.get(ticker, period)).not.toBeNull() }) diff --git a/src/custom-functions/v3/bcode-quarter.ts b/src/custom-functions/v3/bcode-quarter.ts index 98a74b8..eba4eb8 100644 --- a/src/custom-functions/v3/bcode-quarter.ts +++ b/src/custom-functions/v3/bcode-quarter.ts @@ -1,12 +1,12 @@ import { CompanyService } from '~/api/company-service' import { BuffettCodeApiClientV3 } from '~/api/v3/client' -import { BcodeResult } from '~/custom-functions/bcode-result' import { ApiResponseError, OndemandApiNotEnabledError, PropertyNotFoundError, UnsupportedTickerError } from '~/custom-functions/error' +import { BcodeResult } from '~/custom-functions/v3/bcode-result' import { Quarter } from '~/entities/v3/quarter' import { YearQuarterParam } from '~/fiscal-periods/year-quarter-param' @@ -47,5 +47,5 @@ export function bcodeQuarter( const value = quarter.data[propertyName] const unit = property['unit'] - return new BcodeResult(value, unit) + return new BcodeResult(propertyName, value, unit) } diff --git a/src/custom-functions/v3/bcode-result.test.ts b/src/custom-functions/v3/bcode-result.test.ts new file mode 100644 index 0000000..08bb2ac --- /dev/null +++ b/src/custom-functions/v3/bcode-result.test.ts @@ -0,0 +1,52 @@ +import { BcodeResult } from '~/custom-functions/v3/bcode-result' + +test('format (days)', () => { + expect(new BcodeResult('ccc', 1234.5678, '日').format(false, false)).toBe( + 1234.6 + ) + expect(new BcodeResult('ccc', 1234.5678, '日').format(true, false)).toBe( + 1234.5678 + ) + expect(new BcodeResult('ccc', 1234.5678, '日').format(false, true)).toBe( + '1,234.6日' + ) + expect(new BcodeResult('ccc', 1234.5678, '日').format(true, true)).toBe( + '1,234.5678日' + ) +}) + +test('format (yen)', () => { + expect(new BcodeResult('', 430602000000, '円').format(false, false)).toBe( + 430602000000 + ) + expect(new BcodeResult('', 430602000000, '円').format(true, false)).toBe( + 430602000000 + ) + expect(new BcodeResult('', 430602000000, '円').format(false, true)).toBe( + '430,602,000,000円' + ) + expect(new BcodeResult('', 430602000000, '円').format(true, true)).toBe( + '430,602,000,000円' + ) +}) + +test('format (million-yen)', () => { + // using unit-mapping.json + expect( + new BcodeResult('market_capital', 550294097166, '円').format(false, false) + ).toBe(550294) + expect( + new BcodeResult('market_capital', 550294097166, '円').format(true, false) + ).toBe(550294097166) + expect( + new BcodeResult('market_capital', 550294097166, '円').format(false, true) + ).toBe('550,294百万円') + expect( + new BcodeResult('market_capital', 550294097166, '円').format(true, true) + ).toBe('550,294,097,166円') +}) + +test('format (null)', () => { + expect(new BcodeResult('net_sales', null, '円').format(true, false)).toBe('') + expect(new BcodeResult('net_sales', null, '円').format(true, true)).toBe('') +}) diff --git a/src/custom-functions/v3/bcode-result.ts b/src/custom-functions/v3/bcode-result.ts new file mode 100644 index 0000000..a11a892 --- /dev/null +++ b/src/custom-functions/v3/bcode-result.ts @@ -0,0 +1,42 @@ +import * as unitConversion from '~/custom-functions/v3/unit-conversion.js' +import { Formatter } from '~/services/formatter' + +export class BcodeResult { + constructor( + public name: string, + public value: number | string | null, + public unit: string + ) {} + + private isMillionYenProperty(): boolean { + return unitConversion.millionYen.includes(this.name) + } + + public format(isRawValue: boolean, isWithUnits: boolean): number | string { + let value = this.value + let unit = this.unit + + if (value === null) { + return '' + } + + if (!isRawValue && typeof value === 'number') { + if (unit === '円' && this.isMillionYenProperty()) { + value = Formatter.round(value / 1000000, 0) // 整数に丸める + unit = '百万円' + } else { + value = Formatter.round(value, 1) // 小数点第1位までに丸める + } + } + + if (isWithUnits) { + if (typeof value == 'number') { + return Formatter.commaStyled(value) + unit + } else { + return value + unit + } + } else { + return value + } + } +} diff --git a/src/custom-functions/v3/bcode.ts b/src/custom-functions/v3/bcode.ts index d7dbb4c..1c0b9b5 100644 --- a/src/custom-functions/v3/bcode.ts +++ b/src/custom-functions/v3/bcode.ts @@ -1,6 +1,5 @@ import { HttpError } from '~/api/http-error' import { CachingBuffettCodeApiClientV3 } from '~/api/v3/caching-client' -import { BcodeResult } from '~/custom-functions/bcode-result' import { ApiResponseError, OndemandApiNotEnabledError, @@ -8,6 +7,7 @@ import { } from '~/custom-functions/error' import { bcodeDaily } from '~/custom-functions/v3/bcode-daily' import { bcodeQuarter } from '~/custom-functions/v3/bcode-quarter' +import { BcodeResult } from '~/custom-functions/v3/bcode-result' import { DateParam } from '~/fiscal-periods/date-param' import { InvalidLYLQError, diff --git a/src/custom-functions/v3/unit-conversion.js b/src/custom-functions/v3/unit-conversion.js new file mode 100644 index 0000000..2ecb20c --- /dev/null +++ b/src/custom-functions/v3/unit-conversion.js @@ -0,0 +1,138 @@ +module.exports = { + millionYen: [ + 'accounts_payable', + 'accounts_receivable', + 'accrual', + 'additional_capital_stock', + 'advances_received', + 'amortization', + 'assets', + 'bonds_issuance', + 'bonds_payable', + 'bonds_repayment', + 'buildings', + 'capital_stock', + 'cash_and_deposits', + 'cash_translation_difference', + 'commercial_papers_liabilities', + 'construction_in_progress', + 'convertible_bond_type_bonds_with_subscription_rights', + 'convertible_bonds', + 'corporate_tax_payable', + 'cost_of_sales', + 'current_allowance_doubtful_accounts', + 'current_assets', + 'current_dta', + 'current_lease_obligations', + 'current_liabilities', + 'current_portion_of_bonds', + 'current_portion_of_bonds_with_subscription_rights', + 'current_portion_of_convertible_bonds', + 'current_portion_of_long_term_loans', + 'current_securities', + 'debt', + 'decrease_inventories_op_cf', + 'decrease_trade_receivables_op_cf', + 'depreciation', + 'dividend_payment', + 'dividends_income', + 'ebitda_actual', + 'ebitda_forecast', + 'enterprise_value', + 'equity', + 'equity_method_income', + 'equity_method_loss', + 'ex_net_income', + 'ex_net_sales', + 'ex_operating_income', + 'ex_ordinary_income', + 'extraordinary_income', + 'extraordinary_loss', + 'financial_cash_flow', + 'free_cash_flow', + 'gain_of_sales_investment_securities', + 'gain_of_sales_non_current_assets', + 'good_will', + 'gross_profit', + 'impairment_loss', + 'income_before_income_taxes', + 'income_before_taxes', + 'income_taxes', + 'increase_in_properties', + 'increase_trade_payables_op_cf', + 'intangible_assets', + 'interest_and_dividends_income', + 'interest_expense', + 'interest_income', + 'inventories', + 'investment_cash_flow', + 'investment_securities', + 'investments_and_other_assets', + 'land', + 'lease_and_guarantee_deposits', + 'lending', + 'liabilities', + 'long_term_debt_issuance', + 'long_term_debt_repayment', + 'long_term_loans_payable', + 'loss_of_sales_non_current_assets', + 'loss_of_valuation_investment_securities', + 'machineries', + 'market_capital', + 'merchandise', + 'net_assets', + 'net_debt', + 'net_income', + 'net_sales', + 'net_sales_per_employee', + 'net_short_term_debt', + 'non_controling_interests', + 'non_controlling_interests', + 'non_current_allowance_doubtful_accounts', + 'non_current_assets', + 'non_current_bonds_with_subscription_right', + 'non_current_dta', + 'non_current_dtl', + 'non_current_lease_obligations', + 'non_current_liabilities', + 'non_operating_expenses', + 'non_operating_income', + 'notes_accounts_payable', + 'notes_accounts_receivable', + 'notes_payable', + 'notes_receivable', + 'operating_cash_flow', + 'operating_income', + 'operating_income_per_employee', + 'ordinary_income', + 'prepaid_expenses', + 'profit_loss_attributable_to_owners_of_parent', + 'purchase_of_intangible_assets', + 'purchase_of_investment_securities', + 'purchase_of_non_current_assets', + 'purchase_of_property', + 'purchase_of_securities', + 'r_and_d_expenses', + 'raw_materials_and_supplies', + 'retained_earnings', + 'return_of_lending', + 'sale_of_intangible_assets', + 'sale_of_investment_securities', + 'sale_of_non_current_assets', + 'sale_of_property', + 'sale_of_securities', + 'sga', + 'share_repurchase', + 'share_sales', + 'shareholders_equity', + 'short_term_bonds_payable', + 'short_term_loans_payable', + 'tangible_fixed_assets', + 'trade_payables', + 'trade_receivables', + 'treasury_stock', + 'valuation_and_translation_adjustments', + 'work_in_process', + 'working_capital' + ] +} From 4e31415b31031dab1d505d3064e45431b6585431 Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Fri, 19 Nov 2021 16:41:07 +0900 Subject: [PATCH 18/20] Update JSDoc --- src/main.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main.ts b/src/main.ts index 14d9203..ffa0db4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -42,30 +42,27 @@ global.exportCsv = exportCsv * @param {"net_sales"} propertyName 項目名 * @param {TRUE} isRawValue (オプション) 数値をRAWデータで表示するかどうか (デフォルト値: FALSE) * @param {TRUE} isWithUnits (オプション) 単位を末尾に付加するかどうか (デフォルト値: FALSE) + * @param {TRUE} param5 (オプション) 廃止予定のオプション * @return 指定した銘柄の財務数字または指標 * @customfunction */ global.BCODE = ( ticker, - fiscalYear, - fiscalQuarter, + period, propertyName, isRawValue = false, - isWithUnits = false + isWithUnits = false, + param5 = false ): number | string => { - return bcode( - ticker, - fiscalYear, - fiscalQuarter, - propertyName, - isRawValue, - isWithUnits - ) + return bcode(ticker, period, propertyName, isRawValue, isWithUnits, param5) } /** + * **近日廃止予定です。** + * * 指定した項目の名称を日本語で取得します。 * + * @deprecated 近日廃止予定です * @param {"net_sales"} propertyName 項目名 * @return 指定した項目の名称 * @customfunction @@ -75,8 +72,11 @@ global.BCODE_LABEL = (propertyName: string): string => { } /** + * **近日廃止予定です。** + * * 指定した項目の単位を取得します。 * + * @deprecated 近日廃止予定です * @param {"net_sales"} propertyName 項目名 * @return 指定した項目の単位 * @customfunction From 8132afefb066a12e9752b64fb3eb2b98da1134f8 Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Fri, 19 Nov 2021 16:41:17 +0900 Subject: [PATCH 19/20] Improve error handling --- src/custom-functions/bcode.ts | 4 +++- src/main.ts | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/custom-functions/bcode.ts b/src/custom-functions/bcode.ts index 79bd1be..6246977 100644 --- a/src/custom-functions/bcode.ts +++ b/src/custom-functions/bcode.ts @@ -9,7 +9,9 @@ export function isV3Call( param1: string | number, param2: string | number ): boolean { - if (typeof param1 === 'number' || typeof param2 === 'number') { + if (param1 == undefined || param2 == undefined) { + throw new Error('引数が正しくありません') + } else if (typeof param1 === 'number' || typeof param2 === 'number') { return false } else if (param1 === '' || param2 === '') { return false diff --git a/src/main.ts b/src/main.ts index ffa0db4..0db9b6c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -58,7 +58,7 @@ global.BCODE = ( } /** - * **近日廃止予定です。** + * 近日廃止予定です。 * * 指定した項目の名称を日本語で取得します。 * @@ -72,7 +72,7 @@ global.BCODE_LABEL = (propertyName: string): string => { } /** - * **近日廃止予定です。** + * 近日廃止予定です。 * * 指定した項目の単位を取得します。 * From 353db054fa61a21f5f30e4c37b983372a8622326 Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Fri, 19 Nov 2021 16:58:23 +0900 Subject: [PATCH 20/20] Bump version to v9 --- src/version.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.ts b/src/version.ts index c51e528..e0d226e 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const version = 'v7' +export const version = 'v9'