Package: indicina-decide
decide-python-packageTable of contentsAboutUsing the AppInstallationAuthorizationshPythonSample CodeJSON StatementPDF StatementCSV StatementSample ResponseSupported BanksAnalysis ResultRules Engine DocumentationContributionSetup ProjectCloning the projectFor HTTPSFor SSHRunning the projectContribute to ProjectIssuesAboutDecide helps users make risk-free decisions based on an analysis of their banking transactions using extracted financial data.
The Decide SDK helps developers plug into the functionalities of Decide from their python projects.Website:https://developers.indicina.coDocumentation:https://developers.indicina.co/docs/decide-python-libraryUsing the AppInstallationIn your python project use the commandpip install indicina-decideto install the Decide SDKAuthorizationshexport INDICINA_CLIENT_ID=my_id
export INDICINA_CLIENT_SECRET=my_secretPythonimport os

os.environ["INDICINA_CLIENT_ID"] = "my_id"
os.environ["INDICINA_CLIENT_SECRET"] = "my_secret"You can get yourINDICINA_CLIENT_IDandINDICINA_CLIENT_SECRETfromGitHub Pages.Sample CodeJSON Statementfrom decide.models.analysis import Analysis
from decide.models.customer import Customer
from decide.models.statement import JSONStatement
import json

customer = Customer(customer_id="my_customer")

json_statement = json.loads("""{
    "paging": {
        "total": 190,
        "page": 2,
        "previous": "https://api.withmono.com/accounts/:id/transactions?page=2",
        "next": "https://api.withmono.com/accounts/:id/transactions?page=3"
    },
    "data": [
        {
            "_id": "12345",
            "amount": 10000,
            "date": "2020-07-21T00:00:00.000Z",
            "narration": "TRANSFER from *************",
            "type": "debit",
            "category": "E-CHANNELS"
        },
        {
            "_id": "12345",
            "amount": 20000,
            "date": "2020-07-21T00:00:00.000Z",
            "narration": "TRANSFER from ***********",
            "type": "debit",
            "category": "E-CHANNELS"
        }
    ]
}""")

statement = JSONStatement(statement_format=StatementFormat.MONO, statement=json_statement, customer=customer)
analysis: Analysis = statement.analyze()

print(analysis)
print(f"Analysis status is: {analysis.status}")PDF Statementfrom decide import PDFStatement
from decide.models.analysis import Analysis
...

customer = Customer(customer_id="my_customer")
statement = PDFStatement(customer=customer,
                         pdf_path="statement.pdf",
                         bank=Bank.ACCESS,
                         currency=Currency.NGN)

analysis: Analysis = statement.analyze()

print(analysis)
print(f"Analysis status is: {analysis.status}")CSV Statementfrom decide import CSVStatement
from decide.models.analysis import Analysis
...

customer = Customer(customer_id="my_customer")
statement = CSVStatement(csv_path="AverageOtherIncome.csv",
                         customer=customer)
analysis: Analysis = statement.analyze()

