nanopy-bank 1.0.8__py3-none-any.whl

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.
Files changed (47) hide show
  1. nanopy_bank/__init__.py +20 -0
  2. nanopy_bank/api/__init__.py +10 -0
  3. nanopy_bank/api/server.py +242 -0
  4. nanopy_bank/app.py +282 -0
  5. nanopy_bank/cli.py +152 -0
  6. nanopy_bank/core/__init__.py +85 -0
  7. nanopy_bank/core/audit.py +404 -0
  8. nanopy_bank/core/auth.py +306 -0
  9. nanopy_bank/core/bank.py +407 -0
  10. nanopy_bank/core/beneficiary.py +258 -0
  11. nanopy_bank/core/branch.py +319 -0
  12. nanopy_bank/core/fees.py +243 -0
  13. nanopy_bank/core/holding.py +416 -0
  14. nanopy_bank/core/models.py +308 -0
  15. nanopy_bank/core/products.py +300 -0
  16. nanopy_bank/data/__init__.py +31 -0
  17. nanopy_bank/data/demo.py +846 -0
  18. nanopy_bank/documents/__init__.py +11 -0
  19. nanopy_bank/documents/statement.py +304 -0
  20. nanopy_bank/sepa/__init__.py +10 -0
  21. nanopy_bank/sepa/sepa.py +452 -0
  22. nanopy_bank/storage/__init__.py +11 -0
  23. nanopy_bank/storage/json_storage.py +127 -0
  24. nanopy_bank/storage/sqlite_storage.py +326 -0
  25. nanopy_bank/ui/__init__.py +14 -0
  26. nanopy_bank/ui/pages/__init__.py +33 -0
  27. nanopy_bank/ui/pages/accounts.py +85 -0
  28. nanopy_bank/ui/pages/advisor.py +140 -0
  29. nanopy_bank/ui/pages/audit.py +73 -0
  30. nanopy_bank/ui/pages/beneficiaries.py +115 -0
  31. nanopy_bank/ui/pages/branches.py +64 -0
  32. nanopy_bank/ui/pages/cards.py +36 -0
  33. nanopy_bank/ui/pages/common.py +18 -0
  34. nanopy_bank/ui/pages/dashboard.py +100 -0
  35. nanopy_bank/ui/pages/fees.py +60 -0
  36. nanopy_bank/ui/pages/holding.py +943 -0
  37. nanopy_bank/ui/pages/loans.py +105 -0
  38. nanopy_bank/ui/pages/login.py +174 -0
  39. nanopy_bank/ui/pages/sepa.py +118 -0
  40. nanopy_bank/ui/pages/settings.py +48 -0
  41. nanopy_bank/ui/pages/transfers.py +94 -0
  42. nanopy_bank/ui/pages.py +16 -0
  43. nanopy_bank-1.0.8.dist-info/METADATA +72 -0
  44. nanopy_bank-1.0.8.dist-info/RECORD +47 -0
  45. nanopy_bank-1.0.8.dist-info/WHEEL +5 -0
  46. nanopy_bank-1.0.8.dist-info/entry_points.txt +2 -0
  47. nanopy_bank-1.0.8.dist-info/top_level.txt +1 -0
