Abid Ali Awan Codex commited on
Commit
773ad7d
·
1 Parent(s): 09e22a1

Trim trace dataset columns

Browse files

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

README.md CHANGED
@@ -132,7 +132,8 @@ images use a fixed `image: ...` description without OCR or image storage. The
132
  trace also records category, urgency, fixed signals, result counts, and a
133
  deterministic `result_summary` explaining the scam pattern and risk label.
134
  All trace columns are flat scalar values; no dataset cell contains a nested
135
- dictionary.
 
136
  It never stores raw messages, screenshots, links, detected identifiers, model
137
  explanations, reply text, exceptions, or credentials.
138
 
 
132
  trace also records category, urgency, fixed signals, result counts, and a
133
  deterministic `result_summary` explaining the scam pattern and risk label.
134
  All trace columns are flat scalar values; no dataset cell contains a nested
135
+ dictionary. Detected signals are combined into the readable `scam_tactics`
136
+ column.
137
  It never stores raw messages, screenshots, links, detected identifiers, model
138
  explanations, reply text, exceptions, or credentials.
139
 
tests/test_tracing.py CHANGED
@@ -72,7 +72,6 @@ class TraceTests(unittest.TestCase):
72
  image_record["result_summary"],
73
  )
74
  self.assertIn("Strong scam indicators", image_record["result_summary"])
75
- self.assertEqual(image_record["input_storage"], "image_description_only")
76
  for removed in (
77
  "pipeline_steps",
78
  "cache",
@@ -85,6 +84,23 @@ class TraceTests(unittest.TestCase):
85
  "image_size_bucket",
86
  "language_hint",
87
  "modal",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  ):
89
  self.assertNotIn(removed, image_record)
90
 
@@ -104,11 +120,11 @@ class TraceTests(unittest.TestCase):
104
  "No completed assessment result was available",
105
  text_record["result_summary"],
106
  )
107
- self.assertEqual(text_record["input_storage"], "redacted_text")
108
  self.assertFalse(any(
109
  isinstance(value, (dict, list))
110
  for value in text_record.values()
111
  ))
 
112
 
113
  def test_opt_out_does_not_queue_trace(self) -> None:
114
  with patch("app.queue_trace") as queue_mock:
 
72
  image_record["result_summary"],
73
  )
74
  self.assertIn("Strong scam indicators", image_record["result_summary"])
 
75
  for removed in (
76
  "pipeline_steps",
77
  "cache",
 
84
  "image_size_bucket",
85
  "language_hint",
86
  "modal",
87
+ "exception_text_stored",
88
+ "identifiers_stored",
89
+ "input_storage",
90
+ "raw_image_stored",
91
+ "raw_input_stored",
92
+ "raw_model_output_stored",
93
+ "red_flag_count",
94
+ "reply_draft_returned",
95
+ "signal_account_threat",
96
+ "signal_challan",
97
+ "signal_cnic",
98
+ "signal_courier",
99
+ "signal_credentials",
100
+ "signal_link",
101
+ "signal_otp",
102
+ "signal_payment",
103
+ "signal_refund_or_prize",
104
  ):
105
  self.assertNotIn(removed, image_record)
106
 
 
120
  "No completed assessment result was available",
121
  text_record["result_summary"],
122
  )
 
123
  self.assertFalse(any(
124
  isinstance(value, (dict, list))
125
  for value in text_record.values()
126
  ))
127
+ self.assertEqual(next(iter(text_record)), "trace_id")
128
 
129
  def test_opt_out_does_not_queue_trace(self) -> None:
130
  with patch("app.queue_trace") as queue_mock:
