Asem75 commited on
Commit
cabbf27
·
verified ·
1 Parent(s): 1d7dfaf

Upload 4 files

Browse files
badge_game_manager.py CHANGED
@@ -39,7 +39,7 @@ except Exception:
39
  class BadgeGameManager:
40
  """مدير لعبة الوسام والتشخيص التفاعلي."""
41
 
42
- VERSION = "badge_game_manager_v31_adaptive_probe_dedup"
43
  UNKNOWN_LABEL = "لا أعلم"
44
  UNKNOWN_GUIDANCE = (
45
  "لا تخمّن. إذا لم تكن متأكدًا من الإجابة اختر «لا أعلم»؛ "
@@ -653,6 +653,9 @@ class BadgeGameManager:
653
  options.sort()
654
  return "|".join([
655
  str(q.get("skill_id") or ""),
 
 
 
656
  str(q.get("media_fact_name") or q.get("question_type") or q.get("item_type") or ""),
657
  self.normalize_answer(q.get("correct_answer") or q.get("answer") or ""),
658
  *options,
@@ -688,8 +691,13 @@ class BadgeGameManager:
688
  old_sig = str(old.get("runtime_evidence_signature") or "").strip()
689
  if runtime_sig and old_sig and runtime_sig == old_sig:
690
  return {"success": False, "error": "duplicate_evidence_signature", "runtime_evidence_signature": runtime_sig, "version": self.version}
691
- if not fresh_recheck_role and semantic_key and semantic_key == self._adaptive_probe_semantic_key(old):
692
- return {"success": False, "error": "duplicate_diagnostic_probe", "semantic_key": semantic_key, "version": self.version}
 
 
 
 
 
693
 
694
  state_dict.setdefault("questions", []).append(cleaned)
695
  state_dict["total_questions"] = len(state_dict.get("questions") or [])
 
39
  class BadgeGameManager:
40
  """مدير لعبة الوسام والتشخيص التفاعلي."""
41
 
42
+ VERSION = "badge_game_manager_v32_evidence_signature_dedup"
43
  UNKNOWN_LABEL = "لا أعلم"
44
  UNKNOWN_GUIDANCE = (
45
  "لا تخمّن. إذا لم تكن متأكدًا من الإجابة اختر «لا أعلم»؛ "
 
653
  options.sort()
654
  return "|".join([
655
  str(q.get("skill_id") or ""),
656
+ str(q.get("condition_slot_id") or (q.get("stimulus_plan") or {}).get("condition_slot_id") or ""),
657
+ str(q.get("diagnostic_condition") or (q.get("stimulus_plan") or {}).get("diagnostic_condition") or ""),
658
+ str(q.get("stimulus_identity_group") or ""),
659
  str(q.get("media_fact_name") or q.get("question_type") or q.get("item_type") or ""),
660
  self.normalize_answer(q.get("correct_answer") or q.get("answer") or ""),
661
  *options,
 
691
  old_sig = str(old.get("runtime_evidence_signature") or "").strip()
692
  if runtime_sig and old_sig and runtime_sig == old_sig:
693
  return {"success": False, "error": "duplicate_evidence_signature", "runtime_evidence_signature": runtime_sig, "version": self.version}
694
+
695
+ # إذا كان السؤالان يحملان Evidence Signature صحيحة ومختلفة،
696
+ # لا نرفضهما بسبب صياغة/خيارات متشابهة؛ الاختلاف في stimulus/slot
697
+ # قد يكون هو الدليل التشخيصي المقصود.
698
+ if not (runtime_sig and old_sig):
699
+ if not fresh_recheck_role and semantic_key and semantic_key == self._adaptive_probe_semantic_key(old):
700
+ return {"success": False, "error": "duplicate_diagnostic_probe", "semantic_key": semantic_key, "version": self.version}
701
 
702
  state_dict.setdefault("questions", []).append(cleaned)
703
  state_dict["total_questions"] = len(state_dict.get("questions") or [])
question_bank_builder.py CHANGED
@@ -48,7 +48,7 @@ def _fmt(value: Any) -> str:
48
  return f"{value:.3f}".rstrip("0").rstrip(".")
49
  return str(value)
50
 
51
- VERSION = "question_bank_builder_v29_diagnostic_condition_media_truth"
52
 
53
  _RUNTIME_CLARIFICATION_SERVICE = None
54
  _RUNTIME_QUESTION_GENERATION_CALLBACK = None
@@ -2503,6 +2503,18 @@ class QuestionBankBuilder(_GeneratorBase):
2503
  "remedial_practice": ["applied", "direct", "contrast"],
2504
  "prerequisite_probe": ["direct", "foundation"],
2505
  "micro_step_probe": ["direct", "guided"],
 
 
 
 
 
 
 
 
 
 
 
 
2506
  # compatibility with older names
2507
  "diagnostic_probe": ["direct", "contrast"],
2508
  "guided_practice": ["direct", "applied", "contrast"],
@@ -2545,17 +2557,30 @@ class QuestionBankBuilder(_GeneratorBase):
2545
  or ""
2546
  ).strip().lower()
2547
 
2548
- role_filtered = [
 
2549
  item for item in templates
2550
- if template_kind(item) in wanted_kinds
2551
  ]
2552
- if role_filtered:
2553
- templates = role_filtered
 
 
 
 
 
 
 
2554
 
2555
  excluded_template_set = {str(x) for x in (exclude_template_ids or []) if str(x).strip()}
2556
  if excluded_template_set:
2557
  templates = [x for x in templates if str(x.get("template_id") or "") not in excluded_template_set]
2558
 
 
 
 
 
 
2559
  if condition_slot_id:
2560
  matched = [
2561
  x for x in templates
@@ -2563,6 +2588,8 @@ class QuestionBankBuilder(_GeneratorBase):
2563
  ]
2564
  if matched:
2565
  templates = matched
 
 
2566
 
2567
  if diagnostic_condition_family:
2568
  matched = [
@@ -2571,6 +2598,8 @@ class QuestionBankBuilder(_GeneratorBase):
2571
  ]
2572
  if matched:
2573
  templates = matched
 
 
2574
 
2575
  if primary_target_attribute:
2576
  matched = [x for x in templates if str(x.get("primary_target_attribute") or "") == str(primary_target_attribute)]
@@ -3541,6 +3570,10 @@ class QuestionBankBuilder(_GeneratorBase):
3541
 
3542
  stable_seed = (
3543
  f"{skill_id}|{template_id}|{sequence}|"
 
 
 
 
3544
  f"{fact_name}|{answer}|{prompt}"
3545
  )
3546
  question_id = (
@@ -3583,6 +3616,8 @@ class QuestionBankBuilder(_GeneratorBase):
3583
  "generation_mode": "media_ground_truth_direct",
3584
  "generator_owner": "QuestionBankBuilder",
3585
  "generator_version": VERSION,
 
 
3586
  "answer_source": "media_ground_truth",
3587
  "media_fact_name": fact_name,
3588
  "media_ground_truth": dict(ground_truth),
@@ -3609,6 +3644,8 @@ class QuestionBankBuilder(_GeneratorBase):
3609
  f"MEDIA_GROUND_TRUTH_ITEM_BUILT=True "
3610
  f"QUESTION_ID={question_id} "
3611
  f"FACT={fact_name} "
 
 
3612
  f"ANSWER={answer!r} "
3613
  f"CHOICES={len(options)} "
3614
  f"SEQUENCE={sequence} "
@@ -3655,6 +3692,10 @@ class QuestionBankBuilder(_GeneratorBase):
3655
  (
3656
  f"{request.get('skill_id','')}|"
3657
  f"{request.get('template_id','')}|"
 
 
 
 
3658
  f"{index + 1}"
3659
  ).encode("utf-8")
3660
  ).hexdigest()[:14]
@@ -4028,6 +4069,8 @@ class QuestionBankBuilder(_GeneratorBase):
4028
  error_target=error_target,
4029
  )
4030
  request.update({
 
 
4031
  "difficulty": difficulty,
4032
  "representation_type": representation_type,
4033
  "cognitive_domain": cognitive_domain,
@@ -4266,14 +4309,55 @@ class QuestionBankBuilder(_GeneratorBase):
4266
 
4267
  @staticmethod
4268
  def question_fingerprint(question: Dict[str, Any]) -> str:
4269
- """بصمة مستقرة تمنع تكرار نص السؤال حتى لو تغير question_id."""
4270
- skill_id = str(question.get("skill_id") or "").strip()
 
 
 
 
 
 
4271
  text = re.sub(
4272
  r"\s+",
4273
  " ",
4274
- str(question.get("question") or question.get("prompt") or "").strip().lower(),
4275
  )
4276
- return hashlib.sha256(f"{skill_id}|{text}".encode("utf-8")).hexdigest()[:24]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4277
 
4278
  def get_challenge_questions(
4279
  self,
@@ -4947,6 +5031,8 @@ def generate_by_contract(
4947
 
4948
  item["question_role"] = question_role
4949
  item["question_kind"] = question_role
 
 
4950
 
4951
  ok, reason = _contract_question_is_acceptable(item, c)
4952
  if not ok:
 
48
  return f"{value:.3f}".rstrip("0").rstrip(".")
49
  return str(value)
50
 
51
+ VERSION = "question_bank_builder_v30_strict_arabic_probe_identity"
52
 
53
  _RUNTIME_CLARIFICATION_SERVICE = None
54
  _RUNTIME_QUESTION_GENERATION_CALLBACK = None
 
2503
  "remedial_practice": ["applied", "direct", "contrast"],
2504
  "prerequisite_probe": ["direct", "foundation"],
2505
  "micro_step_probe": ["direct", "guided"],
2506
+
2507
+ # canonical diagnostic compatibility vocabulary
2508
+ "screening_probe": ["direct"],
2509
+ "diagnostic_discrimination": ["contrast", "applied", "direct"],
2510
+ "diagnostic_confirmation": ["contrast", "evidence", "applied"],
2511
+ "representation_probe": ["contrast", "applied"],
2512
+ "transfer_probe": ["transfer"],
2513
+ "parallel_reassessment": ["recheck"],
2514
+ "progress_monitoring_probe": ["recheck", "applied"],
2515
+ "spaced_recheck_probe": ["recheck"],
2516
+ "maintenance_worksheet": ["recheck", "applied"],
2517
+
2518
  # compatibility with older names
2519
  "diagnostic_probe": ["direct", "contrast"],
2520
  "guided_practice": ["direct", "applied", "contrast"],
 
2557
  or ""
2558
  ).strip().lower()
2559
 
2560
+ requested_role = str(question_role or "").strip()
2561
+ exact_role_filtered = [
2562
  item for item in templates
2563
+ if str(item.get("question_role") or "").strip() == requested_role
2564
  ]
2565
+ if exact_role_filtered:
2566
+ templates = exact_role_filtered
2567
+ else:
2568
+ role_filtered = [
2569
+ item for item in templates
2570
+ if template_kind(item) in wanted_kinds
2571
+ ]
2572
+ if role_filtered:
2573
+ templates = role_filtered
2574
 
2575
  excluded_template_set = {str(x) for x in (exclude_template_ids or []) if str(x).strip()}
2576
  if excluded_template_set:
2577
  templates = [x for x in templates if str(x.get("template_id") or "") not in excluded_template_set]
2578
 
2579
+ strict_arabic_diagnostic = (
2580
+ str(skill_id or "").startswith("AR-")
2581
+ and str(assessment_mode or "").strip().lower() == "diagnostic"
2582
+ )
2583
+
2584
  if condition_slot_id:
2585
  matched = [
2586
  x for x in templates
 
2588
  ]
2589
  if matched:
2590
  templates = matched
2591
+ elif strict_arabic_diagnostic:
2592
+ return []
2593
 
2594
  if diagnostic_condition_family:
2595
  matched = [
 
2598
  ]
2599
  if matched:
2600
  templates = matched
2601
+ elif strict_arabic_diagnostic:
2602
+ return []
2603
 
2604
  if primary_target_attribute:
2605
  matched = [x for x in templates if str(x.get("primary_target_attribute") or "") == str(primary_target_attribute)]
 
3570
 
3571
  stable_seed = (
3572
  f"{skill_id}|{template_id}|{sequence}|"
3573
+ f"{request.get('condition_slot_id','')}|"
3574
+ f"{request.get('diagnostic_condition','')}|"
3575
+ f"{request.get('runtime_generation_seed','')}|"
3576
+ f"{request.get('runtime_generation_attempt','')}|"
3577
  f"{fact_name}|{answer}|{prompt}"
3578
  )
3579
  question_id = (
 
3616
  "generation_mode": "media_ground_truth_direct",
3617
  "generator_owner": "QuestionBankBuilder",
3618
  "generator_version": VERSION,
3619
+ "diagnostic_runtime_schema": "arabic_diag_runtime_v2",
3620
+ "runtime_generation_seed": str(request.get("runtime_generation_seed") or ""),
3621
  "answer_source": "media_ground_truth",
3622
  "media_fact_name": fact_name,
3623
  "media_ground_truth": dict(ground_truth),
 
3644
  f"MEDIA_GROUND_TRUTH_ITEM_BUILT=True "
3645
  f"QUESTION_ID={question_id} "
3646
  f"FACT={fact_name} "
3647
+ f"SOUND_PRESENT={ground_truth.get('sound_present') if isinstance(ground_truth.get('sound_present'), bool) else ''} "
3648
+ f"DIAG_CONDITION={actual_diagnostic_condition} "
3649
  f"ANSWER={answer!r} "
3650
  f"CHOICES={len(options)} "
3651
  f"SEQUENCE={sequence} "
 
3692
  (
3693
  f"{request.get('skill_id','')}|"
3694
  f"{request.get('template_id','')}|"
3695
+ f"{request.get('condition_slot_id','')}|"
3696
+ f"{request.get('diagnostic_condition','')}|"
3697
+ f"{request.get('runtime_generation_seed','')}|"
3698
+ f"{request.get('runtime_generation_attempt','')}|"
3699
  f"{index + 1}"
3700
  ).encode("utf-8")
3701
  ).hexdigest()[:14]
 
4069
  error_target=error_target,
4070
  )
4071
  request.update({
4072
+ "runtime_generation_seed": str(seed or ""),
4073
+ "runtime_generation_attempt": int(attempts),
4074
  "difficulty": difficulty,
4075
  "representation_type": representation_type,
4076
  "cognitive_domain": cognitive_domain,
 
4309
 
4310
  @staticmethod
4311
  def question_fingerprint(question: Dict[str, Any]) -> str:
4312
+ """
4313
+ بصمة السؤال.
4314
+
4315
+ في السؤال التشخيصي لا يجوز اعتبار نفس الصياغة مع Stimulus/Condition
4316
+ مختلف سؤالًا مكررًا؛ Evidence Signature هي التي تحسم الاستقلال.
4317
+ """
4318
+ q = question if isinstance(question, dict) else {}
4319
+ skill_id = str(q.get("skill_id") or "").strip()
4320
  text = re.sub(
4321
  r"\s+",
4322
  " ",
4323
+ str(q.get("question") or q.get("prompt") or "").strip().lower(),
4324
  )
4325
+ slot = str(
4326
+ q.get("condition_slot_id")
4327
+ or (q.get("stimulus_plan") or {}).get("condition_slot_id")
4328
+ or ""
4329
+ ).strip()
4330
+ condition = str(
4331
+ q.get("diagnostic_condition")
4332
+ or (q.get("stimulus_plan") or {}).get("diagnostic_condition")
4333
+ or ""
4334
+ ).strip()
4335
+
4336
+ media_tokens = []
4337
+ for asset in q.get("media_assets") or []:
4338
+ if not isinstance(asset, dict):
4339
+ continue
4340
+ token = (
4341
+ asset.get("remote_path")
4342
+ or asset.get("source_id")
4343
+ or asset.get("path")
4344
+ or asset.get("url")
4345
+ or asset.get("asset_id")
4346
+ )
4347
+ if token:
4348
+ media_tokens.append(str(token))
4349
+
4350
+ if slot or condition or media_tokens:
4351
+ payload = "|".join([
4352
+ skill_id,
4353
+ text,
4354
+ f"slot={slot}",
4355
+ f"condition={condition}",
4356
+ "media=" + "|".join(sorted(media_tokens)),
4357
+ ])
4358
+ else:
4359
+ payload = f"{skill_id}|{text}"
4360
+ return hashlib.sha256(payload.encode("utf-8")).hexdigest()[:24]
4361
 
4362
  def get_challenge_questions(
4363
  self,
 
5031
 
5032
  item["question_role"] = question_role
5033
  item["question_kind"] = question_role
5034
+ if str(item.get("skill_id") or "").startswith("AR-") and str(c.get("assessment_mode") or "") == "diagnostic":
5035
+ item["diagnostic_runtime_schema"] = "arabic_diag_runtime_v2"
5036
 
5037
  ok, reason = _contract_question_is_acceptable(item, c)
5038
  if not ok:
question_bank_manager.py CHANGED
@@ -18,7 +18,7 @@ import json, os, threading
18
  from pathlib import Path
19
  from typing import Any, Dict, Iterable, List, Optional
20
 
21
- VERSION = "question_bank_manager_v29_diagnostic_condition_match"
22
 
23
  BANK_PERSISTENCE_OWNER = "QuestionBankManager"
24
  QUESTION_BANK_REPO_KEY = (
@@ -811,6 +811,14 @@ class QuestionBankManager:
811
 
812
  c = dict(c or {})
813
 
 
 
 
 
 
 
 
 
814
  sid = str(
815
  c.get("skill_id")
816
  or c.get("target_skill_id")
 
18
  from pathlib import Path
19
  from typing import Any, Dict, Iterable, List, Optional
20
 
21
+ VERSION = "question_bank_manager_v30_arabic_runtime_schema_guard"
22
 
23
  BANK_PERSISTENCE_OWNER = "QuestionBankManager"
24
  QUESTION_BANK_REPO_KEY = (
 
811
 
812
  c = dict(c or {})
813
 
814
+ # أسئلة العربية التشخيصية القديمة V29 قد تشترك في media path واحد
815
+ # لأن هوية الـstimulus لم تكن تتضمن condition/seed. لا نعيد استخدامها.
816
+ requested_subject = _norm_subject(c.get("subject"))
817
+ requested_mode = str(c.get("assessment_mode") or "").strip().lower()
818
+ if requested_subject == "arabic" and requested_mode == "diagnostic":
819
+ if str(row.get("diagnostic_runtime_schema") or "").strip() != "arabic_diag_runtime_v2":
820
+ return False
821
+
822
  sid = str(
823
  c.get("skill_id")
824
  or c.get("target_skill_id")
strategy_manager.py CHANGED
@@ -42,7 +42,7 @@ except Exception:
42
 
43
 
44
  STRATEGY_MANAGER_VERSION = "strategy_manager_v14_arabic_qmatrix_conditions_runtime"
45
- STRATEGY_SCHEMA_VERSION = "strategy_schema_v14"
46
 
47
 
48
  class StrategyManager:
@@ -773,7 +773,12 @@ class StrategyManager:
773
  "representation": str(template.get("representation") or ""),
774
  "modality": str(template.get("modality") or ""),
775
  "response_mode": str(template.get("response_mode") or ""),
776
- "cognitive_domain": str(template.get("normalized_cognitive_domain") or (row.get("cognitive_domain_profile") or {}).get("primary") or ""),
 
 
 
 
 
777
  "preferred_question_types": [str(template.get("item_type"))] if template.get("item_type") else [],
778
  "hypothesis_to_test": str(hypothesis or ""),
779
  "exclude_question_ids": excluded_ids,
 
42
 
43
 
44
  STRATEGY_MANAGER_VERSION = "strategy_manager_v14_arabic_qmatrix_conditions_runtime"
45
+ STRATEGY_SCHEMA_VERSION = "strategy_schema_v15"
46
 
47
 
48
  class StrategyManager:
 
773
  "representation": str(template.get("representation") or ""),
774
  "modality": str(template.get("modality") or ""),
775
  "response_mode": str(template.get("response_mode") or ""),
776
+ "cognitive_domain": str(
777
+ template.get("normalized_cognitive_domain")
778
+ or (row.get("cognitive_domain_profile") or {}).get("primary")
779
+ or contract.get("cognitive_domain")
780
+ or "applying"
781
+ ),
782
  "preferred_question_types": [str(template.get("item_type"))] if template.get("item_type") else [],
783
  "hypothesis_to_test": str(hypothesis or ""),
784
  "exclude_question_ids": excluded_ids,