COCODEDE04 commited on
Commit
f95e7a2
·
verified ·
1 Parent(s): dbb37d9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +63 -13
app.py CHANGED
@@ -13,6 +13,10 @@ STATS_PATH = os.getenv("STATS_PATH", "means_std.json")
13
  CLASSES = ["Top", "Mid-Top", "Mid", "Mid-Low", "Low"]
14
  # ------------------------------------------
15
 
 
 
 
 
16
  print("Loading model and stats...")
17
  model = tf.keras.models.load_model(MODEL_PATH, compile=False)
18
 
@@ -23,6 +27,7 @@ with open(STATS_PATH, "r") as f:
23
  FEATURES = list(stats.keys())
24
  print("Feature order:", FEATURES)
25
 
 
26
  # ---------- robust numeric coercion ----------
27
  def coerce_float(val: Any) -> float:
28
  """
@@ -60,12 +65,11 @@ def coerce_float(val: Any) -> float:
60
  elif has_comma and not has_dot:
61
  # likely decimal is comma
62
  s = s.replace(",", ".")
63
- else:
64
- # dots only or pure digits -> leave as is
65
- pass
66
 
67
  return float(s)
68
 
 
69
  def _z(val: Any, mean: float, sd: float) -> float:
70
  try:
71
  v = coerce_float(val)
@@ -75,15 +79,17 @@ def _z(val: Any, mean: float, sd: float) -> float:
75
  return 0.0
76
  return (v - mean) / sd
77
 
 
78
  def coral_probs_from_logits(logits_np: np.ndarray) -> np.ndarray:
79
  """(N, K-1) logits -> (N, K) probabilities for CORAL ordinal output."""
80
  logits = tf.convert_to_tensor(logits_np, dtype=tf.float32)
81
  sig = tf.math.sigmoid(logits) # (N, K-1)
82
- left = tf.concat([tf.ones_like(sig[:, :1]), sig], axis=1)
83
  right = tf.concat([sig, tf.zeros_like(sig[:, :1])], axis=1)
84
  probs = tf.clip_by_value(left - right, 1e-12, 1.0)
85
  return probs.numpy()
86
 
 
87
  # ------------- FastAPI app ----------------
88
  app = FastAPI(title="Static Fingerprint API", version="1.0.0")
89
 
@@ -96,9 +102,14 @@ app.add_middleware(
96
  allow_headers=["*"],
97
  )
98
 
 
99
  @app.get("/")
100
  def root():
101
- return {"message": "Static Fingerprint API is running.", "try": ["GET /health", "POST /predict"]}
 
 
 
 
102
 
103
  @app.get("/health")
104
  def health():
@@ -110,11 +121,13 @@ def health():
110
  "stats_file": STATS_PATH,
111
  }
112
 
 
113
  @app.post("/echo")
114
  async def echo(req: Request):
115
  payload = await req.json()
116
  return {"received": payload}
117
 
 
118
  @app.post("/predict")
119
  async def predict(req: Request):
120
  """
@@ -136,7 +149,7 @@ async def predict(req: Request):
136
  missing = []
137
  for f in FEATURES:
138
  mean = stats[f]["mean"]
139
- sd = stats[f]["std"]
140
  if f in payload:
141
  zf = _z(payload[f], mean, sd)
142
  else:
@@ -148,20 +161,57 @@ async def predict(req: Request):
148
  X = np.array([z], dtype=np.float32)
149
  raw = model.predict(X, verbose=0)
150
 
151
- # Detect CORAL (K-1) vs softmax (K)
152
- if raw.ndim == 2 and raw.shape[1] == (len(CLASSES) - 1):
153
- probs = coral_probs_from_logits(raw)[0]
154
- else:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  probs = raw[0]
156
  s = float(np.sum(probs))
157
  if s > 0:
158
  probs = probs / s
159
 
160
  pred_idx = int(np.argmax(probs))