traces/data/trace_samples.jsonl CHANGED
@@ -1,6 +1,6 @@
1
- {"exception_text_stored": false, "identifiers_stored": false, "input": "text: PAKISTAN POST: [NAME_OR_ENTITY] parcel [ADDRESS]. [NAME_OR_ENTITY] Rs. 85 today at [LINK] or the parcel will be destroyed.", "input_category": "courier", "input_storage": "redacted_text", "raw_image_stored": false, "raw_input_stored": false, "raw_model_output_stored": false, "red_flag_count": 4, "reply_draft_policy": "suppressed", "reply_draft_returned": false, "result_summary": "Likely scam: Courier-style content with link, urgency, payment, courier signals. Known courier pattern. Strong scam indicators were found; avoid payments, links, and sharing credentials.", "risk_label": "Likely scam", "safe_next_step_count": 4, "scam_tactics": "link, urgency, payment, courier", "signal_account_threat": false, "signal_challan": false, "signal_cnic": false, "signal_courier": true, "signal_credentials": false, "signal_link": true, "signal_otp": false, "signal_payment": true, "signal_refund_or_prize": false, "timestamp": "2026-06-07T06:49:33.145291+00:00", "trace_id": "57c92b49-6166-41d4-9f98-ded53729c963", "urgency": true}
2
- {"exception_text_stored": false, "identifiers_stored": false, "input": "text: FBR REFUND: [NAME_OR_ENTITY] are eligible for Rs 42,[NUMBER]. [NAME_OR_ENTITY] your CNIC and bank card [REDACTED] at the link today to receive payment.", "input_category": "fbr", "input_storage": "redacted_text", "raw_image_stored": false, "raw_input_stored": false, "raw_model_output_stored": false, "red_flag_count": 4, "reply_draft_policy": "suppressed", "reply_draft_returned": false, "result_summary": "Likely scam: FBR-style content with CNIC, credential, urgency, payment signals. Known FBR pattern. Strong scam indicators were found; avoid payments, links, and sharing credentials.", "risk_label": "Likely scam", "safe_next_step_count": 4, "scam_tactics": "cnic, credentials, urgency, payment, refund_or_prize", "signal_account_threat": false, "signal_challan": false, "signal_cnic": true, "signal_courier": false, "signal_credentials": true, "signal_link": false, "signal_otp": false, "signal_payment": true, "signal_refund_or_prize": true, "timestamp": "2026-06-07T06:49:33.145489+00:00", "trace_id": "18b01fe5-eed0-49c8-9457-a6c9f49ccf71", "urgency": true}
3
- {"exception_text_stored": false, "identifiers_stored": false, "input": "text: HBL [NAME_OR_ENTITY]: [NAME_OR_ENTITY] account [REDACTED] be suspended. [NAME_OR_ENTITY] the OTP [REDACTED] to your phone with our support team immediately.", "input_category": "bank", "input_storage": "redacted_text", "raw_image_stored": false, "raw_input_stored": false, "raw_model_output_stored": false, "red_flag_count": 3, "reply_draft_policy": "suppressed", "reply_draft_returned": false, "result_summary": "Likely scam: Bank-style content with OTP, urgency, account-threat signals. Known bank pattern. Strong scam indicators were found; avoid payments, links, and sharing credentials.", "risk_label": "Likely scam", "safe_next_step_count": 5, "scam_tactics": "otp, urgency, account_threat", "signal_account_threat": true, "signal_challan": false, "signal_cnic": false, "signal_courier": false, "signal_credentials": false, "signal_link": false, "signal_otp": true, "signal_payment": false, "signal_refund_or_prize": false, "timestamp": "2026-06-07T06:49:33.145659+00:00", "trace_id": "fc38b636-2907-4ace-890a-c70f2f5350bc", "urgency": true}
4
- {"exception_text_stored": false, "identifiers_stored": false, "input": "image: Courier-style content with link, urgency, courier signals", "input_category": "courier", "input_storage": "image_description_only", "raw_image_stored": false, "raw_input_stored": false, "raw_model_output_stored": false, "red_flag_count": 4, "reply_draft_policy": "suppressed", "reply_draft_returned": false, "result_summary": "Likely scam: Courier-style content with link, urgency, courier signals. Known courier pattern. Strong scam indicators were found; avoid payments, links, and sharing credentials.", "risk_label": "Likely scam", "safe_next_step_count": 4, "scam_tactics": "link, urgency, courier", "signal_account_threat": false, "signal_challan": false, "signal_cnic": false, "signal_courier": true, "signal_credentials": false, "signal_link": true, "signal_otp": false, "signal_payment": false, "signal_refund_or_prize": false, "timestamp": "2026-06-07T06:49:33.145799+00:00", "trace_id": "d83cb31c-44bb-4fc1-8d13-68776e509b33", "urgency": true}
5
- {"exception_text_stored": false, "identifiers_stored": false, "input": "image: Marketplace-style content with credential signals", "input_category": "marketplace", "input_storage": "image_description_only", "raw_image_stored": false, "raw_input_stored": false, "raw_model_output_stored": false, "red_flag_count": 3, "reply_draft_policy": "suppressed", "reply_draft_returned": false, "result_summary": "Likely scam: Marketplace-style content with credential signals. Known marketplace pattern. Strong scam indicators were found; avoid payments, links, and sharing credentials.", "risk_label": "Likely scam", "safe_next_step_count": 4, "scam_tactics": "credentials", "signal_account_threat": false, "signal_challan": false, "signal_cnic": false, "signal_courier": false, "signal_credentials": true, "signal_link": false, "signal_otp": false, "signal_payment": false, "signal_refund_or_prize": false, "timestamp": "2026-06-07T06:49:33.146161+00:00", "trace_id": "aed94a0c-db03-48fc-92f7-ec8ba14b9124", "urgency": false}
6
- {"exception_text_stored": false, "identifiers_stored": false, "input": "image: Traffic-challan-style content with link, urgency, payment, challan signals", "input_category": "traffic_challan", "input_storage": "image_description_only", "raw_image_stored": false, "raw_input_stored": false, "raw_model_output_stored": false, "red_flag_count": 3, "reply_draft_policy": "suppressed", "reply_draft_returned": false, "result_summary": "Likely scam: Traffic-challan-style content with link, urgency, payment, challan signals. Known traffic-challan pattern. Strong scam indicators were found; avoid payments, links, and sharing credentials.", "risk_label": "Likely scam", "safe_next_step_count": 4, "scam_tactics": "link, urgency, payment, challan", "signal_account_threat": false, "signal_challan": true, "signal_cnic": false, "signal_courier": false, "signal_credentials": false, "signal_link": true, "signal_otp": false, "signal_payment": true, "signal_refund_or_prize": false, "timestamp": "2026-06-07T06:49:33.146349+00:00", "trace_id": "bf534dfe-991e-4291-93d5-5994f0964c19", "urgency": true}
 
