Контракт scorer
00 / module api
CONSUMES
PointSceneCandidate
MetricResult[]
Candidate.hardViolations[]
Candidate.auditEvidence[]
StylePreset.metricWeights?
StylePreset.scoreThresholds?
Compo0.seed
PROVIDES
PointSceneScoreResult {
status,
localScore,
rawPositiveScore,
softPenalty,
confidence,
metricBreakdown[],
rejectedBy[],
repairHints[],
unresolvedInputs[],
comparisonKey
}
DOES NOT OWN
global.balance_score
global.negative_space_score
global.hierarchy_score
global.eye_flow_score
typography.*
imageCrop.*
posterGlobalScore
semantic correctness
Жёсткое правило: Compo 1.12 не пересчитывает метрики Compo 1.01–1.10. Он агрегирует только значения, полученные от owner-модулей, и не создаёт дублирующие spacingPenalty, balancePenalty или другие «удобные» оценки.
Что именно может входить в score
01 / canonical owners
| Источник | Собственные метрики | Использование в 1.12 |
| Compo 1.01 | point.visual_weight_base, point.visual_weight_contextual | owner должен экспортировать fitness/target deviation как MetricResult; scorer не выводит его из weight повторно |
| Compo 1.02 | point.safe_clearance_score, landmark_fit_score, anchor_fit_score, edge_relation_score, local_role_fit, local_placement_score | предпочтительно агрегировать только owner composite local_placement_score, а компоненты хранить как evidence |
| Compo 1.03 | pair geometry + pair local metrics | одна агрегированная pair metric на применимую пару/сцену |
| Compo 1.04 | cluster local metrics | cluster metric только если кластер реально существует |
| Compo 1.05 | rhythm.profile_match, interval_variation, monotonicity, trajectory_consistency, size_profile_match, rhythm.local_score | в общий score входит rhythm.local_score; компоненты — diagnostics |
| Compo 1.06 | gridFitScore + grid metric components | в общий score входит owner composite grid fit, если grid включён |
| Compo 1.07 | localConnectivityScore, localTopologyScore | только если connections/network действительно запрошены |
| Compo 1.08 | noise.count_fit, spacing_distribution, field_match, soft_intrusion_penalty, local_neighbor_pressure, noise.local_score | в общий score входит noise.local_score; компоненты — evidence |
| Compo 1.09 | point.clearance_score, contact_conflict_penalty, tangency_penalty, contact_resolution_cost | hard contact может reject; soft penalties входят один раз |
| Compo 1.10 | point.local_contrast, local_tonal_contrast, local_oklab_distance, background_complexity, color_separation_score, color_candidate_fit | в общий score предпочтительно входит color_candidate_fit; contrast используется 1.01 для weight и не должен дублироваться |
Composite-first policy: если owner уже отдаёт собственный composite score, scorer использует его как основную метрику, а внутренние компоненты не суммирует повторно. Это главный механизм защиты от double-counting.
Единый MetricResult
02 / normalized interface
CANONICAL
MetricResult {
key: string,
owner: string,
value: float, // canonical 0..1, higher = better
applicability: float, // 0..1
confidence: float, // 0..1
status: "ok|warn|fail|na",
source: "DERIVED|EMPIRICAL|HEURISTIC|STYLE_PRESET",
composite: bool,
componentOf: string|null,
evidence: object,
repairHints: []
}
Правила
if metric.status == "na":
applicability = 0
if composite == true:
descendants are diagnostic-only
if confidence == 0:
metric carries no voting weight
if value outside [0,1]:
reject metric payload
normalization belongs to OWNER
never normalize twice
Hard reject раньше score
03 / feasibility gate
REJECT
if Candidate.hardViolations.length > 0:
return {
status: "rejected",
localScore: null,
confidence: 1,
rejectedBy: hardViolations,
rankEligible: false
}
Нарушение hard-constraint нельзя компенсировать хорошим ритмом, цветом или сеткой.
SOFT ISSUE
soft penalty is allowed only if:
owner is explicit
penalty is normalized
same defect is not already in composite score
applicability > 0
evidence exists
Штраф без owner и evidence запрещён.
Applicability-aware веса
04 / dynamic weighting
baseWeight_i = weightPolicy[metric.key]
sourceConfidenceFactor_i = metric.confidence
applicabilityFactor_i = metric.applicability
styleMultiplier_i = stylePreset.metricMultiplier[metric.key] ?? 1
effectiveWeight_i =
baseWeight_i
× sourceConfidenceFactor_i
× applicabilityFactor_i
× styleMultiplier_i
W = Σ effectiveWeight_i
if W == 0:
status = "insufficient_metrics"
else:
normalizedWeight_i = effectiveWeight_i / W
1.0
applicable
Полный вклад.
0.5
partial
Половинный вклад.
0
not applicable
Метрика исключается из знаменателя.
seed
reproducible
Одинаковые inputs → одинаковый score.
Весовые коэффициенты = INITIAL_HEURISTIC / STYLE_PRESET. Они не являются законами композиции. Их задача — дать стартовую рабочую агрегацию до последующей калибровки на корпусе реальных работ.
Коррелирующие метрики
05 / anti double-counting
Плохая агрегация
score += point.local_contrast
score += color_candidate_fit
score += visual_weight_contextual
// local contrast уже повлиял на visual weight
// и может быть частью color candidate fit
// дефект получает 2–3 голоса
Правильная агрегация
PRIMARY VOTES:
visual_weight_fitness
local_placement_score
pair_or_cluster_composite
rhythm.local_score
gridFitScore
topology composite
noise.local_score
contact safety composite
color_candidate_fit
COMPONENTS:
diagnostics only
correlationPolicy:
if metric.componentOf != null:
do not vote when parent composite is present
if metric.key in dependencyGraph[anotherPrimaryMetric]:
choose owner-designated primary metric
if two primary metrics share > correlationThreshold evidence:
apply correlationGroup cap OR reduce lower-confidence vote
INITIAL_HEURISTIC:
correlationThreshold = 0.65
groupVoteCap = 1.25 × max(singleMetricWeight)
Формула локального score
06 / aggregation
positiveScore = Σ(normalizedWeight_i × metric.value_i)
softPenalty = Σ(
penalty.weight
× penalty.severity
× penalty.applicability
× penalty.confidence
)
localScoreRaw = positiveScore - softPenalty
localScore = clamp(localScoreRaw, 0, 1)
IMPORTANT:
penalties owned by metric owner only
no global metrics here
no bonus for missing metrics
no penalty for non-applicable metrics
Candidate A
localScore ≈ .89
Candidate B
localScore ≈ .70
Candidate C
grid applicability = 0; score не занижается.
Score и confidence — разные вещи
07 / epistemic quality
Scene confidence
sceneConfidence =
Σ(normalizedWeight_i × metric.confidence_i)
coverage =
applicableMetricFamiliesPresent /
applicableMetricFamiliesExpected
finalConfidence =
sceneConfidence × coverage
Почему это важно
Кандидат может иметь score 0.91, но confidence 0.43, если половина необходимых owner-модулей ещё не рассчитана. Такой вариант нельзя считать эквивалентным сцене 0.89 с confidence 0.96.
rankKey = {
feasibility,
localScore,
confidence,
correctionCost,
stableTieBreak
}
Как сравнивать кандидатов
08 / deterministic ranking
compare(A,B):
1. feasible beats rejected
2. if |A.score-B.score| > scoreEpsilon:
higher score wins
3. else if |A.confidence-B.confidence| > confidenceEpsilon:
higher confidence wins
4. else if correctionCost differs:
lower correctionCost wins
5. else:
stable deterministic tie-break by candidateId/seed
INITIAL_HEURISTIC:
scoreEpsilon = 0.015
confidenceEpsilon = 0.03
Scorer возвращает локальный порядок. Глобальный solver имеет право выбрать локально второй вариант, если тот существенно улучшает hierarchy, negative space, balance, image/text interaction или другие будущие глобальные метрики.
Scorer должен объяснять провал
09 / repair interface
Диагностика
lowestMetrics =
sort(metricBreakdown by contributionLoss)
take top 3
Не просто «score 0.57», а какие owner-модули тянут сцену вниз.
Repair hints
repairHints = mergeUnique(
lowestMetrics.repairHints,
softPenalty.repairHints
)
Scorer не придумывает repair самостоятельно — получает подсказки от владельцев.
В Compo 1.11
if score < target:
send repairHints
mark affected modules dirty
regenerate limited parameters
rescore
Так появляется настоящий corrective loop.
Детерминированные тесты
10 / regression
T01 · N/A не штрафует
grid.enabled = false
M_gridFit.applicability = 0
EXPECTED:
M_gridFit excluded from W
same other metrics → same score
as scene without grid metric
T02 · Hard reject
hardViolations = ["protected_zone"]
other metrics = all 1.0
EXPECTED:
status = rejected
localScore = null
rankEligible = false
T03 · Composite suppresses components
rhythm.local_score present
rhythm.profile_match present
rhythm.monotonicity present
EXPECTED:
only rhythm.local_score votes
components diagnostics-only
T04 · Confidence
A.score = .90 / confidence=.40
B.score = .89 / confidence=.95
scoreEpsilon=.015
EXPECTED:
score delta=.01 < epsilon
B wins by confidence
T05 · No global leakage
same point metrics
global.balance changes .2 → .9
EXPECTED:
Compo1.12.localScore unchanged
T06 · Deterministic tie
score/confidence/cost equal
candidate IDs fixed
seed fixed
EXPECTED:
repeat run → same winner
DATA FOR LAYOUT ENGINE
11 / machine contract
{
"module": "Compo 1.12",
"name": "point_scene_scoring",
"version": "2.0",
"owner_of": [
"pointSystem.local_score",
"pointSystem.local_score_confidence",
"pointSystem.local_rank_key"
],
"consumes": [
"PointSceneCandidate",
"MetricResult[]",
"Candidate.hardViolations[]",
"Candidate.auditEvidence[]",
"Compo0.StylePreset.metricWeights?",
"Compo0.seed"
],
"deferred_inputs": [
"global.balance_score",
"global.negative_space_score",
"global.hierarchy_score",
"global.eye_flow_score",
"typography.*",
"imageCrop.*",
"posterGlobalScore"
],
"metric_policy": {
"range": [0,1],
"higher_is_better": true,
"normalization_owner": "metric_owner",
"composite_first": true,
"exclude_na_from_denominator": true,
"exclude_zero_confidence": true,
"double_counting": "forbidden"
},
"aggregation": {
"positive": "sum(normalizedWeight_i * value_i)",
"softPenalty": "sum(ownerPenalty_i)",
"localScore": "clamp(positive-softPenalty,0,1)"
},
"hard_policy": {
"any_hard_violation": "REJECT",
"hard_violation_can_be_compensated": false
},
"confidence": {
"metric_weighted": true,
"coverage_factor": true,
"reported_separately_from_score": true
},
"defaults": {
"scoreEpsilon": {"value":0.015,"status":"INITIAL_HEURISTIC"},
"confidenceEpsilon": {"value":0.03,"status":"INITIAL_HEURISTIC"},
"correlationThreshold": {"value":0.65,"status":"INITIAL_HEURISTIC"}
},
"pipeline": [
"validate_metric_payloads",
"hard_reject_gate",
"select_owner_composites",
"remove_inapplicable_metrics",
"apply_confidence_and_style_weights",
"apply_correlation_policy",
"aggregate_positive_score",
"subtract_owner_soft_penalties",
"compute_confidence_and_coverage",
"build_diagnostics",
"build_repair_hints",
"build_deterministic_rank_key"
],
"finality": "LOCAL_POINT_SYSTEM_SCORE_ONLY"
}