Abid Ali Awan codex commited on
Commit
25f0a2d
·
1 Parent(s): ad3a3d5

Cache built-in example assessments

Browse files

Co-Authored-By: codex <codex@openai.com>

Files changed (4) hide show
  1. README.md +8 -3
  2. app.py +129 -3
  3. static/app.js +15 -2
  4. static/index.html +6 -6
README.md CHANGED
@@ -84,9 +84,14 @@ See [local model setup](docs/local_model_setup.md) and
84
  The app sends text and optional image data to the configured multimodal
85
  OpenAI-compatible endpoint and validates its structured response.
86
 
87
- There is no rule-based or sample fallback. If credentials are missing, the
88
- endpoint is unavailable, or the model returns invalid output, the app displays
89
- a clear error and does not manufacture an assessment.
 
 
 
 
 
90
 
91
  ## Architecture
92
 
 
84
  The app sends text and optional image data to the configured multimodal
85
  OpenAI-compatible endpoint and validates its structured response.
86
 
87
+ The six built-in text and screenshot examples use precomputed assessments.
88
+ Trying those examples does not call or wake the Modal endpoint. Editing an
89
+ example or uploading a different image switches back to normal model analysis.
90
+
91
+ There is no rule-based or sample fallback for user-submitted input. If
92
+ credentials are missing, the endpoint is unavailable, or the model returns
93
+ invalid output, the app displays a clear error and does not manufacture an
94
+ assessment.
95
 
96
  ## Architecture
97
 