1
+ {"trace_id": "44123b4e-1e27-42db-a25e-c152d40db44f", "timestamp": "2026-06-07T06:57:14.262543+00:00", "input": "text: PAKISTAN POST: [NAME_OR_ENTITY] parcel [ADDRESS]. [NAME_OR_ENTITY] Rs. 85 today at [LINK] or the parcel will be destroyed.", "input_category": "courier", "urgency": true, "scam_tactics": "link, urgency, payment, courier", "result_summary": "Likely scam: Courier-style content with link, urgency, payment, courier signals. Known courier pattern. Strong scam indicators were found; avoid payments, links, and sharing credentials.", "risk_label": "Likely scam", "safe_next_step_count": 4, "reply_draft_policy": "suppressed"}
2
+ {"trace_id": "166594ae-e201-49cb-bbff-07482bcdd779", "timestamp": "2026-06-07T06:57:14.262745+00:00", "input": "text: FBR REFUND: [NAME_OR_ENTITY] are eligible for Rs 42,[NUMBER]. [NAME_OR_ENTITY] your CNIC and bank card [REDACTED] at the link today to receive payment.", "input_category": "fbr", "urgency": true, "scam_tactics": "cnic, credentials, urgency, payment, refund_or_prize", "result_summary": "Likely scam: FBR-style content with CNIC, credential, urgency, payment signals. Known FBR pattern. Strong scam indicators were found; avoid payments, links, and sharing credentials.", "risk_label": "Likely scam", "safe_next_step_count": 4, "reply_draft_policy": "suppressed"}
3
+ {"trace_id": "a8c5849b-19af-4ecd-9e31-482f594055ac", "timestamp": "2026-06-07T06:57:14.262936+00:00", "input": "text: HBL [NAME_OR_ENTITY]: [NAME_OR_ENTITY] account [REDACTED] be suspended. [NAME_OR_ENTITY] the OTP [REDACTED] to your phone with our support team immediately.", "input_category": "bank", "urgency": true, "scam_tactics": "otp, urgency, account_threat", "result_summary": "Likely scam: Bank-style content with OTP, urgency, account-threat signals. Known bank pattern. Strong scam indicators were found; avoid payments, links, and sharing credentials.", "risk_label": "Likely scam", "safe_next_step_count": 5, "reply_draft_policy": "suppressed"}
4
+ {"trace_id": "1f832008-9ab6-497a-9a15-1beaa0bf6410", "timestamp": "2026-06-07T06:57:14.263070+00:00", "input": "image: Courier-style content with link, urgency, courier signals", "input_category": "courier", "urgency": true, "scam_tactics": "link, urgency, courier", "result_summary": "Likely scam: Courier-style content with link, urgency, courier signals. Known courier pattern. Strong scam indicators were found; avoid payments, links, and sharing credentials.", "risk_label": "Likely scam", "safe_next_step_count": 4, "reply_draft_policy": "suppressed"}
5
+ {"trace_id": "4478636b-37d7-4646-b58c-7f8cb4d0be90", "timestamp": "2026-06-07T06:57:14.263462+00:00", "input": "image: Marketplace-style content with credential signals", "input_category": "marketplace", "urgency": false, "scam_tactics": "credentials", "result_summary": "Likely scam: Marketplace-style content with credential signals. Known marketplace pattern. Strong scam indicators were found; avoid payments, links, and sharing credentials.", "risk_label": "Likely scam", "safe_next_step_count": 4, "reply_draft_policy": "suppressed"}
6
+ {"trace_id": "c439562d-cafd-4c07-a44e-03002728d7ba", "timestamp": "2026-06-07T06:57:14.263640+00:00", "input": "image: Traffic-challan-style content with link, urgency, payment, challan signals", "input_category": "traffic_challan", "urgency": true, "scam_tactics": "link, urgency, payment, challan", "result_summary": "Likely scam: Traffic-challan-style content with link, urgency, payment, challan signals. Known traffic-challan pattern. Strong scam indicators were found; avoid payments, links, and sharing credentials.", "risk_label": "Likely scam", "safe_next_step_count": 4, "reply_draft_policy": "suppressed"}
traces/dataset_card.md CHANGED
@@ -41,16 +41,13 @@ and convert it into allow-listed categories, booleans, buckets, and counts.
41
  - `result_summary`: deterministic summary of the mapped pattern, whether it is
