* Aggregate costs by campaign slug. * Returns a map of campaign slug -> { sessions, total_cost, agents_spawned, total_minutes, tokens }. * Sessions with no campaign_slug are grouped under "_unattached". * * Cost priority: override_cost > real_cost > estimated_cost * * @returns {Object<string,
()
| 160 | * total_minutes:number, input_tokens:number, output_tokens:number, has_real_data:boolean}>} |
| 161 | */ |
| 162 | function readCostByCampaign() { |
| 163 | const events = readSessionCosts(); |
| 164 | const result = {}; |
| 165 | |
| 166 | for (const e of events) { |
| 167 | const slug = e.campaign_slug || '_unattached'; |
| 168 | if (!result[slug]) { |
| 169 | result[slug] = { |
| 170 | sessions: 0, total_cost: 0, agents_spawned: 0, total_minutes: 0, |
| 171 | input_tokens: 0, output_tokens: 0, has_real_data: false, |
| 172 | }; |
| 173 | } |
| 174 | const r = result[slug]; |
| 175 | r.sessions++; |
| 176 | r.total_cost += bestCost(e); |
| 177 | r.agents_spawned += (e.agent_count || 0); |
| 178 | r.total_minutes += (e.duration_minutes || 0); |
| 179 | if (typeof e.input_tokens === 'number') { |
| 180 | r.input_tokens += e.input_tokens; |
| 181 | r.output_tokens += (e.output_tokens || 0); |
| 182 | r.has_real_data = true; |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | return result; |
| 187 | } |
| 188 | |
| 189 | /** |
| 190 | * Get total spend across all campaigns. |
no test coverage detected