app.py CHANGED
@@ -36,6 +36,116 @@ REQUIRED_FIELDS = {
36
  "safe_next_steps",
37
  "reply_draft",
38
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
  SYSTEM_PROMPT = """You help people in Pakistan assess notices and messages.
41
  Return only JSON matching the supplied schema. Use simple, calm English.
@@ -251,10 +361,20 @@ def call_model(text: str, image_data_url: str) -> dict[str, Any]:
251
  raise RuntimeError("Model request ended without a response.")
252
 
253
 
254
- def analyze_notice(text: str = "", image_data_url: str = "") -> dict[str, Any]:
 
 
255
  """Analyze supplied text/image using the configured model only."""
256
  text = (text or "").strip()
257
  image_data_url = image_data_url or ""
 
 
 
 
 
 
 
 
258
  if not text and not image_data_url:
259
  return {
260
  "ok": False,
@@ -298,8 +418,10 @@ app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
298
 
299
 
300
  @app.api(name="analyze", description="Assess a notice for common scam signals.", concurrency_limit=1)
301
- def analyze_api(text: str = "", image_data_url: str = "") -> dict[str, Any]:
302
- return analyze_notice(text, image_data_url)
 
 
303
 
304
 
305
  @app.api(name="status", description="Return model and privacy status.", queue=False)
@@ -351,6 +473,10 @@ def run_self_tests() -> None:
351
  }
352
  )
353
  assert inappropriate["reply_draft"] == ""
 
 
 
 
354
  assert analyze_notice("", "")["ok"] is False
355
  try:
356
  normalize_assessment({"risk_label": "Looks normal"})
 
36
  "safe_next_steps",
37
  "reply_draft",
38
  }
39
+ EXAMPLE_ASSESSMENTS: dict[str, dict[str, Any]] = {
40
+ "text-courier": {
41
+ "risk_label": "Likely scam",
42
+ "simple_explanation": (
43
+ "This message uses an urgent parcel fee and an unofficial-looking "
44
+ "link to pressure the recipient into paying."
45
+ ),
46
+ "red_flags": [
47
+ "Unexpected delivery fee",
48
+ "Threat that the parcel will be destroyed",
49
+ "Link does not clearly belong to Pakistan Post",
50
+ ],
51
+ "safe_next_steps": [
52
+ "Do not open the link or make the payment.",
53
+ "Check the parcel through the courier's independently found official website.",
54
+ "Report the message as spam or phishing.",
55
+ ],
56
+ "reply_draft": "",
57
+ },
58
+ "text-fbr": {
59
+ "risk_label": "Likely scam",
60
+ "simple_explanation": (
61
+ "An unexpected tax refund that asks for CNIC and bank-card details "
62
+ "is a common identity and payment phishing pattern."
63
+ ),
64
+ "red_flags": [
65
+ "Unexpected refund offer",
66
+ "Request for CNIC and card details",
67
+ "Pressure to act today",
68
+ ],
69
+ "safe_next_steps": [
70
+ "Do not submit personal or banking information.",
71
+ "Check your tax account by navigating to the official FBR website yourself.",
72
+ "Report the sender through an official complaint channel.",
73
+ ],
74
+ "reply_draft": "",
75
+ },
76
+ "text-bank": {
77
+ "risk_label": "Likely scam",
78
+ "simple_explanation": (
79
+ "A bank or support representative should never ask you to share an "
80
+ "OTP. The account-suspension threat is intended to create panic."
81
+ ),
82
+ "red_flags": [
83
+ "Direct request for an OTP",
84
+ "Threat of immediate account suspension",
85
+ "Pressure to bypass normal bank security",
86
+ ],
87
+ "safe_next_steps": [
88
+ "Do not share the OTP or reply to the sender.",
89
+ "Contact the bank using the number on your card or official app.",
90
+ "Change credentials immediately if an OTP or password was already shared.",
91
+ ],
92
+ "reply_draft": "",
93
+ },
94
+ "image-courier": {
95
+ "risk_label": "Likely scam",
96
+ "simple_explanation": (
97
+ "This sample screenshot shows a fake delivery-fee message designed "
98
+ "to send the recipient to an untrusted payment link."
99
+ ),
100
+ "red_flags": [
101
+ "Unexpected courier payment request",
102
+ "Urgent delivery language",
103
+ "Untrusted link in the message",
104
+ ],
105
+ "safe_next_steps": [
106
+ "Do not use the link in the screenshot.",
107
+ "Verify any shipment through the courier's official tracking page.",
108
+ "Block and report the sender.",
109
+ ],
110
+ "reply_draft": "",
111
+ },
112
+ "image-mobile": {
113
+ "risk_label": "Likely scam",
114
+ "simple_explanation": (
115
+ "This sample screenshot uses a fake mobile-related offer or alert "
116
+ "to prompt unsafe contact or disclosure of personal information."
117
+ ),
118
+ "red_flags": [
119
+ "Unsolicited mobile-related claim",
120
+ "Pressure to respond outside an official app",
121
+ "Unverified sender details",
122
+ ],
123
+ "safe_next_steps": [
124
+ "Do not reply or share account information.",
125
+ "Check the claim in the mobile operator's official app or website.",
126
+ "Report and block the sender.",
127
+ ],
128
+ "reply_draft": "",
129
+ },
130
+ "image-traffic": {
131
+ "risk_label": "Likely scam",
132
+ "simple_explanation": (
133
+ "This sample screenshot imitates an e-challan notice and pressures "
134
+ "the recipient to use an unverified payment route."
135
+ ),
136
+ "red_flags": [
137
+ "Unexpected traffic fine",
138
+ "Unverified payment instructions",
139
+ "Urgency intended to discourage independent checks",
140
+ ],
141
+ "safe_next_steps": [
142
+ "Do not pay through the message link.",
143
+ "Check the challan on the relevant official traffic authority website.",
144
+ "Report the message if no official record exists.",
145
+ ],
146
+ "reply_draft": "",
147
+ },
148
+ }
149
 
150
  SYSTEM_PROMPT = """You help people in Pakistan assess notices and messages.
151
  Return only JSON matching the supplied schema. Use simple, calm English.
 
361
  raise RuntimeError("Model request ended without a response.")
362
 
363
 
364
+ def analyze_notice(
365
+ text: str = "", image_data_url: str = "", example_id: str = ""
366
+ ) -> dict[str, Any]:
367
  """Analyze supplied text/image using the configured model only."""
368
  text = (text or "").strip()
369
  image_data_url = image_data_url or ""
370
+ example_id = (example_id or "").strip()
371
+ if example_id in EXAMPLE_ASSESSMENTS:
372
+ return {
373
+ "ok": True,
374
+ "assessment": dict(EXAMPLE_ASSESSMENTS[example_id]),
375
+ "status": model_status(),
376
+ "source": "cached_example",
377
+ }
378
  if not text and not image_data_url:
379
  return {
380
  "ok": False,
 
418
 
419
 
420
  @app.api(name="analyze", description="Assess a notice for common scam signals.", concurrency_limit=1)
421
+ def analyze_api(
422
+ text: str = "", image_data_url: str = "", example_id: str = ""
423
+ ) -> dict[str, Any]:
424
+ return analyze_notice(text, image_data_url, example_id)
425
 
426
 
427
  @app.api(name="status", description="Return model and privacy status.", queue=False)
 
473
  }
474
  )