42
  known or unclassified, and why the risk label matters
43
  - `scam_tactics`: readable comma-separated tactics
44
- - Flat boolean signal columns such as `signal_link`, `signal_payment`, and
45
- `signal_credentials`
46
- - Flat result columns such as `risk_label`, `red_flag_count`, and
47
  `reply_draft_policy`
48
- - `input_storage`: `redacted_text` or `image_description_only`
49
- - Flat privacy flags confirming that raw input, images, identifiers, model
50
- output, and exception text are not stored
51
 
52
  Every dataset cell is a scalar string, number, or boolean. No column contains a
53
  dictionary or nested object, which keeps the Hugging Face table easy to read.
 
 
54
 
55
  Records do not contain pipeline steps, cache fields, app commits, failure
56
  details, request source, schema versions, size buckets, language hints, or
 
41
  - `result_summary`: deterministic summary of the mapped pattern, whether it is
42
  known or unclassified, and why the risk label matters
43
  - `scam_tactics`: readable comma-separated tactics
44
+ - Flat result columns such as `risk_label`, `safe_next_step_count`, and
 
 
45
  `reply_draft_policy`
 
 
 
46
 
47
  Every dataset cell is a scalar string, number, or boolean. No column contains a
48
  dictionary or nested object, which keeps the Hugging Face table easy to read.
49
+ `trace_id` is the first serialized column, and all detected boolean signals are
50
+ combined into the single `scam_tactics` category column.
51
 
52
  Records do not contain pipeline steps, cache fields, app commits, failure
53
  details, request source, schema versions, size buckets, language hints, or
