Spaces:
Running
Running
Add configurable BERT stage target and stage visibility in optimizer.
Browse filesAllow users to set custom BERT threshold for Stage A completion (e.g. accept 0.61), persist it in UI project state, and expose stage transitions in logs for transparent progression to BM25/Semantic/N-gram/Title.
Made-with: Cursor
- docs/FULL_FUNCTIONAL_DOCUMENTATION.md +3 -1
- models.py +2 -0
- optimizer.py +30 -9
- templates/index.html +16 -2
docs/FULL_FUNCTIONAL_DOCUMENTATION.md
CHANGED
|
@@ -164,9 +164,10 @@
|
|
| 164 |
### Вход (`OptimizerRequest`)
|
| 165 |
- аналитические данные: `target_text`, `competitors`, `keywords`, `language`, `target_title`, `competitor_titles`
|
| 166 |
- LLM: `api_key`, `api_base_url`, `model`, `temperature`
|
| 167 |
-
- стратегия: `max_iterations`, `candidates_per_iteration`, `optimization_mode`, `phrase_strategy_mode`
|
| 168 |
- `phrase_strategy_mode`: `auto | distributed_preferred | exact_preferred | ensemble`
|
| 169 |
- `ensemble`: в пределах итерации циклически пробует несколько phrase-стратегий и ранжирует кандидаты общей utility-функцией.
|
|
|
|
| 170 |
|
| 171 |
### Выход (`OptimizerResponse`)
|
| 172 |
- `optimized_text`
|
|
@@ -175,6 +176,7 @@
|
|
| 175 |
- `applied_changes`
|
| 176 |
- `optimization_mode`
|
| 177 |
- `phrase_strategy_mode`
|
|
|
|
| 178 |
- `error` (если есть)
|
| 179 |
|
| 180 |
---
|
|
|
|
| 164 |
### Вход (`OptimizerRequest`)
|
| 165 |
- аналитические данные: `target_text`, `competitors`, `keywords`, `language`, `target_title`, `competitor_titles`
|
| 166 |
- LLM: `api_key`, `api_base_url`, `model`, `temperature`
|
| 167 |
+
- стратегия: `max_iterations`, `candidates_per_iteration`, `optimization_mode`, `phrase_strategy_mode`, `bert_stage_target`
|
| 168 |
- `phrase_strategy_mode`: `auto | distributed_preferred | exact_preferred | ensemble`
|
| 169 |
- `ensemble`: в пределах итерации циклически пробует несколько phrase-стратегий и ранжирует кандидаты общей utility-функцией.
|
| 170 |
+
- `bert_stage_target`: пользовательский порог завершения этапа A (BERT), например `0.61` вместо `0.70`.
|
| 171 |
|
| 172 |
### Выход (`OptimizerResponse`)
|
| 173 |
- `optimized_text`
|
|
|
|
| 176 |
- `applied_changes`
|
| 177 |
- `optimization_mode`
|
| 178 |
- `phrase_strategy_mode`
|
| 179 |
+
- `bert_stage_target`
|
| 180 |
- `error` (если есть)
|
| 181 |
|
| 182 |
---
|
models.py
CHANGED
|
@@ -92,6 +92,7 @@ class OptimizerRequest(BaseModel):
|
|
| 92 |
temperature: float = 0.25
|
| 93 |
optimization_mode: str = "balanced"
|
| 94 |
phrase_strategy_mode: str = "auto" # auto | exact_preferred | distributed_preferred | ensemble
|
|
|
|
| 95 |
|
| 96 |
|
| 97 |
class OptimizerResponse(BaseModel):
|
|
@@ -103,4 +104,5 @@ class OptimizerResponse(BaseModel):
|
|
| 103 |
applied_changes: int = 0
|
| 104 |
optimization_mode: str = "balanced"
|
| 105 |
phrase_strategy_mode: str = "auto"
|
|
|
|
| 106 |
error: str = ""
|
|
|
|
| 92 |
temperature: float = 0.25
|
| 93 |
optimization_mode: str = "balanced"
|
| 94 |
phrase_strategy_mode: str = "auto" # auto | exact_preferred | distributed_preferred | ensemble
|
| 95 |
+
bert_stage_target: float = 0.70
|
| 96 |
|
| 97 |
|
| 98 |
class OptimizerResponse(BaseModel):
|
|
|
|
| 104 |
applied_changes: int = 0
|
| 105 |
optimization_mode: str = "balanced"
|
| 106 |
phrase_strategy_mode: str = "auto"
|
| 107 |
+
bert_stage_target: float = 0.70
|
| 108 |
error: str = ""
|
optimizer.py
CHANGED
|
@@ -232,12 +232,18 @@ def _is_semantic_gap(target_weight: float, competitor_avg_weight: float) -> bool
|
|
| 232 |
return (competitor_avg_weight > rel_threshold) and (abs_gap >= SEMANTIC_GAP_MIN_ABS)
|
| 233 |
|
| 234 |
|
| 235 |
-
def _compute_metrics(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
competitor_count = len(analysis.get("word_counts", {}).get("competitors", []))
|
| 237 |
min_signal = 1 if competitor_count <= 1 else 2
|
| 238 |
|
| 239 |
bert_details = analysis.get("bert_analysis", {}).get("detailed", []) or []
|
| 240 |
-
bert_low = [d for d in bert_details if float(d.get("my_max_score", 0)) <
|
| 241 |
bert_phrase_scores = {
|
| 242 |
str(d.get("phrase", "")).strip().lower(): float(d.get("my_max_score", 0) or 0.0)
|
| 243 |
for d in bert_details
|
|
@@ -346,10 +352,11 @@ def _choose_optimization_goal(
|
|
| 346 |
keywords: List[str],
|
| 347 |
language: str,
|
| 348 |
stage: str = "bert",
|
|
|
|
| 349 |
) -> Dict[str, Any]:
|
| 350 |
candidates: Dict[str, Dict[str, Any]] = {}
|
| 351 |
bert_details = analysis.get("bert_analysis", {}).get("detailed", []) or []
|
| 352 |
-
low_bert = [x for x in bert_details if float(x.get("my_max_score", 0)) <
|
| 353 |
if low_bert:
|
| 354 |
worst = sorted(low_bert, key=lambda x: float(x.get("my_max_score", 0)))[0]
|
| 355 |
focus_terms = _filter_stopwords(_tokenize(worst.get("phrase", "")), language)[:4]
|
|
@@ -963,9 +970,11 @@ def _stage_primary_progress(stage: str, prev_metrics: Dict[str, Any], next_metri
|
|
| 963 |
return False
|
| 964 |
|
| 965 |
|
| 966 |
-
def _is_stage_complete(stage: str, metrics: Dict[str, Any]) -> bool:
|
| 967 |
if stage == "bert":
|
| 968 |
-
|
|
|
|
|
|
|
| 969 |
if stage == "bm25":
|
| 970 |
return int(metrics.get("bm25_remove_count", 0)) <= 3
|
| 971 |
if stage == "semantic":
|
|
@@ -1089,12 +1098,16 @@ def optimize_text(request_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 1089 |
phrase_strategy_mode = str(request_data.get("phrase_strategy_mode", "auto") or "auto").strip().lower()
|
| 1090 |
if phrase_strategy_mode not in {"auto", "exact_preferred", "distributed_preferred", "ensemble"}:
|
| 1091 |
phrase_strategy_mode = "auto"
|
|
|
|
|
|
|
| 1092 |
|
| 1093 |
baseline_analysis = _build_analysis_snapshot(
|
| 1094 |
target_text, competitors, keywords, language, target_title, competitor_titles
|
| 1095 |
)
|
| 1096 |
baseline_semantic = _build_semantic_snapshot(target_text, competitors, language)
|
| 1097 |
-
baseline_metrics = _compute_metrics(
|
|
|
|
|
|
|
| 1098 |
|
| 1099 |
current_text = target_text
|
| 1100 |
current_analysis = baseline_analysis
|
|
@@ -1112,7 +1125,9 @@ def optimize_text(request_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 1112 |
stage_no_progress_steps = 0
|
| 1113 |
|
| 1114 |
for step in range(max_iterations):
|
| 1115 |
-
while stage_idx < len(STAGE_ORDER) and _is_stage_complete(
|
|
|
|
|
|
|
| 1116 |
stage_idx += 1
|
| 1117 |
stage_no_progress_steps = 0
|
| 1118 |
if stage_idx >= len(STAGE_ORDER):
|
|
@@ -1126,6 +1141,7 @@ def optimize_text(request_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 1126 |
keywords,
|
| 1127 |
language,
|
| 1128 |
stage=active_stage,
|
|
|
|
| 1129 |
)
|
| 1130 |
if goal["type"] == "none":
|
| 1131 |
stage_idx += 1
|
|
@@ -1314,7 +1330,9 @@ def optimize_text(request_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 1314 |
candidate_text, competitors, keywords, language, target_title, competitor_titles
|
| 1315 |
)
|
| 1316 |
cand_semantic = _build_semantic_snapshot(candidate_text, competitors, language)
|
| 1317 |
-
cand_metrics = _compute_metrics(
|
|
|
|
|
|
|
| 1318 |
valid, invalid_reasons, goal_improved = _is_candidate_valid(
|
| 1319 |
current_metrics, cand_metrics, goal["type"], goal["label"], optimization_mode
|
| 1320 |
)
|
|
@@ -1548,7 +1566,9 @@ def optimize_text(request_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 1548 |
batch_text, competitors, keywords, language, target_title, competitor_titles
|
| 1549 |
)
|
| 1550 |
batch_semantic = _build_semantic_snapshot(batch_text, competitors, language)
|
| 1551 |
-
batch_metrics = _compute_metrics(
|
|
|
|
|
|
|
| 1552 |
b_valid, b_reasons, b_goal = _is_candidate_valid(
|
| 1553 |
current_metrics, batch_metrics, goal["type"], goal["label"], optimization_mode
|
| 1554 |
)
|
|
@@ -1801,4 +1821,5 @@ def optimize_text(request_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 1801 |
"applied_changes": applied_changes,
|
| 1802 |
"optimization_mode": optimization_mode,
|
| 1803 |
"phrase_strategy_mode": phrase_strategy_mode,
|
|
|
|
| 1804 |
}
|
|
|
|
| 232 |
return (competitor_avg_weight > rel_threshold) and (abs_gap >= SEMANTIC_GAP_MIN_ABS)
|
| 233 |
|
| 234 |
|
| 235 |
+
def _compute_metrics(
|
| 236 |
+
analysis: Dict[str, Any],
|
| 237 |
+
semantic: Dict[str, Any],
|
| 238 |
+
keywords: List[str],
|
| 239 |
+
language: str,
|
| 240 |
+
bert_stage_target: float = BERT_TARGET_THRESHOLD,
|
| 241 |
+
) -> Dict[str, Any]:
|
| 242 |
competitor_count = len(analysis.get("word_counts", {}).get("competitors", []))
|
| 243 |
min_signal = 1 if competitor_count <= 1 else 2
|
| 244 |
|
| 245 |
bert_details = analysis.get("bert_analysis", {}).get("detailed", []) or []
|
| 246 |
+
bert_low = [d for d in bert_details if float(d.get("my_max_score", 0)) < float(bert_stage_target)]
|
| 247 |
bert_phrase_scores = {
|
| 248 |
str(d.get("phrase", "")).strip().lower(): float(d.get("my_max_score", 0) or 0.0)
|
| 249 |
for d in bert_details
|
|
|
|
| 352 |
keywords: List[str],
|
| 353 |
language: str,
|
| 354 |
stage: str = "bert",
|
| 355 |
+
bert_stage_target: float = BERT_TARGET_THRESHOLD,
|
| 356 |
) -> Dict[str, Any]:
|
| 357 |
candidates: Dict[str, Dict[str, Any]] = {}
|
| 358 |
bert_details = analysis.get("bert_analysis", {}).get("detailed", []) or []
|
| 359 |
+
low_bert = [x for x in bert_details if float(x.get("my_max_score", 0)) < float(bert_stage_target)]
|
| 360 |
if low_bert:
|
| 361 |
worst = sorted(low_bert, key=lambda x: float(x.get("my_max_score", 0)))[0]
|
| 362 |
focus_terms = _filter_stopwords(_tokenize(worst.get("phrase", "")), language)[:4]
|
|
|
|
| 970 |
return False
|
| 971 |
|
| 972 |
|
| 973 |
+
def _is_stage_complete(stage: str, metrics: Dict[str, Any], bert_stage_target: float = BERT_TARGET_THRESHOLD) -> bool:
|
| 974 |
if stage == "bert":
|
| 975 |
+
scores = [float(v) for v in (metrics.get("bert_phrase_scores") or {}).values()]
|
| 976 |
+
max_phrase = max([0.0] + scores)
|
| 977 |
+
return max_phrase >= float(bert_stage_target)
|
| 978 |
if stage == "bm25":
|
| 979 |
return int(metrics.get("bm25_remove_count", 0)) <= 3
|
| 980 |
if stage == "semantic":
|
|
|
|
| 1098 |
phrase_strategy_mode = str(request_data.get("phrase_strategy_mode", "auto") or "auto").strip().lower()
|
| 1099 |
if phrase_strategy_mode not in {"auto", "exact_preferred", "distributed_preferred", "ensemble"}:
|
| 1100 |
phrase_strategy_mode = "auto"
|
| 1101 |
+
bert_stage_target = float(request_data.get("bert_stage_target", BERT_TARGET_THRESHOLD) or BERT_TARGET_THRESHOLD)
|
| 1102 |
+
bert_stage_target = max(0.0, min(1.0, bert_stage_target))
|
| 1103 |
|
| 1104 |
baseline_analysis = _build_analysis_snapshot(
|
| 1105 |
target_text, competitors, keywords, language, target_title, competitor_titles
|
| 1106 |
)
|
| 1107 |
baseline_semantic = _build_semantic_snapshot(target_text, competitors, language)
|
| 1108 |
+
baseline_metrics = _compute_metrics(
|
| 1109 |
+
baseline_analysis, baseline_semantic, keywords, language, bert_stage_target=bert_stage_target
|
| 1110 |
+
)
|
| 1111 |
|
| 1112 |
current_text = target_text
|
| 1113 |
current_analysis = baseline_analysis
|
|
|
|
| 1125 |
stage_no_progress_steps = 0
|
| 1126 |
|
| 1127 |
for step in range(max_iterations):
|
| 1128 |
+
while stage_idx < len(STAGE_ORDER) and _is_stage_complete(
|
| 1129 |
+
STAGE_ORDER[stage_idx], current_metrics, bert_stage_target=bert_stage_target
|
| 1130 |
+
):
|
| 1131 |
stage_idx += 1
|
| 1132 |
stage_no_progress_steps = 0
|
| 1133 |
if stage_idx >= len(STAGE_ORDER):
|
|
|
|
| 1141 |
keywords,
|
| 1142 |
language,
|
| 1143 |
stage=active_stage,
|
| 1144 |
+
bert_stage_target=bert_stage_target,
|
| 1145 |
)
|
| 1146 |
if goal["type"] == "none":
|
| 1147 |
stage_idx += 1
|
|
|
|
| 1330 |
candidate_text, competitors, keywords, language, target_title, competitor_titles
|
| 1331 |
)
|
| 1332 |
cand_semantic = _build_semantic_snapshot(candidate_text, competitors, language)
|
| 1333 |
+
cand_metrics = _compute_metrics(
|
| 1334 |
+
cand_analysis, cand_semantic, keywords, language, bert_stage_target=bert_stage_target
|
| 1335 |
+
)
|
| 1336 |
valid, invalid_reasons, goal_improved = _is_candidate_valid(
|
| 1337 |
current_metrics, cand_metrics, goal["type"], goal["label"], optimization_mode
|
| 1338 |
)
|
|
|
|
| 1566 |
batch_text, competitors, keywords, language, target_title, competitor_titles
|
| 1567 |
)
|
| 1568 |
batch_semantic = _build_semantic_snapshot(batch_text, competitors, language)
|
| 1569 |
+
batch_metrics = _compute_metrics(
|
| 1570 |
+
batch_analysis, batch_semantic, keywords, language, bert_stage_target=bert_stage_target
|
| 1571 |
+
)
|
| 1572 |
b_valid, b_reasons, b_goal = _is_candidate_valid(
|
| 1573 |
current_metrics, batch_metrics, goal["type"], goal["label"], optimization_mode
|
| 1574 |
)
|
|
|
|
| 1821 |
"applied_changes": applied_changes,
|
| 1822 |
"optimization_mode": optimization_mode,
|
| 1823 |
"phrase_strategy_mode": phrase_strategy_mode,
|
| 1824 |
+
"bert_stage_target": round(bert_stage_target, 4),
|
| 1825 |
}
|
templates/index.html
CHANGED
|
@@ -302,6 +302,10 @@
|
|
| 302 |
<label class="form-label small text-muted mb-1">Temp</label>
|
| 303 |
<input type="number" id="optimizerTemp" class="form-control" min="0" max="1.2" step="0.05" value="0.25">
|
| 304 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
<div class="col-md-3">
|
| 306 |
<label class="form-label small text-muted mb-1">Режим оптимизации</label>
|
| 307 |
<select id="optimizerMode" class="form-select">
|
|
@@ -558,6 +562,7 @@
|
|
| 558 |
optimizer_iterations: Number(document.getElementById('optimizerIterations').value || 2),
|
| 559 |
optimizer_candidates: Number(document.getElementById('optimizerCandidates').value || 2),
|
| 560 |
optimizer_temperature: Number(document.getElementById('optimizerTemp').value || 0.25),
|
|
|
|
| 561 |
optimizer_mode: document.getElementById('optimizerMode').value,
|
| 562 |
optimizer_phrase_strategy: document.getElementById('optimizerPhraseStrategy').value
|
| 563 |
},
|
|
@@ -609,6 +614,7 @@
|
|
| 609 |
document.getElementById('optimizerIterations').value = 2;
|
| 610 |
document.getElementById('optimizerCandidates').value = 2;
|
| 611 |
document.getElementById('optimizerTemp').value = 0.25;
|
|
|
|
| 612 |
document.getElementById('optimizerMode').value = 'balanced';
|
| 613 |
document.getElementById('optimizerPhraseStrategy').value = 'auto';
|
| 614 |
|
|
@@ -663,6 +669,7 @@
|
|
| 663 |
document.getElementById('optimizerIterations').value = inp.optimizer_iterations ?? 2;
|
| 664 |
document.getElementById('optimizerCandidates').value = inp.optimizer_candidates ?? 2;
|
| 665 |
document.getElementById('optimizerTemp').value = inp.optimizer_temperature ?? 0.25;
|
|
|
|
| 666 |
document.getElementById('optimizerMode').value = inp.optimizer_mode || 'balanced';
|
| 667 |
document.getElementById('optimizerPhraseStrategy').value = inp.optimizer_phrase_strategy || 'auto';
|
| 668 |
|
|
@@ -853,9 +860,12 @@
|
|
| 853 |
const after = it.metrics_after ? it.metrics_after.score : '-';
|
| 854 |
const baseline = (it.current_score ?? before);
|
| 855 |
const reason = it.reason || (it.candidates ? 'all candidates rejected by constraints' : '-');
|
|
|
|
|
|
|
| 856 |
return `<tr>
|
| 857 |
<td>${it.step}</td>
|
| 858 |
<td>${it.status}</td>
|
|
|
|
| 859 |
<td>${it.goal ? (it.goal.type + ': ' + (it.goal.label || '')) : '-'}</td>
|
| 860 |
<td>L${it.cascade_level || '-'} / ${it.operation || '-'}</td>
|
| 861 |
<td>${baseline}</td>
|
|
@@ -919,9 +929,11 @@
|
|
| 919 |
<div><strong>Шаг ${it.step}</strong> — ${safeHtml(it.status || '-')}</div>
|
| 920 |
<span class="badge bg-secondary">${safeHtml(it.goal ? (it.goal.type + ': ' + (it.goal.label || '')) : '-')}</span>
|
| 921 |
</div>
|
|
|
|
| 922 |
<div class="small mb-2"><strong>Каскад:</strong> L${safeHtml(it.cascade_level || '-')} / ${safeHtml(it.operation || '-')}</div>
|
| 923 |
<div class="small mb-2"><strong>Причина:</strong> ${safeHtml(it.reason || '-')}</div>
|
| 924 |
${it.escalated_to_level ? `<div class="small mb-2 text-warning"><strong>Эскалация:</strong> переход на L${safeHtml(it.escalated_to_level)}</div>` : ''}
|
|
|
|
| 925 |
<div class="small mb-2"><strong>Исходное предложение:</strong><br><span class="text-muted">${sentBefore}</span></div>
|
| 926 |
<div class="small mb-2"><strong>Выбранный вариант:</strong><br><span class="text-muted">${sentAfterChosen}</span></div>
|
| 927 |
<div class="table-responsive">
|
|
@@ -945,6 +957,7 @@
|
|
| 945 |
<div class="small mb-2">
|
| 946 |
Режим: <strong>${data.optimization_mode || 'balanced'}</strong>
|
| 947 |
· Phrase Strategy: <strong>${data.phrase_strategy_mode || 'auto'}</strong>
|
|
|
|
| 948 |
</div>
|
| 949 |
<div class="table-responsive">
|
| 950 |
<table class="table table-sm table-bordered mb-0">
|
|
@@ -957,8 +970,8 @@
|
|
| 957 |
<h6 class="card-title">Лог итераций</h6>
|
| 958 |
<div class="table-responsive">
|
| 959 |
<table class="table table-sm table-hover mb-0">
|
| 960 |
-
<thead><tr><th>#</th><th>Статус</th><th>Цель</th><th>Каскад</th><th>Baseline шага</th><th>Score до</th><th>Score после</th><th>Δ</th><th>Причина/комментарий</th></tr></thead>
|
| 961 |
-
<tbody>${iterRows || '<tr><td colspan="
|
| 962 |
</table>
|
| 963 |
</div>
|
| 964 |
</div>
|
|
@@ -1002,6 +1015,7 @@
|
|
| 1002 |
max_iterations: Number(document.getElementById('optimizerIterations').value || 2),
|
| 1003 |
candidates_per_iteration: Number(document.getElementById('optimizerCandidates').value || 2),
|
| 1004 |
temperature: Number(document.getElementById('optimizerTemp').value || 0.25),
|
|
|
|
| 1005 |
optimization_mode: document.getElementById('optimizerMode').value || 'balanced',
|
| 1006 |
phrase_strategy_mode: document.getElementById('optimizerPhraseStrategy').value || 'auto'
|
| 1007 |
};
|
|
|
|
| 302 |
<label class="form-label small text-muted mb-1">Temp</label>
|
| 303 |
<input type="number" id="optimizerTemp" class="form-control" min="0" max="1.2" step="0.05" value="0.25">
|
| 304 |
</div>
|
| 305 |
+
<div class="col-md-2">
|
| 306 |
+
<label class="form-label small text-muted mb-1">BERT target A-stage</label>
|
| 307 |
+
<input type="number" id="optimizerBertStageTarget" class="form-control" min="0" max="1" step="0.01" value="0.70">
|
| 308 |
+
</div>
|
| 309 |
<div class="col-md-3">
|
| 310 |
<label class="form-label small text-muted mb-1">Режим оптимизации</label>
|
| 311 |
<select id="optimizerMode" class="form-select">
|
|
|
|
| 562 |
optimizer_iterations: Number(document.getElementById('optimizerIterations').value || 2),
|
| 563 |
optimizer_candidates: Number(document.getElementById('optimizerCandidates').value || 2),
|
| 564 |
optimizer_temperature: Number(document.getElementById('optimizerTemp').value || 0.25),
|
| 565 |
+
optimizer_bert_stage_target: Number(document.getElementById('optimizerBertStageTarget').value || 0.70),
|
| 566 |
optimizer_mode: document.getElementById('optimizerMode').value,
|
| 567 |
optimizer_phrase_strategy: document.getElementById('optimizerPhraseStrategy').value
|
| 568 |
},
|
|
|
|
| 614 |
document.getElementById('optimizerIterations').value = 2;
|
| 615 |
document.getElementById('optimizerCandidates').value = 2;
|
| 616 |
document.getElementById('optimizerTemp').value = 0.25;
|
| 617 |
+
document.getElementById('optimizerBertStageTarget').value = 0.70;
|
| 618 |
document.getElementById('optimizerMode').value = 'balanced';
|
| 619 |
document.getElementById('optimizerPhraseStrategy').value = 'auto';
|
| 620 |
|
|
|
|
| 669 |
document.getElementById('optimizerIterations').value = inp.optimizer_iterations ?? 2;
|
| 670 |
document.getElementById('optimizerCandidates').value = inp.optimizer_candidates ?? 2;
|
| 671 |
document.getElementById('optimizerTemp').value = inp.optimizer_temperature ?? 0.25;
|
| 672 |
+
document.getElementById('optimizerBertStageTarget').value = inp.optimizer_bert_stage_target ?? 0.70;
|
| 673 |
document.getElementById('optimizerMode').value = inp.optimizer_mode || 'balanced';
|
| 674 |
document.getElementById('optimizerPhraseStrategy').value = inp.optimizer_phrase_strategy || 'auto';
|
| 675 |
|
|
|
|
| 860 |
const after = it.metrics_after ? it.metrics_after.score : '-';
|
| 861 |
const baseline = (it.current_score ?? before);
|
| 862 |
const reason = it.reason || (it.candidates ? 'all candidates rejected by constraints' : '-');
|
| 863 |
+
const stage = (it.stage || (it.goal && it.goal.type) || '-');
|
| 864 |
+
const advanced = it.advanced_to_stage ? ` → ${it.advanced_to_stage}` : '';
|
| 865 |
return `<tr>
|
| 866 |
<td>${it.step}</td>
|
| 867 |
<td>${it.status}</td>
|
| 868 |
+
<td>${stage}${advanced}</td>
|
| 869 |
<td>${it.goal ? (it.goal.type + ': ' + (it.goal.label || '')) : '-'}</td>
|
| 870 |
<td>L${it.cascade_level || '-'} / ${it.operation || '-'}</td>
|
| 871 |
<td>${baseline}</td>
|
|
|
|
| 929 |
<div><strong>Шаг ${it.step}</strong> — ${safeHtml(it.status || '-')}</div>
|
| 930 |
<span class="badge bg-secondary">${safeHtml(it.goal ? (it.goal.type + ': ' + (it.goal.label || '')) : '-')}</span>
|
| 931 |
</div>
|
| 932 |
+
<div class="small mb-2"><strong>Этап:</strong> ${safeHtml(it.stage || (it.goal ? it.goal.type : '-') || '-')}</div>
|
| 933 |
<div class="small mb-2"><strong>Каскад:</strong> L${safeHtml(it.cascade_level || '-')} / ${safeHtml(it.operation || '-')}</div>
|
| 934 |
<div class="small mb-2"><strong>Причина:</strong> ${safeHtml(it.reason || '-')}</div>
|
| 935 |
${it.escalated_to_level ? `<div class="small mb-2 text-warning"><strong>Эскалация:</strong> переход на L${safeHtml(it.escalated_to_level)}</div>` : ''}
|
| 936 |
+
${it.advanced_to_stage ? `<div class="small mb-2 text-info"><strong>Смена этапа:</strong> переход на ${safeHtml(it.advanced_to_stage)}</div>` : ''}
|
| 937 |
<div class="small mb-2"><strong>Исходное предложение:</strong><br><span class="text-muted">${sentBefore}</span></div>
|
| 938 |
<div class="small mb-2"><strong>Выбранный вариант:</strong><br><span class="text-muted">${sentAfterChosen}</span></div>
|
| 939 |
<div class="table-responsive">
|
|
|
|
| 957 |
<div class="small mb-2">
|
| 958 |
Режим: <strong>${data.optimization_mode || 'balanced'}</strong>
|
| 959 |
· Phrase Strategy: <strong>${data.phrase_strategy_mode || 'auto'}</strong>
|
| 960 |
+
· BERT target A-stage: <strong>${data.bert_stage_target ?? 0.7}</strong>
|
| 961 |
</div>
|
| 962 |
<div class="table-responsive">
|
| 963 |
<table class="table table-sm table-bordered mb-0">
|
|
|
|
| 970 |
<h6 class="card-title">Лог итераций</h6>
|
| 971 |
<div class="table-responsive">
|
| 972 |
<table class="table table-sm table-hover mb-0">
|
| 973 |
+
<thead><tr><th>#</th><th>Статус</th><th>Этап</th><th>Цель</th><th>Каскад</th><th>Baseline шага</th><th>Score до</th><th>Score после</th><th>Δ</th><th>Причина/комментарий</th></tr></thead>
|
| 974 |
+
<tbody>${iterRows || '<tr><td colspan="10" class="text-muted text-center">Нет данных</td></tr>'}</tbody>
|
| 975 |
</table>
|
| 976 |
</div>
|
| 977 |
</div>
|
|
|
|
| 1015 |
max_iterations: Number(document.getElementById('optimizerIterations').value || 2),
|
| 1016 |
candidates_per_iteration: Number(document.getElementById('optimizerCandidates').value || 2),
|
| 1017 |
temperature: Number(document.getElementById('optimizerTemp').value || 0.25),
|
| 1018 |
+
bert_stage_target: Number(document.getElementById('optimizerBertStageTarget').value || 0.70),
|
| 1019 |
optimization_mode: document.getElementById('optimizerMode').value || 'balanced',
|
| 1020 |
phrase_strategy_mode: document.getElementById('optimizerPhraseStrategy').value || 'auto'
|
| 1021 |
};
|