475
  assert inappropriate["reply_draft"] == ""
476
+ cached = analyze_notice(example_id="text-bank")
477
+ assert cached["ok"] is True
478
+ assert cached["source"] == "cached_example"
479
+ assert cached["assessment"]["risk_label"] == "Likely scam"
480
  assert analyze_notice("", "")["ok"] is False
481
  try:
482
  normalize_assessment({"risk_label": "Looks normal"})
static/app.js CHANGED
@@ -19,6 +19,7 @@ const elements = {
19
 
20
  let imageDataUrl = "";
21
  let activeMode = null;
 
22
 
23
  async function callGradioApi(name, data) {
24
  const response = await fetch(`/gradio_api/call/${name}`, {
@@ -124,13 +125,16 @@ function renderResult(payload) {
124
 
125
  elements.source.textContent = payload.source === "model"
126
  ? "Analyzed by the deployed Qwen model endpoint."
127
- : "";
 
 
128
  elements.results.hidden = false;
129
  elements.results.scrollIntoView({ behavior: "smooth", block: "start" });
130
  }
131
 
132
  function useImage(file) {
133
  if (!file) return;
 
134
  const allowed = ["image/png", "image/jpeg", "image/webp"];
135
  if (!allowed.includes(file.type)) return showError("Use a PNG, JPG, or WebP image.");
136
  if (file.size > 8 * 1024 * 1024) return showError("Please choose an image smaller than 8 MB.");
@@ -150,6 +154,7 @@ elements.removeImage.addEventListener("click", (event) => {
150
  event.preventDefault();
151
  event.stopPropagation();
152
  imageDataUrl = "";
 
153
  elements.image.value = "";
154
  elements.preview.removeAttribute("src");
155
  elements.dropZone.classList.remove("has-image");
@@ -165,6 +170,7 @@ elements.removeImage.addEventListener("click", (event) => {
165
  }));
166
  elements.dropZone.addEventListener("drop", (event) => useImage(event.dataTransfer.files[0]));
167
  elements.text.addEventListener("input", () => {
 
168
  elements.charCount.textContent = `${elements.text.value.length.toLocaleString()} / 12,000`;
169
  if (elements.text.value.trim().length === 1) {
170
  setMode("text");
@@ -183,6 +189,7 @@ document.querySelectorAll(".example-card").forEach((button) => {
183
  const reader = new FileReader();
184
  reader.addEventListener("load", () => {
185
  imageDataUrl = String(reader.result);
 
186
  elements.preview.src = imageDataUrl;
187
  elements.dropZone.classList.add("has-image");
188
  showError();
@@ -196,6 +203,7 @@ document.querySelectorAll(".example-card").forEach((button) => {
196
  } else if (button.dataset.example) {
197
  elements.text.value = button.dataset.example;
198
  elements.text.dispatchEvent(new Event("input"));
 
199
  elements.text.focus();
200
  setMode("text");
201
  document.querySelector(".workspace").scrollIntoView({ behavior: "smooth" });
@@ -205,6 +213,7 @@ document.querySelectorAll(".example-card").forEach((button) => {
205
 
206
  elements.resetButton.addEventListener("click", () => {
207
  imageDataUrl = "";
 
208
  elements.image.value = "";
209
  elements.preview.removeAttribute("src");
210
  elements.dropZone.classList.remove("has-image");
@@ -234,7 +243,11 @@ elements.form.addEventListener("submit", async (event) => {
234
 
235
  setLoading(true);
236
  try {
237
- renderResult(await callGradioApi("analyze", [elements.text.value, imageDataUrl]));
 
 
 
 
238
  } catch (error) {
239
  showError(error.message || "The request could not be completed.");
240
  } finally {
 
19
 
20
  let imageDataUrl = "";
21
  let activeMode = null;
22
+ let activeExampleId = "";
23
 
24
  async function callGradioApi(name, data) {
25
  const response = await fetch(`/gradio_api/call/${name}`, {
 
125
 
126
  elements.source.textContent = payload.source === "model"
127
  ? "Analyzed by the deployed Qwen model endpoint."
128
+ : payload.source === "cached_example"
129
+ ? "Loaded from a precomputed example assessment. No model request was made."
130
+ : "";
131
  elements.results.hidden = false;
132
  elements.results.scrollIntoView({ behavior: "smooth", block: "start" });
133
  }
134
 
135
  function useImage(file) {
136
  if (!file) return;
137
+ activeExampleId = "";
138
  const allowed = ["image/png", "image/jpeg", "image/webp"];
139
  if (!allowed.includes(file.type)) return showError("Use a PNG, JPG, or WebP image.");
140
  if (file.size > 8 * 1024 * 1024) return showError("Please choose an image smaller than 8 MB.");
 
154
  event.preventDefault();
155
  event.stopPropagation();
156
  imageDataUrl = "";
157
+ activeExampleId = "";
158
  elements.image.value = "";
159
  elements.preview.removeAttribute("src");
160
  elements.dropZone.classList.remove("has-image");
 
170
  }));
171
  elements.dropZone.addEventListener("drop", (event) => useImage(event.dataTransfer.files[0]));
172
  elements.text.addEventListener("input", () => {
173
+ activeExampleId = "";
174
  elements.charCount.textContent = `${elements.text.value.length.toLocaleString()} / 12,000`;
175
  if (elements.text.value.trim().length === 1) {
176
  setMode("text");
 
189
  const reader = new FileReader();
190
  reader.addEventListener("load", () => {
191
  imageDataUrl = String(reader.result);
192
+ activeExampleId = button.dataset.exampleId || "";
193
  elements.preview.src = imageDataUrl;
194
  elements.dropZone.classList.add("has-image");
195
  showError();
 
203
  } else if (button.dataset.example) {
204
  elements.text.value = button.dataset.example;
205
  elements.text.dispatchEvent(new Event("input"));
206
+ activeExampleId = button.dataset.exampleId || "";
207
  elements.text.focus();
208
  setMode("text");
209
  document.querySelector(".workspace").scrollIntoView({ behavior: "smooth" });
 
213
 
214
  elements.resetButton.addEventListener("click", () => {
215
  imageDataUrl = "";
216
+ activeExampleId = "";
217
  elements.image.value = "";
218
  elements.preview.removeAttribute("src");
219
  elements.dropZone.classList.remove("has-image");
 
243
 
244
  setLoading(true);
245
  try {
246
+ const submittedImage = activeExampleId ? "" : imageDataUrl;
247
+ renderResult(await callGradioApi(
248
+ "analyze",
249
+ [elements.text.value, submittedImage, activeExampleId],
250
+ ));
251
  } catch (error) {
252
  showError(error.message || "The request could not be completed.");
253
  } finally {
static/index.html CHANGED
@@ -91,28 +91,28 @@
91
  <div><p class="eyebrow">Try an example</p><h2 id="examplesTitle">Common messages in Pakistan</h2></div>
92
  </div>
93
  <div class="example-grid">
94
- <button class="example-card" data-example="PAKISTAN POST: Your parcel address is incomplete. Pay Rs. 85 today at http://pakpost-delivery.xyz or the parcel will be destroyed.">
95
  <span class="example-icon">PK</span><span><strong>Courier fee</strong><small>Urgent parcel payment link</small></span>
96
  </button>
97
- <button class="example-card" data-example="FBR REFUND: You are eligible for Rs 42,500. Submit your CNIC and bank card details at the link today to receive payment.">
98
  <span class="example-icon">FBR</span><span><strong>Tax refund</strong><small>Unexpected refund request</small></span>
99
  </button>
100
- <button class="example-card" data-example="HBL Security: Your account will be suspended. Share the OTP sent to your phone with our support team immediately.">
101
  <span class="example-icon">OTP</span><span><strong>Bank alert</strong><small>Security code request</small></span>
102
  </button>
103
  </div>
104
 
105
  <p class="eyebrow" style="margin:28px 0 14px">Real scam screenshots</p>
106
  <div class="example-grid example-grid-images">
107
- <button class="example-card example-image-card" data-image="/static/example-courier.jpeg">
108
  <img src="/static/example-courier.jpeg" alt="Courier scam screenshot" class="example-thumb">
109
  <span><strong>Courier scam</strong><small>Fake delivery fee message</small></span>
110
  </button>
111
- <button class="example-card example-image-card" data-image="/static/example-mobile.png">
112
  <img src="/static/example-mobile.png" alt="Mobile scam screenshot" class="example-thumb">
113
  <span><strong>Mobile scam</strong><small>Fake mobile operator message</small></span>
114
  </button>
115
- <button class="example-card example-image-card" data-image="/static/example-trafic.png">
116
  <img src="/static/example-trafic.png" alt="Traffic challan scam screenshot" class="example-thumb">
117
  <span><strong>Traffic challan</strong><small>Fake e-challan fine message</small></span>
118
  </button>
 
91
  <div><p class="eyebrow">Try an example</p><h2 id="examplesTitle">Common messages in Pakistan</h2></div>
92
  </div>
93
  <div class="example-grid">
94
+ <button class="example-card" data-example-id="text-courier" data-example="PAKISTAN POST: Your parcel address is incomplete. Pay Rs. 85 today at http://pakpost-delivery.xyz or the parcel will be destroyed.">
95
  <span class="example-icon">PK</span><span><strong>Courier fee</strong><small>Urgent parcel payment link</small></span>
96
  </button>
97
+ <button class="example-card" data-example-id="text-fbr" data-example="FBR REFUND: You are eligible for Rs 42,500. Submit your CNIC and bank card details at the link today to receive payment.">
98
  <span class="example-icon">FBR</span><span><strong>Tax refund</strong><small>Unexpected refund request</small></span>
99
  </button>
100
+ <button class="example-card" data-example-id="text-bank" data-example="HBL Security: Your account will be suspended. Share the OTP sent to your phone with our support team immediately.">
101
  <span class="example-icon">OTP</span><span><strong>Bank alert</strong><small>Security code request</small></span>
102
  </button>
103
  </div>
104
 
105
  <p class="eyebrow" style="margin:28px 0 14px">Real scam screenshots</p>
106
  <div class="example-grid example-grid-images">
107
+ <button class="example-card example-image-card" data-example-id="image-courier" data-image="/static/example-courier.jpeg">
108
  <img src="/static/example-courier.jpeg" alt="Courier scam screenshot" class="example-thumb">
109
  <span><strong>Courier scam</strong><small>Fake delivery fee message</small></span>
110
  </button>
111
+ <button class="example-card example-image-card" data-example-id="image-mobile" data-image="/static/example-mobile.png">
112
  <img src="/static/example-mobile.png" alt="Mobile scam screenshot" class="example-thumb">
113
  <span><strong>Mobile scam</strong><small>Fake mobile operator message</small></span>
114
  </button>
115
+ <button class="example-card example-image-card" data-example-id="image-traffic" data-image="/static/example-trafic.png">
116
  <img src="/static/example-trafic.png" alt="Traffic challan scam screenshot" class="example-thumb">
117
  <span><strong>Traffic challan</strong><small>Fake e-challan fine message</small></span>
118
  </button>