This commit is contained in:
Linrador 2026-06-09 15:26:06 +02:00
parent f310804b4d
commit d55c6b5854
2 changed files with 126 additions and 12 deletions

View File

@ -874,6 +874,12 @@ func mergeRatingActivitySegments(
return valid[i].Label < valid[j].Label
})
type segmentBounds struct {
start float64
end float64
ok bool
}
type activityBlock struct {
start float64
end float64
@ -881,13 +887,28 @@ func mergeRatingActivitySegments(
scoreWeight float64
labels map[string]bool
positionWeights map[string]float64
positionBounds map[string]segmentBounds
marker float64
markerWeight float64
}
var addRatingPositionWeight func(weights map[string]float64, label string, weight float64)
var addRatingPositionSignal func(
weights map[string]float64,
bounds map[string]segmentBounds,
label string,
weight float64,
start float64,
end float64,
)
addRatingPositionWeight = func(weights map[string]float64, label string, weight float64) {
addRatingPositionSignal = func(
weights map[string]float64,
bounds map[string]segmentBounds,
label string,
weight float64,
start float64,
end float64,
) {
label = strings.ToLower(strings.TrimSpace(label))
if label == "" {
return
@ -896,18 +917,45 @@ func mergeRatingActivitySegments(
if strings.HasPrefix(label, "combo:") {
raw := strings.TrimPrefix(label, "combo:")
for _, part := range strings.Split(raw, "+") {
addRatingPositionWeight(weights, part, weight)
addRatingPositionSignal(weights, bounds, part, weight, start, end)
}
return
}
normalized := normalizeRatingSignalLabel(label)
if strings.HasPrefix(normalized, "position:") {
if !strings.HasPrefix(normalized, "position:") {
return
}
if end < start {
end = start
}
weights[normalized] += weight
b := bounds[normalized]
if !b.ok {
b = segmentBounds{
start: start,
end: end,
ok: true,
}
} else {
if start < b.start {
b.start = start
}
if end > b.end {
b.end = end
}
}
effectiveRatingLabelsForBlock := func(labels map[string]bool, positionWeights map[string]float64) map[string]bool {
bounds[normalized] = b
}
effectiveRatingLabelsForBlock := func(
labels map[string]bool,
positionWeights map[string]float64,
) (map[string]bool, string) {
out := map[string]bool{}
bestPosition := ""
@ -940,7 +988,7 @@ func mergeRatingActivitySegments(
out[bestPosition] = true
}
return out
return out, bestPosition
}
segmentMarker := func(s aiSegmentMeta) float64 {
@ -981,7 +1029,16 @@ func mergeRatingActivitySegments(
}
positionWeights := map[string]float64{}
addRatingPositionWeight(positionWeights, s.Label, conf*math.Max(1, dur))
positionBounds := map[string]segmentBounds{}
addRatingPositionSignal(
positionWeights,
positionBounds,
s.Label,
conf*math.Max(1, dur),
s.StartSeconds,
s.EndSeconds,
)
marker := segmentMarker(s)
markerWeight := segmentMarkerWeight(s)
@ -993,6 +1050,7 @@ func mergeRatingActivitySegments(
scoreWeight: dur,
labels: labels,
positionWeights: positionWeights,
positionBounds: positionBounds,
marker: marker,
markerWeight: markerWeight,
}
@ -1004,7 +1062,7 @@ func mergeRatingActivitySegments(
return aiSegmentMeta{}, false
}
effectiveLabels := effectiveRatingLabelsForBlock(b.labels, b.positionWeights)
effectiveLabels, bestPosition := effectiveRatingLabelsForBlock(b.labels, b.positionWeights)
label := buildRatingComboLabel(effectiveLabels)
if label == "" {
return aiSegmentMeta{}, false
@ -1012,6 +1070,24 @@ func mergeRatingActivitySegments(
sev := ratingSegmentSeverity(label)
start := b.start
// Wenn der finale Block eine Position trägt, soll die Anzeige/der Klick
// beim ersten echten Auftreten dieser Position starten — nicht schon bei
// früheren Body-/Object-Signalen aus demselben Aktivitätsblock.
if bestPosition != "" {
if bounds, ok := b.positionBounds[bestPosition]; ok && bounds.ok {
if bounds.start > start {
start = bounds.start
}
}
}
dur = b.end - start
if dur <= 0 {
return aiSegmentMeta{}, false
}
// Kurze Segmente dürfen bleiben, wenn sie stark genug sind.
if dur < minDurationSec && sev < 0.72 {
return aiSegmentMeta{}, false
@ -1028,7 +1104,7 @@ func mergeRatingActivitySegments(
return aiSegmentMeta{
Label: label,
Score: ratingClamp01(score),
StartSeconds: b.start,
StartSeconds: start,
EndSeconds: b.end,
DurationSeconds: dur,
AutoSelected: true,
@ -1095,7 +1171,14 @@ func mergeRatingActivitySegments(
cur.scoreSum += conf * dur
cur.scoreWeight += dur
addRatingPositionWeight(cur.positionWeights, n.Label, conf*math.Max(1, dur))
addRatingPositionSignal(
cur.positionWeights,
cur.positionBounds,
n.Label,
conf*math.Max(1, dur),
n.StartSeconds,
n.EndSeconds,
)
}
nextMarkerWeight := segmentMarkerWeight(n)

View File

@ -518,7 +518,7 @@ function readMetaDurationSeconds(metaRaw: unknown): number {
return Number.isFinite(n) && n > 0 ? n : 0
}
const RATING_SEGMENT_OPEN_PREROLL_SECONDS = 3
const RATING_SEGMENT_OPEN_PREROLL_SECONDS = 0
function segmentAnchorTime(
obj: Record<string, unknown>,
@ -603,10 +603,41 @@ function segmentPreviewTime(
return undefined
}
function segmentStartTime(
obj: Record<string, unknown>,
durationSeconds: number
): number | undefined {
return firstSegmentTimeNumber(obj, [
'openAtSec',
'openSeconds',
'seekSeconds',
'seekSec',
'jumpSeconds',
'jumpSec',
'startSec',
'startSeconds',
'start_s',
'start',
'from',
'startMs',
'startRatio',
'startPercent',
'startPct',
], durationSeconds)
}
function segmentOpenTime(
obj: Record<string, unknown>,
durationSeconds: number
): number | undefined {
// Klick soll auf den Segment-Anfang springen, nicht auf den Preview-/Peak-Marker.
const start = segmentStartTime(obj, durationSeconds)
if (typeof start === 'number') {
return Math.max(0, start - RATING_SEGMENT_OPEN_PREROLL_SECONDS)
}
// Fallback für alte Daten ohne startSeconds.
const anchor =
segmentAnchorTime(obj, durationSeconds) ??
segmentPreviewTime(obj, durationSeconds)