Report Data Models

Data structures for generating time tracking reports. For rendering, see 307-svg-renderer.

Architecture

ReportData.build()  -->  SVGReportRenderer  -->  WebKitReportService
     |                        |                        |
  Filters &              Pure SVG              PDF/PNG via
  aggregates             string                WKWebView

See also: 307-svg-renderer, 310-webkit-service

ReportData

Complete data structure for generating a report.

struct ReportData: Codable, Sendable {
    let title: String
    let dateRange: String
    let totalHours: Double
    let dayItems: [ReportDayItem]         // For stacked bar chart
    let commentItems: [ReportCommentItem] // Comments with images
    let chartData: [ReportChartItem]      // Legacy tag-based chart
    let records: [ReportRecordItem]       // All filtered records
}

ReportDayItem

A day’s data for the stacked bar chart.

struct ReportDayItem: Codable, Sendable {
    let date: Date
    let totalHours: Double
    let tagSegments: [ReportDayTagSegment]
}

ReportDayTagSegment

A tag segment within a day’s bar.

struct ReportDayTagSegment: Codable, Sendable {
    let tagName: String?   // nil for untagged
    let tagColor: String?  // hex color
    let hours: Double
}

ReportCommentItem

A record’s comment for the 309-comments-section.

struct ReportCommentItem: Codable, Sendable {
    let recordId: UUID
    let date: Date
    let tagName: String?
    let tagColor: String?
    let comment: String
    let imageFilenames: [String]
}

ReportOptions

Configuration for report generation.

struct ReportOptions: Sendable {
    var dateRange: ClosedRange<Date>
    var tagIds: Set<UUID>?      // nil = all tags
    var includeUntagged: Bool   // default: true
    var title: String           // default: "Time Report"
}

ReportData.build()

Static factory method to create ReportData from raw records.

static func build(
    from records: [TimeRecord],
    tags: [Tag],
    options: ReportOptions
) -> ReportData

Filtering Logic

  1. Date Range: Records where startTime is within range
  2. Tag Filter: If tagIds set, only matching records
  3. Untagged: Excluded if includeUntagged is false

Day Items

Groups records by calendar day AND tag:

  • Segments sorted by hours descending
  • Days sorted by date ascending
  • Untagged uses gray (#888888)

Comment Items

Filters records with content:

  • Non-empty comment OR attached images
  • Sorted by date descending (newest first)

Test Coverage

62 tests covering all models, build logic, and edge cases.

Related