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,105 @@
1
+ """
2
+ Loans page
3
+ """
4
+
5
+ import streamlit as st
6
+
7
+ from .common import page_header
8
+
9
+
10
+ def render_loans():
11
+ """Render loans page"""
12
+ page_header("Loans & Products")
13
+
14
+ tab1, tab2, tab3 = st.tabs(["My Loans", "Savings Products", "Insurance"])
15
+
16
+ with tab1:
17
+ st.markdown("### Active Loans")
18
+
19
+ st.markdown("""
20
+ <div style="background: linear-gradient(135deg, #1e3a5f 0%, #2d5a87 100%); border-radius: 16px; padding: 24px; margin-bottom: 20px;">
21
+ <div style="display: flex; justify-content: space-between; align-items: start;">
22
+ <div>
23
+ <div style="color: #00d4ff; font-size: 12px; text-transform: uppercase;">Personal Loan</div>
24
+ <div style="color: white; font-size: 24px; font-weight: bold; margin: 8px 0;">15,000.00 EUR</div>
25
+ <div style="color: #aaa; font-size: 14px;">Travaux maison</div>
26
+ </div>
27
+ <div style="text-align: right;">
28
+ <div style="color: #00ff88; font-size: 18px; font-weight: bold;">5.50%</div>
29
+ <div style="color: #aaa; font-size: 12px;">Annual Rate</div>
30
+ </div>
31
+ </div>
32
+ <div style="margin-top: 20px; background: rgba(255,255,255,0.1); border-radius: 8px; height: 8px;">
33
+ <div style="background: linear-gradient(90deg, #00d4ff, #00ff88); border-radius: 8px; height: 8px; width: 35%;"></div>
34
+ </div>
35
+ <div style="display: flex; justify-content: space-between; margin-top: 8px; color: #888; font-size: 12px;">
36
+ <span>Paid: 5,250.00 EUR (35%)</span>
37
+ <span>Remaining: 9,750.00 EUR</span>
38
+ </div>
39
+ <div style="display: flex; gap: 20px; margin-top: 20px; color: white; font-size: 14px;">
40
+ <div><span style="color: #888;">Monthly:</span> 350.00 EUR</div>
41
+ <div><span style="color: #888;">Duration:</span> 48 months</div>
42
+ <div><span style="color: #888;">Next payment:</span> 05/02/2026</div>
43
+ </div>
44
+ </div>
45
+ """, unsafe_allow_html=True)
46
+
47
+ with st.expander("Request New Loan"):
48
+ with st.form("loan_request"):
49
+ col1, col2 = st.columns(2)
50
+ with col1:
51
+ st.selectbox("Loan Type", ["Personal", "Auto", "Mortgage", "Student"])
52
+ st.number_input("Amount", min_value=1000, max_value=500000, value=10000, step=1000)
53
+ with col2:
54
+ st.slider("Duration (months)", 12, 360, 48)
55
+ st.text_input("Purpose")
56
+
57
+ if st.form_submit_button("Submit Request"):
58
+ st.info("Your loan request has been submitted for review.")
59
+
60
+ with tab2:
61
+ st.markdown("### Savings Products")
62
+
63
+ savings = [
64
+ {"name": "Livret A", "balance": "15,000.00", "rate": "3.00%", "ceiling": "22,950.00"},
65
+ {"name": "LDDS", "balance": "8,500.00", "rate": "3.00%", "ceiling": "12,000.00"},
66
+ ]
67
+
68
+ for s in savings:
69
+ col1, col2, col3, col4 = st.columns([2, 2, 2, 2])
70
+ with col1:
71
+ st.markdown(f"**{s['name']}**")
72
+ with col2:
73
+ st.markdown(f":green[{s['balance']} EUR]")
74
+ with col3:
75
+ st.markdown(f"Rate: {s['rate']}")
76
+ with col4:
77
+ st.caption(f"Ceiling: {s['ceiling']} EUR")
78
+ st.divider()
79
+
80
+ with st.expander("Open New Savings Account"):
81
+ with st.form("new_savings"):
82
+ st.selectbox("Product", ["Livret A", "LDDS", "LEP", "PEL"])
83
+ st.number_input("Initial Deposit", min_value=10, value=100)
84
+ if st.form_submit_button("Open Account"):
85
+ st.success("Savings account opened!")
86
+
87
+ with tab3:
88
+ st.markdown("### Insurance Products")
89
+
90
+ insurances = [
91
+ {"name": "Home Insurance", "type": "Habitation", "premium": "35.00/month", "status": "Active"},
92
+ {"name": "Loan Insurance", "type": "Emprunteur", "premium": "25.00/month", "status": "Active"},
93
+ ]
94
+
95
+ for ins in insurances:
96
+ col1, col2, col3, col4 = st.columns([2, 2, 2, 2])
97
+ with col1:
98
+ st.markdown(f"**{ins['name']}**")
99
+ with col2:
100
+ st.caption(ins['type'])
101
+ with col3:
102
+ st.markdown(f":blue[{ins['premium']}]")
103
+ with col4:
104
+ st.success(ins['status'])
105
+ st.divider()
@@ -0,0 +1,174 @@
1
+ """
2
+ Login page - Identification bancaire
3
+ """
4
+
5
+ import streamlit as st
6
+
7
+
8
+ def render_login():
9
+ """Render login page with bank-style authentication"""
10
+ st.markdown("""
11
+ <style>
12
+ .login-container {
13
+ max-width: 400px;
14
+ margin: 0 auto;
15
+ padding: 40px;
16
+ }
17
+ .step-indicator {
18
+ display: flex;
19
+ justify-content: center;
20
+ gap: 20px;
21
+ margin-bottom: 30px;
22
+ }
23
+ .step {
24
+ width: 32px;
25
+ height: 32px;
26
+ border-radius: 50%;
27
+ display: flex;
28
+ align-items: center;
29
+ justify-content: center;
30
+ font-weight: bold;
31
+ }
32
+ .step-active {
33
+ background: linear-gradient(90deg, #00d4ff, #7b2cbf);
34
+ color: white;
35
+ }
36
+ .step-inactive {
37
+ background: #333;
38
+ color: #666;
39
+ }
40
+ </style>
41
+ """, unsafe_allow_html=True)
42
+
43
+ # Initialize login state
44
+ if "login_step" not in st.session_state:
45
+ st.session_state.login_step = 1
46
+ if "login_client_id" not in st.session_state:
47
+ st.session_state.login_client_id = ""
48
+
49
+ col1, col2, col3 = st.columns([1, 2, 1])
50
+
51
+ with col2:
52
+ st.markdown("""
53
+ <div style="text-align: center; margin-bottom: 40px;">
54
+ <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="#00d4ff" stroke-width="2">
55
+ <rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect>
56
+ <line x1="1" y1="10" x2="23" y2="10"></line>
57
+ </svg>
58
+ <h1 style="background: linear-gradient(90deg, #00d4ff, #7b2cbf); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-top: 16px;">NanoPy Bank</h1>
59
+ <p style="color: #888;">Espace client securise</p>
60
+ </div>
61
+ """, unsafe_allow_html=True)
62
+
63
+ # Step indicator
64
+ step1_class = "step-active" if st.session_state.login_step >= 1 else "step-inactive"
65
+ step2_class = "step-active" if st.session_state.login_step >= 2 else "step-inactive"
66
+ st.markdown(f"""
67
+ <div class="step-indicator">
68
+ <div class="step {step1_class}">1</div>
69
+ <div style="width: 60px; height: 2px; background: {'#00d4ff' if st.session_state.login_step >= 2 else '#333'}; align-self: center;"></div>
70
+ <div class="step {step2_class}">2</div>
71
+ </div>
72
+ """, unsafe_allow_html=True)
73
+
74
+ # Step 1: Client ID
75
+ if st.session_state.login_step == 1:
76
+ st.markdown("### Etape 1: Identifiant")
77
+ st.markdown("<p style='color: #888; font-size: 14px;'>Saisissez votre identifiant client (8 chiffres)</p>", unsafe_allow_html=True)
78
+
79
+ with st.form("login_step1"):
80
+ client_id = st.text_input(
81
+ "Identifiant client",
82
+ placeholder="12345678",
83
+ max_chars=8,
84
+ help="Votre identifiant a 8 chiffres figure sur votre releve de compte"
85
+ )
86
+
87
+ submitted = st.form_submit_button("Continuer", use_container_width=True, type="primary")
88
+
89
+ if submitted:
90
+ if not client_id:
91
+ st.error("Veuillez saisir votre identifiant")
92
+ elif len(client_id) != 8 or not client_id.isdigit():
93
+ st.error("L'identifiant doit contenir 8 chiffres")
94
+ else:
95
+ try:
96
+ from nanopy_bank.core.auth import get_auth_service
97
+ except ImportError:
98
+ from ...core.auth import get_auth_service
99
+
100
+ auth = get_auth_service()
101
+ user = auth.get_user_by_client_id(client_id)
102
+
103
+ if user:
104
+ st.session_state.login_client_id = client_id
105
+ st.session_state.login_step = 2
106
+ st.rerun()
107
+ else:
108
+ st.error("Identifiant non reconnu")
109
+
110
+ # Step 2: Password
111
+ elif st.session_state.login_step == 2:
112
+ st.markdown("### Etape 2: Mot de passe")
113
+ st.markdown(f"<p style='color: #888; font-size: 14px;'>Identifiant: <strong>{st.session_state.login_client_id}</strong></p>", unsafe_allow_html=True)
114
+
115
+ with st.form("login_step2"):
116
+ password = st.text_input(
117
+ "Mot de passe",
118
+ type="password",
119
+ placeholder="••••••••",
120
+ help="Votre mot de passe personnel"
121
+ )
122
+
123
+ col_a, col_b = st.columns(2)
124
+ with col_a:
125
+ if st.form_submit_button("Retour", use_container_width=True):
126
+ st.session_state.login_step = 1
127
+ st.rerun()
128
+ with col_b:
129
+ submitted = st.form_submit_button("Valider", use_container_width=True, type="primary")
130
+
131
+ if submitted:
132
+ if not password:
133
+ st.error("Veuillez saisir votre mot de passe")
134
+ else:
135
+ try:
136
+ from nanopy_bank.core.auth import get_auth_service
137
+ except ImportError:
138
+ from ...core.auth import get_auth_service
139
+
140
+ auth = get_auth_service()
141
+ session = auth.login_by_client_id(st.session_state.login_client_id, password)
142
+
143
+ if session:
144
+ user = auth.get_user_by_id(session.user_id)
145
+ st.session_state.session_id = session.session_id
146
+ st.session_state.user = user
147
+ st.session_state.logged_in = True
148
+ # Reset login state
149
+ st.session_state.login_step = 1
150
+ st.session_state.login_client_id = ""
151
+ st.success(f"Bienvenue {user.display_name}!")
152
+ st.rerun()
153
+ else:
154
+ st.error("Mot de passe incorrect")
155
+
156
+ # Reset link
157
+ st.markdown("""
158
+ <div style='text-align: center; margin-top: 10px;'>
159
+ <a href='#' style='color: #00d4ff; font-size: 12px;'>Mot de passe oublie?</a>
160
+ </div>
161
+ """, unsafe_allow_html=True)
162
+
163
+ st.divider()
164
+
165
+ st.markdown("### Comptes demo")
166
+ st.markdown("""
167
+ | Role | Identifiant | Mot de passe |
168
+ |------|-------------|--------------|
169
+ | Client | `10000001` | `demo123` |
170
+ | Conseiller | `20000001` | `demo123` |
171
+ | Directeur | `30000001` | `demo123` |
172
+ | Admin | `40000001` | `demo123` |
173
+ | Holding | `50000001` | `demo123` |
174
+ """)
@@ -0,0 +1,118 @@
1
+ """
2
+ SEPA page
3
+ """
4
+
5
+ import streamlit as st
6
+ from datetime import datetime, timedelta
7
+ from decimal import Decimal
8
+
9
+ from .common import page_header
10
+
11
+
12
+ def render_sepa():
13
+ """Render SEPA XML page"""
14
+ page_header("SEPA XML")
15
+
16
+ bank = st.session_state.bank
17
+
18
+ tab1, tab2, tab3 = st.tabs(["Export Statement", "Export Credit Transfer", "Import XML"])
19
+
20
+ with tab1:
21
+ st.markdown("### Export Bank Statement (camt.053)")
22
+
23
+ if bank.accounts:
24
+ account_options = {f"{acc.account_name} ({acc.format_iban()})": acc.iban for acc in bank.accounts.values()}
25
+ selected = st.selectbox("Select Account", options=list(account_options.keys()), key="stmt_account")
26
+
27
+ col1, col2 = st.columns(2)
28
+ with col1:
29
+ from_date = st.date_input("From Date", value=datetime.now() - timedelta(days=30))
30
+ with col2:
31
+ to_date = st.date_input("To Date", value=datetime.now())
32
+
33
+ if st.button("Generate Statement XML", key="gen_stmt"):
34
+ from nanopy_bank.sepa import SEPAGenerator
35
+
36
+ iban = account_options[selected]
37
+ account = bank.get_account(iban)
38
+ transactions = bank.get_account_transactions(iban, limit=100)
39
+
40
+ generator = SEPAGenerator(
41
+ initiator_name="NanoPy Bank",
42
+ initiator_iban=iban,
43
+ initiator_bic=account.bic
44
+ )
45
+
46
+ xml_content = generator.generate_statement(
47
+ iban=iban,
48
+ transactions=transactions,
49
+ opening_balance=account.balance - sum(tx.signed_amount for tx in transactions),
50
+ closing_balance=account.balance,
51
+ from_date=datetime.combine(from_date, datetime.min.time()),
52
+ to_date=datetime.combine(to_date, datetime.max.time())
53
+ )
54
+
55
+ st.download_button(
56
+ label="Download camt.053.xml",
57
+ data=xml_content,
58
+ file_name=f"statement_{iban}_{to_date.strftime('%Y%m%d')}.xml",
59
+ mime="application/xml"
60
+ )
61
+
62
+ with st.expander("Preview XML"):
63
+ st.code(xml_content, language="xml")
64
+
65
+ with tab2:
66
+ st.markdown("### Export Credit Transfer (pain.001)")
67
+ st.info("Create a SEPA batch payment file")
68
+
69
+ with st.form("sepa_export_form"):
70
+ if bank.accounts:
71
+ account_options = {f"{acc.account_name}": acc.iban for acc in bank.accounts.values()}
72
+ from_account = st.selectbox("From Account", options=list(account_options.keys()))
73
+
74
+ to_name = st.text_input("Beneficiary Name")
75
+ to_iban = st.text_input("Beneficiary IBAN")
76
+ to_bic = st.text_input("Beneficiary BIC")
77
+ amount = st.number_input("Amount", min_value=0.01, value=100.0)
78
+ reference = st.text_input("Reference/Label")
79
+
80
+ if st.form_submit_button("Generate XML"):
81
+ from nanopy_bank.sepa import SEPAGenerator
82
+
83
+ from_iban = account_options[from_account]
84
+ account = bank.get_account(from_iban)
85
+
86
+ generator = SEPAGenerator(
87
+ initiator_name="NanoPy Bank Client",
88
+ initiator_iban=from_iban,
89
+ initiator_bic=account.bic
90
+ )
91
+
92
+ xml_content = generator.generate_credit_transfer([{
93
+ "amount": Decimal(str(amount)),
94
+ "creditor_name": to_name,
95
+ "creditor_iban": to_iban,
96
+ "creditor_bic": to_bic,
97
+ "remittance_info": reference
98
+ }])
99
+
100
+ st.download_button(
101
+ label="Download pain.001.xml",
102
+ data=xml_content,
103
+ file_name=f"sepa_transfer_{datetime.now().strftime('%Y%m%d%H%M%S')}.xml",
104
+ mime="application/xml"
105
+ )
106
+
107
+ with st.expander("Preview XML"):
108
+ st.code(xml_content, language="xml")
109
+
110
+ with tab3:
111
+ st.markdown("### Import SEPA XML")
112
+
113
+ uploaded_file = st.file_uploader("Upload XML file", type=["xml"])
114
+
115
+ if uploaded_file:
116
+ content = uploaded_file.read().decode()
117
+ st.code(content[:2000] + "..." if len(content) > 2000 else content, language="xml")
118
+ st.info("XML parsing available. Transactions can be imported.")
@@ -0,0 +1,48 @@
1
+ """
2
+ Settings page
3
+ """
4
+
5
+ import streamlit as st
6
+
7
+ from .common import page_header
8
+
9
+
10
+ def render_settings():
11
+ """Render settings page"""
12
+ page_header("Settings")
13
+
14
+ bank = st.session_state.bank
15
+
16
+ st.markdown("### Bank Information")
17
+ col1, col2 = st.columns(2)
18
+ with col1:
19
+ st.text_input("Bank Name", value="NanoPy Bank", disabled=True)
20
+ st.text_input("BIC", value="NANPFRPP", disabled=True)
21
+ with col2:
22
+ st.text_input("Country", value="France", disabled=True)
23
+ st.text_input("Data Directory", value=str(bank.data_dir), disabled=True)
24
+
25
+ st.divider()
26
+
27
+ st.markdown("### Customers")
28
+ if bank.customers:
29
+ for cust_id, customer in bank.customers.items():
30
+ with st.expander(f"👤 {customer.full_name} ({cust_id})"):
31
+ st.markdown(f"**Email:** {customer.email}")
32
+ st.markdown(f"**Phone:** {customer.phone}")
33
+ st.markdown(f"**Address:** {customer.address}, {customer.postal_code} {customer.city}")
34
+ st.markdown(f"**Created:** {customer.created_at.strftime('%d/%m/%Y')}")
35
+
36
+ st.divider()
37
+
38
+ st.markdown("### Danger Zone")
39
+ if st.button("Reset All Data", type="secondary"):
40
+ if st.checkbox("I confirm I want to delete all data"):
41
+ import shutil
42
+ shutil.rmtree(bank.data_dir, ignore_errors=True)
43
+ bank.data_dir.mkdir(parents=True, exist_ok=True)
44
+ bank.customers.clear()
45
+ bank.accounts.clear()
46
+ bank.transactions.clear()
47
+ st.success("All data has been reset")
48
+ st.rerun()
@@ -0,0 +1,94 @@
1
+ """
2
+ Transfers page
3
+ """
4
+
5
+ import streamlit as st
6
+ from decimal import Decimal
7
+
8
+ from .common import format_currency, page_header
9
+
10
+
11
+ def render_transfers():
12
+ """Render transfers page"""
13
+ page_header("Transfers")
14
+
15
+ bank = st.session_state.bank
16
+
17
+ if not bank.accounts:
18
+ st.warning("No accounts available. Create one first.")
19
+ return
20
+
21
+ tab1, tab2 = st.tabs(["New Transfer", "Transfer History"])
22
+
23
+ with tab1:
24
+ st.markdown("### New Transfer")
25
+
26
+ with st.form("transfer_form"):
27
+ col1, col2 = st.columns(2)
28
+
29
+ with col1:
30
+ st.markdown("**From Account**")
31
+ from_options = {f"{acc.account_name} ({format_currency(acc.balance)})": acc.iban for acc in bank.accounts.values()}
32
+ from_account = st.selectbox("Source Account", options=list(from_options.keys()))
33
+
34
+ st.markdown("**Amount**")
35
+ amount = st.number_input("Amount (EUR)", min_value=0.01, value=100.0, step=10.0)
36
+
37
+ with col2:
38
+ st.markdown("**To Account**")
39
+ transfer_type = st.radio("Transfer Type", ["Internal", "External (SEPA)"])
40
+
41
+ if transfer_type == "Internal":
42
+ to_options = {f"{acc.account_name}": acc.iban for acc in bank.accounts.values()}
43
+ to_account = st.selectbox("Destination Account", options=list(to_options.keys()))
44
+ to_iban = to_options[to_account]
45
+ to_name = ""
46
+ to_bic = ""
47
+ else:
48
+ to_iban = st.text_input("Beneficiary IBAN", placeholder="FR76 1234 5678 9012 3456 7890 123")
49
+ to_bic = st.text_input("BIC (optional)", placeholder="BNPAFRPP")
50
+ to_name = st.text_input("Beneficiary Name")
51
+
52
+ label = st.text_input("Label/Reference", placeholder="Rent payment")
53
+
54
+ submitted = st.form_submit_button("Send Transfer", type="primary")
55
+
56
+ if submitted:
57
+ try:
58
+ from_iban = from_options[from_account]
59
+
60
+ if transfer_type == "Internal":
61
+ debit_tx, credit_tx = bank.transfer(from_iban, to_iban, Decimal(str(amount)), label)
62
+ st.success(f"Transfer completed! Reference: {debit_tx.reference}")
63
+ else:
64
+ tx = bank.sepa_credit_transfer(from_iban, to_iban, to_bic, to_name, Decimal(str(amount)), label)
65
+ st.success(f"SEPA transfer initiated! Reference: {tx.reference}")
66
+
67
+ st.rerun()
68
+ except Exception as e:
69
+ st.error(f"Error: {e}")
70
+
71
+ with tab2:
72
+ st.markdown("### Recent Transfers")
73
+
74
+ from nanopy_bank.core import TransactionType
75
+
76
+ all_transfers = [
77
+ tx for tx in bank.transactions.values()
78
+ if tx.transaction_type in [TransactionType.TRANSFER, TransactionType.SEPA_CREDIT]
79
+ ]
80
+ all_transfers.sort(key=lambda x: x.created_at, reverse=True)
81
+
82
+ if all_transfers:
83
+ for tx in all_transfers[:20]:
84
+ col1, col2, col3 = st.columns([3, 2, 2])
85
+ with col1:
86
+ st.markdown(f"**{tx.label}**")
87
+ st.caption(f"{tx.from_iban or 'N/A'} → {tx.to_iban or tx.counterparty_iban or 'N/A'}")
88
+ with col2:
89
+ st.caption(tx.created_at.strftime("%d/%m/%Y %H:%M"))
90
+ with col3:
91
+ st.markdown(f":red[-{format_currency(tx.amount)}]")
92
+ st.divider()
93
+ else:
94
+ st.info("No transfers yet")
@@ -0,0 +1,16 @@
1
+ """
2
+ UI Pages - Modular page components
3
+ """
4
+
5
+ # This module contains page components imported by the main app
6
+ # Each function renders a specific section of the UI
7
+
8
+ # Page functions are defined in app.py for now
9
+ # This module serves as a placeholder for future modularization
10
+
11
+ dashboard = None
12
+ accounts = None
13
+ transfers = None
14
+ cards = None
15
+ sepa = None
16
+ settings = None
@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.4
2
+ Name: nanopy-bank
3
+ Version: 1.0.8
4
+ Summary: Online Banking System with Streamlit UI and SEPA XML support
5
+ Home-page: https://github.com/Web3-League/nanopy-bank
6
+ Author: NanoPy Team
7
+ Author-email: dev@nanopy.chain
8
+ Keywords: banking sepa xml iso20022 streamlit fintech
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Requires-Python: >=3.8
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: streamlit>=1.28.0
19
+ Requires-Dist: streamlit-shadcn-ui>=0.1.0
20
+ Requires-Dist: streamlit-extras>=0.3.0
21
+ Requires-Dist: streamlit-option-menu>=0.3.0
22
+ Requires-Dist: lxml>=4.9.0
23
+ Requires-Dist: pydantic>=2.0.0
24
+ Requires-Dist: sqlalchemy>=2.0.0
25
+ Requires-Dist: aiohttp>=3.8.0
26
+ Requires-Dist: python-dateutil>=2.8.0
27
+ Requires-Dist: schwifty>=2023.0.0
28
+ Requires-Dist: reportlab>=4.0.0
29
+ Requires-Dist: qrcode>=7.4.0
30
+ Requires-Dist: pillow>=10.0.0
31
+ Requires-Dist: click>=8.0.0
32
+ Dynamic: author
33
+ Dynamic: author-email
34
+ Dynamic: classifier
35
+ Dynamic: description
36
+ Dynamic: description-content-type
37
+ Dynamic: home-page
38
+ Dynamic: keywords
39
+ Dynamic: requires-dist
40
+ Dynamic: requires-python
41
+ Dynamic: summary
42
+
43
+ # NanoPy Bank
44
+
45
+ Online Banking System built with Python, Streamlit and shadcn-ui.
46
+
47
+ ## Features
48
+
49
+ - Account Management (IBAN, BIC)
50
+ - Transaction History
51
+ - SEPA/ISO20022 XML Import/Export
52
+ - Real-time Balance Updates
53
+ - Multi-currency Support
54
+ - PDF Statements
55
+ - API for integrations
56
+
57
+ ## Installation
58
+
59
+ ```bash
60
+ pip install nanopy-bank
61
+ nanopy-bank serve
62
+ ```
63
+
64
+ ## Usage
65
+
66
+ ```bash
67
+ # Start the banking UI
68
+ nanopy-bank serve --port 8501
69
+
70
+ # Generate SEPA XML
71
+ nanopy-bank export-sepa --account FR7612345678901234567890123
72
+ ```
@@ -0,0 +1,47 @@
1
+ nanopy_bank/__init__.py,sha256=5HoUAbjRpOgOw_W0T5QGdCi9LPUfeen-7HRiDmlRStA,396
2
+ nanopy_bank/app.py,sha256=Vqna-bHSHEXQEWLxC5zeu4jHta9YfQ7pCBpsfnrJ60g,10363
3
+ nanopy_bank/cli.py,sha256=PDJY3q6s1KESZ3osvUwfpA_X4UctDHO3eqP5Ro1sQ0w,4845
4
+ nanopy_bank/api/__init__.py,sha256=2f6_BP4BTTRly8TxWsotQ1i7KX7xglmO2ahHyD8I_Ew,146
5
+ nanopy_bank/api/server.py,sha256=BnkQ4BelJrRNYEa2eYhxNf1-UQC1OYXrSK-JuSvu4mo,10049
6
+ nanopy_bank/core/__init__.py,sha256=1IppYfqmKRwlOhEM2t93d0UCigK1qpzP4NOOrMDAGq4,1761
7
+ nanopy_bank/core/audit.py,sha256=QRLDlv7ij7BlM4z88J3La_GmFi7qc_mKggHAiEW9WM4,12853
8
+ nanopy_bank/core/auth.py,sha256=2k77FeITfGZXvuASQcrKEcHyZzfG9jq74BNSgm2wJA8,9570
9
+ nanopy_bank/core/bank.py,sha256=4GjRW3t3EWMO_-fY-DXuDhET2wJyu3dKmQnQ6p-4lSY,15223
10
+ nanopy_bank/core/beneficiary.py,sha256=ZqVGLyrwPstichsZNwnN9y6gFSvZuQXrX_GpOvdiaUY,8610
11
+ nanopy_bank/core/branch.py,sha256=7Tje3FbUzut-ThqUPmdMbmvRdo7V5P9G4fVvs_-p-HM,11138
12
+ nanopy_bank/core/fees.py,sha256=6iCDQ5dIcmuijCE1h2_ySRKdqgVWFvRb9Gk_2pXVpX0,8927
13
+ nanopy_bank/core/holding.py,sha256=1u2yijNBN5fvrbYOcSE72PtsU7i4U_xyplTKcjTRSns,14240
14
+ nanopy_bank/core/models.py,sha256=AjdxWRrjhkd2LhtbTs-T3h2aPZgYZ59GJ3GsULhRoqs,10346
15
+ nanopy_bank/core/products.py,sha256=Z8yAiVQpgNE8ww3NeG_8fTcgvjTguJHHvVlWzW1EDi0,11496
16
+ nanopy_bank/data/__init__.py,sha256=6IT2do_H-zAupW1wAE-ALw8sIXhY6_ZnVKz8Lumsk-8,572
17
+ nanopy_bank/data/demo.py,sha256=I6dEbVsI2X6RSQBPm8ZnuvMsAAnxAsAlfMeLGRMWskY,39413
18
+ nanopy_bank/documents/__init__.py,sha256=mF4jvOKVX2-OXs2U8mfQ8gTZnkjPo31HQ2phn45O4D4,204
19
+ nanopy_bank/documents/statement.py,sha256=X8dw4Zt-DErfcrhyNJZOo-y2baoSBAyVVSKKAK10ayM,11013
20
+ nanopy_bank/sepa/__init__.py,sha256=fQeaKo3C4ARKRlGKUDKQQZP7ZN8ajZKzDeCExHe01JM,145
21
+ nanopy_bank/sepa/sepa.py,sha256=HszXec-yKQnp0fkKkdj6vqskMhScoss_8ezML0Ju1lw,18940
22
+ nanopy_bank/storage/__init__.py,sha256=8Rf0jygfM8ZEGZa5CkpkVOM9MCEMtYDq2dnQjPyDe50,196
23
+ nanopy_bank/storage/json_storage.py,sha256=ec7m09BzftqoNiEeUh8zSh22ri78twIDP-5vs6V65u8,3984
24
+ nanopy_bank/storage/sqlite_storage.py,sha256=r5Z3cHyv2IRaf0-RGuFMoinTi3vSe37T2Mk8hKzcaUw,13622
25
+ nanopy_bank/ui/__init__.py,sha256=vcpfgNk8_b6a--ii7GhQJqoNHNPqzWefIEDChALa2G0,236
26
+ nanopy_bank/ui/pages.py,sha256=gt60uZ9B5XZT6nK2tPyDjA1DghhWXvx_LSXaaPzF5Kg,381
27
+ nanopy_bank/ui/pages/__init__.py,sha256=Cq6Q6H_a0HODNhP_cbq60msCwc9WG9Zn8xE3Sp68xYM,853
28
+ nanopy_bank/ui/pages/accounts.py,sha256=ueexihflR1r1Tkv19jqsJF_MsHgq3_7Amnu1vlXOTLs,3538
29
+ nanopy_bank/ui/pages/advisor.py,sha256=0J3XQgsUkvnRBoemOGbs7W4kjJeraOCEkJ50mhMfLFQ,5725
30
+ nanopy_bank/ui/pages/audit.py,sha256=NpID0zVZ6w_lLyR77ZEDYuOPxtBu_ZLONNPju3tmcWQ,2885
31
+ nanopy_bank/ui/pages/beneficiaries.py,sha256=QT9qk35w-Kh6vS-IGT0d3MiMGkQDIdUTAJ7JHUv5asE,4958
32
+ nanopy_bank/ui/pages/branches.py,sha256=o2fF_2ejhT2VnQjz79TcGgyPTZCGeivVvPP6FW8fgdE,2256
33
+ nanopy_bank/ui/pages/cards.py,sha256=y73cFQgS33MTmbFL7S3i45Yy-YlxFLiH5rXm9YR9Qxg,1132
34
+ nanopy_bank/ui/pages/common.py,sha256=BuGDBERfKn-RGLn_p0QPl53mBG3qHvLSQZPvcLSLwT0,514
35
+ nanopy_bank/ui/pages/dashboard.py,sha256=91cZuWc6DMrUkgIXdQuFoF81zBSrUG_jrsHrmvmg_Jw,3931
36
+ nanopy_bank/ui/pages/fees.py,sha256=zkRGZ0Xxuf02BJgjhgMehr2tSUlzwqLulePHc9TBEoY,2334
37
+ nanopy_bank/ui/pages/holding.py,sha256=PJoV_m_ZSXuUXdAFjdlGTE4NwRCe2QiA9XFAOEu7NIE,43970
38
+ nanopy_bank/ui/pages/loans.py,sha256=wkIix4gJVlGzmsY-1APZHmAJMCX--NWW2yULHgDGjvI,4635
39
+ nanopy_bank/ui/pages/login.py,sha256=Irg3omWM1LpzMjlxjFi7dqFWlMUj3Huw9y0sZW5Ru5o,7195
40
+ nanopy_bank/ui/pages/sepa.py,sha256=sKZPkFNFhcp15f8yieDN3K9mcDYCvApv6MUh1U5R7rM,4648
41
+ nanopy_bank/ui/pages/settings.py,sha256=pgmS3g5v6a7_ehfoo36lOUjCLibkjrAbU5qZKPDuwuQ,1640
42
+ nanopy_bank/ui/pages/transfers.py,sha256=NQ_LYhbpLA3JFA5HQdneFgwDA5GVjBlHMdGlGwOai94,3735
43
+ nanopy_bank-1.0.8.dist-info/METADATA,sha256=zyobSWisptp6XNlRzt2l_ipHJ8quzmsZwjk1oMRblgc,1952
44
+ nanopy_bank-1.0.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
45
+ nanopy_bank-1.0.8.dist-info/entry_points.txt,sha256=nXwEPBtjV-N5yI0PBtv7MnI9dWUceqxfHexhVSgpxGU,53
46
+ nanopy_bank-1.0.8.dist-info/top_level.txt,sha256=Rm_HWM6virTPC-zKy8xu0Ml0QqYeCLYnl-pEgFfV6p0,12
47
+ nanopy_bank-1.0.8.dist-info/RECORD,,