dscf-credit 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c03116320f239a1f9f953742c429298bf629e0731e1938dc1870107bd2e3a74f
|
4
|
+
data.tar.gz: da5c7c4a877ce2c82cd0300c25c9686f8ea1254bcfd9b82c856ecd99fb8ef5a7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ae96ca00a9c2cef5bb46b59a58e0b4cf13263cb89903a19bbf8c1a03c709d2a75ac59e4ea394668c4a5f2b2ea298cbfd963c22eb1af810a5fb4c6ed9c266bb3
|
7
|
+
data.tar.gz: 879a870adbb92ac789ed00096d658e1c729107b94572c4ee66df10c31fcf123b549ceec50499b295cb16e94f446f0218ad7f0c6430fd5839c9c91f31e4ff19fb
|
@@ -2,6 +2,26 @@ module Dscf::Credit
|
|
2
2
|
class LoanApplicationsController < ApplicationController
|
3
3
|
include Dscf::Core::Common
|
4
4
|
include Dscf::Core::ReviewableController
|
5
|
+
reviewable_context :default,
|
6
|
+
statuses: %w[pending approved rejected modify],
|
7
|
+
initial_status: "pending",
|
8
|
+
transitions: {
|
9
|
+
"pending" => %w[approved rejected modify],
|
10
|
+
"modify" => [ "pending" ]
|
11
|
+
},
|
12
|
+
actions: {
|
13
|
+
approve: {
|
14
|
+
status: "approved",
|
15
|
+
after: ->(review) {
|
16
|
+
loan_application = review.reviewable
|
17
|
+
score = loan_application.score
|
18
|
+
LoanApplicationsController.new.create_loan_profile_for_approved_application(loan_application, score)
|
19
|
+
}
|
20
|
+
},
|
21
|
+
reject: { status: "rejected", require_feedback: true },
|
22
|
+
request_modification: { status: "modify", require_feedback: true },
|
23
|
+
resubmit: { status: "pending", update_model: true }
|
24
|
+
}
|
5
25
|
|
6
26
|
def create
|
7
27
|
super do
|
@@ -187,6 +207,27 @@ module Dscf::Credit
|
|
187
207
|
profile_result
|
188
208
|
end
|
189
209
|
|
210
|
+
def create_loan_profile_from_review(review)
|
211
|
+
loan_application = review.reviewable # Assumes Review belongs_to :reviewable (polymorphic or direct association to LoanApplication)
|
212
|
+
score = loan_application.score # Assumes score is already set on the loan application
|
213
|
+
|
214
|
+
unless score
|
215
|
+
Rails.logger.warn "No score available for loan application #{loan_application.id} during approve callback"
|
216
|
+
return
|
217
|
+
end
|
218
|
+
|
219
|
+
profile_service = LoanProfileCreationService.new(loan_application, score)
|
220
|
+
profile_result = profile_service.create_loan_profile
|
221
|
+
|
222
|
+
unless profile_result[:success]
|
223
|
+
error_message = "Failed to create loan profile for approved application #{loan_application.id}: #{profile_result[:error]}"
|
224
|
+
Rails.logger.error error_message
|
225
|
+
raise StandardError, error_message # This will rollback the transaction in perform_review_action
|
226
|
+
else
|
227
|
+
Rails.logger.info "Loan profile created successfully for approved application #{loan_application.id} via review callback"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
190
231
|
def model_params
|
191
232
|
params.require(:loan_application).permit(
|
192
233
|
:bank_id,
|
@@ -107,41 +107,47 @@ module Dscf::Credit
|
|
107
107
|
end
|
108
108
|
|
109
109
|
def extract_parameter_value(parameter)
|
110
|
-
param_type_name = parameter.scoring_param_type.name
|
111
110
|
parameter_id = parameter.id.to_s
|
112
111
|
|
113
|
-
|
114
|
-
|
115
|
-
|
112
|
+
all_json_fields = {
|
113
|
+
user_info: loan_application.user_info || {},
|
114
|
+
bank_info: loan_application.bank_info || {},
|
115
|
+
facilitator_info: loan_application.facilitator_info || {},
|
116
|
+
field_assessment: loan_application.field_assessment || {}
|
117
|
+
}
|
118
|
+
|
119
|
+
param_data = nil
|
120
|
+
found_in_field = nil
|
121
|
+
|
122
|
+
all_json_fields.each do |field_name, field_data|
|
123
|
+
if field_data.key?(parameter_id)
|
124
|
+
param_data = field_data[parameter_id]
|
125
|
+
found_in_field = field_name
|
126
|
+
break # Stop at first match (IDs should be unique across fields)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
if param_data.nil?
|
131
|
+
Rails.logger.warn "Parameter #{parameter_id} (#{parameter.name}) not found in any JSON field for loan_app #{loan_application.id}"
|
132
|
+
return nil
|
133
|
+
end
|
116
134
|
|
117
|
-
|
118
|
-
|
119
|
-
|
135
|
+
unless param_data.is_a?(Hash)
|
136
|
+
Rails.logger.warn "Invalid param_data for #{parameter_id} in #{found_in_field}: expected Hash, got #{param_data.class}"
|
137
|
+
return nil
|
138
|
+
end
|
120
139
|
|
121
|
-
# Validate
|
140
|
+
# Validate param_name matches (optional safety check)
|
122
141
|
expected_param_name = param_data["param_name"]
|
123
142
|
if expected_param_name && expected_param_name != parameter.name
|
124
|
-
Rails.logger.warn "Parameter name mismatch for ID #{parameter_id}: expected '#{parameter.name}', got '#{expected_param_name}'"
|
143
|
+
Rails.logger.warn "Parameter name mismatch for ID #{parameter_id} in #{found_in_field}: expected '#{parameter.name}', got '#{expected_param_name}'"
|
125
144
|
end
|
126
145
|
|
127
|
-
param_data["value"]
|
146
|
+
raw_value = param_data["value"]
|
147
|
+
Rails.logger.debug "Extracted #{parameter.name} (ID #{parameter_id}) from #{found_in_field}: value=#{raw_value}"
|
148
|
+
raw_value
|
128
149
|
end
|
129
150
|
|
130
|
-
def get_json_field_for_param_type(param_type_name)
|
131
|
-
case param_type_name.downcase
|
132
|
-
when "user_info"
|
133
|
-
loan_application.user_info || {}
|
134
|
-
when "bank_info"
|
135
|
-
loan_application.bank_info || {}
|
136
|
-
when "facilitator_info"
|
137
|
-
loan_application.facilitator_info || {}
|
138
|
-
when "field_assessment"
|
139
|
-
loan_application.field_assessment || {}
|
140
|
-
else
|
141
|
-
Rails.logger.warn "Unknown parameter type: #{param_type_name}"
|
142
|
-
nil
|
143
|
-
end
|
144
|
-
end
|
145
151
|
|
146
152
|
def normalize_parameter_value(parameter, raw_value)
|
147
153
|
return 0.0 if raw_value.nil?
|
@@ -249,7 +255,7 @@ module Dscf::Credit
|
|
249
255
|
when 0...50
|
250
256
|
"rejected"
|
251
257
|
when 50..60
|
252
|
-
"
|
258
|
+
"pending"
|
253
259
|
else # score > 60
|
254
260
|
"approved"
|
255
261
|
end
|
data/db/seeds.rb
CHANGED
@@ -568,99 +568,6 @@ puts "Seeding loan profile scoring specs..."
|
|
568
568
|
# Note: This will be moved after loan profiles are created
|
569
569
|
|
570
570
|
# 10.5. Loan Applications (depends on banks, users, and bank branches)
|
571
|
-
puts "Seeding loan applications..."
|
572
|
-
loan_application1 = Dscf::Credit::LoanApplication.find_or_create_by(
|
573
|
-
bank: bunna_bank,
|
574
|
-
user_id: user2.id,
|
575
|
-
review_branch: bunna_head_office
|
576
|
-
) do |application|
|
577
|
-
application.backer = user1
|
578
|
-
application.bank_statement_source = "internal"
|
579
|
-
application.user_info = {
|
580
|
-
name: "Jane Smith",
|
581
|
-
phone: "+251922334455",
|
582
|
-
email: "jane.smith@example.com",
|
583
|
-
address: "123 Main St, Addis Ababa",
|
584
|
-
id_number: "ID123456789",
|
585
|
-
date_of_birth: "1985-05-15"
|
586
|
-
}
|
587
|
-
application.facilitator_info = {
|
588
|
-
business_name: "ABC Trading Company",
|
589
|
-
license_number: "LIC001234",
|
590
|
-
business_type: "retail",
|
591
|
-
years_in_business: 5,
|
592
|
-
monthly_turnover: 150000.00
|
593
|
-
}
|
594
|
-
application.bank_info = {
|
595
|
-
account_number: "123456789012",
|
596
|
-
account_holder: "Jane Smith",
|
597
|
-
bank_name: "Bunna Bank",
|
598
|
-
branch_name: "Head Office"
|
599
|
-
}
|
600
|
-
application.field_assessment = {
|
601
|
-
credit_score: 720,
|
602
|
-
risk_level: "medium",
|
603
|
-
recommendation: "approved",
|
604
|
-
assessed_by: "Field Officer A",
|
605
|
-
assessment_date: Time.current.iso8601
|
606
|
-
}
|
607
|
-
end
|
608
|
-
|
609
|
-
# Create approved review for loan_application1
|
610
|
-
Dscf::Core::Review.find_or_create_by(
|
611
|
-
reviewable: loan_application1,
|
612
|
-
reviewed_by: admin_user
|
613
|
-
) do |review|
|
614
|
-
review.status = 'approved'
|
615
|
-
review.reviewed_at = Time.current
|
616
|
-
end
|
617
|
-
|
618
|
-
loan_application2 = Dscf::Credit::LoanApplication.find_or_create_by(
|
619
|
-
bank: bunna_bank,
|
620
|
-
user_id: user3.id,
|
621
|
-
review_branch: bunna_merkato_branch
|
622
|
-
) do |application|
|
623
|
-
application.backer = user1
|
624
|
-
application.bank_statement_source = "external"
|
625
|
-
application.user_info = {
|
626
|
-
name: "Mike Johnson",
|
627
|
-
phone: "+251933445566",
|
628
|
-
email: "mike.johnson@example.com",
|
629
|
-
address: "456 Oak Ave, Addis Ababa",
|
630
|
-
id_number: "ID987654321",
|
631
|
-
date_of_birth: "1980-12-10"
|
632
|
-
}
|
633
|
-
application.facilitator_info = {
|
634
|
-
business_name: "XYZ Wholesale",
|
635
|
-
license_number: "LIC005678",
|
636
|
-
business_type: "wholesale",
|
637
|
-
years_in_business: 8,
|
638
|
-
monthly_turnover: 500000.00
|
639
|
-
}
|
640
|
-
application.bank_info = {
|
641
|
-
account_number: "987654321098",
|
642
|
-
account_holder: "Mike Johnson",
|
643
|
-
bank_name: "Commercial Bank",
|
644
|
-
branch_name: "Main Branch"
|
645
|
-
}
|
646
|
-
application.field_assessment = {
|
647
|
-
credit_score: 650,
|
648
|
-
risk_level: "high",
|
649
|
-
recommendation: "conditional_approval",
|
650
|
-
assessed_by: "Field Officer B",
|
651
|
-
assessment_date: Time.current.iso8601
|
652
|
-
}
|
653
|
-
application.score = 65.0
|
654
|
-
end
|
655
|
-
|
656
|
-
# Create pending review for loan_application2
|
657
|
-
Dscf::Core::Review.find_or_create_by(
|
658
|
-
reviewable: loan_application2,
|
659
|
-
reviewed_by: bank_admin
|
660
|
-
) do |review|
|
661
|
-
review.status = 'pending'
|
662
|
-
review.reviewed_at = Time.current
|
663
|
-
end
|
664
571
|
|
665
572
|
# 10.6. Facilitator Applications (depends on users and banks)
|
666
573
|
puts "Seeding facilitator applications..."
|
@@ -750,119 +657,6 @@ Dscf::Core::Review.find_or_create_by(
|
|
750
657
|
end
|
751
658
|
|
752
659
|
# 11. Loan Profiles (depends on loan applications)
|
753
|
-
puts "Seeding loan profiles..."
|
754
|
-
loan_profile1 = Dscf::Credit::LoanProfile.find_or_create_by(
|
755
|
-
loan_application: loan_application1
|
756
|
-
) do |profile|
|
757
|
-
profile.code = "BB000001"
|
758
|
-
profile.score = 78.5
|
759
|
-
profile.total_limit = 100000.00
|
760
|
-
end
|
761
|
-
|
762
|
-
# Create approved review for loan_profile1
|
763
|
-
Dscf::Core::Review.find_or_create_by(
|
764
|
-
reviewable: loan_profile1,
|
765
|
-
reviewed_by: admin_user
|
766
|
-
) do |review|
|
767
|
-
review.status = 'approved'
|
768
|
-
review.reviewed_at = Time.current
|
769
|
-
end
|
770
|
-
|
771
|
-
# 12. Loan Profile Scoring Specs (depends on loan profiles)
|
772
|
-
puts "Seeding loan profile scoring specs..."
|
773
|
-
Dscf::Credit::LoanProfileScoringSpec.create!(
|
774
|
-
loan_profile: loan_profile1,
|
775
|
-
scoring_input_data: {
|
776
|
-
'monthly_income' => 45000,
|
777
|
-
'credit_history_score' => 720,
|
778
|
-
'employment_type' => 'permanent',
|
779
|
-
'years_at_job' => 3,
|
780
|
-
'existing_debt' => 15000,
|
781
|
-
'average_daily_purchase' => 15000 # Wholesaler's average daily purchase volume
|
782
|
-
},
|
783
|
-
score: 78.5,
|
784
|
-
total_limit: 100000.00,
|
785
|
-
active: true,
|
786
|
-
created_by: admin_user
|
787
|
-
)
|
788
|
-
|
789
|
-
# 11. Eligible Credit Lines (depends on loan profiles and credit lines)
|
790
|
-
puts "Seeding eligible credit lines..."
|
791
|
-
Dscf::Credit::EligibleCreditLine.create!(
|
792
|
-
loan_profile: loan_profile1,
|
793
|
-
credit_line: credit_line1,
|
794
|
-
credit_limit: 75000.00,
|
795
|
-
available_limit: 67500.00,
|
796
|
-
risk: 0.35
|
797
|
-
)
|
798
|
-
|
799
|
-
Dscf::Credit::EligibleCreditLine.create!(
|
800
|
-
loan_profile: loan_profile1,
|
801
|
-
credit_line: credit_line2,
|
802
|
-
credit_limit: 50000.00,
|
803
|
-
available_limit: 42500.00,
|
804
|
-
risk: 0.45
|
805
|
-
)
|
806
|
-
|
807
|
-
# 12. Loans (depends on loan profiles and credit lines)
|
808
|
-
puts "Seeding loans..."
|
809
|
-
loan1 = Dscf::Credit::Loan.create!(
|
810
|
-
loan_profile: loan_profile1,
|
811
|
-
credit_line: credit_line1,
|
812
|
-
status: 'active',
|
813
|
-
principal_amount: 50000.00,
|
814
|
-
remaining_amount: 50000.00,
|
815
|
-
due_date: 3.months.from_now.to_date,
|
816
|
-
disbursed_at: 1.day.ago,
|
817
|
-
active: true
|
818
|
-
)
|
819
|
-
|
820
|
-
# 12.5. Loan Accruals (depends on loans)
|
821
|
-
puts "Seeding loan accruals..."
|
822
|
-
Dscf::Credit::LoanAccrual.create!(
|
823
|
-
loan: loan1,
|
824
|
-
accrual_type: 'interest',
|
825
|
-
amount: 250.00,
|
826
|
-
applied_on: 1.month.ago.to_date,
|
827
|
-
status: 'pending'
|
828
|
-
)
|
829
|
-
|
830
|
-
Dscf::Credit::LoanAccrual.create!(
|
831
|
-
loan: loan1,
|
832
|
-
accrual_type: 'facilitation_fee',
|
833
|
-
amount: 1000.00,
|
834
|
-
applied_on: 1.day.ago.to_date,
|
835
|
-
status: 'paid'
|
836
|
-
)
|
837
|
-
|
838
|
-
# 13. Loan Transactions (depends on loans)
|
839
|
-
puts "Seeding loan transactions..."
|
840
|
-
Dscf::Credit::LoanTransaction.create!(
|
841
|
-
loan: loan1,
|
842
|
-
transaction_type: 'disbursement',
|
843
|
-
amount: 50000.00,
|
844
|
-
transaction_reference: "TXN-DISB-#{Time.current.to_i}",
|
845
|
-
status: 'completed'
|
846
|
-
)
|
847
|
-
|
848
|
-
Dscf::Credit::LoanTransaction.create!(
|
849
|
-
loan: loan1,
|
850
|
-
transaction_type: 'interest_accrual',
|
851
|
-
amount: 2500.00,
|
852
|
-
transaction_reference: "TXN-INT-#{Time.current.to_i}",
|
853
|
-
status: 'completed'
|
854
|
-
)
|
855
|
-
|
856
|
-
# 14. Daily Routine Transactions (depends on loans)
|
857
|
-
puts "Seeding daily routine transactions..."
|
858
|
-
Dscf::Credit::DailyRoutineTransaction.create!(
|
859
|
-
loan: loan1,
|
860
|
-
routine_type: 'interest_calculation',
|
861
|
-
amount: 83.33,
|
862
|
-
status: 'completed',
|
863
|
-
initiated_at: Date.current.beginning_of_day,
|
864
|
-
approved_at: Date.current.beginning_of_day + 1.hour
|
865
|
-
)
|
866
660
|
|
867
661
|
puts "DSCF Credit Engine seed data completed successfully!"
|
868
662
|
puts "Created sample data for all models with proper relationships."
|
data/lib/dscf/credit/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dscf-credit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adoniyas
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-10-
|
10
|
+
date: 2025-10-07 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: dscf-core
|