traces/runtime.py CHANGED
@@ -230,11 +230,6 @@ def build_input_profile(text: str, image_data_url: str, example_id: str = "") ->
230
  "input_category": category,
231
  "urgency": signals["urgency"],
232
  "scam_tactics": ", ".join(tactics) if tactics else "none",
233
- **{
234
- f"signal_{name}": enabled
235
- for name, enabled in signals.items()
236
- if name != "urgency"
237
- },
238
  }
239
 
240
 
@@ -259,12 +254,10 @@ def build_trace_record(
259
  **input_profile,
260
  "result_summary": result_summary(risk_label, category, signals),
261
  "risk_label": risk_label,
262
- "red_flag_count": min(len(assessment.get("red_flags", [])), 50),
263
  "safe_next_step_count": min(
264
  len(assessment.get("safe_next_steps", [])),
265
  50,
266
  ),
267
- "reply_draft_returned": bool(assessment.get("reply_draft")),
268
  "reply_draft_policy": (
269
  "allowed"
270
  if risk_label in {"Verify first", "Suspicious"}
@@ -272,16 +265,6 @@ def build_trace_record(
272
  if risk_label != "none"
273
  else "not_applicable"
274
  ),
275
- "input_storage": (
276
- "redacted_text"
277
- if input_profile["input"].startswith("text: ")
278
- else "image_description_only"
279
- ),
280
- "raw_input_stored": False,
281
- "raw_image_stored": False,
282
- "raw_model_output_stored": False,
283
- "exception_text_stored": False,
284
- "identifiers_stored": False,
285
  }
286
 
287
 
@@ -296,31 +279,16 @@ def validate_trace(record: Any) -> list[str]:
296
  "input_category",
297
  "urgency",
298
  "scam_tactics",
299
- "signal_otp",
300
- "signal_cnic",
301
- "signal_credentials",
302
- "signal_link",
303
- "signal_payment",
304
- "signal_refund_or_prize",
305
- "signal_courier",
306
- "signal_challan",
307
- "signal_account_threat",
308
  "result_summary",
309
  "risk_label",
310
- "red_flag_count",
311
  "safe_next_step_count",
312
- "reply_draft_returned",
313
  "reply_draft_policy",
314
- "input_storage",
315
- "raw_input_stored",
316
- "raw_image_stored",
317
- "raw_model_output_stored",
318
- "exception_text_stored",
319
- "identifiers_stored",
320
  }
321
  missing = required - record.keys()
322
  if missing:
323
  errors.append("Missing fields: " + ", ".join(sorted(missing)))
 
 
324
  input_value = record.get("input")
325
  if not (
326
  isinstance(input_value, str)
@@ -338,22 +306,6 @@ def validate_trace(record: Any) -> list[str]:
338
  errors.append("Result summary must be a string.")
339
  if record.get("risk_label") not in RISK_LABELS:
340
  errors.append("Invalid risk label.")
341
- private_storage_flags = (
342
- "raw_input_stored",
343
- "raw_image_stored",
344
- "raw_model_output_stored",
345
- "exception_text_stored",
346
- "identifiers_stored",
347
- )
348
- if any(
349
- record.get(key) is not False for key in private_storage_flags
350
- ):
351
- errors.append("Raw/private storage flags must all be false.")
352
- if record.get("input_storage") not in {
353
- "redacted_text",
354
- "image_description_only",
355
- }:
356
- errors.append("Invalid input storage category.")
357
  if any(isinstance(value, (dict, list)) for value in record.values()):
358
  errors.append("Trace columns must contain scalar values only.")
359
  forbidden_keys = {
@@ -368,6 +320,23 @@ def validate_trace(record: Any) -> list[str]:
368
  "image_size_bucket",
369
  "language_hint",
370
  "modal",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
371
  "raw_input",
372
  "raw_text",
373
  "image_data_url",
@@ -493,7 +462,7 @@ class TracePublisher:
493
  final_path = PENDING_DIR / filename
494
  temporary_path = final_path.with_suffix(".tmp")
495
  content = "".join(
496
- json.dumps(record, sort_keys=True, ensure_ascii=True) + "\n"
497
  for record in accepted
498
  )
499
  temporary_path.write_text(content, encoding="utf-8")
 
230
  "input_category": category,
231
  "urgency": signals["urgency"],
232
  "scam_tactics": ", ".join(tactics) if tactics else "none",
 
 
 
 
 
233
  }
234
 
235
 
 
254
  **input_profile,
255
  "result_summary": result_summary(risk_label, category, signals),
256
  "risk_label": risk_label,
 
257
  "safe_next_step_count": min(
258
  len(assessment.get("safe_next_steps", [])),
259
  50,
260
  ),
 
261
  "reply_draft_policy": (
262
  "allowed"
263
  if risk_label in {"Verify first", "Suspicious"}
 
265
  if risk_label != "none"
266
  else "not_applicable"
267
  ),
 
 
 
 
 
 
 
 
 
 
268
  }
