Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5e88956
Carry coarse label-source provenance into Under the Hood reports
cursoragent Aug 14, 2026
816315c
Tighten UTH reportJson source to automated|manual|llm|unknown
cursoragent Aug 14, 2026
e43ac6f
Collapse unset UTH sources and map Grok to llm without a hard case
cursoragent Aug 14, 2026
a0178ea
Note that unset UTH source becomes reportJson unknown
cursoragent Aug 14, 2026
f0dd4fd
Coalesce leftover UTH source tokens with unset
cursoragent Aug 14, 2026
4185ebf
Rewrite latest-as-of UTH rows to a persistable source token
cursoragent Aug 14, 2026
50dddaf
Clamp UTH source before daily label counts
cursoragent Aug 14, 2026
d19e74b
Normalize UTH source tokens before persist and report
cursoragent Aug 14, 2026
bafda60
Avoid Scrooge .copy when rewriting latest-as-of source
cursoragent Aug 14, 2026
b29d203
Write month post-label source with named Scrooge fields
cursoragent Aug 14, 2026
6d0d4b7
Dedupe UTH reportJson post labels that clamp to the same source
cursoragent Aug 14, 2026
58445ef
Sort UTH reportJson post labels by label and source
cursoragent Aug 14, 2026
418b20c
Tighten Grok union name match for UTH llm source
cursoragent Aug 14, 2026
e3b92eb
Leave tweet-flag UTH rows with an explicit unset source
cursoragent Aug 14, 2026
4bdb34e
Merge leftover UTH post-label days instead of taking max posts
cursoragent Aug 14, 2026
90a0711
Avoid Map.flatMap when merging UTH leftover source rows
cursoragent Aug 14, 2026
5d842d5
Fix Grok class-name split: $ is regex end-of-string
cursoragent Aug 14, 2026
fb5ece6
Pattern-match UTH leftover source groups instead of _1._1
cursoragent Aug 14, 2026
1a097b1
Close postLabelSource and look up about/effect by raw id
cursoragent Aug 14, 2026
089a909
Drop older asOf UTH rows when source is rewritten
cursoragent Aug 14, 2026
988af0c
Skip trailing synthetic $ when matching UTH llm source
cursoragent Aug 14, 2026
0425294
Keep event source when an unset snapshot apply is later
cursoragent Aug 14, 2026
6a8944e
Pattern-match UTH post-label leftover group keys
cursoragent Aug 14, 2026
8db548b
Skip getSimpleName when detecting UTH llm source
cursoragent Aug 14, 2026
d98fe35
Pattern-match leftover UTH month day counts
cursoragent Aug 14, 2026
7b6ca47
Only inherit source from an unset snapshot gap-fill
cursoragent Aug 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 33 additions & 4 deletions under-the-hood/scalding/UnderTheHoodCommon.scala
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,46 @@ object UnderTheHoodCommon {
def finalThroughDay(completeThroughDay: Int, asOfDay: Int, postObservationDays: Int): Int =
math.min(completeThroughDay, addCalendarDays(asOfDay, -postObservationDays))

// Latest asOf is a full rewrite of (user, authored day, label). Keep every
// persistable source from that asOf; drop older asOf rows so a later sourced
// rewrite cannot sit beside an earlier unset row.
def latestAsOfPostLabelRows(
rows: TypedPipe[UthDailyPostLabel],
reducers: Int
): TypedPipe[UthDailyPostLabel] =
applyReducers(
rows.groupBy(r => (r.userId, r.authoredYyyymmdd, r.label)),
reducers
).reduce { (a, b) =>
if (a.asOfYyyymmdd.getOrElse(Int.MinValue) >= b.asOfYyyymmdd.getOrElse(Int.MinValue)) a
else b
}.values
).mapValueStream { values =>
val all = values.toSeq
if (all.isEmpty) Iterator.empty
else {
val latest = all.map(_.asOfYyyymmdd.getOrElse(Int.MinValue)).max
all
.filter(_.asOfYyyymmdd.getOrElse(Int.MinValue) == latest)
.groupBy(r => UthLabelSource.persistToken(r.source))
.values
.iterator
.map { sameSource =>
val r = sameSource.maxBy { x =>
(x.carried.getOrElse(0L), x.removed.getOrElse(0L))
}
UthDailyPostLabel(
userId = r.userId,
authoredYyyymmdd = r.authoredYyyymmdd,
label = r.label,
carried = r.carried,
removed = r.removed,
asOfYyyymmdd = r.asOfYyyymmdd,
observationAgeDays = r.observationAgeDays,
isFinal = r.isFinal,
postObservationDays = r.postObservationDays,
source = UthLabelSource.persistToken(r.source)
)
}
}
}.toTypedPipe
.map { case (_, row) => row }

