buraktrk commited on
Commit
93dc968
·
verified ·
1 Parent(s): 950b5bb

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +293 -0
app.py ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -----------------------------------------------------------------------------
2
+ # app_fhe.py – Gradio UI that performs audit‑opinion (credit‑risk) prediction
3
+ # with a Concrete‑ML FHE‑compiled XGBoost model.
4
+ # -----------------------------------------------------------------------------
5
+ # Workflow
6
+ # --------
7
+ # 1. User uploads Excel ⇒ 24 financial ratios are computed.
8
+ # 2. Feature vector is encrypted with a freshly generated **public key**.
9
+ # 3. FHE inference runs server‑side, returns ciphertext + **secret key**.
10
+ # 4. User pastes secret key → presses **Çöz** → result is decrypted client‑side.
11
+ # -----------------------------------------------------------------------------
12
+ # NOTE: Key exchange is demo‑grade (base64). In production, the secret key
13
+ # should never leave the client; consider WebAssembly or hybrid encryption.
14
+ # -----------------------------------------------------------------------------
15
+
16
+ from __future__ import annotations
17
+
18
+ import base64
19
+ import io
20
+ from pathlib import Path
21
+ from typing import Any
22
+
23
+ import gradio as gr
24
+ import joblib
25
+ import numpy as np
26
+ import pandas as pd
27
+
28
+ # Concrete‑ML – change path if needed
29
+ from concrete.ml.sklearn import FHEModel # type: ignore
30
+
31
+ # -----------------------------------------------------------------------------
32
+ # CONSTANTS & PATHS
33
+ # -----------------------------------------------------------------------------
34
+ SAMPLE_DIR = "Sample Inputs (Excel)"
35
+ EXAMPLE_XLSX = [
36
+ f"{SAMPLE_DIR}/ADESE_2021.xlsx",
37
+ f"{SAMPLE_DIR}/YYAPI_2017.xlsx",
38
+ f"{SAMPLE_DIR}/SRVGY_2022.xlsx",
39
+ f"{SAMPLE_DIR}/THYAO_2023.xlsx",
40
+ f"{SAMPLE_DIR}/TTRAK_2024.xlsx",
41
+ ]
42
+
43
+ FHE_MODEL_PATH = "fhe_xgb.joblib" # Concrete‑ML‑compiled model
44
+ ENCODER_PATH = "label_encoder.joblib" # scikit‑learn LabelEncoder
45
+
46
+ # 24‑FEATURE SET (user‑provided)
47
+ SELECTED_FEATS = [
48
+ "Finansal Kaldıraç", "Zmijewski Skoru", "Cari Oran", "Asit Test Oranı",
49
+ "Nakit Oranı", "Aktif Devir Hızı", "Duran Varlıklar / Aktif ",
50
+ "Altman Z-Skoru", "Brüt Kar Marjı (%)", "Özsermaye / Aktif",
51
+ "Kısa Vade Borç / Aktif", "ROCE Oranı", "L Model Skoru", "Net Kar Marjı",
52
+ "Dönen Varlıklar Devir Hızı", "Dönen Varlıklar / Aktif (%)",
53
+ "Esas Faaliyet Karı / Kısa Vadeli Borç", "Kısa Vade Borç / Özsermaye",
54
+ "Kısa Vade Borç / Toplam Borç", "Finansman Gider / Net Satış",
55
+ "Faaliyet Kar Marjı", "Aktif Karlılık (%)", "Stok Devir Hızı",
56
+ "Özsermaye / Maddi Duran Varlıklar",
57
+ ]
58
+
59
+ DISPLAY_FEATS = ["Altman Z-Skoru", "L Model Skoru", "Zmijewski Skoru"]
60
+
61
+ # -----------------------------------------------------------------------------
62
+ # LOAD FHE MODEL (compile if necessary)
63
+ # -----------------------------------------------------------------------------
64
+ FHE_MODEL: FHEModel = joblib.load(FHE_MODEL_PATH)
65
+ LABEL_ENCODER = joblib.load(ENCODER_PATH)
66
+
67
+ if not getattr(FHE_MODEL, "is_compiled", False):
68
+ FHE_MODEL.compile(np.zeros((1, len(SELECTED_FEATS)), dtype=np.float32))
69
+
70
+ # -----------------------------------------------------------------------------
71
+ # FINANCIAL‑RATIO UTILITIES
72
+ # -----------------------------------------------------------------------------
73
+
74
+ def safe_div(num: pd.Series, denom: pd.Series) -> pd.Series:
75
+ """Safe division that returns 0 when denominator is 0."""
76
+ denom_replaced = denom.replace(0, np.nan)
77
+ return (num / denom_replaced).fillna(0)
78
+
79
+
80
+ def compute_ratios(df: pd.DataFrame) -> pd.DataFrame:
81
+ """Compute the 24 ratios needed for the FHE model and add them to *df*."""
82
+
83
+ # Totals
84
+ total_assets = df["Dönen Varlıklar"] + df["Duran Varlıklar"]
85
+ total_liab = df["Kısa Vadeli Yükümlülükler"] + df["Uzun Vadeli Yükümlülükler"]
86
+
87
+ # 1‑3 Likidite
88
+ df["Cari Oran"] = safe_div(df["Dönen Varlıklar"], df["Kısa Vadeli Yükümlülükler"])
89
+ df["Asit Test Oranı"] = safe_div(df["Dönen Varlıklar"] - df["Stoklar"] - df["Diğer Dönen Varlıklar"],
90
+ df["Kısa Vadeli Yükümlülükler"])
91
+ df["Nakit Oranı"] = safe_div(df["Nakit ve Nakit Benzerleri"], df["Kısa Vadeli Yükümlülükler"])
92
+
93
+ # 4‑7 Marj & kârlılık
94
+ df["Faaliyet Kar Marjı"] = safe_div(df["FAALİYET KARI (ZARARI)"]*100, df["Satış Gelirleri"])
95
+ df["Brüt Kar Marjı (%)"] = safe_div(df["Ticari Faaliyetlerden Brüt Kar (Zarar)"]*100,
96
+ df["Satış Gelirleri"])
97
+ df["Net Kar Marjı"] = safe_div(df["Dönem Net Kar/Zararı"]*100, df["Satış Gelirleri"])
98
+ df["Aktif Karlılık (%)"] = safe_div(df["Dönem Net Kar/Zararı"]*100, total_assets)
99
+
100
+ # 8‑10 Verimlilik
101
+ df["Aktif Devir Hızı"] = safe_div(df["Satış Gelirleri"], total_assets)
102
+ df["Dönen Varlıklar Devir Hızı"] = safe_div(df["Dönen Varlıklar"], df["Satış Gelirleri"])
103
+ df["Stok Devir Hızı"] = -safe_div(df["Satışların Maliyeti (-)"], df["Stoklar"])
104
+
105
+ # 11‑15 Borç & kaldıraç
106
+ df["Finansal Kaldıraç"] = safe_div(total_liab, total_assets)*100
107
+ df["Kısa Vade Borç / Aktif"] = safe_div(df["Kısa Vadeli Yükümlülükler"], total_assets)
108
+ df["Kısa Vade Borç / Özsermaye"] = safe_div(df["Kısa Vadeli Yükümlülükler"], df["Özkaynaklar"])
109
+ df["Kısa Vade Borç / Toplam Borç"] = safe_div(df["Kısa Vadeli Yükümlülükler"], total_liab)
110
+ df["Özsermaye / Aktif"] = safe_div(df["Özkaynaklar"], total_assets)
111
+
112
+ # 16‑18 Diğer oranlar
113
+ df["Duran Varlıklar / Aktif "] = safe_div(df["Duran Varlıklar"]*100, total_assets)
114
+ df["Dönen Varlıklar / Aktif (%)"] = safe_div(df["Dönen Varlıklar"]*100, total_assets)
115
+ df["Özsermaye / Maddi Duran Varlıklar"] = safe_div(df["Özkaynaklar"], df["Maddi Duran Varlıklar"])
116
+
117
+ # 19‑20 Finansman
118
+ df["Finansman Gider / Net Satış"] = safe_div(df["Finansman Giderleri"], df["Satış Gelirleri"])
119
+ df["Esas Faaliyet Karı / Kısa Vadeli Borç"] = safe_div(df["Net Faaliyet Kar/Zararı"],
120
+ df["Kısa Vadeli Yükümlülükler"])
121
+
122
+ # ROCE
123
+ df["ROCE Oranı"] = safe_div(df["FAALİYET KARI (ZARARI)"]*100, total_assets)
124
+
125
+ # ----- Distress & lifetime scores ---------------------------------------------------------
126
+ # Altman Z
127
+ X1 = safe_div(df["Dönen Varlıklar"] - df["Kısa Vadeli Yükümlülükler"], total_assets)
128
+ X2 = safe_div(df["Geçmiş Yıllar Kar/Zararları"] + df["Dönem Net Kar/Zararı"], total_assets)
129
+ X3 = safe_div(df["SÜRDÜRÜLEN FAALİYETLER VERGİ ÖNCESİ KARI (ZARARI)"], total_assets)
130
+ X4 = safe_div(df["Özkaynaklar"], total_liab)
131
+ X5 = safe_div(df["Satış Gelirleri"], total_assets)
132
+
133
+ df["Altman Z-Skoru"] = 1.2*X1 + 1.4*X2 + 3.3*X3 + 0.6*X4 + X5
134
+
135
+ # Zmijewski
136
+ Z1 = safe_div(df["Dönem Net Kar/Zararı"], total_assets)
137
+ Z2 = safe_div(total_liab, total_assets)
138
+ Z3 = safe_div(df["Dönen Varlıklar"], df["Kısa Vadeli Yükümlülükler"])
139
+ df["Zmijewski Skoru"] = -4.3 - 4.5*Z1 + 5.7*Z2 - 0.004*Z3
140
+
141
+ # L‑Model
142
+ L6 = safe_div(safe_div(df["Nakit ve Nakit Benzerleri"], df["Kısa Vadeli Yükümlülükler"]), total_liab)
143
+ L7 = safe_div(total_liab, total_assets)
144
+ df["L Model Skoru"] = -0.113*X1 + 0.238*X2 - 0.052*X3 - 0.051*X4 + 0.011*X5 + 0.729*L6 - 0.639*L7
145
+
146
+ return df
147
+
148
+ # -----------------------------------------------------------------------------
149
+ # BASE64 HELPERS
150
+ # -----------------------------------------------------------------------------
151
+
152
+ def _b64_dump(obj: Any) -> str:
153
+ buff = io.BytesIO()
154
+ joblib.dump(obj, buff)
155
+ buff.seek(0)
156
+ return base64.b64encode(buff.read()).decode()
157
+
158
+
159
+ def _b64_load(txt: str) -> Any:
160
+ return joblib.load(io.BytesIO(base64.b64decode(txt.encode())))
161
+
162
+ # -----------------------------------------------------------------------------
163
+ # SESSION STATE
164
+ # -----------------------------------------------------------------------------
165
+
166
+ encrypted_pred_state = gr.State(value=None)
167
+
168
+ # -----------------------------------------------------------------------------
169
+ # CALLBACKS
170
+ # -----------------------------------------------------------------------------
171
+
172
+ def encrypt_and_predict(excel_file: gr.File):
173
+ """Encrypt features and run FHE inference; return secret key to the user."""
174
+
175
+ if excel_file is None:
176
+ raise gr.Error("Lütfen analiz edilecek Excel dosyasını yükleyin.")
177
+
178
+ p = gr.Progress(track_tqdm=False)
179
+ p(0.05, "Excel okunuyor…")
180
+
181
+ # ---- 1. Read + pivot ----------------------------------------------------
182
+ raw_vert = pd.read_excel(excel_file.name, header=None, sheet_name=0)
183
+ raw_df = (
184
+ raw_vert.set_index(0).T.rename_axis(None).reset_index(drop=True)
185
+ )
186
+ raw_df.columns = raw_df.columns.str.strip()
187
+ raw_df = raw_df.loc[:, ~raw_df.columns.duplicated()]
188
+ raw_df.rename(columns={"Desc": "Periyot"}, inplace=True)
189
+ raw_df["Periyot"] = raw_df["Periyot"].astype(str).str.replace(r"\s+", " ", regex=True).str.strip()
190
+
191
+ # ---- 2. Ratios ----------------------------------------------------------
192
+ p(0.30, "Oranlar hesaplanıyor…")
193
+ enriched = compute_ratios(raw_df.copy())
194
+ model_input = enriched[SELECTED_FEATS].copy().dropna()
195
+ if model_input.empty:
196
+ raise gr.Error("Hiç analiz edilebilir satır kalmadı – oran hesaplanamadı.")
197
+
198
+ # ---- 3. Keygen ----------------------------------------------------------
199
+ p(0.55, "Anahtarlar oluşturuluyor…")
200
+ public_key, secret_key = FHE_MODEL.keygen()
201
+
202
+ # ---- 4. Encrypted inference --------------------------------------------
203
+ p(0.75, "Şifreli tahmin yapılıyor…")
204
+ x_enc = FHE_MODEL.encrypt(model_input.values.astype(np.float32), public_key)
205
+ y_enc = FHE_MODEL.run(x_enc)
206
+
207
+ encrypted_pred_state.value = y_enc
208
+
209
+ # ---- 5. Small bar chart -------------------------------------------------
210
+ ratio_row = model_input.iloc[0][DISPLAY_FEATS]
211
+ max_abs = ratio_row.abs().max() or 1
212
+ bars_html = "<table style='width:100%;font-size:0.85rem'>"
213
+ for k, v in ratio_row.items():
214
+ pct = abs(v)/max_abs*100
215
+ bars_html += (
216
+ f"<tr><td style='padding:2px 6px;white-space:nowrap'>{k}</td>"
217
+ f"<td style='width:100%'><div style='background:#e5e5e5;height:8px;border-radius:4px'>"
218
+ f"<div style='width:{pct:.1f}%;height:8px;background:#3b82f6;border-radius:4px'></div>"
219
+ f"</div></td><td style='padding-left:6px'>{v:.2f}</td></tr>"
220
+ )
221
+ bars_html += "</table>"
222
+
223
+ secret_key_b64 = _b64_dump(secret_key)
224
+
225
+ return (
226
+ gr.update(value="### Ham Veri", visible=True),
227
+ gr.update(value=raw_df.head(), visible=True),
228
+ gr.update(value="### Finansal Oranlar", visible=True),
229
+ gr.update(value=bars_html, visible=True),
230
+ gr.update(value="### Tahmin (Şifreli)", visible=True),
231
+ gr.update(value="Şifreli tahmin hazır. Aşağıdaki gizli anahtarı kaydedin ve 'Çöz' düğmesine bastığınızda tahmin açığa çıkacaktır.", visible=True),
232
+ gr.update(value=secret_key_b64, visible=True),
233
+ )
234
+
235
+
236
+ def decrypt_prediction(secret_key_b64: str):
237
+ """Decrypt the FHE ciphertext using the user‑supplied secret key."""
238
+
239
+ if encrypted_pred_state.value is None:
240
+ raise gr.Error("Önce 'Şifrele & Tahmin Et' adımını tamamlayın.")
241
+ if not secret_key_b64:
242
+ raise gr.Error("Gizli anahtarı girin.")
243
+
244
+ secret_key = _b64_load(secret_key_b64)
245
+ y_enc = encrypted_pred_state.value
246
+ y_pred = FHE_MODEL.decrypt(y_enc, secret_key)
247
+ labels = LABEL_ENCODER.inverse_transform(np.array(y_pred, dtype=int))
248
+
249
+ result_df = pd.DataFrame({"Tahmin Görüş Tipi": labels})
250
+
251
+ return (
252
+ gr.update(value="### Tahmin Sonuçları", visible=True),
253
+ gr.update(value=result_df, visible=True),
254
+ )
255
+
256
+ # -----------------------------------------------------------------------------
257
+ # GRADIO UI
258
+ # -----------------------------------------------------------------------------
259
+ with gr.Blocks(theme=gr.themes.Default(primary_hue="blue")) as demo:
260
+ gr.Markdown("# Denetçi Görüşü Tahmini – FHE (24 Oran)")
261
+
262
+ with gr.Row():
263
+ file_input = gr.File(file_types=[".xlsx", ".xls", ".xlsm"], label="Excel Yükleyin")
264
+ predict_btn = gr.Button("Şifrele & Tahmin Et", variant="primary")
265
+
266
+ gr.Examples(EXAMPLE_XLSX, inputs=file_input, label="Örnek Dosyayı Deneyin", cache_examples=False)
267
+
268
+ secret_key_output = gr.Textbox(label="Gizli Anahtar (kopyalayın)", visible=False, interactive=False)
269
+ decrypt_key_input = gr.Textbox(label="Gizli Anahtarı Yapıştırın ve Çözün", visible=False)
270
+ decrypt_btn = gr.Button("Çöz", variant="secondary", visible=False)
271
+
272
+ ham_title = gr.Markdown(visible=False)
273
+ raw_table = gr.Dataframe(visible=False, wrap=True, show_label=False)
274
+
275
+ ratio_title = gr.Markdown(visible=False)
276
+ ratio_html = gr.HTML(visible=False)
277
+
278
+ enc_title = gr.Markdown(visible=False)
279
+ enc_msg = gr.Markdown(visible=False)
280
+
281
+ pred_title = gr.Markdown(visible=False)
282
+ pred_table = gr.Dataframe(visible=False, wrap=True, show_label=False)
283
+
284
+ predict_btn.click(
285
+ encrypt_and_predict,
286
+ inputs=[file_input],
287
+ outputs=[ham_title, raw_table, ratio_title, ratio_html, enc_title, enc_msg, secret_key_output]
288
+ ).then(lambda x: gr.update(visible=True), None, decrypt_key_input
289
+ ).then(lambda x: gr.update(visible=True), None, decrypt_btn)
290
+
291
+ decrypt_btn.click(
292
+ decrypt_prediction,
293
+ inputs=[decrypt_key