Spaces:
Sleeping
Sleeping
Sync server/grader.py
Browse files- server/grader.py +128 -51
server/grader.py
CHANGED
|
@@ -2,17 +2,20 @@
|
|
| 2 |
Grader for DataWranglerEnv.
|
| 3 |
|
| 4 |
Multi-dimensional scoring system that compares the agent's cleaned dataset
|
| 5 |
-
against the ground truth. Produces deterministic scores in
|
| 6 |
|
| 7 |
Dimensions:
|
| 8 |
-
- Missing values fixed
|
| 9 |
-
- Duplicates removed
|
| 10 |
-
- Type correctness
|
| 11 |
-
- Value accuracy
|
| 12 |
-
- Data preservation
|
|
|
|
|
|
|
|
|
|
| 13 |
"""
|
| 14 |
|
| 15 |
-
from typing import Any, Dict, Tuple
|
| 16 |
|
| 17 |
import numpy as np
|
| 18 |
import pandas as pd
|
|
@@ -24,6 +27,9 @@ def compute_score(
|
|
| 24 |
clean_df: pd.DataFrame,
|
| 25 |
original_dirty_df: pd.DataFrame,
|
| 26 |
issue_manifest: Dict[str, Any],
|
|
|
|
|
|
|
|
|
|
| 27 |
) -> Tuple[float, Dict[str, float]]:
|
| 28 |
"""Compute the composite data quality score.
|
| 29 |
|
|
@@ -33,13 +39,18 @@ def compute_score(
|
|
| 33 |
clean_df: The ground truth clean dataset
|
| 34 |
original_dirty_df: The very first dirty state (for reference)
|
| 35 |
issue_manifest: Manifest of all injected issues
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
Returns:
|
| 38 |
Tuple of (composite_score, dimension_scores_dict)
|
| 39 |
"""
|
| 40 |
scores = {}
|
|
|
|
|
|
|
| 41 |
|
| 42 |
-
# ββ Dimension 1: Missing Values Fixed (
|
| 43 |
original_missing = original_dirty_df.isnull().sum().sum()
|
| 44 |
current_missing = current_df.isnull().sum().sum()
|
| 45 |
target_missing = clean_df.isnull().sum().sum()
|
|
@@ -51,7 +62,7 @@ def compute_score(
|
|
| 51 |
else:
|
| 52 |
scores["missing_fixed"] = 1.0
|
| 53 |
|
| 54 |
-
# ββ Dimension 2: Duplicates Removed (
|
| 55 |
original_dupes = original_dirty_df.duplicated().sum()
|
| 56 |
current_dupes = current_df.duplicated().sum()
|
| 57 |
target_dupes = clean_df.duplicated().sum()
|
|
@@ -63,7 +74,7 @@ def compute_score(
|
|
| 63 |
else:
|
| 64 |
scores["duplicates_removed"] = 1.0
|
| 65 |
|
| 66 |
-
# ββ Dimension 3: Type Correctness (
|
| 67 |
type_matches = 0
|
| 68 |
type_total = len(clean_df.columns)
|
| 69 |
|
|
@@ -73,7 +84,6 @@ def compute_score(
|
|
| 73 |
clean_dtype = clean_df[col].dtype
|
| 74 |
current_dtype = current_df[col].dtype
|
| 75 |
|
| 76 |
-
# Allow some flexibility in type matching
|
| 77 |
if clean_dtype == current_dtype:
|
| 78 |
type_matches += 1
|
| 79 |
elif pd.api.types.is_numeric_dtype(clean_dtype) and pd.api.types.is_numeric_dtype(current_dtype):
|
|
@@ -85,14 +95,9 @@ def compute_score(
|
|
| 85 |
|
| 86 |
scores["type_correctness"] = type_matches / type_total if type_total > 0 else 1.0
|
| 87 |
|
| 88 |
-
# ββ Dimension 4: Value Accuracy (
|
| 89 |
-
# Compare cell-by-cell against the clean dataset
|
| 90 |
-
# We align rows by index where possible
|
| 91 |
try:
|
| 92 |
-
# Get the minimum row count for comparison
|
| 93 |
min_rows = min(len(current_df), len(clean_df))
|
| 94 |
-
min_cols = min(len(current_df.columns), len(clean_df.columns))
|
| 95 |
-
|
| 96 |
common_cols = [c for c in clean_df.columns if c in current_df.columns]
|
| 97 |
if common_cols and min_rows > 0:
|
| 98 |
clean_subset = clean_df[common_cols].head(min_rows).reset_index(drop=True)
|
|
@@ -110,7 +115,6 @@ def compute_score(
|
|
| 110 |
except (KeyError, IndexError):
|
| 111 |
continue
|
| 112 |
|
| 113 |
-
# Handle NaN comparison
|
| 114 |
if pd.isna(clean_val) and pd.isna(current_val):
|
| 115 |
matching_cells += 1
|
| 116 |
elif pd.isna(clean_val) or pd.isna(current_val):
|
|
@@ -118,12 +122,11 @@ def compute_score(
|
|
| 118 |
elif str(clean_val).strip().lower() == str(current_val).strip().lower():
|
| 119 |
matching_cells += 1
|
| 120 |
else:
|
| 121 |
-
# Partial match for numeric values (within 1% tolerance)
|
| 122 |
try:
|
| 123 |
cv = float(clean_val)
|
| 124 |
av = float(current_val)
|
| 125 |
if cv != 0 and abs(cv - av) / abs(cv) < 0.01:
|
| 126 |
-
matching_cells += 0.8
|
| 127 |
except (ValueError, TypeError):
|
| 128 |
pass
|
| 129 |
|
|
@@ -134,31 +137,122 @@ def compute_score(
|
|
| 134 |
scores["value_accuracy"] = 0.0
|
| 135 |
|
| 136 |
# ββ Dimension 5: Data Preservation (10%) ββββββββββββββββββββββββββββββ
|
| 137 |
-
# Penalize if valid rows from clean_df were incorrectly removed
|
| 138 |
expected_rows = len(clean_df)
|
| 139 |
current_rows = len(current_df)
|
| 140 |
|
| 141 |
if current_rows >= expected_rows:
|
| 142 |
-
# No valid data lost (might have extra rows from un-removed dupes)
|
| 143 |
scores["data_preservation"] = 1.0
|
| 144 |
elif current_rows == 0:
|
| 145 |
scores["data_preservation"] = 0.0
|
| 146 |
else:
|
| 147 |
-
# Proportional penalty for missing rows
|
| 148 |
scores["data_preservation"] = max(0.0, current_rows / expected_rows)
|
| 149 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
# ββ Composite Score ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 151 |
weights = {
|
| 152 |
-
"missing_fixed": 0.
|
| 153 |
-
"duplicates_removed": 0.
|
| 154 |
-
"type_correctness": 0.
|
| 155 |
-
"value_accuracy": 0.
|
| 156 |
"data_preservation": 0.10,
|
|
|
|
|
|
|
|
|
|
| 157 |
}
|
| 158 |
|
| 159 |
composite = sum(scores[dim] * weights[dim] for dim in weights)
|
| 160 |
-
# Clamp to open interval (0, 1)
|
| 161 |
-
# scores of exactly 0.0 or 1.0.
|
| 162 |
composite = max(0.001, min(0.999, composite))
|
| 163 |
|
| 164 |
return composite, scores
|
|
@@ -173,30 +267,17 @@ def compute_step_reward(
|
|
| 173 |
command: str,
|
| 174 |
data_modified: bool,
|
| 175 |
) -> float:
|
| 176 |
-
"""Compute the reward for a single step.
|
| 177 |
-
|
| 178 |
-
Args:
|
| 179 |
-
before_df: DataFrame state before this step
|
| 180 |
-
after_df: DataFrame state after this step
|
| 181 |
-
clean_df: Ground truth
|
| 182 |
-
original_dirty_df: Original dirty state
|
| 183 |
-
issue_manifest: Issue manifest
|
| 184 |
-
command: The command that was executed
|
| 185 |
-
data_modified: Whether the command modified the data
|
| 186 |
-
|
| 187 |
-
Returns:
|
| 188 |
-
Step reward (can be negative)
|
| 189 |
-
"""
|
| 190 |
cmd = command.strip().split()[0].lower() if command.strip() else ""
|
| 191 |
|
| 192 |
-
# Diagnostic commands get small positive reward
|
| 193 |
diagnostic_cmds = {"help", "view", "profile", "profile_column", "find_missing",
|
| 194 |
-
"find_duplicates", "find_outliers"}
|
| 195 |
if cmd in diagnostic_cmds:
|
| 196 |
return 0.02
|
| 197 |
|
| 198 |
-
# Validate
|
| 199 |
-
if cmd
|
| 200 |
return 0.01
|
| 201 |
|
| 202 |
# Submit doesn't give step reward (final score is computed separately)
|
|
@@ -204,7 +285,6 @@ def compute_step_reward(
|
|
| 204 |
return 0.001
|
| 205 |
|
| 206 |
if not data_modified:
|
| 207 |
-
# Command tried to modify but failed (or no change)
|
| 208 |
return -0.01
|
| 209 |
|
| 210 |
# For data-modifying commands, compare improvement
|
|
@@ -218,11 +298,8 @@ def compute_step_reward(
|
|
| 218 |
improvement = after_score - before_score
|
| 219 |
|
| 220 |
if improvement > 0:
|
| 221 |
-
# Positive improvement β scale reward
|
| 222 |
return min(0.15, improvement * 2.0 + 0.03)
|
| 223 |
elif improvement < -0.01:
|
| 224 |
-
# Made things worse β negative reward
|
| 225 |
return max(-0.20, improvement * 2.0)
|
| 226 |
else:
|
| 227 |
-
# Negligible change
|
| 228 |
return 0.001
|
|
|
|
| 2 |
Grader for DataWranglerEnv.
|
| 3 |
|
| 4 |
Multi-dimensional scoring system that compares the agent's cleaned dataset
|
| 5 |
+
against the ground truth. Produces deterministic scores in (0.001, 0.999).
|
| 6 |
|
| 7 |
Dimensions:
|
| 8 |
+
- Missing values fixed (20%)
|
| 9 |
+
- Duplicates removed (15%)
|
| 10 |
+
- Type correctness (15%)
|
| 11 |
+
- Value accuracy (20%)
|
| 12 |
+
- Data preservation (10%)
|
| 13 |
+
- Constraint compliance (10%) β NEW: business rule satisfaction
|
| 14 |
+
- Step efficiency (5%) β NEW: fewer steps = better
|
| 15 |
+
- Golden row integrity (5%) β NEW: anti-exploit mechanism
|
| 16 |
"""
|
| 17 |
|
| 18 |
+
from typing import Any, Dict, List, Tuple
|
| 19 |
|
| 20 |
import numpy as np
|
| 21 |
import pandas as pd
|
|
|
|
| 27 |
clean_df: pd.DataFrame,
|
| 28 |
original_dirty_df: pd.DataFrame,
|
| 29 |
issue_manifest: Dict[str, Any],
|
| 30 |
+
step_count: int = 0,
|
| 31 |
+
max_steps: int = 30,
|
| 32 |
+
golden_indices: List[int] = None,
|
| 33 |
) -> Tuple[float, Dict[str, float]]:
|
| 34 |
"""Compute the composite data quality score.
|
| 35 |
|
|
|
|
| 39 |
clean_df: The ground truth clean dataset
|
| 40 |
original_dirty_df: The very first dirty state (for reference)
|
| 41 |
issue_manifest: Manifest of all injected issues
|
| 42 |
+
step_count: Current step number
|
| 43 |
+
max_steps: Maximum allowed steps
|
| 44 |
+
golden_indices: Indices of golden rows in clean_df
|
| 45 |
|
| 46 |
Returns:
|
| 47 |
Tuple of (composite_score, dimension_scores_dict)
|
| 48 |
"""
|
| 49 |
scores = {}
|
| 50 |
+
if golden_indices is None:
|
| 51 |
+
golden_indices = issue_manifest.get("golden_indices", [])
|
| 52 |
|
| 53 |
+
# ββ Dimension 1: Missing Values Fixed (20%) ββββββββββββββββββββββββββ
|
| 54 |
original_missing = original_dirty_df.isnull().sum().sum()
|
| 55 |
current_missing = current_df.isnull().sum().sum()
|
| 56 |
target_missing = clean_df.isnull().sum().sum()
|
|
|
|
| 62 |
else:
|
| 63 |
scores["missing_fixed"] = 1.0
|
| 64 |
|
| 65 |
+
# ββ Dimension 2: Duplicates Removed (15%) βββββββββββββββββββββββββββββ
|
| 66 |
original_dupes = original_dirty_df.duplicated().sum()
|
| 67 |
current_dupes = current_df.duplicated().sum()
|
| 68 |
target_dupes = clean_df.duplicated().sum()
|
|
|
|
| 74 |
else:
|
| 75 |
scores["duplicates_removed"] = 1.0
|
| 76 |
|
| 77 |
+
# ββ Dimension 3: Type Correctness (15%) βββββββββββββββββββββββββββββββ
|
| 78 |
type_matches = 0
|
| 79 |
type_total = len(clean_df.columns)
|
| 80 |
|
|
|
|
| 84 |
clean_dtype = clean_df[col].dtype
|
| 85 |
current_dtype = current_df[col].dtype
|
| 86 |
|
|
|
|
| 87 |
if clean_dtype == current_dtype:
|
| 88 |
type_matches += 1
|
| 89 |
elif pd.api.types.is_numeric_dtype(clean_dtype) and pd.api.types.is_numeric_dtype(current_dtype):
|
|
|
|
| 95 |
|
| 96 |
scores["type_correctness"] = type_matches / type_total if type_total > 0 else 1.0
|
| 97 |
|
| 98 |
+
# ββ Dimension 4: Value Accuracy (20%) βββββββββββββββββββββββββββββββββ
|
|
|
|
|
|
|
| 99 |
try:
|
|
|
|
| 100 |
min_rows = min(len(current_df), len(clean_df))
|
|
|
|
|
|
|
| 101 |
common_cols = [c for c in clean_df.columns if c in current_df.columns]
|
| 102 |
if common_cols and min_rows > 0:
|
| 103 |
clean_subset = clean_df[common_cols].head(min_rows).reset_index(drop=True)
|
|
|
|
| 115 |
except (KeyError, IndexError):
|
| 116 |
continue
|
| 117 |
|
|
|
|
| 118 |
if pd.isna(clean_val) and pd.isna(current_val):
|
| 119 |
matching_cells += 1
|
| 120 |
elif pd.isna(clean_val) or pd.isna(current_val):
|
|
|
|
| 122 |
elif str(clean_val).strip().lower() == str(current_val).strip().lower():
|
| 123 |
matching_cells += 1
|
| 124 |
else:
|
|
|
|
| 125 |
try:
|
| 126 |
cv = float(clean_val)
|
| 127 |
av = float(current_val)
|
| 128 |
if cv != 0 and abs(cv - av) / abs(cv) < 0.01:
|
| 129 |
+
matching_cells += 0.8
|
| 130 |
except (ValueError, TypeError):
|
| 131 |
pass
|
| 132 |
|
|
|
|
| 137 |
scores["value_accuracy"] = 0.0
|
| 138 |
|
| 139 |
# ββ Dimension 5: Data Preservation (10%) ββββββββββββββββββββββββββββββ
|
|
|
|
| 140 |
expected_rows = len(clean_df)
|
| 141 |
current_rows = len(current_df)
|
| 142 |
|
| 143 |
if current_rows >= expected_rows:
|
|
|
|
| 144 |
scores["data_preservation"] = 1.0
|
| 145 |
elif current_rows == 0:
|
| 146 |
scores["data_preservation"] = 0.0
|
| 147 |
else:
|
|
|
|
| 148 |
scores["data_preservation"] = max(0.0, current_rows / expected_rows)
|
| 149 |
|
| 150 |
+
# ββ Dimension 6: Constraint Compliance (10%) β NEW ββββββββββββββββββββ
|
| 151 |
+
business_rules = issue_manifest.get("business_rules", [])
|
| 152 |
+
if business_rules:
|
| 153 |
+
rules_satisfied = 0
|
| 154 |
+
rules_total = len(business_rules)
|
| 155 |
+
for rule in business_rules:
|
| 156 |
+
rule_type = rule.get("type", "")
|
| 157 |
+
col = rule.get("column", "")
|
| 158 |
+
|
| 159 |
+
if rule_type == "range" and col in current_df.columns:
|
| 160 |
+
lo, hi = rule.get("min", float("-inf")), rule.get("max", float("inf"))
|
| 161 |
+
numeric = pd.to_numeric(current_df[col], errors="coerce")
|
| 162 |
+
valid = numeric.dropna()
|
| 163 |
+
if len(valid) > 0:
|
| 164 |
+
pct_ok = ((valid >= lo) & (valid <= hi)).mean()
|
| 165 |
+
rules_satisfied += pct_ok
|
| 166 |
+
|
| 167 |
+
elif rule_type == "not_null" and col in current_df.columns:
|
| 168 |
+
pct_ok = 1.0 - (current_df[col].isna().sum() / max(1, len(current_df)))
|
| 169 |
+
rules_satisfied += pct_ok
|
| 170 |
+
|
| 171 |
+
elif rule_type == "cross_column":
|
| 172 |
+
col_a = rule.get("column_a", "")
|
| 173 |
+
col_b = rule.get("column_b", "")
|
| 174 |
+
if col_a in current_df.columns and col_b in current_df.columns:
|
| 175 |
+
a = pd.to_numeric(current_df[col_a], errors="coerce")
|
| 176 |
+
b = pd.to_numeric(current_df[col_b], errors="coerce")
|
| 177 |
+
valid_mask = a.notna() & b.notna()
|
| 178 |
+
if valid_mask.sum() > 0:
|
| 179 |
+
pct_ok = (a[valid_mask] > b[valid_mask]).mean()
|
| 180 |
+
rules_satisfied += pct_ok
|
| 181 |
+
|
| 182 |
+
elif rule_type == "categorical" and col in current_df.columns:
|
| 183 |
+
allowed = set(rule.get("allowed_values", []))
|
| 184 |
+
if allowed:
|
| 185 |
+
non_null = current_df[col].dropna()
|
| 186 |
+
if len(non_null) > 0:
|
| 187 |
+
pct_ok = non_null.isin(allowed).mean()
|
| 188 |
+
rules_satisfied += pct_ok
|
| 189 |
+
|
| 190 |
+
elif rule_type == "pattern" and col in current_df.columns:
|
| 191 |
+
pat = rule.get("pattern", "")
|
| 192 |
+
if pat:
|
| 193 |
+
non_null = current_df[col].dropna().astype(str)
|
| 194 |
+
if len(non_null) > 0:
|
| 195 |
+
pct_ok = non_null.str.match(pat).mean()
|
| 196 |
+
rules_satisfied += pct_ok
|
| 197 |
+
|
| 198 |
+
scores["constraint_compliance"] = rules_satisfied / rules_total if rules_total > 0 else 0.8
|
| 199 |
+
else:
|
| 200 |
+
scores["constraint_compliance"] = 0.8 # Default for tasks without rules
|
| 201 |
+
|
| 202 |
+
# ββ Dimension 7: Step Efficiency (5%) β NEW βββββββββββββββββββββββββββ
|
| 203 |
+
if step_count > 0 and max_steps > 0:
|
| 204 |
+
efficiency = 1.0 - (step_count / max_steps)
|
| 205 |
+
scores["step_efficiency"] = max(0.1, min(1.0, efficiency * 1.5))
|
| 206 |
+
else:
|
| 207 |
+
scores["step_efficiency"] = 0.5
|
| 208 |
+
|
| 209 |
+
# ββ Dimension 8: Golden Row Integrity (5%) β NEW ββββββββββββββββββββββ
|
| 210 |
+
if golden_indices and len(current_df) > 0:
|
| 211 |
+
golden_ok = 0
|
| 212 |
+
golden_total = len(golden_indices)
|
| 213 |
+
for gi in golden_indices:
|
| 214 |
+
if gi >= len(clean_df):
|
| 215 |
+
continue
|
| 216 |
+
golden_row = clean_df.iloc[gi]
|
| 217 |
+
# Check if this golden row still exists in current_df (approximately)
|
| 218 |
+
found = False
|
| 219 |
+
for _, row in current_df.iterrows():
|
| 220 |
+
match_count = 0
|
| 221 |
+
total_check = 0
|
| 222 |
+
for col in clean_df.columns:
|
| 223 |
+
if col not in current_df.columns:
|
| 224 |
+
continue
|
| 225 |
+
total_check += 1
|
| 226 |
+
gv = golden_row[col]
|
| 227 |
+
cv = row[col]
|
| 228 |
+
if pd.isna(gv) and pd.isna(cv):
|
| 229 |
+
match_count += 1
|
| 230 |
+
elif not pd.isna(gv) and not pd.isna(cv):
|
| 231 |
+
if str(gv).strip().lower() == str(cv).strip().lower():
|
| 232 |
+
match_count += 1
|
| 233 |
+
if total_check > 0 and match_count / total_check > 0.8:
|
| 234 |
+
found = True
|
| 235 |
+
break
|
| 236 |
+
if found:
|
| 237 |
+
golden_ok += 1
|
| 238 |
+
scores["golden_row_integrity"] = golden_ok / golden_total if golden_total > 0 else 1.0
|
| 239 |
+
else:
|
| 240 |
+
scores["golden_row_integrity"] = 1.0
|
| 241 |
+
|
| 242 |
# ββ Composite Score ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 243 |
weights = {
|
| 244 |
+
"missing_fixed": 0.20,
|
| 245 |
+
"duplicates_removed": 0.15,
|
| 246 |
+
"type_correctness": 0.15,
|
| 247 |
+
"value_accuracy": 0.20,
|
| 248 |
"data_preservation": 0.10,
|
| 249 |
+
"constraint_compliance": 0.10,
|
| 250 |
+
"step_efficiency": 0.05,
|
| 251 |
+
"golden_row_integrity": 0.05,
|
| 252 |
}
|
| 253 |
|
| 254 |
composite = sum(scores[dim] * weights[dim] for dim in weights)
|
| 255 |
+
# Clamp to open interval (0, 1)
|
|
|
|
| 256 |
composite = max(0.001, min(0.999, composite))
|
| 257 |
|
| 258 |
return composite, scores
|
|
|
|
| 267 |
command: str,
|
| 268 |
data_modified: bool,
|
| 269 |
) -> float:
|
| 270 |
+
"""Compute the reward for a single step."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
cmd = command.strip().split()[0].lower() if command.strip() else ""
|
| 272 |
|
| 273 |
+
# Diagnostic commands get small positive reward
|
| 274 |
diagnostic_cmds = {"help", "view", "profile", "profile_column", "find_missing",
|
| 275 |
+
"find_duplicates", "find_outliers", "check_rules", "history"}
|
| 276 |
if cmd in diagnostic_cmds:
|
| 277 |
return 0.02
|
| 278 |
|
| 279 |
+
# Validate and undo get small reward
|
| 280 |
+
if cmd in ("validate", "undo"):
|
| 281 |
return 0.01
|
| 282 |
|
| 283 |
# Submit doesn't give step reward (final score is computed separately)
|
|
|
|
| 285 |
return 0.001
|
| 286 |
|
| 287 |
if not data_modified:
|
|
|
|
| 288 |
return -0.01
|
| 289 |
|
| 290 |
# For data-modifying commands, compare improvement
|
|
|
|
| 298 |
improvement = after_score - before_score
|
| 299 |
|
| 300 |
if improvement > 0:
|
|
|
|
| 301 |
return min(0.15, improvement * 2.0 + 0.03)
|
| 302 |
elif improvement < -0.01:
|
|
|
|
| 303 |
return max(-0.20, improvement * 2.0)
|
| 304 |
else:
|
|
|
|
| 305 |
return 0.001
|