@@ -0,0 +1,846 @@
1
+ """
2
+ Demo/Test Data for NanoPy Bank
3
+
4
+ This file contains all fake data for testing and demonstration purposes.
5
+ Nothing is hardcoded in the main model files.
6
+ """
7
+
8
+ from datetime import datetime, date, timedelta
9
+ from decimal import Decimal
10
+ import random
11
+
12
+ # Import models
13
+ from ..core.models import (
14
+ Customer, Account, Transaction, Card,
15
+ AccountType, AccountStatus, Currency,
16
+ TransactionType,
17
+ CardType, CardStatus
18
+ )
19
+ from ..core.beneficiary import (
20
+ Beneficiary, StandingOrder, SEPAMandate,
21
+ OrderFrequency, OrderStatus, MandateType, MandateStatus
22
+ )
23
+ from ..core.products import (
24
+ Loan, Insurance, SavingsProduct,
25
+ LoanType, LoanStatus, InsuranceType, SavingsType
26
+ )
27
+ from ..core.fees import Fee, InterestRate, FeeType, RateType
28
+ from ..core.branch import Branch, Employee, ATM, BranchType, EmployeeRole
29
+
30
+
31
+ # =============================================================================
32
+ # DEMO CUSTOMERS
33
+ # =============================================================================
34
+
35
+ DEMO_CUSTOMERS = [
36
+ {
37
+ "customer_id": "CUST001",
38
+ "first_name": "Jean",
39
+ "last_name": "Dupont",
40
+ "email": "jean.dupont@email.fr",
41
+ "phone": "+33612345678",
42
+ "address": "15 Rue de la Paix",
43
+ "city": "Paris",
44
+ "postal_code": "75001",
45
+ "country": "FR",
46
+ "birth_date": date(1985, 3, 15),
47
+ "nationality": "FR",
48
+ "occupation": "Ingenieur",
49
+ "income_range": "50000-75000",
50
+ },
51
+ {
52
+ "customer_id": "CUST002",
53
+ "first_name": "Marie",
54
+ "last_name": "Martin",
55
+ "email": "marie.martin@email.fr",
56
+ "phone": "+33623456789",
57
+ "address": "42 Avenue des Champs",
58
+ "city": "Lyon",
59
+ "postal_code": "69001",
60
+ "country": "FR",
61
+ "birth_date": date(1990, 7, 22),
62
+ "nationality": "FR",
63
+ "occupation": "Medecin",
64
+ "income_range": "75000-100000",
65
+ },
66
+ {
67
+ "customer_id": "CUST003",
68
+ "first_name": "Pierre",
69
+ "last_name": "Bernard",
70
+ "email": "pierre.bernard@email.fr",
71
+ "phone": "+33634567890",
72
+ "address": "8 Boulevard Victor Hugo",
73
+ "city": "Marseille",
74
+ "postal_code": "13001",
75
+ "country": "FR",
76
+ "birth_date": date(1978, 11, 8),
77
+ "nationality": "FR",
78
+ "occupation": "Commercant",
79
+ "income_range": "35000-50000",
80
+ },
81
+ {
82
+ "customer_id": "CUST004",
83
+ "first_name": "Sophie",
84
+ "last_name": "Petit",
85
+ "email": "sophie.petit@email.fr",
86
+ "phone": "+33645678901",
87
+ "address": "25 Rue du Commerce",
88
+ "city": "Bordeaux",
89
+ "postal_code": "33000",
90
+ "country": "FR",
91
+ "birth_date": date(1995, 1, 30),
92
+ "nationality": "FR",
93
+ "occupation": "Etudiante",
94
+ "income_range": "0-15000",
95
+ },
96
+ ]
97
+
98
+
99
+ # =============================================================================
100
+ # DEMO ACCOUNTS
101
+ # =============================================================================
102
+
103
+ DEMO_ACCOUNTS = [
104
+ # Jean Dupont - Compte courant
105
+ {
106
+ "iban": "FR7630001007941234567890185",
107
+ "bic": "NANPFRPP",
108
+ "customer_id": "CUST001",
109
+ "account_type": AccountType.CHECKING,
110
+ "account_name": "Compte Courant",
111
+ "currency": Currency.EUR,
112
+ "balance": Decimal("3542.87"),
113
+ "overdraft_limit": Decimal("500.00"),
114
+ },
115
+ # Jean Dupont - Livret A
116
+ {
117
+ "iban": "FR7630001007941234567890186",
118
+ "bic": "NANPFRPP",
119
+ "customer_id": "CUST001",
120
+ "account_type": AccountType.SAVINGS,
121
+ "account_name": "Livret A",
122
+ "currency": Currency.EUR,
123
+ "balance": Decimal("15000.00"),
124
+ },
125
+ # Marie Martin - Compte courant
126
+ {
127
+ "iban": "FR7630001007942345678901234",
128
+ "bic": "NANPFRPP",
129
+ "customer_id": "CUST002",
130
+ "account_type": AccountType.CHECKING,
131
+ "account_name": "Compte Courant",
132
+ "currency": Currency.EUR,
133
+ "balance": Decimal("8234.56"),
134
+ "overdraft_limit": Decimal("1000.00"),
135
+ },
136
+ # Pierre Bernard - Compte courant
137
+ {
138
+ "iban": "FR7630001007943456789012345",
139
+ "bic": "NANPFRPP",
140
+ "customer_id": "CUST003",
141
+ "account_type": AccountType.CHECKING,
142
+ "account_name": "Compte Pro",
143
+ "currency": Currency.EUR,
144
+ "balance": Decimal("12456.00"),
145
+ "overdraft_limit": Decimal("2000.00"),
146
+ },
147
+ # Sophie Petit - Compte courant
148
+ {
149
+ "iban": "FR7630001007944567890123456",
150
+ "bic": "NANPFRPP",
151
+ "customer_id": "CUST004",
152
+ "account_type": AccountType.CHECKING,
153
+ "account_name": "Compte Courant",
154
+ "currency": Currency.EUR,
155
+ "balance": Decimal("456.23"),
156
+ "overdraft_limit": Decimal("200.00"),
157
+ },
158
+ ]
159
+
160
+
161
+ # =============================================================================
162
+ # DEMO TRANSACTIONS
163
+ # =============================================================================
164
+
165
+ def generate_demo_transactions():
166
+ """Generate realistic demo transactions"""
167
+ transactions = []
168
+ base_date = datetime.now() - timedelta(days=90)
169
+
170
+ # Transaction templates
171
+ templates = [
172
+ # Credits
173
+ {"label": "VIREMENT SALAIRE", "amount": Decimal("2850.00"), "type": TransactionType.CREDIT_TRANSFER, "is_salary": True},
174
+ {"label": "REMBOURSEMENT SECU", "amount": Decimal("45.60"), "type": TransactionType.CREDIT_TRANSFER},
175
+ {"label": "VIREMENT DE M. DUPONT", "amount": Decimal("150.00"), "type": TransactionType.CREDIT_TRANSFER},
176
+
177
+ # Debits
178
+ {"label": "LOYER JANVIER", "amount": Decimal("850.00"), "type": TransactionType.DIRECT_DEBIT},
179
+ {"label": "EDF ELECTRICITE", "amount": Decimal("78.45"), "type": TransactionType.DIRECT_DEBIT},
180
+ {"label": "ORANGE MOBILE", "amount": Decimal("29.99"), "type": TransactionType.DIRECT_DEBIT},
181
+ {"label": "CARREFOUR PARIS", "amount": Decimal("156.78"), "type": TransactionType.CARD_PAYMENT},
182
+ {"label": "AMAZON EU", "amount": Decimal("45.99"), "type": TransactionType.CARD_PAYMENT},
183
+ {"label": "SNCF CONNECT", "amount": Decimal("89.00"), "type": TransactionType.CARD_PAYMENT},
184
+ {"label": "RETRAIT DAB 75001", "amount": Decimal("60.00"), "type": TransactionType.ATM_WITHDRAWAL},
185
+ {"label": "BOULANGERIE PAUL", "amount": Decimal("8.50"), "type": TransactionType.CARD_PAYMENT},
186
+ {"label": "UBER EATS", "amount": Decimal("22.40"), "type": TransactionType.CARD_PAYMENT},
187
+ ]
188
+
189
+ # Generate transactions for each account
190
+ for account in DEMO_ACCOUNTS:
191
+ if account["account_type"] != AccountType.CHECKING:
192
+ continue
193
+
194
+ iban = account["iban"]
195
+ current_date = base_date
196
+
197
+ for i in range(30):
198
+ # Pick random templates
199
+ num_tx = random.randint(1, 4)
200
+ for _ in range(num_tx):
201
+ template = random.choice(templates)
202
+
203
+ is_credit = template["type"] == TransactionType.CREDIT_TRANSFER
204
+
205
+ tx = {
206
+ "account_iban": iban,
207
+ "transaction_type": template["type"],
208
+ "amount": template["amount"] + Decimal(str(random.randint(-10, 10))),
209
+ "currency": "EUR",
210
+ "label": template["label"],
211
+ "description": f"Transaction du {current_date.strftime('%d/%m/%Y')}",
212
+ "created_at": current_date + timedelta(hours=random.randint(8, 20)),
213
+ "is_credit": is_credit,
214
+ }
215
+
216
+ if not is_credit:
217
+ tx["counterparty_name"] = template["label"].split()[0]
218
+
219
+ transactions.append(tx)
220
+
221
+ current_date += timedelta(days=random.randint(1, 5))
222
+ if current_date > datetime.now():
223
+ break
224
+
225
+ return transactions
226
+
227
+ DEMO_TRANSACTIONS = generate_demo_transactions()
228
+
229
+
230
+ # =============================================================================
231
+ # DEMO CARDS
232
+ # =============================================================================
233
+
234
+ DEMO_CARDS = [
235
+ {
236
+ "card_number": "4970XXXXXXXX1234",
237
+ "customer_id": "CUST001",
238
+ "account_iban": "FR7630001007941234567890185",
239
+ "card_type": CardType.DEBIT,
240
+ "cardholder_name": "JEAN DUPONT",
241
+ "expiry_date": "12/27",
242
+ "daily_limit": Decimal("1000.00"),
243
+ "monthly_limit": Decimal("5000.00"),
244
+ "contactless_enabled": True,
245
+ "online_enabled": True,
246
+ },
247
+ {
248
+ "card_number": "4970XXXXXXXX2345",
249
+ "customer_id": "CUST002",
250
+ "account_iban": "FR7630001007942345678901234",
251
+ "card_type": CardType.CREDIT,
252
+ "cardholder_name": "MARIE MARTIN",
253
+ "expiry_date": "06/28",
254
+ "daily_limit": Decimal("2000.00"),
255
+ "monthly_limit": Decimal("10000.00"),
256
+ "contactless_enabled": True,
257
+ "online_enabled": True,
258
+ },
259
+ {
260
+ "card_number": "4970XXXXXXXX3456",
261
+ "customer_id": "CUST003",
262
+ "account_iban": "FR7630001007943456789012345",
263
+ "card_type": CardType.BUSINESS,
264
+ "cardholder_name": "PIERRE BERNARD",
265
+ "expiry_date": "09/26",
266
+ "daily_limit": Decimal("3000.00"),
267
+ "monthly_limit": Decimal("15000.00"),
268
+ "contactless_enabled": True,
269
+ "online_enabled": True,
270
+ },
271
+ ]
272
+
273
+
274
+ # =============================================================================
275
+ # DEMO BENEFICIARIES
276
+ # =============================================================================
277
+
278
+ DEMO_BENEFICIARIES = [
279
+ {
280
+ "customer_id": "CUST001",
281
+ "name": "Marie Dupont",
282
+ "iban": "FR7614410000011234567890123",
283
+ "bic": "AGRIFRPP",
284
+ "alias": "Maman",
285
+ "category": "family",
286
+ "is_favorite": True,
287
+ },
288
+ {
289
+ "customer_id": "CUST001",
290
+ "name": "SCI Les Lilas",
291
+ "iban": "FR7630004000031234567890143",
292
+ "bic": "BNPAFRPP",
293
+ "alias": "Proprietaire",
294
+ "category": "bills",
295
+ "is_favorite": True,
296
+ },
297
+ {
298
+ "customer_id": "CUST002",
299
+ "name": "Auto-Ecole Victor",
300
+ "iban": "FR7610278060000002038650128",
301
+ "bic": "CMCIFR2A",
302
+ "alias": "Permis",
303
+ "category": "services",
304
+ },
305
+ ]
306
+
307
+
308
+ # =============================================================================
309
+ # DEMO STANDING ORDERS
310
+ # =============================================================================
311
+
312
+ DEMO_STANDING_ORDERS = [
313
+ {
314
+ "from_iban": "FR7630001007941234567890185",
315
+ "customer_id": "CUST001",
316
+ "to_iban": "FR7630004000031234567890143",
317
+ "to_name": "SCI Les Lilas",
318
+ "amount": Decimal("850.00"),
319
+ "frequency": OrderFrequency.MONTHLY,
320
+ "execution_day": 5,
321
+ "label": "Loyer mensuel",
322
+ "category": "housing",
323
+ },
324
+ {
325
+ "from_iban": "FR7630001007941234567890185",
326
+ "customer_id": "CUST001",
327
+ "to_iban": "FR7630001007941234567890186",
328
+ "to_name": "Mon Livret A",
329
+ "amount": Decimal("200.00"),
330
+ "frequency": OrderFrequency.MONTHLY,
331
+ "execution_day": 1,
332
+ "label": "Epargne mensuelle",
333
+ "category": "savings",
334
+ },
335
+ ]
336
+
337
+
338
+ # =============================================================================
339
+ # DEMO FEES (French banking fees)
340
+ # =============================================================================
341
+
342
+ DEMO_FEES = [
343
+ Fee(
344
+ fee_type=FeeType.ACCOUNT_MAINTENANCE,
345
+ name="Frais de tenue de compte",
346
+ description="Frais mensuels de gestion du compte",
347
+ amount=Decimal("2.00"),
348
+ frequency="monthly"
349
+ ),
350
+ Fee(
351
+ fee_type=FeeType.CARD_ANNUAL,
352
+ name="Cotisation carte Visa",
353
+ description="Cotisation annuelle carte bancaire",
354
+ amount=Decimal("45.00"),
355
+ frequency="yearly"
356
+ ),
357
+ Fee(
358
+ fee_type=FeeType.TRANSFER_SEPA,
359
+ name="Virement SEPA",
360
+ description="Virement SEPA occasionnel",
361
+ amount=Decimal("0.00"),
362
+ frequency="per_transaction"
363
+ ),
364
+ Fee(
365
+ fee_type=FeeType.TRANSFER_INTERNATIONAL,
366
+ name="Virement international",
367
+ description="Virement hors zone SEPA",
368
+ amount=Decimal("15.00"),
369
+ frequency="per_transaction"
370
+ ),
371
+ Fee(
372
+ fee_type=FeeType.OVERDRAFT_FEE,
373
+ name="Commission d'intervention",
374
+ description="Par operation en decouvert non autorise",
375
+ amount=Decimal("8.00"),
376
+ frequency="per_transaction"
377
+ ),
378
+ Fee(
379
+ fee_type=FeeType.REJECTED_PAYMENT,
380
+ name="Rejet de prelevement",
381
+ description="Frais pour prelevement rejete",
382
+ amount=Decimal("20.00"),
383
+ frequency="per_transaction"
384
+ ),
385
+ Fee(
386
+ fee_type=FeeType.ATM_WITHDRAWAL_OTHER,
387
+ name="Retrait autre banque",
388
+ description="Au-dela de 3 retraits/mois",
389
+ amount=Decimal("1.00"),
390
+ frequency="per_transaction"
391
+ ),
392
+ Fee(
393
+ fee_type=FeeType.CURRENCY_CONVERSION,
394
+ name="Conversion de devise",
395
+ description="Commission sur operations en devise",
396
+ amount=Decimal("2.00"),
397
+ is_percentage=True,
398
+ frequency="per_transaction"
399
+ ),
400
+ ]
401
+
402
+
403
+ # =============================================================================
404
+ # DEMO INTEREST RATES (French rates 2024)
405
+ # =============================================================================
406
+
407
+ DEMO_RATES = [
408
+ InterestRate(
409
+ rate_type=RateType.SAVINGS,
410
+ name="Livret A",
411
+ rate=Decimal("3.00"),
412
+ product_types=["livret_a"]
413
+ ),
414
+ InterestRate(
415
+ rate_type=RateType.SAVINGS,
416
+ name="LDDS",
417
+ rate=Decimal("3.00"),
418
+ product_types=["ldds"]
419
+ ),
420
+ InterestRate(
421
+ rate_type=RateType.SAVINGS,
422
+ name="LEP",
423
+ rate=Decimal("5.00"),
424
+ product_types=["lep"]
425
+ ),
426
+ InterestRate(
427
+ rate_type=RateType.OVERDRAFT,
428
+ name="Taux decouvert autorise",
429
+ rate=Decimal("7.00"),
430
+ description="Taux annuel pour decouvert autorise"
431
+ ),
432
+ InterestRate(
433
+ rate_type=RateType.OVERDRAFT,
434
+ name="Taux decouvert non autorise",
435
+ rate=Decimal("16.00"),
436
+ description="Taux annuel pour decouvert non autorise"
437
+ ),
438
+ InterestRate(
439
+ rate_type=RateType.LOAN,
440
+ name="Pret personnel",
441
+ rate=Decimal("5.50"),
442
+ product_types=["personal"],
443
+ min_duration_months=12,
444
+ max_duration_months=84
445
+ ),
446
+ InterestRate(
447
+ rate_type=RateType.MORTGAGE,
448
+ name="Pret immobilier 20 ans",
449
+ rate=Decimal("3.80"),
450
+ product_types=["mortgage"],
451
+ min_duration_months=180,
452
+ max_duration_months=300
453
+ ),
454
+ ]
455
+
456
+
457
+ # =============================================================================
458
+ # DEMO BRANCHES
459
+ # =============================================================================
460
+
461
+ DEMO_BRANCHES = [
462
+ Branch(
463
+ branch_id="BR001",
464
+ branch_code="00794",
465
+ name="Agence Paris Opera",
466
+ branch_type=BranchType.BRANCH,
467
+ address="1 Place de l'Opera",
468
+ city="Paris",
469
+ postal_code="75009",
470
+ phone="+33 1 42 68 00 00",
471
+ email="paris.opera@nanopybank.fr",
472
+ has_atm=True,
473
+ has_safe_deposit=True,
474
+ ),
475
+ Branch(
476
+ branch_id="BR002",
477
+ branch_code="00795",
478
+ name="Agence Lyon Part-Dieu",
479
+ branch_type=BranchType.BRANCH,
480
+ address="17 Rue de la Part-Dieu",
481
+ city="Lyon",
482
+ postal_code="69003",
483
+ phone="+33 4 72 00 00 00",
484
+ email="lyon.partdieu@nanopybank.fr",
485
+ has_atm=True,
486
+ ),
487
+ Branch(
488
+ branch_id="BRHQ",
489
+ branch_code="00001",
490
+ name="Siege Social",
491
+ branch_type=BranchType.HEADQUARTERS,
492
+ address="1 Rue de la Banque",
493
+ city="Paris",
494
+ postal_code="75001",
495
+ phone="+33 1 23 45 67 89",
496
+ email="siege@nanopybank.fr",
497
+ ),
498
+ ]
499
+
500
+
501
+ # =============================================================================
502
+ # DEMO EMPLOYEES
503
+ # =============================================================================
504
+
505
+ DEMO_EMPLOYEES = [
506
+ Employee(
507
+ employee_id="EMP001",
508
+ employee_number="M001234",
509
+ first_name="Laurent",
510
+ last_name="Dubois",
511
+ email="laurent.dubois@nanopybank.fr",
512
+ role=EmployeeRole.DIRECTOR,
513
+ title="Directeur General",
514
+ branch_id="BRHQ",
515
+ can_approve_loans=True,
516
+ max_approval_amount=Decimal("1000000.00"),
517
+ can_view_all_customers=True,
518
+ ),
519
+ Employee(
520
+ employee_id="EMP002",
521
+ employee_number="M001235",
522
+ first_name="Camille",
523
+ last_name="Leroy",
524
+ email="camille.leroy@nanopybank.fr",
525
+ role=EmployeeRole.MANAGER,
526
+ title="Directrice d'Agence",
527
+ branch_id="BR001",
528
+ can_approve_loans=True,
529
+ max_approval_amount=Decimal("50000.00"),
530
+ ),
531
+ Employee(
532
+ employee_id="EMP003",
533
+ employee_number="M001236",
534
+ first_name="Thomas",
535
+ last_name="Moreau",
536
+ email="thomas.moreau@nanopybank.fr",
537
+ role=EmployeeRole.ADVISOR,
538
+ title="Conseiller Clientele",
539
+ branch_id="BR001",
540
+ manager_id="EMP002",
541
+ ),
542
+ Employee(
543
+ employee_id="EMP004",
544
+ employee_number="M001237",
545
+ first_name="Emma",
546
+ last_name="Simon",
547
+ email="emma.simon@nanopybank.fr",
548
+ role=EmployeeRole.TELLER,
549
+ title="Guichetiere",
550
+ branch_id="BR002",
551
+ ),
552
+ ]
553
+
554
+
555
+ # =============================================================================
556
+ # DEMO LOANS
557
+ # =============================================================================
558
+
559
+ DEMO_LOANS = [
560
+ Loan(
561
+ loan_id="LN0001",
562
+ customer_id="CUST001",
563
+ account_iban="FR7630001007941234567890185",
564
+ loan_type=LoanType.PERSONAL,
565
+ purpose="Travaux maison",
566
+ principal=Decimal("15000.00"),
567
+ interest_rate=Decimal("5.50"),
568
+ duration_months=48,
569
+ status=LoanStatus.ACTIVE,
570
+ has_insurance=True,
571
+ insurance_premium=Decimal("25.00"),
572
+ ),
573
+ Loan(
574
+ loan_id="LN0002",
575
+ customer_id="CUST002",
576
+ account_iban="FR7630001007942345678901234",
577
+ loan_type=LoanType.AUTO,
578
+ purpose="Achat vehicule",
579
+ principal=Decimal("25000.00"),
580
+ interest_rate=Decimal("4.90"),
581
+ duration_months=60,
582
+ status=LoanStatus.ACTIVE,
583
+ ),
584
+ ]
585
+
586
+
587
+ # =============================================================================
588
+ # DEMO INSURANCE
589
+ # =============================================================================
590
+
591
+ DEMO_INSURANCE = [
592
+ Insurance(
593
+ customer_id="CUST001",
594
+ insurance_type=InsuranceType.HOME,
595
+ product_name="Assurance Habitation Complete",
596
+ coverage_amount=Decimal("200000.00"),
597
+ deductible=Decimal("150.00"),
598
+ premium_amount=Decimal("35.00"),
599
+ premium_frequency="monthly",
600
+ account_iban="FR7630001007941234567890185",
601
+ ),
602
+ Insurance(
603
+ customer_id="CUST002",
604
+ insurance_type=InsuranceType.AUTO,
605
+ product_name="Assurance Auto Tous Risques",
606
+ coverage_amount=Decimal("50000.00"),
607
+ deductible=Decimal("300.00"),
608
+ premium_amount=Decimal("85.00"),
609
+ premium_frequency="monthly",
610
+ account_iban="FR7630001007942345678901234",
611
+ ),
612
+ ]
613
+
614
+
615
+ # =============================================================================
616
+ # DEMO SAVINGS PRODUCTS
617
+ # =============================================================================
618
+
619
+ DEMO_SAVINGS = [
620
+ SavingsProduct(
621
+ customer_id="CUST001",
622
+ account_iban="FR7630001007941234567890186",
623
+ savings_type=SavingsType.LIVRET_A,
624
+ product_name="Livret A",
625
+ interest_rate=Decimal("3.00"),
626
+ max_balance=Decimal("22950.00"),
627
+ is_tax_exempt=True,
628
+ ),
629
+ ]
630
+
631
+
632
+ # =============================================================================
633
+ # HELPER FUNCTION TO CREATE DEMO BANK
634
+ # =============================================================================
635
+
636
+ # =============================================================================
637
+ # DEMO HOLDING DATA (Nova x Genesis SASU)
638
+ # Holding specialisee dans l'investissement en dette souveraine
639
+ # Activite principale: Dette d'Etat / Obligations de guerre
640
+ # Filiales: activite secondaire
641
+ # =============================================================================
642
+
643
+ DEMO_HOLDING = {
644
+ "holding": {
645
+ "name": "Nova x Genesis",
646
+ "legal_name": "Nova x Genesis Financial Services SASU",
647
+ "siren": "912 345 678",
648
+ "lei": "969500XXXXXXXXXXXXXX",
649
+ "address": "42 Avenue des Champs-Elysees",
650
+ "city": "Paris",
651
+ "postal_code": "75008",
652
+ "country": "France",
653
+ "capital": Decimal("500000000.00"), # 500M EUR capital
654
+ "activity": "Investissement en dette souveraine",
655
+ },
656
+ "accounts": {
657
+ "principal": {
658
+ "name": "Compte Principal",
659
+ "iban": "FR76 3000 6000 0112 3456 7890 189",
660
+ "balance": Decimal("85000000.00"), # 85M EUR
661
+ "currency": "EUR",
662
+ },
663
+ "tresorerie": {
664
+ "name": "Compte Tresorerie",
665
+ "iban": "FR76 3000 6000 0198 7654 3210 012",
666
+ "balance": Decimal("45000000.00"), # 45M EUR
667
+ "currency": "EUR",
668
+ },
669
+ "titres": {
670
+ "name": "Compte Titres (Dette Souveraine)",
671
+ "iban": "FR76 3000 6000 0199 8765 4321 098",
672
+ "balance": Decimal("120000000.00"), # 120M EUR cash pour achats
673
+ "currency": "EUR",
674
+ },
675
+ },
676
+ # Filiales
677
+ "subsidiaries": [
678
+ {"id": "SUB001", "name": "NanoPy Bank France", "type": "Banque de detail", "ownership": Decimal("100.00"), "assets": Decimal("2400000000"), "employees": 1250, "status": "active", "pool_balance": Decimal("8500000"), "pool_limit": Decimal("10000000")},
679
+ {"id": "SUB002", "name": "Nova Asset Management", "type": "Gestion d'actifs", "ownership": Decimal("100.00"), "assets": Decimal("850000000"), "employees": 85, "status": "active", "pool_balance": Decimal("-1200000"), "pool_limit": Decimal("5000000")},
680
+ {"id": "SUB003", "name": "Nova Insurance", "type": "Assurance", "ownership": Decimal("85.00"), "assets": Decimal("450000000"), "employees": 320, "status": "active", "pool_balance": Decimal("3800000"), "pool_limit": Decimal("8000000")},
681
+ {"id": "SUB004", "name": "Nova Leasing", "type": "Credit-bail", "ownership": Decimal("100.00"), "assets": Decimal("180000000"), "employees": 45, "status": "active", "pool_balance": Decimal("-450000"), "pool_limit": Decimal("2000000")},
682
+ {"id": "SUB005", "name": "Nova Digital", "type": "Fintech", "ownership": Decimal("70.00"), "assets": Decimal("25000000"), "employees": 60, "status": "startup", "pool_balance": Decimal("650000"), "pool_limit": Decimal("1000000")},
683
+ ],
684
+ "intra_group_loans": [
685
+ {"id": "IGL001", "borrower": "Nova Leasing", "borrower_id": "SUB004", "principal": Decimal("5000000"), "outstanding": Decimal("4200000"), "rate": Decimal("1.25"), "start_date": date(2024, 1, 15), "maturity": date(2027, 12, 31), "status": "active"},
686
+ {"id": "IGL002", "borrower": "Nova Digital", "borrower_id": "SUB005", "principal": Decimal("2500000"), "outstanding": Decimal("2500000"), "rate": Decimal("1.50"), "start_date": date(2025, 3, 1), "maturity": date(2026, 6, 30), "status": "active"},
687
+ {"id": "IGL003", "borrower": "Nova Insurance", "borrower_id": "SUB003", "principal": Decimal("10000000"), "outstanding": Decimal("8500000"), "rate": Decimal("1.00"), "start_date": date(2023, 6, 1), "maturity": date(2028, 12, 31), "status": "active"},
688
+ ],
689
+ "dividends": [
690
+ {"id": "DIV001", "subsidiary": "NanoPy Bank France", "subsidiary_id": "SUB001", "year": "2025", "gross": Decimal("15000000"), "tax": Decimal("0"), "net": Decimal("15000000"), "status": "paid", "payment_date": date(2025, 4, 15)},
691
+ {"id": "DIV002", "subsidiary": "Nova Asset Management", "subsidiary_id": "SUB002", "year": "2025", "gross": Decimal("3200000"), "tax": Decimal("0"), "net": Decimal("3200000"), "status": "approved", "payment_date": None},
692
+ {"id": "DIV003", "subsidiary": "Nova Insurance", "subsidiary_id": "SUB003", "year": "2025", "gross": Decimal("1800000"), "tax": Decimal("270000"), "net": Decimal("1530000"), "status": "declared", "payment_date": None},
693
+ ],
694
+ "pool_rates": {"credit": Decimal("0.50"), "debit": Decimal("2.00")},
695
+ # Revenus - Dette souveraine = 85% des revenus
696
+ "revenue_breakdown": {
697
+ "sovereign_bonds_coupons": Decimal("98500000"), # 98.5M EUR - Coupons annuels
698
+ "sovereign_bonds_trading": Decimal("15200000"), # 15.2M EUR - Plus-values trading
699
+ "subsidiaries_dividends": Decimal("2800000"), # 2.8M EUR - Dividendes filiales
700
+ "other": Decimal("1500000"), # 1.5M EUR - Autres
701
+ },
702
+ # Assurances Groupe
703
+ "insurances": [
704
+ # RC Dirigeants (D&O)
705
+ {"id": "INS001", "type": "D&O", "name": "RC Dirigeants et Mandataires Sociaux", "insurer": "AXA Corporate", "policy_number": "DO-2024-NXG-001", "coverage": Decimal("50000000"), "premium": Decimal("185000"), "frequency": "annual", "start_date": date(2024, 1, 1), "end_date": date(2025, 12, 31), "status": "active"},
706
+ # RC Pro Groupe
707
+ {"id": "INS002", "type": "RC_PRO", "name": "RC Professionnelle Groupe", "insurer": "Allianz", "policy_number": "RCP-2024-NXG-002", "coverage": Decimal("100000000"), "premium": Decimal("320000"), "frequency": "annual", "start_date": date(2024, 1, 1), "end_date": date(2025, 12, 31), "status": "active"},
708
+ # Cyber assurance
709
+ {"id": "INS003", "type": "CYBER", "name": "Cyber Assurance Groupe", "insurer": "Chubb", "policy_number": "CYB-2024-NXG-003", "coverage": Decimal("25000000"), "premium": Decimal("95000"), "frequency": "annual", "start_date": date(2024, 3, 1), "end_date": date(2025, 2, 28), "status": "active"},
710
+ # Assurance Immeubles
711
+ {"id": "INS004", "type": "PROPERTY", "name": "Multirisque Siege Social", "insurer": "Generali", "policy_number": "MR-2024-NXG-004", "coverage": Decimal("15000000"), "premium": Decimal("42000"), "frequency": "annual", "start_date": date(2024, 1, 1), "end_date": date(2025, 12, 31), "status": "active"},
712
+ # Key Man
713
+ {"id": "INS005", "type": "KEY_MAN", "name": "Assurance Homme-Cle (PDG)", "insurer": "Swiss Life", "policy_number": "KM-2024-NXG-005", "coverage": Decimal("10000000"), "premium": Decimal("28000"), "frequency": "annual", "start_date": date(2024, 6, 1), "end_date": date(2025, 5, 31), "status": "active"},
714
+ # CDS Ukraine (couverture risque souverain)
715
+ {"id": "INS006", "type": "CDS", "name": "CDS Ukraine Sovereign 5Y", "insurer": "JP Morgan (contrepartie)", "policy_number": "CDS-UA-2024-001", "coverage": Decimal("150000000"), "premium": Decimal("4500000"), "frequency": "annual", "start_date": date(2024, 1, 15), "end_date": date(2029, 1, 15), "status": "active", "spread_bps": 3000},
716
+ ],
717
+ # Sinistres
718
+ "claims": [
719
+ {"id": "CLM001", "insurance_id": "INS003", "date": date(2024, 8, 15), "type": "Tentative intrusion", "amount_claimed": Decimal("150000"), "amount_paid": Decimal("125000"), "status": "paid"},
720
+ {"id": "CLM002", "insurance_id": "INS004", "date": date(2024, 11, 20), "type": "Degat des eaux", "amount_claimed": Decimal("35000"), "amount_paid": Decimal("0"), "status": "pending"},
721
+ ],
722
+ }
723
+
724
+
725
+ # =============================================================================
726
+ # DEMO SOVEREIGN BONDS PORTFOLIO - PORTEFEUILLE MASSIF (2.8 Milliards EUR)
727
+ # Dette de guerre / Obligations d'Etat - Activite principale
728
+ # =============================================================================
729
+
730
+ DEMO_SOVEREIGN_BONDS = [
731
+ # ============ FRANCE - OAT (850M EUR) ============
732
+ {"isin": "FR0014007L00", "name": "OAT 2.50% 25/05/2030 (Defense)", "country": "FR", "country_name": "France", "coupon": Decimal("2.50"), "maturity": date(2030, 5, 25), "nominal": Decimal("250000000"), "purchase_price": Decimal("99.50"), "current_price": Decimal("98.75"), "purchase_date": date(2023, 6, 15), "quantity": 2500},
733
+ {"isin": "FR0013508470", "name": "OAT 1.50% 25/05/2031", "country": "FR", "country_name": "France", "coupon": Decimal("1.50"), "maturity": date(2031, 5, 25), "nominal": Decimal("200000000"), "purchase_price": Decimal("96.25"), "current_price": Decimal("95.80"), "purchase_date": date(2023, 9, 1), "quantity": 2000},
734
+ {"isin": "FR0014003513", "name": "OAT 0.75% 25/11/2028", "country": "FR", "country_name": "France", "coupon": Decimal("0.75"), "maturity": date(2028, 11, 25), "nominal": Decimal("150000000"), "purchase_price": Decimal("97.00"), "current_price": Decimal("96.50"), "purchase_date": date(2024, 1, 10), "quantity": 1500},
735
+ {"isin": "FR0013154028", "name": "OAT 1.25% 25/05/2036", "country": "FR", "country_name": "France", "coupon": Decimal("1.25"), "maturity": date(2036, 5, 25), "nominal": Decimal("250000000"), "purchase_price": Decimal("92.00"), "current_price": Decimal("91.25"), "purchase_date": date(2024, 3, 20), "quantity": 2500},
736
+
737
+ # ============ ALLEMAGNE - Bund (600M EUR) ============
738
+ {"isin": "DE0001102580", "name": "Bund 2.30% 15/02/2033", "country": "DE", "country_name": "Allemagne", "coupon": Decimal("2.30"), "maturity": date(2033, 2, 15), "nominal": Decimal("300000000"), "purchase_price": Decimal("102.00"), "current_price": Decimal("101.50"), "purchase_date": date(2023, 4, 1), "quantity": 3000},
739
+ {"isin": "DE0001102614", "name": "Bund 1.70% 15/08/2032", "country": "DE", "country_name": "Allemagne", "coupon": Decimal("1.70"), "maturity": date(2032, 8, 15), "nominal": Decimal("200000000"), "purchase_price": Decimal("98.75"), "current_price": Decimal("98.25"), "purchase_date": date(2023, 7, 15), "quantity": 2000},
740
+ {"isin": "DE0001141836", "name": "Bund 0.00% 15/08/2031", "country": "DE", "country_name": "Allemagne", "coupon": Decimal("0.00"), "maturity": date(2031, 8, 15), "nominal": Decimal("100000000"), "purchase_price": Decimal("88.50"), "current_price": Decimal("87.75"), "purchase_date": date(2024, 2, 1), "quantity": 1000},
741
+
742
+ # ============ ITALIE - BTP (400M EUR) ============
743
+ {"isin": "IT0005436693", "name": "BTP 3.85% 01/09/2049", "country": "IT", "country_name": "Italie", "coupon": Decimal("3.85"), "maturity": date(2049, 9, 1), "nominal": Decimal("200000000"), "purchase_price": Decimal("95.50"), "current_price": Decimal("94.25"), "purchase_date": date(2023, 5, 10), "quantity": 2000},
744
+ {"isin": "IT0005494239", "name": "BTP 2.80% 01/06/2029", "country": "IT", "country_name": "Italie", "coupon": Decimal("2.80"), "maturity": date(2029, 6, 1), "nominal": Decimal("200000000"), "purchase_price": Decimal("99.00"), "current_price": Decimal("98.50"), "purchase_date": date(2024, 1, 5), "quantity": 2000},
745
+
746
+ # ============ ESPAGNE - Bonos (300M EUR) ============
747
+ {"isin": "ES0000012L29", "name": "Bonos 2.55% 31/10/2032", "country": "ES", "country_name": "Espagne", "coupon": Decimal("2.55"), "maturity": date(2032, 10, 31), "nominal": Decimal("150000000"), "purchase_price": Decimal("98.00"), "current_price": Decimal("97.50"), "purchase_date": date(2023, 8, 1), "quantity": 1500},
748
+ {"isin": "ES00000128Q6", "name": "Bonos 1.85% 30/07/2035", "country": "ES", "country_name": "Espagne", "coupon": Decimal("1.85"), "maturity": date(2035, 7, 30), "nominal": Decimal("150000000"), "purchase_price": Decimal("94.25"), "current_price": Decimal("93.75"), "purchase_date": date(2024, 2, 15), "quantity": 1500},
749
+
750
+ # ============ UKRAINE - War Bonds (250M EUR) ============
751
+ {"isin": "XS2010028699", "name": "Ukraine 7.75% 01/09/2026 (War Bond)", "country": "UA", "country_name": "Ukraine", "coupon": Decimal("7.75"), "maturity": date(2026, 9, 1), "nominal": Decimal("100000000"), "purchase_price": Decimal("45.00"), "current_price": Decimal("52.50"), "purchase_date": date(2023, 3, 1), "quantity": 1000},
752
+ {"isin": "XS2010028772", "name": "Ukraine 6.876% 21/05/2029 (War Bond)", "country": "UA", "country_name": "Ukraine", "coupon": Decimal("6.876"), "maturity": date(2029, 5, 21), "nominal": Decimal("150000000"), "purchase_price": Decimal("38.00"), "current_price": Decimal("48.25"), "purchase_date": date(2023, 6, 15), "quantity": 1500},
753
+
754
+ # ============ POLOGNE (200M EUR) - Soutien OTAN ============
755
+ {"isin": "PL0000112736", "name": "Poland 3.25% 25/07/2033", "country": "PL", "country_name": "Pologne", "coupon": Decimal("3.25"), "maturity": date(2033, 7, 25), "nominal": Decimal("200000000"), "purchase_price": Decimal("96.50"), "current_price": Decimal("95.75"), "purchase_date": date(2024, 1, 20), "quantity": 2000},
756
+
757
+ # ============ USA - Treasury (300M EUR) ============
758
+ {"isin": "US912810TM09", "name": "US Treasury 4.125% 15/08/2053", "country": "US", "country_name": "Etats-Unis", "coupon": Decimal("4.125"), "maturity": date(2053, 8, 15), "nominal": Decimal("200000000"), "purchase_price": Decimal("97.00"), "current_price": Decimal("96.25"), "purchase_date": date(2023, 10, 1), "quantity": 2000},
759
+ {"isin": "US91282CJL54", "name": "US Treasury 4.50% 15/11/2033", "country": "US", "country_name": "Etats-Unis", "coupon": Decimal("4.50"), "maturity": date(2033, 11, 15), "nominal": Decimal("100000000"), "purchase_price": Decimal("101.50"), "current_price": Decimal("100.75"), "purchase_date": date(2024, 3, 1), "quantity": 1000},
760
+
761
+ # ============ UK - Gilt (150M EUR) ============
762
+ {"isin": "GB00BDRHNP05", "name": "UK Gilt 3.75% 22/10/2053", "country": "GB", "country_name": "Royaume-Uni", "coupon": Decimal("3.75"), "maturity": date(2053, 10, 22), "nominal": Decimal("150000000"), "purchase_price": Decimal("92.00"), "current_price": Decimal("91.25"), "purchase_date": date(2023, 11, 15), "quantity": 1500},
763
+ ]
764
+
765
+
766
+ # =============================================================================
767
+ # DEMO AVAILABLE BONDS (for purchase) - Focus dette souveraine / guerre
768
+ # =============================================================================
769
+
770
+ DEMO_AVAILABLE_BONDS = [
771
+ # Eurozone - Core
772
+ {"isin": "FR0014006XJ5", "name": "OAT 1.25% 25/05/2034", "country": "FR", "country_name": "France", "coupon": Decimal("1.25"), "maturity": date(2034, 5, 25), "current_price": Decimal("94.50"), "yield": Decimal("1.85")},
773
+ {"isin": "FR0014007TY1", "name": "OAT 3.00% 25/05/2033 (Defense)", "country": "FR", "country_name": "France", "coupon": Decimal("3.00"), "maturity": date(2033, 5, 25), "current_price": Decimal("102.75"), "yield": Decimal("2.65")},
774
+ {"isin": "DE0001102648", "name": "Bund 2.60% 15/08/2033", "country": "DE", "country_name": "Allemagne", "coupon": Decimal("2.60"), "maturity": date(2033, 8, 15), "current_price": Decimal("103.50"), "yield": Decimal("2.25")},
775
+ {"isin": "IT0005519787", "name": "BTP 4.00% 30/04/2035", "country": "IT", "country_name": "Italie", "coupon": Decimal("4.00"), "maturity": date(2035, 4, 30), "current_price": Decimal("100.25"), "yield": Decimal("3.95")},
776
+ {"isin": "ES00001015F8", "name": "Bonos 3.15% 30/04/2033", "country": "ES", "country_name": "Espagne", "coupon": Decimal("3.15"), "maturity": date(2033, 4, 30), "current_price": Decimal("101.00"), "yield": Decimal("3.00")},
777
+
778
+ # Europe de l'Est - Soutien OTAN / Ukraine
779
+ {"isin": "XS2010029077", "name": "Ukraine 8.994% 01/02/2030 (War Bond)", "country": "UA", "country_name": "Ukraine", "coupon": Decimal("8.994"), "maturity": date(2030, 2, 1), "current_price": Decimal("42.50"), "yield": Decimal("28.50")},
780
+ {"isin": "PL0000113783", "name": "Poland 5.75% 25/04/2032", "country": "PL", "country_name": "Pologne", "coupon": Decimal("5.75"), "maturity": date(2032, 4, 25), "current_price": Decimal("108.25"), "yield": Decimal("4.50")},
781
+ {"isin": "ROVRZSDI2CZ7", "name": "Romania 4.625% 03/04/2049", "country": "RO", "country_name": "Roumanie", "coupon": Decimal("4.625"), "maturity": date(2049, 4, 3), "current_price": Decimal("78.50"), "yield": Decimal("6.25")},
782
+
783
+ # USA & UK
784
+ {"isin": "US91282CKR31", "name": "US Treasury 4.625% 15/05/2034", "country": "US", "country_name": "Etats-Unis", "coupon": Decimal("4.625"), "maturity": date(2034, 5, 15), "current_price": Decimal("102.50"), "yield": Decimal("4.35")},
785
+ {"isin": "GB00BM8Z2V59", "name": "UK Gilt 4.25% 07/12/2040", "country": "GB", "country_name": "Royaume-Uni", "coupon": Decimal("4.25"), "maturity": date(2040, 12, 7), "current_price": Decimal("98.75"), "yield": Decimal("4.35")},
786
+
787
+ # Autres
788
+ {"isin": "JP1201551M12", "name": "JGB 0.50% 20/03/2033", "country": "JP", "country_name": "Japon", "coupon": Decimal("0.50"), "maturity": date(2033, 3, 20), "current_price": Decimal("98.00"), "yield": Decimal("0.72")},
789
+ {"isin": "CH0224397130", "name": "Swiss Conf 1.50% 24/07/2042", "country": "CH", "country_name": "Suisse", "coupon": Decimal("1.50"), "maturity": date(2042, 7, 24), "current_price": Decimal("105.00"), "yield": Decimal("1.15")},
790
+ ]
791
+
792
+
793
+ # =============================================================================
794
+ # HELPER FUNCTION TO GET DEMO HOLDING DATA
795
+ # =============================================================================
796
+
797
+ def get_demo_holding_data():
798
+ """
799
+ Get a copy of demo holding data.
800
+ Returns a deep copy to avoid mutations.
801
+ """
802
+ import copy
803
+ data = copy.deepcopy(DEMO_HOLDING)
804
+ data["sovereign_bonds"] = copy.deepcopy(DEMO_SOVEREIGN_BONDS)
805
+ data["available_bonds"] = copy.deepcopy(DEMO_AVAILABLE_BONDS)
806
+ data["bond_transactions"] = []
807
+ data["transactions"] = []
808
+ return data
809
+
810
+
811
+ # =============================================================================
812
+ # HELPER FUNCTION TO CREATE DEMO BANK
813
+ # =============================================================================
814
+
815
+ def create_demo_bank():
816
+ """
817
+ Create a bank instance populated with demo data.
818
+
819
+ Returns:
820
+ Bank: A bank instance with demo customers, accounts, transactions, etc.
821
+ """
822
+ from ..core.bank import Bank
823
+
824
+ bank = Bank()
825
+
826
+ # Add customers
827
+ for cust_data in DEMO_CUSTOMERS:
828
+ customer = Customer(**cust_data)
829
+ bank.customers[customer.customer_id] = customer
830
+
831
+ # Add accounts
832
+ for acc_data in DEMO_ACCOUNTS:
833
+ account = Account(**acc_data)
834
+ bank.accounts[account.iban] = account
835
+
836
+ # Add transactions
837
+ for tx_data in DEMO_TRANSACTIONS:
838
+ tx = Transaction(**tx_data)
839
+ bank.transactions[tx.transaction_id] = tx
840
+
841
+ # Add cards
842
+ for card_data in DEMO_CARDS:
843
+ card = Card(**card_data)
844
+ bank.cards[card.card_id] = card
845
+
846
+ return bank