diff --git a/README.md b/README.md index 59a3efc..937bce4 100644 --- a/README.md +++ b/README.md @@ -1 +1,10 @@ -**Hello world!!!** +## Overview +This block (`block.py`) is responsible for preparing and validating inputs for the model. It performs data cleansing and returns a normalized output dictionary. + +## Key Inputs & Outputs +- **Request**: Refer to `request_schema.json` for detailed input fields and validation rules. +- **Response**: Refer to `response_schema.json` for the returned structure and data types. + +## Implementation Details +- All core logic resides in `block.py` within the `__main__` function. +- Example usage and validation are demonstrated in `test_block.py`. diff --git a/block.py b/block.py index 3b227f9..66a3536 100644 --- a/block.py +++ b/block.py @@ -1,21 +1,220 @@ -@flowx_block -def example_function(request: dict) -> dict: +import logging +import math - # Processing logic here... +# Configure logging +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(name)s - %(message)s", +) +logger = logging.getLogger(__name__) - return { - "meta_info": [ - { - "name": "created_date", - "type": "string", - "value": "2024-11-05" - } - ], - "fields": [ - { - "name": "", - "type": "", - "value": "" - } - ] - } +def __main__(record_counts_negative_trade_count:int, record_counts_installment_trade_count:int, + record_counts_total_trade_count:int, record_counts_total_inquiry_count:int, record_counts_revolving_trade_count:int, + score_results:float, installment_amount_monthly_payment:float, revolving_amount_percent_available_credit:float, G069S:int, + AT24S:int, BR02S:int, BI02S:int, AGG103:float, ALL231:float, AT12S:int, IN20S:int, AT33A:int, AT35A:int, AT28A:int, AT34B:int, + S061S:int, RE102S:int)->dict: + + #record_counts_negative_trade_count treatment + if record_counts_negative_trade_count is None or math.isnan(record_counts_negative_trade_count): + record_counts_negative_trade_count = 8 + elif 0 <= record_counts_negative_trade_count <= 999: + record_counts_negative_trade_count = min(8, max(0, record_counts_negative_trade_count)) + else: + record_counts_negative_trade_count = None + + #record_counts_installment_trade_count treatment + if record_counts_installment_trade_count is None or math.isnan(record_counts_installment_trade_count): + record_counts_installment_trade_count = 20 + elif 0 <= record_counts_installment_trade_count <= 999: + record_counts_installment_trade_count = min(20, max(0, record_counts_installment_trade_count)) + else: + record_counts_installment_trade_count = None + + #record_counts_total_trade_count treatment + if record_counts_total_trade_count is None or math.isnan(record_counts_total_trade_count): + record_counts_total_trade_count = 35 + elif 0 <= record_counts_total_trade_count <= 999: + record_counts_total_trade_count = min(35, max(0, record_counts_total_trade_count)) + else: + record_counts_total_trade_count = None + + #record_counts_total_inquiry_count treatment + if record_counts_total_inquiry_count is None or math.isnan(record_counts_total_inquiry_count): + record_counts_total_inquiry_count = 30 + elif 0 <= record_counts_total_inquiry_count <= 999: + record_counts_total_inquiry_count = min(30, max(0, record_counts_total_inquiry_count)) + else: + record_counts_total_inquiry_count = None + + #record_counts_revolving_trade_count treatment + if record_counts_revolving_trade_count is None or math.isnan(record_counts_revolving_trade_count): + record_counts_revolving_trade_count = 15 + elif 0 <= record_counts_revolving_trade_count <= 999: + record_counts_revolving_trade_count = min(15, max(0, record_counts_revolving_trade_count)) + else: + record_counts_revolving_trade_count = None + + #score_results treatment + if score_results is None or math.isnan(score_results): + score_results = 541.0 + elif 350.0 <= score_results <= 850.0: + score_results = min(700.0, max(541.0, score_results)) + else: + score_results = None + + #installment_amount_monthly_payment treatment + if installment_amount_monthly_payment is None or math.isnan(installment_amount_monthly_payment): + installment_amount_monthly_payment = 456.8794643 + elif 0.0 <= installment_amount_monthly_payment <= 999999999.0: + installment_amount_monthly_payment = min(1500.0, max(0.0, installment_amount_monthly_payment)) + else: + installment_amount_monthly_payment = None + + #revolving_amount_percent_available_credit treatment + if revolving_amount_percent_available_credit is None or math.isnan(revolving_amount_percent_available_credit): + revolving_amount_percent_available_credit = 100.0 + elif 0.0 <= revolving_amount_percent_available_credit <= 100.0: + revolving_amount_percent_available_credit = min(100.0, max(0.0, revolving_amount_percent_available_credit)) + else: + revolving_amount_percent_available_credit = None + + #G069S treatment + if G069S is None or math.isnan(G069S): + G069S = 0 + elif 0 <= G069S <= 999: + G069S = min(6, max(0, G069S)) + else: + G069S = 0 + + #AT24S treatment + if AT24S is None or math.isnan(AT24S): + AT24S = 0 + elif 0 <= AT24S <= 999: + AT24S = min(20, max(0, AT24S)) + else: + AT24S = 0 + + #BR02S treatment + if BR02S is None or math.isnan(BR02S): + BR02S = 0 + elif 0 <= BR02S <= 999: + BR02S = min(5, max(0, BR02S)) + else: + BR02S = 0 + + #BI02S treatment + if BI02S is None or math.isnan(BI02S): + BI02S = 0 + elif 0 <= BI02S <= 999: + BI02S = min(3, max(0, BI02S)) + else: + BI02S = 0 + + #AGG103 treatment + if AGG103 is None or math.isnan(AGG103): + AGG103 = 0.0 + elif 0.0 <= AGG103 <= 999999999.0: + AGG103 = min(60000.0, max(0.0, AGG103)) + else: + AGG103 = 0.0 + + #ALL231 treatment + if ALL231 is None or math.isnan(ALL231): + ALL231 = 0.0 + elif 0.0 <= ALL231 <= 999999999.0: + ALL231 = min(500.0, max(0.0, ALL231)) + else: + ALL231 = 0.0 + + #AT12S treatment + if AT12S is None or math.isnan(AT12S): + AT12S = 0 + elif 0 <= AT12S <= 999: + AT12S = min(10, max(0, AT12S)) + else: + AT12S = 0 + + #IN20S treatment + if IN20S is None or math.isnan(IN20S): + IN20S = 0 + elif 0 <= IN20S <= 999: + IN20S = min(150, max(0, IN20S)) + else: + IN20S = 0 + + #AT33A treatment + if AT33A is None or math.isnan(AT33A): + AT33A = 0 + elif 0 <= AT33A <= 999999999: + AT33A = min(100000, max(0, AT33A)) + else: + AT33A = 0 + + #AT35A treatment + if AT35A is None or math.isnan(AT35A): + AT35A = 0 + elif 0 <= AT35A <= 999999999: + AT35A = min(15000, max(0, AT35A)) + else: + AT35A = 0 + + #AT28A treatment + if AT28A is None or math.isnan(AT28A): + AT28A = 0 + elif 0 <= AT28A <= 999999999: + AT28A = min(80000, max(0, AT28A)) + else: + AT28A = 0 + + #AT34B treatment + if AT34B is None or math.isnan(AT34B): + AT34B = 150 + elif 0 <= AT34B <= 999: + AT34B = min(150, max(0, AT34B)) + else: + AT34B = 0 + + #S061S treatment + if S061S is None or math.isnan(S061S): + S061S = 40 + elif 0 <= S061S <= 84: + S061S = min(40, max(0, S061S)) + else: + S061S = 0 + + #RE102S treatment + if RE102S is None or math.isnan(RE102S): + RE102S = 0 + elif 0 <= RE102S <= 999999999: + RE102S = min(3000, max(0, RE102S)) + else: + RE102S = 0 + + output_data = { + "score_results" : score_results, + "AT34B" : AT34B, + "AT12S" : AT12S, + "revolving_amount_percent_available_credit" : revolving_amount_percent_available_credit, + "AT28A" : AT28A, + "record_counts_total_trade_count" : record_counts_total_trade_count, + "record_counts_negative_trade_count" : record_counts_negative_trade_count, + "record_counts_revolving_trade_count" : record_counts_revolving_trade_count, + "AT33A" : AT33A, + "AT35A" : AT35A, + "record_counts_total_inquiry_count" : record_counts_total_inquiry_count, + "IN20S" : IN20S, + "RE102S" : RE102S, + "installment_amount_monthly_payment" : installment_amount_monthly_payment, + "S061S" : S061S, + "record_counts_installment_trade_count" : record_counts_installment_trade_count, + "BR02S" : BR02S, + "AGG103" : AGG103, + "ALL231" : ALL231, + "G069S" : G069S, + "AT24S" : AT24S, + "BI02S" : BI02S + } + + logger.info(f"Repeat V1 Pre preocessed data: {output_data}") + + return output_data \ No newline at end of file diff --git a/request_schema.json b/request_schema.json index 0967ef4..d924bf0 100644 --- a/request_schema.json +++ b/request_schema.json @@ -1 +1,95 @@ -{} +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "record_counts_negative_trade_count": { + "type": ["integer", "null"], + "description": "Number of financial transactions that resulted in a negative impact." + }, + "record_counts_installment_trade_count": { + "type": ["integer", "null"], + "description": "The total count of installment trade records (fixed repayment schedules)." + }, + "record_counts_total_trade_count": { + "type": ["integer", "null"], + "description": "Total number of trade-related (transaction) records." + }, + "record_counts_total_inquiry_count": { + "type": ["integer", "null"], + "description": "Number of times the user has made an inquiry." + }, + "record_counts_revolving_trade_count": { + "type": ["integer", "null"], + "description": "Records related to revolving trade accounts (e.g., credit card accounts)." + }, + "score_results": { + "type": ["number", "null"], + "description": "TransUnion credit score." + }, + "installment_amount_monthly_payment": { + "type": ["number", "null"], + "description": "The monthly payment amount for installment credit accounts." + }, + "revolving_amount_percent_available_credit": { + "type": ["number", "null"], + "description": "Percentage of available credit utilized in revolving credit accounts." + }, + "G069S": { + "type": ["integer", "null"], + "description": "Number of trades 90 or more days past due in the past 12 months." + }, + "AT24S": { + "type": ["integer", "null"], + "description": "Number of currently open and satisfactory trades that are 6 months or older." + }, + "BR02S": { + "type": ["integer", "null"], + "description": "Number of open bank revolving trades." + }, + "BI02S": { + "type": ["integer", "null"], + "description": "Number of open bank installment trades." + }, + "AGG103": { + "type": ["number", "null"], + "description": "Aggregate non-mortgage balances for month 3." + }, + "ALL231": { + "type": ["number", "null"], + "description": "Aggregate excess payment for all accounts over the past month." + }, + "AT12S": { + "type": ["integer", "null"], + "description": "Number of open trades verified in the past 12 months." + }, + "IN20S": { + "type": ["integer", "null"], + "description": "Months since the oldest installment trade was opened." + }, + "AT33A": { + "type": ["integer", "null"], + "description": "Total balance of open trades verified in the past 12 months." + }, + "AT35A": { + "type": ["integer", "null"], + "description": "Average balance of open trades verified in the past 12 months." + }, + "AT28A": { + "type": ["integer", "null"], + "description": "Total credit line of open trades verified in the past 12 months." + }, + "AT34B": { + "type": ["integer", "null"], + "description": "Utilization for open trades verified in the past 12 months (excluding mortgage and home equity)." + }, + "S061S": { + "type": ["integer", "null"], + "description": "Months since most recent 60 or more days past due." + }, + "RE102S": { + "type": ["integer", "null"], + "description": "Average credit line of open revolving trades verified in the past 12 months." + } + }, + "required": [] +} diff --git a/requirements.txt b/requirements.txt index 0967ef4..8c4acbc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -{} +jsonschema==4.23.0 \ No newline at end of file diff --git a/response_schema.json b/response_schema.json index 0967ef4..8fb21db 100644 --- a/response_schema.json +++ b/response_schema.json @@ -1 +1,94 @@ -{} +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "score_results": { + "type": ["number", "null"], + "description": "TransUnion credit score." + }, + "AT34B": { + "type": ["integer", "null"], + "description": "Utilization for open trades verified in the past 12 months (excluding mortgage and home equity)." + }, + "AT12S": { + "type": ["integer", "null"], + "description": "Number of open trades verified in the past 12 months." + }, + "revolving_amount_percent_available_credit": { + "type": ["number", "null"], + "description": "Percentage of available credit utilized in revolving credit accounts." + }, + "AT28A": { + "type": ["integer", "null"], + "description": "Total credit line of open trades verified in the past 12 months." + }, + "record_counts_total_trade_count": { + "type": ["integer", "null"], + "description": "Total number of trade-related (transaction) records." + }, + "record_counts_negative_trade_count": { + "type": ["integer", "null"], + "description": "Number of financial transactions that resulted in a negative impact." + }, + "record_counts_revolving_trade_count": { + "type": ["integer", "null"], + "description": "Records related to revolving trade accounts (e.g., credit card accounts)." + }, + "AT33A": { + "type": ["integer", "null"], + "description": "Total balance of open trades verified in the past 12 months." + }, + "AT35A": { + "type": ["integer", "null"], + "description": "Average balance of open trades verified in the past 12 months." + }, + "record_counts_total_inquiry_count": { + "type": ["integer", "null"], + "description": "Number of times the user has made an inquiry." + }, + "IN20S": { + "type": ["integer", "null"], + "description": "Months since the oldest installment trade was opened." + }, + "RE102S": { + "type": ["integer", "null"], + "description": "Average credit line of open revolving trades verified in the past 12 months." + }, + "installment_amount_monthly_payment": { + "type": ["number", "null"], + "description": "The monthly payment amount for installment credit accounts." + }, + "S061S": { + "type": ["integer", "null"], + "description": "Months since most recent 60 or more days past due." + }, + "record_counts_installment_trade_count": { + "type": ["integer", "null"], + "description": "The total count of installment trade records (fixed repayment schedules)." + }, + "BR02S": { + "type": ["integer", "null"], + "description": "Number of open bank revolving trades." + }, + "AGG103": { + "type": ["number", "null"], + "description": "Aggregate non-mortgage balances for month 3." + }, + "ALL231": { + "type": ["number", "null"], + "description": "Aggregate excess payment for all accounts over the past month." + }, + "G069S": { + "type": ["integer", "null"], + "description": "Number of trades 90 or more days past due in the past 12 months." + }, + "AT24S": { + "type": ["integer", "null"], + "description": "Number of currently open and satisfactory trades 6 months or older." + }, + "BI02S": { + "type": ["integer", "null"], + "description": "Number of open bank installment trades." + } + } +} diff --git a/test_block.py b/test_block.py new file mode 100644 index 0000000..7ff4642 --- /dev/null +++ b/test_block.py @@ -0,0 +1,32 @@ +import unittest +from block import __main__ + +class TestBlock(unittest.TestCase): + + def test_main_success(self): + result = __main__(record_counts_negative_trade_count=7, record_counts_installment_trade_count=8, + record_counts_total_trade_count=18, record_counts_total_inquiry_count=3, + record_counts_revolving_trade_count=9, score_results=600.0, + installment_amount_monthly_payment=572, revolving_amount_percent_available_credit=18, + G069S=3, AT24S=6, BR02S=1, BI02S=5, AGG103=27642.0, ALL231=-2.0, AT12S=7, IN20S=166, + AT33A=38353, AT35A=5479, AT28A=54087, AT34B=71, S061S=1, RE102S=2000) + + expected_result = {'score_results': 600.0, 'AT34B': 71, 'AT12S': 7, 'revolving_amount_percent_available_credit': 18, 'AT28A': 54087, 'record_counts_total_trade_count': 18, 'record_counts_negative_trade_count': 7, 'record_counts_revolving_trade_count': 9, 'AT33A': 38353, 'AT35A': 5479, 'record_counts_total_inquiry_count': 3, 'IN20S': 150, 'RE102S': 2000, 'installment_amount_monthly_payment': 572, 'S061S': 1, 'record_counts_installment_trade_count': 8, 'BR02S': 1, 'AGG103': 27642.0, 'ALL231': 0.0, 'G069S': 3, 'AT24S': 6, 'BI02S': 3} + + for key, expected_value in expected_result.items(): + if isinstance(expected_value, float): + self.assertAlmostEqual(result[key], expected_value, places=6, msg=f"Mismatch for {key}") + else: + self.assertEqual(result[key], expected_value, msg=f"Mismatch for {key}") + + # def test_main_invalid_input(self): + # with self.assertRaises(TypeError): + # __main__(record_counts_negative_trade_count=7, record_counts_installment_trade_count=8, + # record_counts_total_trade_count=18, record_counts_total_inquiry_count=3, + # record_counts_revolving_trade_count=9, score_results=600, installment_amount_monthly_payment=572, + # revolving_amount_percent_available_credit=18, G069S=3, AT24S=6, BR02S=1, BI02S=5, + # AGG103=27642, ALL231=-2, AT12S=7, IN20S=166, AT33A=38353, AT35A=5479, AT28A=54087, + # AT34B=71, S061S=1, RE102S=2000) + +if __name__ == "__main__": + unittest.main()