269
 
270
 
 
279
  "input_category",
280
  "urgency",
281
  "scam_tactics",
 
 
 
 
 
 
 
 
 
282
  "result_summary",
283
  "risk_label",
 
284
  "safe_next_step_count",
 
285
  "reply_draft_policy",
 
 
 
 
 
 
286
  }
287
  missing = required - record.keys()
288
  if missing:
289
  errors.append("Missing fields: " + ", ".join(sorted(missing)))
290
+ if record and next(iter(record)) != "trace_id":
291
+ errors.append("trace_id must be the first column.")
292
  input_value = record.get("input")
293
  if not (
294
  isinstance(input_value, str)
 
306
  errors.append("Result summary must be a string.")
307
  if record.get("risk_label") not in RISK_LABELS:
308
  errors.append("Invalid risk label.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
  if any(isinstance(value, (dict, list)) for value in record.values()):
310
  errors.append("Trace columns must contain scalar values only.")
311
  forbidden_keys = {
 
320
  "image_size_bucket",
321
  "language_hint",
322
  "modal",
323
+ "exception_text_stored",
324
+ "identifiers_stored",
325
+ "input_storage",
326
+ "raw_image_stored",
327
+ "raw_input_stored",
328
+ "raw_model_output_stored",
329
+ "red_flag_count",
330
+ "reply_draft_returned",
331
+ "signal_account_threat",
332
+ "signal_challan",
333
+ "signal_cnic",
334
+ "signal_courier",
335
+ "signal_credentials",
336
+ "signal_link",
337
+ "signal_otp",
338
+ "signal_payment",
339
+ "signal_refund_or_prize",
340
  "raw_input",
341
  "raw_text",
342
  "image_data_url",
 
462
  final_path = PENDING_DIR / filename
463
  temporary_path = final_path.with_suffix(".tmp")
464
  content = "".join(
465
+ json.dumps(record, ensure_ascii=True) + "\n"
466
  for record in accepted
467
  )
468
  temporary_path.write_text(content, encoding="utf-8")
traces/scripts/export_pending_traces.py CHANGED
@@ -33,7 +33,7 @@ def main() -> int:
33
  raise RuntimeError(f"{path}: {'; '.join(errors)}")
34
  records.append(record)
35
  content = "".join(
36
- json.dumps(record, sort_keys=True, ensure_ascii=True) + "\n"
37
  for record in records
38
  )
39
  if args.dry_run:
 
33
  raise RuntimeError(f"{path}: {'; '.join(errors)}")
34
  records.append(record)
35
  content = "".join(
36
+ json.dumps(record, ensure_ascii=True) + "\n"
37
  for record in records
38
  )
39
  if args.dry_run:
traces/scripts/seed_trace_dataset.py CHANGED
@@ -69,7 +69,7 @@ def main() -> int:
69
  args = parser.parse_args()
70
  records = build_seed_records()
71
  content = "".join(
72
- json.dumps(record, sort_keys=True, ensure_ascii=True) + "\n"
73
  for record in records
74
  )
75
  if args.dry_run:
 
69
  args = parser.parse_args()
70
  records = build_seed_records()
71
  content = "".join(
72
+ json.dumps(record, ensure_ascii=True) + "\n"
73
  for record in records
74
  )
75
  if args.dry_run: