Current section
3 Versions
Jump to
Current section
3 Versions
Compare versions
12
files changed
+784
additions
-351
deletions
| @@ -12,27 +12,45 @@ It contains structs for | |
| 12 12 | |
| 13 13 | ```elixir |
| 14 14 | def deps do |
| 15 | - [{:metro_2, "~> 0.1.0"}] |
| 15 | + [{:metro_2, "~> 0.2.0"}] |
| 16 16 | end |
| 17 17 | ``` |
| 18 18 | |
| 19 | + ## Demo |
| 20 | + |
| 21 | + Try the interactive demo to see the library in action with realistic credit reporting data: |
| 22 | + |
| 23 | + ```bash |
| 24 | + mix run demo.exs |
| 25 | + ``` |
| 26 | + |
| 27 | + The demo showcases: |
| 28 | + - ✅ **Character Validation**: Proper validation for names (with dashes), addresses (with dots/slashes), and regular fields |
| 29 | + - ✅ **Realistic Data**: 3 consumer accounts with different scenarios (current, past due, closed) |
| 30 | + - ✅ **METRO 2® Format**: Generates compliant format with header, base segments, and tailer |
| 31 | + - ✅ **Error Handling**: Demonstrates rejection of invalid characters |
| 32 | + |
| 33 | + **Output**: Creates `demo_output.metro2` with 200+ lines of properly formatted METRO 2® data. |
| 34 | + |
| 35 | + For detailed demo documentation, see [README_DEMO.md](README_DEMO.md). |
| 36 | + |
| 19 37 | ## Usage |
| 20 | - Every Segment stuct contains field structs, which contains field structs. |
| 21 | - Those field structs bear information about the individual field length, type and the allowed characters, wich are important for the |
| 22 | - serialisation process. |
| 38 | + Every segment struct contains field structs, which contain information about the individual field length, type and allowed characters, which are important for the serialization process. |
| 23 39 | |
| 24 | - To access a field value in a segment struct, please use i.E.: |
| 40 | + To access a field value in a segment struct: |
| 25 41 | ```elixir |
| 26 | - # setter |
| 27 | - %Metro2.Records.BaseSegment{} |
| 28 | - |> Metro2.Fields.put(:first_name, "Max") |
| 42 | + # Create a new base segment with proper field initialization |
| 43 | + base_segment = Metro2.Records.BaseSegment.new() |
| 29 44 | |
| 30 | - # getter |
| 31 | - %Metro2.Records.BaseSegment{} |
| 32 | - |> Metro2.Fields.get(:first_name) |
| 45 | + # Set field values |
| 46 | + base_segment = Metro2.Fields.put(base_segment, :first_name, "John-Smith") # Dashes allowed in names |
| 47 | + base_segment = Metro2.Fields.put(base_segment, :address_1, "123 Main St./Apt 2") # Dots/slashes allowed in addresses |
| 48 | + |
| 49 | + # Get field values |
| 50 | + first_name = Metro2.Fields.get(base_segment, :first_name) |
| 33 51 | ``` |
| 34 52 | ### METRO 2® File Structure |
| 35 | - The Metro2 File Structure is the root structure and it has the following inital structure: |
| 53 | + The Metro2 File Structure is the root structure and it has the following initial structure: |
| 36 54 | ```elixir |
| 37 55 | defstruct [ |
| 38 56 | header: %HeaderSegment{}, |
| @@ -40,32 +58,56 @@ defstruct [ | |
| 40 58 | tailer: %TailerSegment{} |
| 41 59 | ] |
| 42 60 | ``` |
| 43 | - This file can be converted into a serialized Metro2 string via: |
| 61 | + |
| 62 | + Create and serialize a Metro2 file: |
| 44 63 | |
| 45 64 | ```elixir |
| 46 | - my_file = %Metro2.File{} |
| 47 | - # update header segment |
| 48 | - # add base segments |
| 49 | - my_file |> Metro2.File.serialize |
| 65 | + # Create a new file with properly initialized segments |
| 66 | + my_file = Metro2.File.new() |
| 67 | + |
| 68 | + # Update header segment |
| 69 | + my_file = %{my_file | header: Metro2.Fields.put(my_file.header, :reporter_name, "My Credit Union")} |
| 70 | + |
| 71 | + # Add base segments |
| 72 | + base_segment = Metro2.Records.BaseSegment.new() |
| 73 | + |> Metro2.Fields.put(:surname, "Smith-Johnson") |
| 74 | + |> Metro2.Fields.put(:first_name, "John") |
| 75 | + |
| 76 | + my_file = Metro2.File.add_base_segment(my_file, base_segment) |
| 77 | + |
| 78 | + # Serialize to METRO 2® format |
| 79 | + metro2_content = Metro2.File.serialize(my_file) |
| 50 80 | ``` |
| 51 81 | ### Header Segment |
| 52 82 | The header segment contains information about the data furnisher. |
| 53 83 | You should simply transform the header segment structure in the file structure. |
| 54 84 | |
| 55 85 | ### Base Segment |
| 56 | - The base segment is stored in a list in the Metro2.File structure. Each Base segment represents one reportable loan. |
| 86 | + The base segment is stored in a list in the Metro2.File structure. Each base segment represents one reportable loan. |
| 57 87 | ```elixir |
| 58 | - base_segment =v%Metro2.Records.BaseSegment{} |
| 59 | - # |
| 60 | - # add information to the base_segment |
| 61 | - # |
| 62 | - %Metro2.File{} |
| 88 | + # Create a base segment with proper character validation |
| 89 | + base_segment = Metro2.Records.BaseSegment.new() |
| 90 | + |> Metro2.Fields.put(:surname, "Smith-Johnson") # Dashes allowed in names |
| 91 | + |> Metro2.Fields.put(:first_name, "John") |
| 92 | + |> Metro2.Fields.put(:address_1, "123 Main St./Apt 2") # Dots/slashes allowed in addresses |
| 93 | + |> Metro2.Fields.put(:account_status, "11") # Current account |
| 94 | + |> Metro2.Fields.put(:current_balance, 1500) |
| 95 | + |
| 96 | + # Add to file |
| 97 | + file = Metro2.File.new() |
| 63 98 | |> Metro2.File.add_base_segment(base_segment) |
| 64 99 | ``` |
| 100 | + |
| 65 101 | ### Tailer Segment |
| 66 102 | The tailer segment contains counters for metrics like the SSN and the account statuses. |
| 67 | - It will be completly autogenerated. |
| 103 | + It will be completely auto-generated based on the base segments. |
| 104 | + |
| 105 | + ### Character Validation |
| 106 | + The library enforces METRO 2® character validation rules: |
| 107 | + - **Name fields** (surname, first_name, middle_name): Alphanumeric + dashes |
| 108 | + - **Address fields** (address_1, address_2, city): Alphanumeric + dots/dashes/slashes |
| 109 | + - **Regular fields**: Alphanumeric only |
| 68 110 | |
| 69 111 | ### Limitations |
| 70 | - The Metro2 Standart contains beside the base segment( wich is the most common one) a coule of special segments, which are not supported yet. |
| 112 | + The Metro2 Standard contains, besides the base segment (which is the most common one), a couple of special segments which are not supported yet. |
| @@ -0,0 +1,79 @@ | |
| 1 | + # Metro2 Library Demo |
| 2 | + |
| 3 | + This demo showcases the Metro2 library's functionality with realistic example data for credit bureau reporting. |
| 4 | + |
| 5 | + ## What the Demo Shows |
| 6 | + |
| 7 | + - **Character Validation**: Demonstrates proper validation for different field types: |
| 8 | + - Name fields (surname, first_name, middle_name) accept alphanumeric + dashes |
| 9 | + - Address fields accept alphanumeric + dots/dashes/slashes |
| 10 | + - Regular fields accept only alphanumeric characters |
| 11 | + - Invalid characters are properly rejected |
| 12 | + |
| 13 | + - **Realistic Data**: Creates 3 consumer accounts with different scenarios: |
| 14 | + - Account 1: John Smith-Johnson (Current account) |
| 15 | + - Account 2: Maria Garcia-Rodriguez (Past due account) |
| 16 | + - Account 3: Robert Williams-Brown (Closed account) |
| 17 | + |
| 18 | + - **METRO 2® Format**: Generates compliant METRO 2® format output with: |
| 19 | + - Header segment with reporter information |
| 20 | + - Base segments for each consumer account |
| 21 | + - Tailer segment with statistics |
| 22 | + - Proper field formatting and validation |
| 23 | + |
| 24 | + ## How to Run |
| 25 | + |
| 26 | + ### Using Mix (Recommended) |
| 27 | + ```bash |
| 28 | + mix run demo.exs |
| 29 | + ``` |
| 30 | + |
| 31 | + ### Using Elixir directly |
| 32 | + ```bash |
| 33 | + elixir demo.exs |
| 34 | + ``` |
| 35 | + |
| 36 | + ## Expected Output |
| 37 | + |
| 38 | + The demo will: |
| 39 | + 1. Create a Metro2 file structure |
| 40 | + 2. Add realistic consumer account data |
| 41 | + 3. Demonstrate character validation (both valid and invalid examples) |
| 42 | + 4. Generate a Metro2 format file |
| 43 | + 5. Save the output to `demo_output.metro2` |
| 44 | + 6. Display statistics and analysis |
| 45 | + |
| 46 | + ## Output Files |
| 47 | + |
| 48 | + - `demo_output.metro2` - The generated METRO 2® format file |
| 49 | + - Console output with detailed progress and validation examples |
| 50 | + |
| 51 | + ## Example Data Features |
| 52 | + |
| 53 | + - **Hyphenated Names**: Smith-Johnson, Garcia-Rodriguez, Williams-Brown |
| 54 | + - **Complex Addresses**: Street addresses with dots, slashes, and unit numbers |
| 55 | + - **Different Account Types**: Revolving, Installment, Mortgage |
| 56 | + - **Various Account Statuses**: Current, Past Due, Closed |
| 57 | + - **Proper SSN and Contact Information** |
| 58 | + |
| 59 | + ## Key Validation Examples |
| 60 | + |
| 61 | + ✅ **Valid Characters:** |
| 62 | + - Names: `Smith-Johnson` (dash allowed) |
| 63 | + - Addresses: `456 Oak Ave./Apt 2B` (dots and slashes allowed) |
| 64 | + - Cities: `St. Petersburg` (dots allowed) |
| 65 | + |
| 66 | + ❌ **Invalid Characters (properly rejected):** |
| 67 | + - Names: `Smith@Johnson` (@ not allowed) |
| 68 | + - Names: `O'Connor` (apostrophe not allowed) |
| 69 | + - Names: `García` (accented characters not allowed) |
| 70 | + - Addresses: `123 Main St%` (% not allowed) |
| 71 | + - Account Types: `ABC-123` (dash not allowed in regular fields) |
| 72 | + |
| 73 | + ## Requirements |
| 74 | + |
| 75 | + - Elixir 1.14+ |
| 76 | + - Mix dependencies installed (`mix deps.get`) |
| 77 | + - Metro2 library compiled |
| 78 | + |
| 79 | + This demo validates that the library correctly handles METRO 2® format requirements and character validation rules. |
| \ No newline at end of file |
| @@ -1,22 +1,23 @@ | |
| 1 | - {<<"app">>,<<"metro_2">>}. |
| 2 | - {<<"build_tools">>,[<<"mix">>]}. |
| 1 | + {<<"links">>,[{<<"GitHub">>,<<"https://github.com/dominikknafelj/metro2">>}]}. |
| 2 | + {<<"name">>,<<"metro2">>}. |
| 3 | + {<<"version">>,<<"0.2.0">>}. |
| 3 4 | {<<"description">>, |
| 4 5 | <<"This library follows the METRO 2 ® data reporting format, which is a data reporting format for consumer credit account data furnishers."/utf8>>}. |
| 5 | - {<<"elixir">>,<<"~> 1.4">>}. |
| 6 | + {<<"elixir">>,<<"~> 1.14">>}. |
| 7 | + {<<"app">>,<<"metro_2">>}. |
| 6 8 | {<<"files">>, |
| 7 | - [<<"lib/metro2/base.ex">>,<<"lib/metro2/fields.ex">>, |
| 8 | - <<"lib/metro2/file.ex">>,<<"lib/metro2/records/base_segment.ex">>, |
| 9 | + [<<"lib">>,<<"lib/metro2.ex">>,<<"lib/metro2">>,<<"lib/metro2/records">>, |
| 10 | + <<"lib/metro2/records/tailer_segment.ex">>, |
| 11 | + <<"lib/metro2/records/base_segment.ex">>, |
| 9 12 | <<"lib/metro2/records/header_segment.ex">>, |
| 10 | - <<"lib/metro2/records/segment.ex">>, |
| 11 | - <<"lib/metro2/records/tailer_segment.ex">>,<<"mix.exs">>,<<"README.md">>, |
| 12 | - <<"LICENSE.txt">>]}. |
| 13 | + <<"lib/metro2/records/segment.ex">>,<<"lib/metro2/fields.ex">>, |
| 14 | + <<"lib/metro2/base.ex">>,<<"lib/metro2/file.ex">>,<<"mix.exs">>, |
| 15 | + <<"README.md">>,<<"README_DEMO.md">>,<<"LICENSE.txt">>]}. |
| 13 16 | {<<"licenses">>,[<<"MIT">>]}. |
| 14 | - {<<"links">>,[{<<"GitHub">>,<<"https://github.com/dominikknafelj/metro2">>}]}. |
| 15 | - {<<"maintainers">>,[<<"Dominik Knafelj">>]}. |
| 16 | - {<<"name">>,<<"metro2">>}. |
| 17 17 | {<<"requirements">>, |
| 18 | - [[{<<"app">>,<<"timex">>}, |
| 19 | - {<<"name">>,<<"timex">>}, |
| 18 | + [[{<<"name">>,<<"timex">>}, |
| 19 | + {<<"app">>,<<"timex">>}, |
| 20 20 | {<<"optional">>,false}, |
| 21 | - {<<"requirement">>,<<"~> 3.0">>}]]}. |
| 22 | - {<<"version">>,<<"0.1.1">>}. |
| 21 | + {<<"requirement">>,<<"~> 3.7">>}, |
| 22 | + {<<"repository">>,<<"hexpm">>}]]}. |
| 23 | + {<<"build_tools">>,[<<"mix">>]}. |
| @@ -0,0 +1,47 @@ | |
| 1 | + defmodule Metro2 do |
| 2 | + @moduledoc """ |
| 3 | + Metro2 is a library for generating METRO 2® format files for credit bureau reporting. |
| 4 | + |
| 5 | + METRO 2® is a data reporting format for consumer credit account data furnishers. |
| 6 | + |
| 7 | + ## Usage |
| 8 | + |
| 9 | + Create a Metro2 file with header, base segments, and tailer: |
| 10 | + |
| 11 | + file = %Metro2.File{} |
| 12 | + |> Metro2.File.add_base_segment(base_segment) |
| 13 | + |> Metro2.File.serialize() |
| 14 | + |
| 15 | + ## Modules |
| 16 | + |
| 17 | + - `Metro2.File` - Main file structure and serialization |
| 18 | + - `Metro2.Fields` - Field type definitions and operations |
| 19 | + - `Metro2.Records.HeaderSegment` - Header segment definition |
| 20 | + - `Metro2.Records.BaseSegment` - Base segment definition |
| 21 | + - `Metro2.Records.TailerSegment` - Tailer segment definition |
| 22 | + - `Metro2.Base` - Base constants and utility functions |
| 23 | + """ |
| 24 | + |
| 25 | + alias Metro2.File |
| 26 | + |
| 27 | + @doc """ |
| 28 | + Creates a new Metro2 file structure. |
| 29 | + """ |
| 30 | + def new_file do |
| 31 | + %File{} |
| 32 | + end |
| 33 | + |
| 34 | + @doc """ |
| 35 | + Adds a base segment to a Metro2 file. |
| 36 | + """ |
| 37 | + def add_base_segment(file, base_segment) do |
| 38 | + File.add_base_segment(file, base_segment) |
| 39 | + end |
| 40 | + |
| 41 | + @doc """ |
| 42 | + Serializes a Metro2 file to the METRO 2® format string. |
| 43 | + """ |
| 44 | + def serialize(file) do |
| 45 | + File.serialize(file) |
| 46 | + end |
| 47 | + end |
| @@ -1,17 +1,17 @@ | |
| 1 1 | defmodule Metro2.Base do |
| 2 2 | @moduledoc """ |
| 3 | - This module contains all the elementar parts of the metro2 generation, like: |
| 3 | + This module contains all the elementar parts of the metro2 generation, like: |
| 4 4 | * Maps wich translates the humanized states into metro2 conform codes |
| 5 5 | * Regular expressions for valid numerics and alphanumerics |
| 6 | - * Functions which are converting datatypes in to a metro2 compatible format |
| 6 | + * Functions which are converting datatypes in to a metro2 compatible format |
| 7 7 | """ |
| 8 8 | @portfolio_type %{ |
| 9 | - line_of_credit: "C", |
| 10 | - installment: "I", |
| 11 | - mortgage: "M", |
| 12 | - open_account: "O", |
| 13 | - revolving: "R" |
| 14 | - } |
| 9 | + line_of_credit: "C", |
| 10 | + installment: "I", |
| 11 | + mortgage: "M", |
| 12 | + open_account: "O", |
| 13 | + revolving: "R" |
| 14 | + } |
| 15 15 | |
| 16 16 | @account_type %{ |
| 17 17 | unsecured: "01", |
| @@ -21,15 +21,15 @@ defmodule Metro2.Base do | |
| 21 21 | |
| 22 22 | @ecoa_code %{ |
| 23 23 | individual: "1", |
| 24 | - deceased: "X" |
| 24 | + deceased: "X" |
| 25 25 | # TODO: add other ECOA codes |
| 26 26 | } |
| 27 27 | |
| 28 28 | @special_comment_code %{ |
| 29 | - partial_payment_agreement: "AC", |
| 30 | - paid_in_full_less_than_full_balance: "AU", |
| 31 | - loan_modified: "CO", |
| 32 | - forbearance: "CP" |
| 29 | + partial_payment_agreement: "AC", |
| 30 | + paid_in_full_less_than_full_balance: "AU", |
| 31 | + loan_modified: "CO", |
| 32 | + forbearance: "CP" |
| 33 33 | # TODO: add other special comment codes |
| 34 34 | } |
| 35 35 | |
| @@ -39,140 +39,139 @@ defmodule Metro2.Base do | |
| 39 39 | } |
| 40 40 | |
| 41 41 | @interest_type_indicator %{ |
| 42 | - fixed: "F", |
| 42 | + fixed: "F", |
| 43 43 | variable: "V" |
| 44 44 | } |
| 45 45 | |
| 46 46 | @correction_indicator "1" |
| 47 47 | |
| 48 48 | @terms_frequency %{ |
| 49 | - deferred: "D", |
| 49 | + deferred: "D", |
| 50 50 | single_payment: "P", |
| 51 | - weekly: "W", |
| 52 | - biweekly: "B", |
| 53 | - semimonthly: "S", |
| 54 | - monthly: "M", |
| 55 | - bimonthly: "L", |
| 56 | - quarterly: "Q", |
| 57 | - triannually: "T", |
| 58 | - semiannually: "S", |
| 59 | - annually: "Y" |
| 51 | + weekly: "W", |
| 52 | + biweekly: "B", |
| 53 | + semimonthly: "S", |
| 54 | + monthly: "M", |
| 55 | + bimonthly: "L", |
| 56 | + quarterly: "Q", |
| 57 | + triannually: "T", |
| 58 | + semiannually: "S", |
| 59 | + annually: "Y" |
| 60 60 | } |
| 61 61 | |
| 62 62 | @account_status %{ |
| 63 | - account_transferred: "05", |
| 64 | - current: "11", |
| 65 | - closed: "13", |
| 63 | + account_transferred: "05", |
| 64 | + current: "11", |
| 65 | + closed: "13", |
| 66 66 | paid_in_full_voluntary_surrender: "61", |
| 67 | - paid_in_full_collection_account: "62", |
| 68 | - paid_in_full_repossession: "63", |
| 69 | - paid_in_full_charge_off: "64", |
| 70 | - paid_in_full_foreclosure: "65", |
| 71 | - past_due_30_59: "71", |
| 72 | - past_due_60_89: "78", |
| 73 | - past_due_90_119: "80", |
| 74 | - past_due_120_149: "82", |
| 75 | - past_due_150_179: "83", |
| 76 | - past_due_180_plus: "84", |
| 77 | - govt_insurance_claim_filed: "88", |
| 78 | - deed_received: "89", |
| 79 | - collections: "93", |
| 80 | - foreclosure_completed: "94", |
| 81 | - voluntary_surrender: "95", |
| 82 | - merch_repossessed: "96", |
| 83 | - charge_off: "97", |
| 84 | - delete_account: "DA", |
| 85 | - delete_account_fraud: "DF" |
| 67 | + paid_in_full_collection_account: "62", |
| 68 | + paid_in_full_repossession: "63", |
| 69 | + paid_in_full_charge_off: "64", |
| 70 | + paid_in_full_foreclosure: "65", |
| 71 | + past_due_30_59: "71", |
| 72 | + past_due_60_89: "78", |
| 73 | + past_due_90_119: "80", |
| 74 | + past_due_120_149: "82", |
| 75 | + past_due_150_179: "83", |
| 76 | + past_due_180_plus: "84", |
| 77 | + govt_insurance_claim_filed: "88", |
| 78 | + deed_received: "89", |
| 79 | + collections: "93", |
| 80 | + foreclosure_completed: "94", |
| 81 | + voluntary_surrender: "95", |
| 82 | + merch_repossessed: "96", |
| 83 | + charge_off: "97", |
| 84 | + delete_account: "DA", |
| 85 | + delete_account_fraud: "DF" |
| 86 86 | } |
| 87 87 | |
| 88 88 | @payment_history_profile %{ |
| 89 | - current: "0", |
| 90 | - past_due_30_59: "1", |
| 91 | - past_due_60_89: "2", |
| 92 | - past_due_90_119: "3", |
| 93 | - past_due_120_149: "4", |
| 94 | - past_due_150_179: "5", |
| 95 | - past_due_180_plus: "6", |
| 96 | - no_history_prior: "B", |
| 97 | - no_history_available: "D", |
| 98 | - zero_balance: "E", |
| 99 | - collection: "G", |
| 100 | - foreclosure_completed: "H", |
| 101 | - voluntary_surrender: "J", |
| 102 | - repossession: "K", |
| 103 | - charge_off: "L" |
| 89 | + current: "0", |
| 90 | + past_due_30_59: "1", |
| 91 | + past_due_60_89: "2", |
| 92 | + past_due_90_119: "3", |
| 93 | + past_due_120_149: "4", |
| 94 | + past_due_150_179: "5", |
| 95 | + past_due_180_plus: "6", |
| 96 | + no_history_prior: "B", |
| 97 | + no_history_available: "D", |
| 98 | + zero_balance: "E", |
| 99 | + collection: "G", |
| 100 | + foreclosure_completed: "H", |
| 101 | + voluntary_surrender: "J", |
| 102 | + repossession: "K", |
| 103 | + charge_off: "L" |
| 104 104 | } |
| 105 105 | |
| 106 106 | @consumer_transaction_type %{ |
| 107 | - new_account_or_new_borrower: "1", |
| 108 | - name_change: "2", |
| 109 | - address_change: "3", |
| 110 | - ssn_change: "5", |
| 111 | - name_and_address_change: "6", |
| 112 | - name_and_ssn_change: "8", |
| 113 | - address_and_ssn_change: "9", |
| 114 | - name_address_and_ssn_change: "A" |
| 107 | + new_account_or_new_borrower: "1", |
| 108 | + name_change: "2", |
| 109 | + address_change: "3", |
| 110 | + ssn_change: "5", |
| 111 | + name_and_address_change: "6", |
| 112 | + name_and_ssn_change: "8", |
| 113 | + address_and_ssn_change: "9", |
| 114 | + name_address_and_ssn_change: "A" |
| 115 115 | } |
| 116 116 | |
| 117 117 | @address_indicator %{ |
| 118 | - confirmed: "C", |
| 119 | - known: "Y", |
| 120 | - not_confirmed: "N", |
| 121 | - military: "M", |
| 122 | - secondary: "S", |
| 123 | - business: "B", |
| 124 | - non_deliverable: "U", |
| 118 | + confirmed: "C", |
| 119 | + known: "Y", |
| 120 | + not_confirmed: "N", |
| 121 | + military: "M", |
| 122 | + secondary: "S", |
| 123 | + business: "B", |
| 124 | + non_deliverable: "U", |
| 125 125 | data_reporters_default: "D", |
| 126 | - bill_payer_service: "P" |
| 126 | + bill_payer_service: "P" |
| 127 127 | } |
| 128 128 | |
| 129 129 | @residence_code %{ |
| 130 | - owns: "O", |
| 131 | - rents: "R" |
| 130 | + owns: "O", |
| 131 | + rents: "R" |
| 132 132 | } |
| 133 133 | |
| 134 134 | @generation_code %{ |
| 135 | - junior: "J", |
| 136 | - senior: "S", |
| 137 | - ii: "2", |
| 138 | - iii: "3", |
| 139 | - iv: "4", |
| 140 | - v: "5", |
| 141 | - vi: "6", |
| 142 | - vii: "7", |
| 143 | - viii: "8", |
| 144 | - ix: "9" |
| 135 | + junior: "J", |
| 136 | + senior: "S", |
| 137 | + ii: "2", |
| 138 | + iii: "3", |
| 139 | + iv: "4", |
| 140 | + v: "5", |
| 141 | + vi: "6", |
| 142 | + vii: "7", |
| 143 | + viii: "8", |
| 144 | + ix: "9" |
| 145 145 | } |
| 146 146 | |
| 147 147 | @consumer_information_indicator %{ |
| 148 | - petition_ch7: "A", |
| 149 | - petition_ch11: "B", |
| 150 | - petition_ch12: "C", |
| 151 | - petition_ch13: "D", |
| 152 | - discharged_ch7: "E", |
| 153 | - discharged_ch11: "F", |
| 154 | - discharged_ch12: "G", |
| 155 | - discharged_ch13: "H", |
| 156 | - dismissed_ch7: "I", |
| 157 | - dismissed_ch11: "J", |
| 158 | - dismissed_ch12: "K", |
| 159 | - dismissed_ch13: "L", |
| 160 | - withdrawn_ch7: "M", |
| 161 | - withdrawn_ch11: "N", |
| 162 | - withdrawn_ch12: "O", |
| 163 | - withdrawn_ch13: "P", |
| 148 | + petition_ch7: "A", |
| 149 | + petition_ch11: "B", |
| 150 | + petition_ch12: "C", |
| 151 | + petition_ch13: "D", |
| 152 | + discharged_ch7: "E", |
| 153 | + discharged_ch11: "F", |
| 154 | + discharged_ch12: "G", |
| 155 | + discharged_ch13: "H", |
| 156 | + dismissed_ch7: "I", |
| 157 | + dismissed_ch11: "J", |
| 158 | + dismissed_ch12: "K", |
| 159 | + dismissed_ch13: "L", |
| 160 | + withdrawn_ch7: "M", |
| 161 | + withdrawn_ch11: "N", |
| 162 | + withdrawn_ch12: "O", |
| 163 | + withdrawn_ch13: "P" |
| 164 164 | } |
| 165 165 | |
| 166 | - @alphanumeric ~r/\A([[:alnum:]]|\s)+\z/ |
| 167 | - @alphanumeric_plus_dash ~r/\A([[:alnum:]]|\s|\-)+\z/ |
| 168 | - @alphanumeric_plus_dot_dash_slash ~r/\A([[:alnum:]]|\s|\-|\.|\\|\/)+\z/ |
| 169 | - @numeric ~r/\A\d+\.?\d*\z/ |
| 170 | - @integer ~r/\d+\z/ |
| 166 | + @alphanumeric ~r/\A([[:alnum:]]|\s)+\z/ |
| 167 | + @alphanumeric_plus_dash ~r/\A([[:alnum:]]|\s|\-)+\z/ |
| 168 | + @alphanumeric_plus_dot_dash_slash ~r/\A([[:alnum:]]|\s|\-|\.|\\|\/)+\z/ |
| 169 | + @numeric ~r/\A\d+\.?\d*\z/ |
| 170 | + @integer ~r/\d+\z/ |
| 171 171 | @fixed_length 426 |
| 172 | - @decimal_seperator "." |
| 172 | + @decimal_separator "." |
| 173 173 | @version_string "01" |
| 174 174 | |
| 175 | - |
| 176 175 | def portfolio_type, do: @portfolio_type |
| 177 176 | def account_type, do: @account_type |
| 178 177 | def ecoa_code, do: @ecoa_code |
| @@ -195,31 +194,36 @@ defmodule Metro2.Base do | |
| 195 194 | def integer, do: @integer |
| 196 195 | def fixed_length, do: @fixed_length |
| 197 196 | def version_string, do: @version_string |
| 198 | - def decimal_seperator, do: @decimal_seperator |
| 197 | + def decimal_separator, do: @decimal_separator |
| 199 198 | |
| 200 199 | @doc """ |
| 201 200 | function to check if a certain account status needs a payment rating |
| 202 201 | """ |
| 203 202 | def account_status_needs_payment_rating?(account_status) do |
| 204 | - account_status in [@account_status[:account_transferred], @account_status[:closed], |
| 205 | - @account_status[:paid_in_full_foreclosure], @account_status[:govt_insurance_claim_filed], |
| 206 | - @account_status[:deed_received], @account_status[:foreclosure_completed], |
| 207 | - @account_status[:voluntary_surrender]] |
| 203 | + account_status in [ |
| 204 | + @account_status[:account_transferred], |
| 205 | + @account_status[:closed], |
| 206 | + @account_status[:paid_in_full_foreclosure], |
| 207 | + @account_status[:govt_insurance_claim_filed], |
| 208 | + @account_status[:deed_received], |
| 209 | + @account_status[:foreclosure_completed], |
| 210 | + @account_status[:voluntary_surrender] |
| 211 | + ] |
| 208 212 | end |
| 209 213 | |
| 210 | - #converts a nil value to an alphanumeric metro2 format |
| 214 | + # converts a nil value to an alphanumeric metro2 format |
| 211 215 | @doc false |
| 212 | - def alphanumeric_to_metro2(nil, required_length, _ ) do |
| 216 | + def alphanumeric_to_metro2(nil, required_length, _) do |
| 213 217 | String.duplicate(" ", required_length) |
| 214 218 | end |
| 215 219 | |
| 216 | - #converts a string to an alphanumeric metro2 format |
| 220 | + # converts a string to an alphanumeric metro2 format |
| 217 221 | @doc false |
| 218 | - def alphanumeric_to_metro2("", required_length, _ ) do |
| 222 | + def alphanumeric_to_metro2("", required_length, _) do |
| 219 223 | String.duplicate(" ", required_length) |
| 220 224 | end |
| 221 225 | |
| 222 | - #converts a numeral to an alphanumeric metro2 format |
| 226 | + # converts a numeral to an alphanumeric metro2 format |
| 223 227 | @doc false |
| 224 228 | def alphanumeric_to_metro2(val, required_length, permitted_chars) when is_number(val) do |
| 225 229 | case val do |
| @@ -230,15 +234,16 @@ defmodule Metro2.Base do | |
| 230 234 | end |
| 231 235 | |
| 232 236 | # converts val into an alphanumeric metro2 format with the required length |
| 233 | - # it checks if val contains just permitted characters |
| 237 | + # it checks if val contains just permitted characters |
| 234 238 | @doc false |
| 235 239 | def alphanumeric_to_metro2(val, required_length, permitted_chars) do |
| 236 240 | unless Regex.match?(permitted_chars, val) do |
| 237 241 | raise ArgumentError, message: "Content (#{val}) contains invalid characters" |
| 238 242 | end |
| 243 | + |
| 239 244 | # if val is too long, just cut it down, else fil it up with leading spaces |
| 240 245 | if String.length(val) > required_length do |
| 241 | - String.slice(val, 0..(required_length-1)) |
| 246 | + String.slice(val, 0..(required_length - 1)) |
| 242 247 | else |
| 243 248 | val |> String.pad_trailing(required_length) |
| 244 249 | end |
| @@ -246,13 +251,13 @@ defmodule Metro2.Base do | |
| 246 251 | |
| 247 252 | # converts a nil value to a numeric metro2 format |
| 248 253 | @doc false |
| 249 | - def numeric_to_metro2( nil , required_length, _) do |
| 254 | + def numeric_to_metro2(nil, required_length, _) do |
| 250 255 | String.duplicate("0", required_length) |
| 251 256 | end |
| 252 257 | |
| 253 258 | # converts an empty string to a numeric metro2 format |
| 254 259 | @doc false |
| 255 | - def numeric_to_metro2( "" , required_length, _) do |
| 260 | + def numeric_to_metro2("", required_length, _) do |
| 256 261 | String.duplicate("0", required_length) |
| 257 262 | end |
| 258 263 | |
| @@ -262,13 +267,18 @@ defmodule Metro2.Base do | |
| 262 267 | # non monetary fields will raise an ArgunmentError for being too long |
| 263 268 | @doc false |
| 264 269 | def numeric_to_metro2(val, required_length, is_monetary) do |
| 265 | - case normalize_numeric(val) |> Tuple.append(is_monetary) do |
| 270 | + case normalize_numeric(val) |> Tuple.insert_at(2, is_monetary) do |
| 266 271 | # when we have a monetary value and we exceed the billion we limit to 999,999,999 |
| 267 | - {x, _, true} when x >= 1_000_000_000 -> String.duplicate("9", required_length) |
| 272 | + {x, _, true} when x >= 1_000_000_000 -> |
| 273 | + String.duplicate("9", required_length) |
| 274 | + |
| 268 275 | # normal case, we return the value with leading 0 as fillup |
| 269 | - {x, length, _ } when length <= required_length -> x |> Integer.to_string |> String.pad_leading( required_length, "0" ) |
| 276 | + {x, length, _} when length <= required_length -> |
| 277 | + x |> Integer.to_string() |> String.pad_leading(required_length, "0") |
| 278 | + |
| 270 279 | # when we don't have a monetary value and we exceed the required_length |
| 271 | - _ -> raise ArgumentError, message: "numeric field (#{val}) is too long (max #{required_length})" |
| 280 | + _ -> |
| 281 | + raise ArgumentError, message: "numeric field (#{val}) is too long (max #{required_length})" |
| 272 282 | end |
| 273 283 | end |
| 274 284 | |
| @@ -283,11 +293,11 @@ defmodule Metro2.Base do | |
| 283 293 | # returns the interger numeric value and the figures in a tupel |
| 284 294 | @doc false |
| 285 295 | defp normalize_numeric(val) when is_integer(val) do |
| 286 | - {val, (Integer.to_string(val) |> String.length())} |
| 296 | + {val, Integer.to_string(val) |> String.length()} |
| 287 297 | end |
| 288 298 | |
| 289 299 | @doc false |
| 290 300 | defp normalize_numeric(val) when is_float(val) do |
| 291 | - val |> Float.floor |> round() |> normalize_numeric() |
| 301 | + val |> Float.floor() |> round() |> normalize_numeric() |
| 292 302 | end |
| 293 303 | end |
Loading more files…