package domain import ( "math" "time" ) type TaskOrigin string const ( OriginImported TaskOrigin = "imported" OriginSubdivided TaskOrigin = "subdivided" OriginExtended TaskOrigin = "extended" ) type External struct { System string `bson:"system" json:"system"` Key string `bson:"key" json:"key"` URL string `bson:"url" json:"url"` Type string `bson:"type" json:"type"` // epic | story | task (icon only) Raw map[string]any `bson:"raw" json:"-"` Orphaned bool `bson:"orphaned" json:"orphaned"` ContentHash string `bson:"contentHash" json:"-"` // change detection for upstream edits } type TaskAttachment struct { Name string `bson:"name" json:"name"` URL string `bson:"url" json:"url"` MimeType string `bson:"mimeType" json:"mimeType"` FileID string `bson:"fileId,omitempty" json:"fileId,omitempty"` } type Assignee struct { Kind string `bson:"kind" json:"kind"` // human | ai UserID string `bson:"userId,omitempty" json:"userId,omitempty"` JobID string `bson:"jobId,omitempty" json:"jobId,omitempty"` } type ClaimRequest struct { DeveloperID string `bson:"developerId" json:"developerId"` Note string `bson:"note" json:"note"` At time.Time `bson:"at" json:"at"` } type TimelineEntry struct { At time.Time `bson:"at" json:"at"` ActorID string `bson:"actorId" json:"actorId"` // user id or "system" Event string `bson:"event" json:"event"` Data map[string]any `bson:"data,omitempty" json:"data,omitempty"` } type Comment struct { ID string `bson:"_id" json:"id"` AuthorID string `bson:"authorId" json:"authorId"` Body string `bson:"body" json:"body"` // sanitized HTML At time.Time `bson:"at" json:"at"` } type TimeLogEntry struct { DeveloperID string `bson:"developerId" json:"developerId"` Minutes int `bson:"minutes" json:"minutes"` Note string `bson:"note" json:"note"` At time.Time `bson:"at" json:"at"` } // Task is the central work item (§4.4). type Task struct { ID string `bson:"_id" json:"id"` CustomerID string `bson:"customerId" json:"customerId"` ConsultantID string `bson:"consultantId" json:"consultantId"` Origin TaskOrigin `bson:"origin" json:"origin"` ParentID string `bson:"parentId,omitempty" json:"parentId,omitempty"` RootID string `bson:"rootId" json:"rootId"` External *External `bson:"external,omitempty" json:"external,omitempty"` Title string `bson:"title" json:"title"` Description string `bson:"description" json:"description"` AcceptanceCriteria []string `bson:"acceptanceCriteria" json:"acceptanceCriteria"` Attachments []TaskAttachment `bson:"attachments" json:"attachments"` Links []string `bson:"links" json:"links"` EffortCoefficient float64 `bson:"effortCoefficient" json:"effortCoefficient"` Budget float64 `bson:"budget" json:"budget"` Bounty float64 `bson:"bounty" json:"bounty"` Status TaskStatus `bson:"status" json:"status"` Assignee *Assignee `bson:"assignee,omitempty" json:"assignee,omitempty"` ClaimRequests []ClaimRequest `bson:"claimRequests" json:"claimRequests"` AtomizationNote string `bson:"atomizationNote,omitempty" json:"atomizationNote,omitempty"` Timeline []TimelineEntry `bson:"timeline" json:"timeline"` Comments []Comment `bson:"comments" json:"comments"` TimeLog []TimeLogEntry `bson:"timeLog" json:"timeLog"` PublishedAt time.Time `bson:"publishedAt,omitempty" json:"publishedAt,omitempty"` // stale-task aging (§11.19) CreatedAt time.Time `bson:"createdAt" json:"createdAt"` UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"` Version int64 `bson:"version" json:"version"` } // ComputeBounty implements §6.1: round(coefficient × budget, 2). func ComputeBounty(coefficient, budget float64) float64 { return math.Round(coefficient*budget*100) / 100 } // IsAssignedTo reports whether the human developer holds the task. func (t *Task) IsAssignedTo(userID string) bool { return t.Assignee != nil && t.Assignee.Kind == "human" && t.Assignee.UserID == userID } // HasClaimFrom reports an open claim request by the developer. func (t *Task) HasClaimFrom(developerID string) bool { for _, cr := range t.ClaimRequests { if cr.DeveloperID == developerID { return true } } return false }