print(analysis)
print(f"Analysis status is: {analysis.status}")Sample Response{
    "status": "success",
    "data": {
        "country": "Nigeria",
        "currency": "NGN",
        "behaviouralAnalysis": {
            "accountSweep": "No",
            "gamblingRate": 0,
            "inflowOutflowRate": "Neutral Cash Flow",
            "loanAmount": 2000,
            "loanInflowRate": 0,
            "loanRepaymentInflowRate": 0,
            "loanRepayments": 54500,
            "topIncomingTransferAccount": "The",
            "topTransferRecipientAccount": "Day"
        },
        "cashFlowAnalysis": {
            "accountActivity": 0.84,
            "averageBalance": 1815942.46,
            "averageCredits": 153741.4861589404,
            "averageDebits": 31563.282248447216,
            "closingBalance": -202978.97,
            "firstDay": "2022-05-18",
            "lastDay": "2022-10-18",
            "monthPeriod": "May - October",
            "netAverageMonthlyEarnings": -1875144.8599999999,
            "noOfTransactingMonths": 6,
            "totalCreditTurnover": 23214964.409999996,
            "totalDebitTurnover": 25408442.21000001,
            "yearInStatement": "2022",
            "maxEmiEligibility": 48871,
            "emiConfidenceScore": 0.04
        },
        "incomeAnalysis": {
            "salaryEarner": "Yes",
            "medianIncome": 83150,
            "averageOtherIncome": 69187.43,
            "expectedSalaryDay": 25,
            "lastSalaryDate": "2022-08-25",
            "averageSalary": 2290408.08,
            "confidenceIntervalonSalaryDetection": 1,
            "salaryFrequency": "1",
            "numberSalaryPayments": 3,
            "numberOtherIncomePayments": 15,
            "gigWorker": "No"
        },
        "spendAnalysis": {
            "averageRecurringExpense": 360140.42,
            "hasRecurringExpense": "Yes",
            "averageMonthlyExpenses": 4234740.37,
            "expenseChannels": `expenseChannels`,(bills, entertainment,savingsAndInvestments,gambling,airtime,bankCharges,chequeWithdrawal,cashWithdrawal,shopping, eatingOut)
            "expenseCategories": `expenseCategories`
        },
        "transactionPatternAnalysis": {
            "MAWWZeroBalanceInAccount": {
                "month": null,
                "week_of_month": 0
            },
            "NODWBalanceLess5000": 35,
            "NODWBalanceLess": {
                "amount": 5000,
                "count": 35
            },
            "highestMAWOCredit": {
                "month": "August",
                "week_of_month": 3
            },
            "highestMAWODebit": {
                "month": "August",
                "week_of_month": 5
            },
            "lastDateOfCredit": "2022-10-17",
            "lastDateOfDebit": "2022-10-18",
            "mostFrequentBalanceRange": ">500000",
            "mostFrequentTransactionRange": "<10000",
            "recurringExpense": [
                {
                    "amount": 1650,
                    "description": "spend and save"
                },
                {
                    "amount": 4000,
                    "description": "airtime purchase 2347049215992"
                },
                {
                    "amount": 5000,
                    "description": "fuel"
                }
            ],
            "transactionsBetween100000And500000": 39,
            "transactionsBetween10000And100000": 176,
            "transactionsGreater500000": 24,
            "transactionsLess10000": 717,
            "transactionRanges": [
                {
                    "min": 10000,
                    "max": 100000,
                    "count": 176
                },
                {
                    "min": 100000,
                    "max": 500000,
                    "count": 39
                },
                {
                    "min": null,
                    "max": 10000,
                    "count": 717
                },
                {
                    "min": 500000,
                    "max": null,
                    "count": 24
                }
            ]
        }
    }
}Supported BanksIn selecting a bank to use for analysis, we maintain an ENUM of supported bankshere. We have also provided a convenient method to fetch a current list of supported banks.from decide import PDFStatement, Bank

# Enum selection of Bank
statement = PDFStatement(
            ...,
            bank=Bank.GTB,
            ...)from decide import Bank

# Get bank list
supported_bank_list = Bank.get_bank_list()


print(supported_bank_list)

output: A list of tuples [(bank_name, bank_code)]
[('Guaranty Trust Bank', '058'), ('Access Bank', '044')...]AnalysisWhen the Decide API sends a response, the response is represented in the Analysis class.Anatomy of an AnalysisAnalysis
|
|-- behaviouralAnalysis: `BehaviouralAnalysis`
|       |-- accountSweep
|       |-- gamblingRate
|       |-- inflowOutflowRate
|       |-- loanInflowRate
|       |-- loanRepaymentInflowRate
|       |-- loanRepayments
|       |-- topIncomingTransferAccount
|       |-- topTransferRecipientAccount
            |-- loanAmount
