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

updated ssn and birth #147

Merged
merged 1 commit into from
Apr 12, 2023
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,6 @@ test/fuzz-reader/corpus/*.tar.gz
go-licenser*.tar.gz
go-licenser

openapi-generator*.jar
openapi-generator*.jar

/vendor/
6 changes: 0 additions & 6 deletions api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -652,8 +652,6 @@ components:
- dateAccountInformation
- surname
- firstName
- socialSecurityNumber
- dateBirth
- ecoaCode
- firstLineAddress
- state
Expand Down Expand Up @@ -693,8 +691,6 @@ components:
- segmentIdentifier
- surname
- firstName
- socialSecurityNumber
- dateBirth
- ecoaCode
J2Segment:
properties:
Expand Down Expand Up @@ -760,8 +756,6 @@ components:
- segmentIdentifier
- surname
- firstName
- socialSecurityNumber
- dateBirth
- ecoaCode
- firstLineAddress
- city
Expand Down
22 changes: 11 additions & 11 deletions pkg/file/file_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,8 @@ func (f *fileInstance) String(isNewLine bool) string {
}

// Bytes return raw byte array
func (r *fileInstance) Bytes() []byte {
return []byte(r.String(false))
func (f *fileInstance) Bytes() []byte {
return []byte(f.String(false))
}

// UnmarshalJSON parses a JSON blob
Expand Down Expand Up @@ -437,29 +437,29 @@ func (f *fileInstance) generatorTrailer() (*lib.TrailerInformation, error) {
trailer.TotalBaseRecords = len(f.Bases)
trailer.BlockCount = len(f.Bases) + 2
for _, base := range f.Bases {
base, ok := base.(*lib.BaseSegment)
if !ok && base.Validate() != nil {
return nil, utils.NewErrInvalidSegment(base.Name())
baseSegment, ok := base.(*lib.BaseSegment)
if !ok && baseSegment.Validate() != nil {
return nil, utils.NewErrInvalidSegment(baseSegment.Name())
}

if isValidSocialSecurityNumber(base.SocialSecurityNumber) {
if isValidSocialSecurityNumber(baseSegment.SocialSecurityNumber) {
trailer.TotalSocialNumbersAllSegments++
trailer.TotalSocialNumbersBaseSegments++
}

if !base.DateBirth.IsZero() {
if !baseSegment.DateBirth.IsZero() {
trailer.TotalDatesBirthAllSegments++
trailer.TotalDatesBirthBaseSegments++
}

if base.ECOACode == lib.ECOACodeZ {
if baseSegment.ECOACode == lib.ECOACodeZ {
trailer.TotalECOACodeZ++
}
if base.TelephoneNumber > 0 {
if baseSegment.TelephoneNumber > 0 {
trailer.TotalTelephoneNumbersAllSegments++
}
f.statisticAccountStatus(base.AccountStatus, trailer)
f.statisticBase(base, trailer)
f.statisticAccountStatus(baseSegment.AccountStatus, trailer)
f.statisticBase(baseSegment, trailer)
}

return trailer, nil
Expand Down
52 changes: 50 additions & 2 deletions pkg/lib/base_segment.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ type BaseSegment struct {
// If the Social Security Number is not reported, the Date of Birth is required to be reported.
// Do not report Individual Tax Identification Numbers (ITINs) in this field. ITINs do not prove identity outside the tax system and should not be offered or accepted as identification for non-tax purposes, per the Social Security Administration.
// Do not report Credit Profile Numbers (CPNs) in this field. The CPN should not be used for credit reporting purposes and does not replace the Social Security Number.
SocialSecurityNumber int `json:"socialSecurityNumber" validate:"required"`
SocialSecurityNumber int `json:"socialSecurityNumber,omitempty"`

// Report the full Date of Birth of the primary consumer, including the month, day and year.
// Reporting of this information is required as the Date of Birth greatly enhances accuracy in matching to the correct consumer.
Expand All @@ -393,7 +393,7 @@ type BaseSegment struct {
// Notes: If the Date of Birth is not reported, the Social Security Number is required to be reported.
// When reporting Authorized Users (ECOA Code 3), the full Date of Birth (MMDDYYYY) must be reported for all newly-added Authorized Users on all pre-existing and newly-opened accounts, even if the Social Security Number is reported.
// Do not report accounts of consumers who are too young to enter into a binding contract.
DateBirth utils.Time `json:"dateBirth" validate:"required"`
DateBirth utils.Time `json:"dateBirth,omitempty"`

// Contains the telephone number of the primary consumer (Area Code + 7 digits).
TelephoneNumber int64 `json:"telephoneNumber"`
Expand Down Expand Up @@ -805,13 +805,15 @@ func (r *BaseSegment) UnmarshalJSON(data []byte) error {
// customized field validation functions
// function name should be "Validate" + field name

// validation of identification number
func (r *BaseSegment) ValidateIdentificationNumber() error {
if validFilledString(r.IdentificationNumber) {
return utils.NewErrInvalidValueOfField("identification number", "base segment")
}
return nil
}

// validation of portfolio type
func (r *BaseSegment) ValidatePortfolioType() error {
switch r.PortfolioType {
case PortfolioTypeCredit, PortfolioTypeInstallment, PortfolioTypeMortgage, PortfolioTypeOpen, PortfolioTypeRevolving:
Expand All @@ -820,6 +822,7 @@ func (r *BaseSegment) ValidatePortfolioType() error {
return utils.NewErrInvalidValueOfField("portfolio type", "base segment")
}

// validation of terms duration
func (r *BaseSegment) ValidateTermsDuration() error {
switch r.TermsDuration {
case TermsDurationCredit, TermsDurationOpen, TermsDurationRevolving:
Expand All @@ -832,6 +835,7 @@ func (r *BaseSegment) ValidateTermsDuration() error {
return nil
}

// validation of terms frequency
func (r *BaseSegment) ValidateTermsFrequency() error {
switch r.TermsFrequency {
case TermsFrequencyDeferred, TermsFrequencyPayment, TermsFrequencyWeekly, TermsFrequencyBiweekly,
Expand All @@ -842,6 +846,7 @@ func (r *BaseSegment) ValidateTermsFrequency() error {
return utils.NewErrInvalidValueOfField("terms frequency", "base segment")
}

// validation of payment rating
func (r *BaseSegment) ValidatePaymentRating() error {
switch r.AccountStatus {
case AccountStatus05, AccountStatus13, AccountStatus65, AccountStatus88, AccountStatus89, AccountStatus94, AccountStatus95:
Expand All @@ -859,6 +864,7 @@ func (r *BaseSegment) ValidatePaymentRating() error {
return utils.NewErrInvalidValueOfField("payment rating", "base segment")
}

// validation of payment history profile
func (r *BaseSegment) ValidatePaymentHistoryProfile() error {
if len(r.PaymentHistoryProfile) != 24 {
return utils.NewErrInvalidValueOfField("payment history profile", "base segment")
Expand All @@ -877,6 +883,7 @@ func (r *BaseSegment) ValidatePaymentHistoryProfile() error {
return nil
}

// validation of interest type indicator
func (r *BaseSegment) ValidateInterestTypeIndicator() error {
switch r.InterestTypeIndicator {
case InterestIndicatorFixed, InterestIndicatorVariable, "":
Expand All @@ -885,13 +892,30 @@ func (r *BaseSegment) ValidateInterestTypeIndicator() error {
return utils.NewErrInvalidValueOfField("interest type indicator", "base segment")
}

// validation of telephone number
func (r *BaseSegment) ValidateTelephoneNumber() error {
if err := r.isPhoneNumber(r.TelephoneNumber, "base segment"); err != nil {
return err
}
return nil
}

// validation of social security number
func (r *BaseSegment) ValidateSocialSecurityNumber() error {
if r.SocialSecurityNumber == 0 && r.DateBirth.IsZero() {
return utils.NewErrInvalidValueOfField("social security number", "base segment")
}
return nil
}

// validation of date of birth
func (r *BaseSegment) ValidateDateBirth() error {
if r.SocialSecurityNumber == 0 && r.DateBirth.IsZero() {
return utils.NewErrInvalidValueOfField("date birth", "base segment")
}
return nil
}

// Name returns name of packed base segment
func (r *PackedBaseSegment) Name() string {
return PackedBaseSegmentName
Expand Down Expand Up @@ -1230,13 +1254,15 @@ func (r *PackedBaseSegment) UnmarshalJSON(data []byte) error {
// customized field validation functions
// function name should be "Validate" + field name

// validation of identification number
func (r *PackedBaseSegment) ValidateIdentificationNumber() error {
if validFilledString(r.IdentificationNumber) {
return utils.NewErrInvalidValueOfField("identification number", "packed base segment")
}
return nil
}

// validation of portfolio type
func (r *PackedBaseSegment) ValidatePortfolioType() error {
switch r.PortfolioType {
case PortfolioTypeCredit, PortfolioTypeInstallment, PortfolioTypeMortgage, PortfolioTypeOpen, PortfolioTypeRevolving:
Expand All @@ -1245,6 +1271,7 @@ func (r *PackedBaseSegment) ValidatePortfolioType() error {
return utils.NewErrInvalidValueOfField("portfolio type", "packed base segment")
}

// validation of terms duration
func (r *PackedBaseSegment) ValidateTermsDuration() error {
switch r.TermsDuration {
case TermsDurationCredit, TermsDurationOpen, TermsDurationRevolving:
Expand All @@ -1257,6 +1284,7 @@ func (r *PackedBaseSegment) ValidateTermsDuration() error {
return nil
}

// validation of terms frequency
func (r *PackedBaseSegment) ValidateTermsFrequency() error {
switch r.TermsFrequency {
case TermsFrequencyDeferred, TermsFrequencyPayment, TermsFrequencyWeekly, TermsFrequencyBiweekly,
Expand All @@ -1267,6 +1295,7 @@ func (r *PackedBaseSegment) ValidateTermsFrequency() error {
return utils.NewErrInvalidValueOfField("terms frequency", "packed base segment")
}

// validation of payment rating
func (r *PackedBaseSegment) ValidatePaymentRating() error {
switch r.AccountStatus {
case AccountStatus05, AccountStatus13, AccountStatus65, AccountStatus88, AccountStatus89, AccountStatus94, AccountStatus95:
Expand All @@ -1284,6 +1313,7 @@ func (r *PackedBaseSegment) ValidatePaymentRating() error {
return utils.NewErrInvalidValueOfField("payment rating", "packed base segment")
}

// validation of payment history profile
func (r *PackedBaseSegment) ValidatePaymentHistoryProfile() error {
if len(r.PaymentHistoryProfile) != 24 {
return utils.NewErrInvalidValueOfField("payment history profile", "packed base segment")
Expand All @@ -1302,6 +1332,7 @@ func (r *PackedBaseSegment) ValidatePaymentHistoryProfile() error {
return nil
}

// validation of interest type indicator
func (r *PackedBaseSegment) ValidateInterestTypeIndicator() error {
switch r.InterestTypeIndicator {
case InterestIndicatorFixed, InterestIndicatorVariable, "":
Expand All @@ -1310,13 +1341,30 @@ func (r *PackedBaseSegment) ValidateInterestTypeIndicator() error {
return utils.NewErrInvalidValueOfField("interest type indicator", "packed base segment")
}

// validation of telephone number
func (r *PackedBaseSegment) ValidateTelephoneNumber() error {
if err := r.isPhoneNumber(r.TelephoneNumber, "packed base segment"); err != nil {
return err
}
return nil
}

// validation of social security number
func (r *PackedBaseSegment) ValidateSocialSecurityNumber() error {
if r.SocialSecurityNumber == 0 && r.DateBirth.IsZero() {
return utils.NewErrInvalidValueOfField("social security number", "base segment")
}
return nil
}

// validation of date of birth
func (r *PackedBaseSegment) ValidateDateBirth() error {
if r.SocialSecurityNumber == 0 && r.DateBirth.IsZero() {
return utils.NewErrInvalidValueOfField("date birth", "base segment")
}
return nil
}

func readApplicableSegments(record []byte, f Record) (int, error) {
var segment Segment
offset := 0
Expand Down
58 changes: 58 additions & 0 deletions pkg/lib/base_segment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"testing"

"gopkg.in/check.v1"

"github.com/moov-io/metro2/pkg/utils"
)

func TestBaseSegmentErr(t *testing.T) {
Expand Down Expand Up @@ -368,3 +370,59 @@ func (t *SegmentTest) TestPackedBaseSegmentJson(c *check.C) {
c.Assert(0, check.Equals, bytes.Compare(segment.Bytes(), t.samplePackedBaseSegment))
c.Assert(segment.Name(), check.Equals, PackedBaseSegmentName)
}

func (t *SegmentTest) TestBaseSegmentWithSocialSecurityNumber(c *check.C) {
segment := &BaseSegment{}
_, err := segment.Parse(t.sampleBaseSegment)
c.Assert(err, check.IsNil)

segment.SocialSecurityNumber = 0
err = segment.Validate()
c.Assert(err, check.Equals, nil)

segment.DateBirth = utils.Time{}
err = segment.Validate()
c.Assert(err, check.Not(check.IsNil))
}

func (t *SegmentTest) TestBaseSegmentWithDateBirth(c *check.C) {
segment := &BaseSegment{}
_, err := segment.Parse(t.sampleBaseSegment)
c.Assert(err, check.IsNil)

segment.DateBirth = utils.Time{}
err = segment.Validate()
c.Assert(err, check.Equals, nil)

segment.SocialSecurityNumber = 0
err = segment.Validate()
c.Assert(err, check.Not(check.IsNil))
}

func (t *SegmentTest) TestPackedBaseSegmentWithSocialSecurityNumber(c *check.C) {
segment := &PackedBaseSegment{}
_, err := segment.Parse(t.samplePackedBaseSegment)
c.Assert(err, check.IsNil)

segment.SocialSecurityNumber = 0
err = segment.Validate()
c.Assert(err, check.Equals, nil)

segment.DateBirth = utils.Time{}
err = segment.Validate()
c.Assert(err, check.Not(check.IsNil))
}

func (t *SegmentTest) TestPackedBaseSegmentWithDateBirth(c *check.C) {
segment := &PackedBaseSegment{}
_, err := segment.Parse(t.samplePackedBaseSegment)
c.Assert(err, check.IsNil)

segment.DateBirth = utils.Time{}
err = segment.Validate()
c.Assert(err, check.Equals, nil)

segment.SocialSecurityNumber = 0
err = segment.Validate()
c.Assert(err, check.Not(check.IsNil))
}
20 changes: 18 additions & 2 deletions pkg/lib/j1_segment.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ type J1Segment struct {
// per the Social Security Administration.
// Do not report Credit Profile Numbers (CPNs) in this field.
// The CPN should not be used for credit reporting purposes and does not replace the Social Security Number.
SocialSecurityNumber int `json:"socialSecurityNumber" validate:"required"`
SocialSecurityNumber int `json:"socialSecurityNumber,omitempty"`

// Report the full Date of Birth of the associated consumer, including the month, day and year.
// Reporting of this information is required as the Date of Birth greatly enhances accuracy in matching to the correct consumer.
Expand All @@ -79,7 +79,7 @@ type J1Segment struct {
// When reporting Authorized Users (ECOA Code 3), the full Date of Birth (MMDDYYYY) must be reported for all newly-added
// Authorized Users on all pre-existing and newly-opened accounts, even if the Social Security Number is reported.
// Do not report accounts of consumers who are too young to enter into a binding contract.
DateBirth utils.Time `json:"dateBirth" validate:"required"`
DateBirth utils.Time `json:"dateBirth,omitempty"`

// Contains the telephone number of the associated consumer (Area Code + 7 digits).
TelephoneNumber int64 `json:"telephoneNumber"`
Expand Down Expand Up @@ -172,3 +172,19 @@ func (s *J1Segment) ValidateTelephoneNumber() error {
}
return nil
}

// validation of social security number
func (r *J1Segment) ValidateSocialSecurityNumber() error {
if r.SocialSecurityNumber == 0 && r.DateBirth.IsZero() {
return utils.NewErrInvalidValueOfField("social security number", "base segment")
}
return nil
}

// validation of date of birth
func (r *J1Segment) ValidateDateBirth() error {
if r.SocialSecurityNumber == 0 && r.DateBirth.IsZero() {
return utils.NewErrInvalidValueOfField("date birth", "base segment")
}
return nil
}
Loading