Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CIK support #85

Merged
merged 1 commit into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Currently supported standards:
[ISIN](https://en.wikipedia.org/wiki/International_Securities_Identification_Number),
[CUSIP](https://en.wikipedia.org/wiki/CUSIP),
[SEDOL](https://en.wikipedia.org/wiki/SEDOL),
[FIGI](https://en.wikipedia.org/wiki/Financial_Instrument_Global_Identifier).
[FIGI](https://en.wikipedia.org/wiki/Financial_Instrument_Global_Identifier), [CIK](https://en.wikipedia.org/wiki/Central_Index_Key).

Work in progress:
[IBAN](https://en.wikipedia.org/wiki/International_Bank_Account_Number).
Expand Down Expand Up @@ -179,6 +179,27 @@ figi.restore! # => 'BBG000DMBXR2'
figi.calculate_check_digit # => 2
```

### SecId::CIK full example

```ruby
# class level
SecId::CIK.valid?('0001094517') # => true
SecId::CIK.valid_format?('0001094517') # => true
SecId::CIK.restore!('1094517') # => '0001094517'
SecId::CIK.check_digit('0001094517') # raises NotImplementedError

# instance level
cik = SecId::CIK.new('0001094517')
cik.full_number # => '0001094517'
cik.padding # => '000'
cik.identifier # => '1094517'
cik.valid? # => true
cik.valid_format? # => true
cik.restore! # => '0001094517'
cik.calculate_check_digit # raises NotImplementedError
cik.check_digit # => nil
```

## Development

After checking out the repo, run `bin/setup` to install dependencies.
Expand Down
1 change: 1 addition & 0 deletions lib/sec_id.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
require 'sec_id/cusip'
require 'sec_id/sedol'
require 'sec_id/figi'
require 'sec_id/cik'

module SecId
Error = Class.new(StandardError)
Expand Down
35 changes: 35 additions & 0 deletions lib/sec_id/cik.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

module SecId
# https://en.wikipedia.org/wiki/Central_Index_Key
class CIK < Base
ID_REGEX = /\A
(?=\d{1,10}\z)(?<padding>0*)(?<identifier>[1-9]\d{0,9})
\z/x

attr_reader :padding

def initialize(cik)
cik_parts = parse cik
@padding = cik_parts[:padding]
@identifier = cik_parts[:identifier]
end

def valid?
valid_format?
end

def valid_format?
!identifier.nil?
end

def restore!
if valid_format?
@padding = '0' * (10 - @identifier.length)
return(@full_number = @identifier.rjust(10, '0'))
end

raise InvalidFormatError, "CIK '#{full_number}' is invalid and cannot be restored!"
end
end
end
129 changes: 129 additions & 0 deletions spec/sec_id/cik_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# frozen_string_literal: true

RSpec.describe SecId::CIK do
let(:cik) { described_class.new(cik_number) }

context 'when CIK is valid' do
let(:cik_number) { '0001521365' }

it 'parses CIK correctly' do
expect(cik.padding).to eq('000')
expect(cik.identifier).to eq('1521365')
expect(cik.check_digit).to be_nil
end

describe '#valid?' do
it 'returns true' do
expect(cik.valid?).to be(true)
end
end

describe '#restore!' do
it 'returns full CIK number' do
expect(cik.restore!).to eq(cik_number)
expect(cik.full_number).to eq(cik_number)
end
end

describe '#calculate_check_digit' do
it 'raises an error' do
expect { cik.calculate_check_digit }.to raise_error(NotImplementedError)
end
end
end

context 'when CIK number is missing leading zeros' do
let(:cik_number) { '10624' }

it 'parses CIK number correctly' do
expect(cik.identifier).to eq(cik_number)
expect(cik.check_digit).to be_nil
end

describe '#valid?' do
it 'returns true' do
expect(cik.valid?).to be(true)
end
end

describe '#restore!' do
it 'returns full CIK number and sets padding' do
expect(cik.restore!).to eq('0000010624')
expect(cik.full_number).to eq('0000010624')
expect(cik.padding).to eq('00000')
end
end

describe '#calculate_check_digit' do
it 'raises an error' do
expect { cik.calculate_check_digit }.to raise_error(NotImplementedError)
end
end
end

describe '.valid?' do
context 'when CIK is malformed' do
it 'returns false' do
expect(described_class.valid?('X9')).to be(false)
expect(described_class.valid?('0000000000')).to be(false)
expect(described_class.valid?('01234567890')).to be(false)
end
end

context 'when CIK is valid' do
it 'returns true' do
%w[0000000003 0000089562 0000010624 0002035979].each do |cik_number|
expect(described_class.valid?(cik_number)).to be(true)
end
end
end
end

describe '.restore!' do
context 'when CIK is malformed' do
it 'raises an error' do
expect { described_class.restore!('X9') }.to raise_error(SecId::InvalidFormatError)
expect { described_class.restore!('0000000000') }.to raise_error(SecId::InvalidFormatError)
expect { described_class.restore!('09876543210') }.to raise_error(SecId::InvalidFormatError)
end
end

context 'when CIK is valid' do
it 'restores check-digit and returns full CIK number' do
expect(described_class.restore!('3')).to eq('0000000003')
expect(described_class.restore!('0000000003')).to eq('0000000003')
expect(described_class.restore!('1072424')).to eq('0001072424')
expect(described_class.restore!('001072424')).to eq('0001072424')
expect(described_class.restore!('0001072424')).to eq('0001072424')
end
end
end

describe '.valid_format?' do
context 'when CIK is malformed' do
it 'returns false' do
expect(described_class.valid_format?('X9')).to be(false)
expect(described_class.valid_format?('0000000000')).to be(false)
expect(described_class.valid_format?('01234567890')).to be(false)
end
end

context 'when CIK is valid or missing leading zeros' do
it 'returns true' do
expect(described_class.valid_format?('3')).to be(true)
expect(described_class.valid_format?('0000000003')).to be(true)
expect(described_class.valid_format?('1072424')).to be(true)
expect(described_class.valid_format?('001072424')).to be(true)
expect(described_class.valid_format?('0001072424')).to be(true)
end
end
end

describe '.check_digit' do
it 'raises an error' do
expect { described_class.check_digit('0000320193') }.to raise_error(NotImplementedError)
expect { described_class.check_digit('320193') }.to raise_error(NotImplementedError)
expect { described_class.check_digit('0') }.to raise_error(NotImplementedError)
end
end
end
Loading