|
|-- cashFlowAnalysis: `CashFlowAnalysis`
|       |-- accountActivity
|       |-- averageBalance
|       |-- averageCredits
|       |-- averageDebits
|       |-- closingBalance
|       |-- firstDay
|       |-- lastDay
|       |-- monthPeriod
|       |-- netAverageMonthlyEarnings
|       |-- noOfTransactingMonths
|       |-- totalCreditTurnover
|       |-- totalDebitTurnover
|       |-- yearInStatement
|
|-- incomeAnalysis: `IncomeAnalysis`
|       |-- averageOtherIncome
|       |-- averageSalary
|       |-- confidenceIntervalOnSalaryDetection
|       |-- expectedSalaryDay
|       |-- lastSalaryDate
|       |-- medianIncome
|       |-- numberOtherIncomePayments
|       |-- numberOfSalaryPayments
|       |-- salaryEarner
|       |-- salaryFrequency
|       |-- gigWorker
|
|-- spendAnalysis: `SpendAnalysis`
|       |-- expenseChannels: `ExpenseChannels`
|       |       |-- atmSpend
|       |       |-- webSpend
|       |       |-- posSpend
|       |       |-- ussdTransactions
|       |       |-- mobileSpend
|       |       |-- spendOnTransfers
|       |       |-- internationalTransactionsSpend
|       |-- expenseCategories: `ExpenseCategories`
|       |       |-- bills
|       |       |-- entertainment
|       |       |-- savingsAndInvestments
|       |       |-- gambling
|       |       |-- airtime
|       |       |-- bankCharges
|       |-- averageRecurringExpense
|       |-- hasRecurringExpense
|       |-- totalExpenses
|-- transactionPatternAnalysis: `TransactionPatternAnalysis`
|       |-- highestMAWOCredit
|       |-- highestMAWODebit
|       |-- lastDateOfCredit
|       |-- lastDateOfDebit
|       |-- MAWWZeroBalanceInAccount
|       |-- mostFrequentBalanceRange
|       |-- mostFrequentTransactionRange
|       |-- NODWBalanceLess5000
|       |-- recurringExpense
|       |-- transactionsBetween100000And500000
|       |-- transactionsBetween10000And100000
|       |-- transactionsGreater500000
|       |-- transactionsLess10000
|       |-- transactionRanges
|       |-- NODWBalanceLess
|-- statusAnalysis StatusSome bank statement analyses (e.g. PDF) are asynchronous. You may not get the results of the analysis immediately.You may need to get the status of an analysis.PDFStatus could take one of the following values.StatusValueDescriptionDONEDONEThe analysis is doneFAILEDFAILEDThe analysis failedIN_PROGRESSIN_PROGRESSThe analysis is still in progress# Example usage on accessing the analysis response
analysis = statement.analyze()
print(f"Analysis status is: {analysis.status}")

# wait for analysis to be done
time.sleep(3)

