Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 23 additions & 3 deletions Sources/CodexLimits/ActiveTimeAvailability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ struct ActiveTimeAvailableEstimate: Equatable, Sendable {

struct ActiveTimeAvailabilitySnapshot: Equatable, Sendable {
let activeTimeThisWeek: TimeInterval
let maximumConcurrency: Int
let waitingTime: TimeInterval?
let pollingTime: TimeInterval?
let activityBreakdownReason: String?
let activeTimeCoverage: CoverageLevel
let activeTimeReason: String?
let observedInterval: DateInterval?
Expand All @@ -54,15 +58,16 @@ enum ActiveTimeWeekEvidenceBuilder {
usage: [WeeklyUsageEvidence],
facts: [LocalActivityFact],
projections: [ThreadProjection],
observation: LocalActivityObservation
observation: LocalActivityObservation,
factIndex: LocalActivityFactIndex? = nil
) -> ActiveTimeHistorySelection {
guard let currentUsage else {
return ActiveTimeHistorySelection(
evidence: [],
unavailableReason: nil
)
}
let factIndex = LocalActivityFactIndex(facts)
let factIndex = factIndex ?? LocalActivityFactIndex(facts)
var result: [ActiveTimeWeekEvidence] = []
var workloadMismatchCount = 0
for candidate in usage.sorted(
Expand All @@ -80,7 +85,10 @@ enum ActiveTimeWeekEvidenceBuilder {
continue
}
let timeline = ActivityTimelineAggregator.evaluate(
facts: factIndex.activityFacts(in: candidate.interval),
facts: factIndex.activityFacts(
in: candidate.interval,
from: facts
),
projections: projections,
interval: candidate.interval,
observation: scopedObservation(
Expand Down Expand Up @@ -168,6 +176,10 @@ enum ActiveTimeAvailabilityEngine {
static func evaluate(
currentUsage: WeeklyUsageEvidence?,
activeTimeThisWeek: TimeInterval,
maximumConcurrency: Int = 0,
waitingTime: TimeInterval? = nil,
pollingTime: TimeInterval? = nil,
activityBreakdownReason: String? = nil,
activeTimeCoverage: CoverageLevel,
activeTimeReason: String?,
history: [ActiveTimeWeekEvidence],
Expand All @@ -177,6 +189,10 @@ enum ActiveTimeAvailabilityEngine {
let unavailable: (String) -> ActiveTimeAvailabilitySnapshot = {
ActiveTimeAvailabilitySnapshot(
activeTimeThisWeek: activeTimeThisWeek,
maximumConcurrency: maximumConcurrency,
waitingTime: waitingTime,
pollingTime: pollingTime,
activityBreakdownReason: activityBreakdownReason,
activeTimeCoverage: activeTimeCoverage,
activeTimeReason: activeTimeReason,
observedInterval: currentUsage?.interval,
Expand Down Expand Up @@ -272,6 +288,10 @@ enum ActiveTimeAvailabilityEngine {
let confidence: ConfidenceLevel = caveats.isEmpty ? .high : .medium
return ActiveTimeAvailabilitySnapshot(
activeTimeThisWeek: activeTimeThisWeek,
maximumConcurrency: maximumConcurrency,
waitingTime: waitingTime,
pollingTime: pollingTime,
activityBreakdownReason: activityBreakdownReason,
activeTimeCoverage: activeTimeCoverage,
activeTimeReason: activeTimeReason,
observedInterval: currentUsage.interval,
Expand Down
50 changes: 33 additions & 17 deletions Sources/CodexLimits/ActivityTimeline.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ struct ActivityTimelineSnapshot: Equatable, Sendable {
fileprivate let observation: LocalActivityObservation
let interval: DateInterval

func updating(
interval: DateInterval,
observation: LocalActivityObservation
) -> ActivityTimelineSnapshot {
ActivityTimelineSnapshot(
turns: turns,
boundaryIssues: boundaryIssues,
observation: observation,
interval: interval
)
}

func slice(
in selectedInterval: DateInterval,
filters: WorkspaceFilters
Expand Down Expand Up @@ -304,27 +316,31 @@ enum ActivityTimelineAggregator {
start: start,
end: end
)
pendingIssues.append(
(
turnKey,
ActivityTimelineSnapshot.BoundaryIssue(
date: issueDate,
affectedStart: affectedBounds.start,
affectedEnd: affectedBounds.end,
reason: root == nil && taskID != nil
? "Task Tree metadata is missing"
: "Some Active Turn boundaries are missing or invalid",
rootTaskID: root,
projectLabel: root.flatMap {
projectionsByTask[$0]?.projectLabel
},
context: context
)
)
let issue = ActivityTimelineSnapshot.BoundaryIssue(
date: issueDate,
affectedStart: affectedBounds.start,
affectedEnd: affectedBounds.end,
reason: root == nil && taskID != nil
? "Task Tree metadata is missing"
: "Some Active Turn boundaries are missing or invalid",
rootTaskID: root,
projectLabel: root.flatMap {
projectionsByTask[$0]?.projectLabel
},
context: context
)
if issue.affects(
DateInterval(
start: interval.start,
end: .distantFuture
)
) {
pendingIssues.append((turnKey, issue))
}
}
continue
}
guard end > interval.start else { continue }
turns.append(
ActivityTimelineSnapshot.TurnInterval(
start: start,
Expand Down
88 changes: 84 additions & 4 deletions Sources/CodexLimits/AnalyticsWorkspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ enum AnalyticsGraph: String, CaseIterable, Codable, Identifiable, Sendable {

var id: String { rawValue }

static let coreCases: [AnalyticsGraph] = [
.usageRemaining,
.tokenActivity
]

var usesAccountScope: Bool {
self == .usageRemaining || self == .usagePerToken
}
Expand Down Expand Up @@ -101,6 +106,15 @@ struct AnalyticsExplorationState: Codable, Equatable, Sendable {
pinnedUsageBaselineID: nil,
pinnedUsageBaselineAccountPartitionID: nil
)

var usesLocalActivity: Bool {
usesStoredTokenActivity
}

var usesStoredTokenActivity: Bool {
section == .graphs
&& graph == .tokenActivity
}
}

@MainActor
Expand Down Expand Up @@ -133,6 +147,10 @@ final class AnalyticsWorkspaceStore: ObservableObject {
) else {
return .initial
}
guard restored.section == .graphs,
AnalyticsGraph.coreCases.contains(restored.graph) else {
return .initial
}
return restored
}

Expand Down Expand Up @@ -353,10 +371,48 @@ struct UsageChartSelection: Equatable, Sendable {
in chart: UsageChartSnapshot,
within visibleRange: DateInterval? = nil
) -> UsageChartSelection? {
candidates(in: chart)
.filter {
visibleRange?.contains($0.point.date) ?? true
}.min {
[
nearestCandidate(
in: chart.allObserved,
series: .observed,
priority: 0,
source: .account,
to: date,
within: visibleRange
),
nearestCandidate(
in: chart.currentProjection,
series: .currentEstimate,
priority: 1,
source: .derivedEstimate,
to: date,
within: visibleRange
),
nearestCandidate(
in: chart.historicalProjection,
series: .pastEstimate,
priority: 2,
source: .accountHistory,
to: date,
within: visibleRange
),
nearestCandidate(
in: chart.estimatedBackfill,
series: .estimatedBackfill,
priority: 3,
source: .tokenEstimate,
to: date,
within: visibleRange
),
nearestCandidate(
in: chart.target,
series: .target,
priority: 4,
source: .weeklyTarget,
to: date,
within: visibleRange
)
].compactMap { $0 }.min {
let leftDistance = abs($0.point.date.timeIntervalSince(date))
let rightDistance = abs($1.point.date.timeIntervalSince(date))
if leftDistance == rightDistance {
Expand All @@ -373,6 +429,30 @@ struct UsageChartSelection: Equatable, Sendable {
}
}

private static func nearestCandidate(
in points: [UsageChartPoint],
series: UsageChartSeries,
priority: Int,
source: UsageChartPointSource,
to date: Date,
within visibleRange: DateInterval?
) -> Candidate? {
guard let point = nearestPoint(
in: points,
to: date,
date: \.date,
within: visibleRange
) else {
return nil
}
return Candidate(
series: series,
point: point,
priority: priority,
source: source
)
}

private static func candidates(
in chart: UsageChartSnapshot
) -> [Candidate] {
Expand Down
Loading