161
- return {
 
162
  "input_ok": (len(missing) == 0),
163
  "missing": missing,
164
  "z_scores": z_detail,
165
- "probabilities": {CLASSES[i]: float(probs[i]) for i in range(len(CLASSES))},
 
 
166
  "predicted_state": CLASSES[pred_idx],
167
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  CLASSES = ["Top", "Mid-Top", "Mid", "Mid-Low", "Low"]
14
  # ------------------------------------------
15
 
16
+ # Debug & decoding control
17
+ FORCE_CORAL = os.getenv("FORCE_CORAL", "0") in ("1", "true", "True", "YES", "yes")
18
+ RETURN_DEBUG = os.getenv("RETURN_DEBUG", "1") in ("1", "true", "True", "YES", "yes")
19
+
20
  print("Loading model and stats...")
21
  model = tf.keras.models.load_model(MODEL_PATH, compile=False)
22
 
 
27
  FEATURES = list(stats.keys())
28
  print("Feature order:", FEATURES)
29
 
30
+
31
  # ---------- robust numeric coercion ----------
32
  def coerce_float(val: Any) -> float:
33
  """
 
65
  elif has_comma and not has_dot:
66
  # likely decimal is comma
67
  s = s.replace(",", ".")
68
+ # dots only or pure digits -> leave as is
 
 
69
 
70
  return float(s)
71
 
72
+
73
  def _z(val: Any, mean: float, sd: float) -> float:
74
  try:
75
  v = coerce_float(val)
 
79
  return 0.0
80
  return (v - mean) / sd
81
 
82
+
83
  def coral_probs_from_logits(logits_np: np.ndarray) -> np.ndarray:
84
  """(N, K-1) logits -> (N, K) probabilities for CORAL ordinal output."""
85
  logits = tf.convert_to_tensor(logits_np, dtype=tf.float32)
86
  sig = tf.math.sigmoid(logits) # (N, K-1)
87
+ left = tf.concat([tf.ones_like(sig[:, :1]), sig], axis=1)
88
  right = tf.concat([sig, tf.zeros_like(sig[:, :1])], axis=1)
89
  probs = tf.clip_by_value(left - right, 1e-12, 1.0)
90
  return probs.numpy()
91
 
92
+
93
  # ------------- FastAPI app ----------------
94
  app = FastAPI(title="Static Fingerprint API", version="1.0.0")
95
 
 
102
  allow_headers=["*"],
103
  )
104
 
105
+
106
  @app.get("/")
107
  def root():
108
+ return {
109
+ "message": "Static Fingerprint API is running.",
110
+ "try": ["GET /health", "POST /predict"],
111
+ }
112
+
113
 
114
  @app.get("/health")
115
  def health():
 
121
  "stats_file": STATS_PATH,
122
  }
123
 
124
+
125
  @app.post("/echo")
126
  async def echo(req: Request):
127
  payload = await req.json()
128
  return {"received": payload}
129
 
130
+
131
  @app.post("/predict")
132
  async def predict(req: Request):
133
  """
 
149
  missing = []
150
  for f in FEATURES:
151
  mean = stats[f]["mean"]
152
+ sd = stats[f]["std"]
153
  if f in payload:
154
  zf = _z(payload[f], mean, sd)
155
  else:
 
161
  X = np.array([z], dtype=np.float32)
162
  raw = model.predict(X, verbose=0)
163
 
164
+ # ---------------- DEBUG INFO ----------------
165
+ raw_shape = tuple(raw.shape)
166
+ # --------------------------------------------
167
+
168
+ # Decode: CORAL vs Softmax
169
+ probs = None
170
+ decode_mode = "auto"
171
+ try:
172
+ if FORCE_CORAL:
173
+ decode_mode = "forced_coral"
174
+ probs = coral_probs_from_logits(raw)[0]
175
+ else:
176
+ if raw.ndim == 2 and raw.shape[1] == (len(CLASSES) - 1):
177
+ decode_mode = "auto_coral"
178
+ probs = coral_probs_from_logits(raw)[0]
179
+ else:
180
+ decode_mode = "auto_softmax_or_logits"
181
+ probs = raw[0]
182
+ s = float(np.sum(probs))
183
+ if s > 0: # defensive normalize
184
+ probs = probs / s
185
+ except Exception as e:
186
+ decode_mode = "fallback_raw_norm"
187
  probs = raw[0]
188
  s = float(np.sum(probs))
189
  if s > 0:
190
  probs = probs / s
191
 
192
  pred_idx = int(np.argmax(probs))
193
+
194
+ resp = {
195
  "input_ok": (len(missing) == 0),
196
  "missing": missing,
197
  "z_scores": z_detail,
198
+ "probabilities": {
199
+ CLASSES[i]: float(probs[i]) for i in range(len(CLASSES))
200
+ },
201
  "predicted_state": CLASSES[pred_idx],
202
+ }
203
+
204
+ # Include debug fields so we can see shape & decode path
205
+ if RETURN_DEBUG:
206
+ resp["debug"] = {
207
+ "raw_shape": raw_shape,
208
+ "decode_mode": decode_mode,
209
+ "raw_first_row": [
210
+ float(x)
211
+ for x in (
212
+ raw[0].tolist() if raw.ndim >= 2 else [float(raw)]
213
+ )
214
+ ],
215
+ }
216
+
217
+ return resp