def parseUserIds(args: Args): Set[Long] = {
val raw = args.list("userIds").flatMap(_.split(",")).map(_.trim).filter(_.nonEmpty)
Expand Down
57 changes: 28 additions & 29 deletions under-the-hood/scalding/UthDailyPostsBackfillJob.scala
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ class UthDailyPostsBackfillApp {
observationMs,
config)
.map {
case (userId, authoredDay, label, asOfDay, carried, removed) =>
case (userId, authoredDay, label, source, asOfDay, carried, removed) =>
val age = calendarDaysBetween(authoredDay, asOfDay)
UthDailyPostLabel(
userId = Some(userId),
Expand All @@ -111,7 +111,8 @@ class UthDailyPostsBackfillApp {
asOfYyyymmdd = Some(asOfDay),
observationAgeDays = Some(age),
isFinal = Some(age >= config.postObservationDays),
postObservationDays = Some(config.postObservationDays)
postObservationDays = Some(config.postObservationDays),
source = UthLabelSource.persistToken(source)
)
}

Expand All @@ -135,7 +136,8 @@ class UthDailyPostsBackfillApp {
asOfYyyymmdd = Some(authoredDay),
observationAgeDays = Some(0),
isFinal = Some(true),
postObservationDays = Some(config.postObservationDays)
postObservationDays = Some(config.postObservationDays),
source = None
)
}

Expand Down Expand Up @@ -204,10 +206,11 @@ object UthDailyPostsBackfillApp {
if (expiresStr == null || expiresStr.isEmpty) Long.MaxValue else expiresStr.toLong
if (expires <= rangeLookbackStartMs) None
else if (createdStr == null || createdStr.isEmpty)
Some((tweetId, (labelName, Long.MinValue, true, expires)))
Some((tweetId, (labelName, Long.MinValue, true, expires, None, true)))
else {
val created = createdStr.toLong
if (created < rangeEndMs) Some((tweetId, (labelName, created, true, expires)))
if (created < rangeEndMs)
Some((tweetId, (labelName, created, true, expires, None, true)))
else None
}
}
Expand Down Expand Up @@ -240,7 +243,7 @@ object UthDailyPostsBackfillApp {
rangeEndMs: Long,
observationMs: Long,
config: UthDailyPostsConfig
): TypedPipe[(Long, Int, String, Int, Long, Long)] = {
): TypedPipe[(Long, Int, String, Option[String], Int, Long, Long)] = {
val postByTweetId = applyReducers(
posts.map {
case (tweetId, userId, day, logicalId, createdMs) =>
Expand All @@ -261,7 +264,10 @@ object UthDailyPostsBackfillApp {
else scopedRows.join(postByTweetId)

val inHorizon = joined.flatMap {
case (_, ((label, eventMs, isApply, expiresMs), (logicalId, userId, day, createdMs))) =>
case (
_,
((label, eventMs, isApply, expiresMs, source, isSnapshot), (logicalId, userId, day, createdMs))
) =>
asOfDayStartsFor(createdMs, rangeStartMs, rangeEndMs, observationMs).flatMap { dStartMs =>
val dayEndMs = dStartMs + DayMs
val deadline = math.min(createdMs + observationMs, dayEndMs)
Expand All @@ -270,43 +276,36 @@ object UthDailyPostsBackfillApp {
else
UthDailyPostsApp
.actionInHorizon(eventMs, isApply, expiresMs, createdMs, deadline)
.map {
case (everApply, ts, lastApply, exp) =>
(
(logicalId, userId, day, label, yyyymmdd(dStartMs), createdMs),
(everApply, ts, lastApply, exp)
)
.map { _ =>
(
(logicalId, userId, day, label, yyyymmdd(dStartMs), createdMs),
UthDailyPostsApp.actionAggFromEvent(isApply, eventMs, expiresMs, source, isSnapshot)
)
}
}
}

val reduced = applyReducers(inHorizon.group, config.reducers).reduce { (a, b) =>
val everApply = a._1 || b._1
val last =
if (a._2 != b._2) {
if (a._2 > b._2) (a._2, a._3, a._4) else (b._2, b._3, b._4)
} else if (a._3 || b._3) {
val exp = if (a._3) a._4 else b._4
(a._2, true, exp)
} else (a._2, false, a._4)
(everApply, last._1, last._2, last._3)
}.toTypedPipe
val reduced = applyReducers(inHorizon.group, config.reducers)
.reduce(UthDailyPostsApp.mergeActionAgg)
.toTypedPipe

applyReducers(
reduced.collect {
case ((_, userId, day, label, asOfDay, createdMs), (everApply, _, lastApply, lastExpires))
if everApply =>
case (
(_, userId, day, label, asOfDay, createdMs),
(everApply, _, lastApply, lastExpires, _, source, _)
) if everApply =>
val deadline = math.min(createdMs + observationMs, yyyymmddToMs(asOfDay) + DayMs)
val removed =
if (UthDailyPostsApp
.removedAfterLastAction(lastApply, lastExpires, createdMs, deadline)) 1L
else 0L
((userId, day, label, asOfDay), (1L, removed))
((userId, day, label, UthLabelSource.persistToken(source), asOfDay), (1L, removed))
}.group,
config.reducers
).sum.toTypedPipe.map {
case ((userId, day, label, asOfDay), (carried, removed)) =>
(userId, day, label, asOfDay, carried, removed)
case ((userId, day, label, source, asOfDay), (carried, removed)) =>
(userId, day, label, source, asOfDay, carried, removed)
}
}
}
Expand Down
119 changes: 89 additions & 30 deletions under-the-hood/scalding/UthDailyPostsJob.scala
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class UthDailyPostsApp {
observationMs,
config)
.map {
case (userId, authoredDay, label, carried, removed) =>
case (userId, authoredDay, label, source, carried, removed) =>
val age = calendarDaysBetween(authoredDay, asOfDay)
UthDailyPostLabel(
userId = Some(userId),
Expand All @@ -118,7 +118,8 @@ class UthDailyPostsApp {
asOfYyyymmdd = Some(asOfDay),
observationAgeDays = Some(age),
isFinal = Some(age >= config.postObservationDays),
postObservationDays = Some(config.postObservationDays)
postObservationDays = Some(config.postObservationDays),
source = UthLabelSource.persistToken(source)
)
}

Expand All @@ -141,7 +142,8 @@ class UthDailyPostsApp {
asOfYyyymmdd = Some(asOfDay),
observationAgeDays = Some(0),
isFinal = Some(true),
postObservationDays = Some(config.postObservationDays)
postObservationDays = Some(config.postObservationDays),
source = None
)
}

Expand Down Expand Up @@ -175,7 +177,10 @@ class UthDailyPostsApp {
object UthDailyPostsApp {
import UnderTheHoodCommon._

type LabelRow = (Long, (String, Long, Boolean, Long))
// tweetId -> (label, eventMs, isApply, expiresMs, coarseSource, isSnapshot)
type LabelRow = (Long, (String, Long, Boolean, Long, Option[String], Boolean))
// everApply, lastTs, lastIsApply, lastExpires, lastApplyTs, lastApplySource, lastApplyIsSnapshot
type ActionAgg = (Boolean, Long, Boolean, Long, Long, Option[String], Boolean)

val NsfwAdminStampedLabel = "NSFW_ADMIN_STAMPED"
val TweetFlagColumns: Set[String] = Set("nsfwAdmin")
Expand Down Expand Up @@ -268,7 +273,12 @@ object UthDailyPostsApp {
val expiresMs =
if (isApply) event.label.flatMap(_.expiresAtMsec).getOrElse(Long.MaxValue)
else Long.MaxValue
Some((event.tweetId, (name, eventMs, isApply, expiresMs)))
Some(
(
event.tweetId,
(name, eventMs, isApply, expiresMs, UthLabelSource.fromEventLabel(event.label), false)
)
)
} else None
}

Expand All @@ -285,8 +295,10 @@ object UthDailyPostsApp {
else
f.createdAtMsec match {
case Some(created) =>
if (created < dayEndMs) Some((f.tweetId, (name, created, true, expires))) else None
case None => Some((f.tweetId, (name, Long.MinValue, true, expires)))
if (created < dayEndMs)
Some((f.tweetId, (name, created, true, expires, None, true)))
else None
case None => Some((f.tweetId, (name, Long.MinValue, true, expires, None, true)))
}
}

Expand All @@ -301,10 +313,12 @@ object UthDailyPostsApp {
if (expiresStr == null || expiresStr.isEmpty) Long.MaxValue else expiresStr.toLong
if (expires <= lookbackStartMs) None
else if (createdStr == null || createdStr.isEmpty)
Some((tweetId, (labelName, Long.MinValue, true, expires)))
Some((tweetId, (labelName, Long.MinValue, true, expires, None, true)))
else {
val created = createdStr.toLong
if (created < dayEndMs) Some((tweetId, (labelName, created, true, expires))) else None
if (created < dayEndMs)
Some((tweetId, (labelName, created, true, expires, None, true)))
else None
}
}

Expand All @@ -322,6 +336,52 @@ object UthDailyPostsApp {
else Some((isApply, eventMs, isApply, expiresMs))
}

private[under_the_hood] def actionAggFromEvent(
isApply: Boolean,
eventMs: Long,
expiresMs: Long,
source: Option[String],
isSnapshot: Boolean
): ActionAgg = {
val applyTs = if (isApply) eventMs else Long.MinValue
val applySrc = if (isApply) UthLabelSource.persistToken(source) else None
(isApply, eventMs, isApply, expiresMs, applyTs, applySrc, isApply && isSnapshot)
}

private[under_the_hood] def mergeActionAgg(a: ActionAgg, b: ActionAgg): ActionAgg = {
val everApply = a._1 || b._1
val last =
if (a._2 != b._2) {
if (a._2 > b._2) (a._2, a._3, a._4) else (b._2, b._3, b._4)
} else if (a._3 || b._3) {
val exp = if (a._3) a._4 else b._4
(a._2, true, exp)
} else (a._2, false, a._4)
val (aTs, aSrc, aSnap) = (a._5, a._6, a._7)
val (bTs, bSrc, bSnap) = (b._5, b._6, b._7)
val (applyTs, applySrc, applySnap) =
if (aTs != bTs) {
val (laterTs, laterSrc, laterSnap, earlierSrc) =
if (aTs > bTs) (aTs, aSrc, aSnap, bSrc) else (bTs, bSrc, bSnap, aSrc)
// Last apply wins. Fall back to the earlier persistable source only
// when the later row is an unset snapshot gap-fill, not a later event.
val src =
if (laterSrc.nonEmpty) laterSrc
else if (laterSnap) earlierSrc.orElse(laterSrc)
else laterSrc
(laterTs, src, laterSnap && src.isEmpty)
} else {
val src =
(aSnap, bSnap) match {
case (false, true) => aSrc.orElse(bSrc)
case (true, false) => bSrc.orElse(aSrc)
case _ => aSrc.orElse(bSrc)
}
(aTs, src, src.isEmpty && aSnap && bSnap)
}
(everApply, last._1, last._2, last._3, applyTs, applySrc, applySnap)
}

private[under_the_hood] def removedAfterLastAction(
lastIsApply: Boolean,
lastExpiresMs: Long,
Expand All @@ -340,7 +400,7 @@ object UthDailyPostsApp {
dayEndMs: Long,
observationMs: Long,
config: UthDailyPostsConfig
): TypedPipe[(Long, Int, String, Long, Long)] = {
): TypedPipe[(Long, Int, String, Option[String], Long, Long)] = {
val postByTweetId = posts.map {
case (tweetId, userId, day, logicalId, createdMs) =>
(tweetId, (logicalId, userId, day, createdMs))
Expand All @@ -358,39 +418,38 @@ object UthDailyPostsApp {
else scopedRows.join(postByTweetId)

val inHorizon = joined.flatMap {
case (_, ((label, eventMs, isApply, expiresMs), (logicalId, userId, day, createdMs))) =>
case (
_,
((label, eventMs, isApply, expiresMs, source, isSnapshot), (logicalId, userId, day, createdMs))
) =>
val deadline = math.min(createdMs + observationMs, dayEndMs)
actionInHorizon(eventMs, isApply, expiresMs, createdMs, deadline).map {
case (everApply, ts, lastApply, exp) =>
((logicalId, userId, day, label, createdMs), (everApply, ts, lastApply, exp))
actionInHorizon(eventMs, isApply, expiresMs, createdMs, deadline).map { _ =>
(
(logicalId, userId, day, label, createdMs),
actionAggFromEvent(isApply, eventMs, expiresMs, source, isSnapshot)
)
}
}

val reduced = applyReducers(inHorizon.group, config.reducers).reduce { (a, b) =>
val everApply = a._1 || b._1
val last =
if (a._2 != b._2) {
if (a._2 > b._2) (a._2, a._3, a._4) else (b._2, b._3, b._4)
} else if (a._3 || b._3) {
val exp = if (a._3) a._4 else b._4
(a._2, true, exp)
} else (a._2, false, a._4)
(everApply, last._1, last._2, last._3)
}.toTypedPipe
val reduced = applyReducers(inHorizon.group, config.reducers)
.reduce(mergeActionAgg)
.toTypedPipe

applyReducers(
reduced.collect {
case ((_, userId, day, label, createdMs), (everApply, _, lastApply, lastExpires))
if everApply =>
case (
(_, userId, day, label, createdMs),
(everApply, _, lastApply, lastExpires, _, source, _)
) if everApply =>
val deadline = math.min(createdMs + observationMs, dayEndMs)
val removed =
if (removedAfterLastAction(lastApply, lastExpires, createdMs, deadline)) 1L else 0L
((userId, day, label), (1L, removed))
((userId, day, label, UthLabelSource.persistToken(source)), (1L, removed))
}.group,
config.reducers
).sum.toTypedPipe.map {
case ((userId, day, label), (carried, removed)) =>
(userId, day, label, carried, removed)
case ((userId, day, label, source), (carried, removed)) =>
(userId, day, label, source, carried, removed)
}
}
}
Expand Down
Loading