print(analysis)
print(f"Analysis status is: {analysis.status}")
print(f"Credit turnover is: {analysis.cashFlowAnalysis.totalCreditTurnover}")
print(f"Average salary is: {analysis.incomeAnalysis.averageSalary}")
print(f"{analysis.behaviouralAnalysis}")
print(analysis.transaction_tags)Rules Engine DocumentationThe Rules Engine enables merchants to set up an automated decisioning process for lending, based on pre-determined conditions tailored to their unique use case. It allows users to:Develop multiple rule-based conditionsAnalyze statements with pre-determined conditions and rules that automate the decision-making processAutomatically filter qualifying applications based on the set rulesSet up an affordability logic to reveal what applicants can pay backImport Required Librariesfromdecide.models.rules_engineimport(Rule,RuleType,Condition,Operator,Outcome,Block,Status,BooleanRuleSet,Affordability,Duration,ScorecardAPI,ScorecardRequest,ScorecardResponse,ScorecardExecuteRequest)Set Environment Variablesimportosos.environ["INDICINA_CLIENT_ID"]="xxxxxx"os.environ["INDICINA_CLIENT_SECRET"]="xxxxx"Initialize ScorecardAPIsc=ScorecardAPI()Create ScorecardDefine RulesA Rule is defined as a condition to be evaluated on a certain value. You can have as many rules as you need. A Rule consists of several properties, including:order: an integer representing the order in which the rule is evaluatedrule_type: an Enum representing the type of rule (e.g., average balance, loan amount, etc.)value: a string representing the value of the rule (e.g., "10000" for an average balance of 10,000)condition: an ENUM representing the condition to be evaluated (e.g., "is equal to", "less than or equal to", etc.)operator: an ENUM representing the logical operator to be used when evaluating the rule (e.g., "and", "or", etc.)For example, the first rule defined in the code states that the average balance of an account must be exactly 10,000.rules=[Rule(order=1,rule_type=RuleType.AVERAGE_BALANCE,value="10000",condition=Condition.IS_EQUAL,operator=Operator.OPERATOR_AND),Rule(order=2,rule_type=RuleType.AVERAGE_CREDITS,value="5000",condition=Condition.IS_EQUAL,operator=Operator.OPERATOR_AND),Rule(order=3,rule_type=RuleType.LOAN_AMOUNT,value="20000",condition=Condition.LESS_THAN_EQUAL_TO,operator=Operator.OPERATOR_AND),]Create a BlockA Block is a collection of Rule objects that are evaluated together using a logical operator (e.g., "and", "or", etc.). A Block consists of several properties, including:rules: a list of Rule objects
order: an integer representing the order in which the block is evaluated
operator: an ENUM representing the logical operator to be used when evaluating the block (e.g., "and", "or", etc.)
negative_outcome: an Enum representing the outcome of the block if the evaluation is falseblock=Block(rules=rules,order=1,operator=Operator.OPERATOR_NONE,negative_outcome=Outcome.DECLINE)Create a Boolean Rule SetA BooleanRuleSet is a collection of Block objects that are evaluated together using a logical operator (e.g., "and", "or", etc.). A BooleanRuleSet consists of several properties, including:name: a string representing the name of the rule setpositive_outcome: an ENUM representing the outcome of the rule set if the evaluation is truenegative_outcome: an ENUM representing the outcome of the rule set if the evaluation is falseowner: a string representing the owner of the rule setblocks: a list of Block objectsboolean_rule_set=BooleanRuleSet(name="DPL",positive_outcome=Outcome.ACCEPT,negative_outcome=Outcome.DECLINE,owner="indicina",blocks=[block])Define Affordability LogicAffordability logic defines the logic to determine what the applicant can pay back. It is made up of two properties:monthly_interest_rate: interest rate per monthmonthly_durations: a list of Duration objects representing the tenures they wish to provide their service foraffordability_logic=Affordability(monthly_interest_rate=10,monthly_durations=[Duration(3),Duration(4)])Create a Scorecard RequestThe ScorecardRequest object is the final object that is created and is used to make requests to the ScorecardAPI. It contains the name of the scorecard, the boolean rule set, the affordability logic, and the status of the scorecard (whether it is enabled or disabled).sc_request=ScorecardRequest(name="DPL",boolean_rule_set=boolean_rule_set,affordability=affordability_logic,status=Status.ENABLED)scorecard=sc.create_scorecard(sc_request)print(scorecard.scorecard_id)Read ScorecardTo retrieve an existing scorecard, we can use the get_scorecard method. The method takes the ID of the scorecard as input and returns a Scorecard object.card_61=sc.read_scorecard("61")Update ScorecardWe can create a new ScorecardRequest object with the updated information and use the update_scorecard method. The method takes the ID of the scorecard and the new ScorecardRequest object as input and updates the scorecard with the new information.rules=[Rule(order=1,rule_type=RuleType.AVERAGE_BALANCE,value="1000",condition=Condition.IS_EQUAL,operator=Operator.OPERATOR_AND),Rule(order=2,rule_type=RuleType.AVERAGE_CREDITS,value="500",condition=Condition.IS_EQUAL,operator=Operator.OPERATOR_AND),Rule(order=3,rule_type=RuleType.LOAN_AMOUNT,value="2000",condition=Condition.LESS_THAN_EQUAL_TO,operator=Operator.OPERATOR_AND),]block=Block(rules=rules,order=1,operator=Operator.OPERATOR_NONE,negative_outcome=Outcome.DECLINE)boolean_rule_set=BooleanRuleSet(name="DPL",positive_outcome=Outcome.ACCEPT,negative_outcome=Outcome.DECLINE,owner="indicina",blocks=[block])affordability_logic=Affordability(20,[Duration(5),Duration(14)])sc_updated_request=ScorecardRequest(name="DPL_CHANGE_NAME",boolean_rule_set=boolean_rule_set,affordability=affordability_logic,status=Status.ENABLED)card_61_updated=sc.update_scorecard(str(card_61.scorecard_id),sc_updated_request)Delete ScorecardTo delete an existing scorecard, we can use the delete_scorecard method. The method takes the ID of the scorecard as input and deletes the scorecard.sc.delete_scorecard(str(card_61.scorecard_id))Execute Scorecard on AnalysisTo execute a scorecard on an existing Analysis, we can use the execute_scorecard method. The method takes the ID of the existing analysis as input and a list of scorecard IDs to run on the analysis.a_id="xxxxxxxxxxxxxxxxxxx"s_ids=[32,45,47]scorecard_execute_request=ScorecardExecuteRequest(analysis_id=a_id,scorecard_ids=s_ids)res=sc.execute_scorecard(scorecard_execute_request)print(res['scorecardResults'])Analyse statement with ScorecardTo analyse a statement with some already created scorecards/rules, pass in the ids of the scorecards as below:statement=JSONStatement(statement_format=StatementFormat.MONO,statement=statement,customer=customer,scorecard_ids=[47])analysis:Analysis=statement.analyze()print(analysis)print(f"Analysis status is:{analysis.status}")This concludes the documentation for using the Rules Engine, which covers creating, updating, deleting, and executing scorecards on existing analyses. Use this guide as a reference for implementing the automated decision-making process tailored to your specific lending use case.ContributionSetup ProjectThe link for this projects's repository can be foundhereCloning the projectFor HTTPSUse this commandgit clone https://github.com/indicina-dev/decide-python.gitFor SSHUse this commandgit clone git@github.com:indicina-dev/decide-python.gitRunning the projectCreate virtual enviromentInstall the requirements.txt filepip install -r requirements.txtRun filesContribute to ProjectDo you find the project interesting and you would like to contribute to our project?Fork the repository to your github accountClone the repository to your local machineCreate a new branch for your fix (preferably descriptive to your contribution)Make appropriate changes and tests for the changesUsegit add insert-paths-of-changed-files-hereto add the file contents of the changed files to the "snapshot" git uses for project managementCommitting: As a means to create a seamless development and contribution flow, we require that commits be standardized, following the conventionalcommits guideline:<type>[optional scope]: <description>

[optional body]

[optional footer(s)]We have included the hook script to verify your commits, you will need to install it as follows:pip install pre-commit
pre-commit install --hook-type commit-msgExamples of good commits:adding a new feature:git commit -m "feat: allow provided config object to extend other configs"adding a breaking change, take note of the!:git commit -m "feat!: send an email to the customer when a product is shipped"Push the changes to the remote repository usinggit push origin branch-name-hereSubmit a pull request to the upstream repositoryTitle the pull request with a short description of the changes made and the issue or bug number associated with your change. For example, you can title an issue like soAdded more log outputting to resolve #4352.Wait for our in-house developers to approve the merge requests or update the merge requests if changes were requested,IssuesTo create an issue, simply click on the issues tab on the menu and create a new issue.