crewly 1.20.302 → 1.20.303
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/roles/developer/prompt.md +7 -0
- package/config/roles/fullstack-dev/prompt.md +7 -0
- package/config/roles/product-manager/prompt.md +7 -0
- package/config/skills/orchestrator/usage-categories/SKILL.md +59 -0
- package/config/skills/orchestrator/usage-categories/execute.sh +81 -0
- package/config/skills/orchestrator/usage-categories/execute.test.sh +45 -0
- package/config/skills/orchestrator/usage-report/SKILL.md +71 -0
- package/config/skills/orchestrator/usage-report/execute.sh +84 -0
- package/config/skills/orchestrator/usage-report/execute.test.sh +39 -0
- package/config/skills/team-leader/usage-report/SKILL.md +71 -0
- package/config/skills/team-leader/usage-report/execute.sh +84 -0
- package/config/skills/team-leader/usage-report/execute.test.sh +39 -0
- package/dist/backend/backend/src/constants.d.ts +11 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +11 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.controller.d.ts +36 -0
- package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.controller.js +158 -0
- package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.routes.d.ts +15 -0
- package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.routes.js +26 -0
- package/dist/backend/backend/src/controllers/usage-ledger/usage-ledger.routes.js.map +1 -0
- package/dist/backend/backend/src/index.d.ts +10 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +75 -17
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +3 -0
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/agent/follow-through.wiring.d.ts +32 -0
- package/dist/backend/backend/src/services/agent/follow-through.wiring.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/follow-through.wiring.js +42 -1
- package/dist/backend/backend/src/services/agent/follow-through.wiring.js.map +1 -1
- package/dist/backend/backend/src/services/agent/resource-mode.service.d.ts +26 -6
- package/dist/backend/backend/src/services/agent/resource-mode.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/resource-mode.service.js +59 -5
- package/dist/backend/backend/src/services/agent/resource-mode.service.js.map +1 -1
- package/dist/backend/backend/src/services/monitoring/agent-turn-state.d.ts +35 -0
- package/dist/backend/backend/src/services/monitoring/agent-turn-state.d.ts.map +1 -1
- package/dist/backend/backend/src/services/monitoring/agent-turn-state.js +101 -0
- package/dist/backend/backend/src/services/monitoring/agent-turn-state.js.map +1 -1
- package/dist/backend/backend/src/services/monitoring/background-work-watchdog.d.ts +58 -0
- package/dist/backend/backend/src/services/monitoring/background-work-watchdog.d.ts.map +1 -0
- package/dist/backend/backend/src/services/monitoring/background-work-watchdog.js +100 -0
- package/dist/backend/backend/src/services/monitoring/background-work-watchdog.js.map +1 -0
- package/dist/backend/backend/src/services/monitoring/token-usage.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/monitoring/token-usage.service.js +3 -0
- package/dist/backend/backend/src/services/monitoring/token-usage.service.js.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts +7 -0
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js +15 -0
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.d.ts +24 -0
- package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.js +63 -1
- package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.js.map +1 -1
- package/dist/backend/backend/src/services/trace/trace-context.service.d.ts +42 -2
- package/dist/backend/backend/src/services/trace/trace-context.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/trace/trace-context.service.js +66 -5
- package/dist/backend/backend/src/services/trace/trace-context.service.js.map +1 -1
- package/dist/backend/backend/src/services/trace/trace-recorder.d.ts.map +1 -1
- package/dist/backend/backend/src/services/trace/trace-recorder.js +11 -0
- package/dist/backend/backend/src/services/trace/trace-recorder.js.map +1 -1
- package/dist/backend/backend/src/services/usage-ledger/categories-param.d.ts +28 -0
- package/dist/backend/backend/src/services/usage-ledger/categories-param.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/categories-param.js +67 -0
- package/dist/backend/backend/src/services/usage-ledger/categories-param.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/excerpt.d.ts +37 -0
- package/dist/backend/backend/src/services/usage-ledger/excerpt.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/excerpt.js +49 -0
- package/dist/backend/backend/src/services/usage-ledger/excerpt.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/index.d.ts +14 -0
- package/dist/backend/backend/src/services/usage-ledger/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/index.js +14 -0
- package/dist/backend/backend/src/services/usage-ledger/index.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/ledger-hooks.d.ts +44 -0
- package/dist/backend/backend/src/services/usage-ledger/ledger-hooks.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/ledger-hooks.js +52 -0
- package/dist/backend/backend/src/services/usage-ledger/ledger-hooks.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/ledger-rollup.d.ts +100 -0
- package/dist/backend/backend/src/services/usage-ledger/ledger-rollup.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/ledger-rollup.js +189 -0
- package/dist/backend/backend/src/services/usage-ledger/ledger-rollup.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/owner-spans.d.ts +41 -0
- package/dist/backend/backend/src/services/usage-ledger/owner-spans.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/owner-spans.js +71 -0
- package/dist/backend/backend/src/services/usage-ledger/owner-spans.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/team-directory.d.ts +47 -0
- package/dist/backend/backend/src/services/usage-ledger/team-directory.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/team-directory.js +106 -0
- package/dist/backend/backend/src/services/usage-ledger/team-directory.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/unit-classifier.d.ts +101 -0
- package/dist/backend/backend/src/services/usage-ledger/unit-classifier.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/unit-classifier.js +263 -0
- package/dist/backend/backend/src/services/usage-ledger/unit-classifier.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-categories.store.d.ts +96 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-categories.store.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-categories.store.js +166 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-categories.store.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.backfill.d.ts +90 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.backfill.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.backfill.js +294 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.backfill.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.recorder.d.ts +102 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.recorder.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.recorder.js +314 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.recorder.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.runtime.d.ts +62 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.runtime.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.runtime.js +106 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.runtime.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.store.d.ts +52 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.store.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.store.js +95 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.store.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.types.d.ts +127 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.types.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.types.js +19 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-ledger.types.js.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-report.service.d.ts +124 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-report.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-report.service.js +372 -0
- package/dist/backend/backend/src/services/usage-ledger/usage-report.service.js.map +1 -0
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +11 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +11 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/agent/resource-mode.service.d.ts +26 -6
- package/dist/cli/backend/src/services/agent/resource-mode.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/resource-mode.service.js +59 -5
- package/dist/cli/backend/src/services/agent/resource-mode.service.js.map +1 -1
- package/dist/cli/backend/src/services/monitoring/agent-turn-state.d.ts +35 -0
- package/dist/cli/backend/src/services/monitoring/agent-turn-state.d.ts.map +1 -1
- package/dist/cli/backend/src/services/monitoring/agent-turn-state.js +101 -0
- package/dist/cli/backend/src/services/monitoring/agent-turn-state.js.map +1 -1
- package/dist/cli/backend/src/services/monitoring/token-usage.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/monitoring/token-usage.service.js +3 -0
- package/dist/cli/backend/src/services/monitoring/token-usage.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.d.ts +24 -0
- package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.js +63 -1
- package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.js.map +1 -1
- package/dist/cli/backend/src/services/trace/trace-context.service.d.ts +42 -2
- package/dist/cli/backend/src/services/trace/trace-context.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/trace/trace-context.service.js +66 -5
- package/dist/cli/backend/src/services/trace/trace-context.service.js.map +1 -1
- package/dist/cli/backend/src/services/trace/trace-recorder.d.ts.map +1 -1
- package/dist/cli/backend/src/services/trace/trace-recorder.js +11 -0
- package/dist/cli/backend/src/services/trace/trace-recorder.js.map +1 -1
- package/dist/cli/backend/src/services/usage-ledger/ledger-hooks.d.ts +44 -0
- package/dist/cli/backend/src/services/usage-ledger/ledger-hooks.d.ts.map +1 -0
- package/dist/cli/backend/src/services/usage-ledger/ledger-hooks.js +52 -0
- package/dist/cli/backend/src/services/usage-ledger/ledger-hooks.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which team and project an agent session belongs to, read from the team
|
|
3
|
+
* configs under CREWLY_HOME (`teams/<id>/config.json`, `projects.json`).
|
|
4
|
+
* Cached; a stale cache is refreshed in the background so hot paths stay
|
|
5
|
+
* synchronous and cheap.
|
|
6
|
+
*
|
|
7
|
+
* @module services/usage-ledger/team-directory
|
|
8
|
+
*/
|
|
9
|
+
import { promises as fs } from 'fs';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import { getCrewlyHomePath } from '../core/crewly-home.utils.js';
|
|
12
|
+
/** How long a loaded cache is trusted. */
|
|
13
|
+
const TTL_MS = 60_000;
|
|
14
|
+
/** The orchestrator's session is not in any team. */
|
|
15
|
+
const ORCHESTRATOR_SESSION = /^crewly-orc(?:$|-)/;
|
|
16
|
+
/**
|
|
17
|
+
* Reads team and project names from disk.
|
|
18
|
+
*/
|
|
19
|
+
export class TeamDirectory {
|
|
20
|
+
home;
|
|
21
|
+
now;
|
|
22
|
+
sessions = new Map();
|
|
23
|
+
projects = new Map();
|
|
24
|
+
loadedAt = 0;
|
|
25
|
+
loading = null;
|
|
26
|
+
/**
|
|
27
|
+
* @param home - Crewly home (default CREWLY_HOME)
|
|
28
|
+
* @param now - Clock (ms)
|
|
29
|
+
*/
|
|
30
|
+
constructor(home = getCrewlyHomePath(), now = () => Date.now()) {
|
|
31
|
+
this.home = home;
|
|
32
|
+
this.now = now;
|
|
33
|
+
}
|
|
34
|
+
lookup(session) {
|
|
35
|
+
if (this.now() - this.loadedAt > TTL_MS)
|
|
36
|
+
void this.refresh();
|
|
37
|
+
if (ORCHESTRATOR_SESSION.test(session))
|
|
38
|
+
return { team: 'Orchestrator' };
|
|
39
|
+
return this.sessions.get(session) ?? null;
|
|
40
|
+
}
|
|
41
|
+
projectName(projectId) {
|
|
42
|
+
return this.projects.get(projectId);
|
|
43
|
+
}
|
|
44
|
+
async ready() {
|
|
45
|
+
if (this.loadedAt === 0)
|
|
46
|
+
await this.refresh();
|
|
47
|
+
else if (this.loading)
|
|
48
|
+
await this.loading;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Reload the cache (one load at a time).
|
|
52
|
+
*/
|
|
53
|
+
refresh() {
|
|
54
|
+
if (!this.loading) {
|
|
55
|
+
this.loading = this.load()
|
|
56
|
+
.catch(() => undefined)
|
|
57
|
+
.finally(() => {
|
|
58
|
+
this.loading = null;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return this.loading;
|
|
62
|
+
}
|
|
63
|
+
async load() {
|
|
64
|
+
const projects = new Map();
|
|
65
|
+
try {
|
|
66
|
+
const list = JSON.parse(await fs.readFile(path.join(this.home, 'projects.json'), 'utf-8'));
|
|
67
|
+
for (const p of Array.isArray(list) ? list : [])
|
|
68
|
+
if (p?.id && p.name)
|
|
69
|
+
projects.set(p.id, p.name);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// no projects file
|
|
73
|
+
}
|
|
74
|
+
const sessions = new Map();
|
|
75
|
+
try {
|
|
76
|
+
const teamsDir = path.join(this.home, 'teams');
|
|
77
|
+
for (const entry of await fs.readdir(teamsDir, { withFileTypes: true })) {
|
|
78
|
+
if (!entry.isDirectory())
|
|
79
|
+
continue;
|
|
80
|
+
try {
|
|
81
|
+
const team = JSON.parse(await fs.readFile(path.join(teamsDir, entry.name, 'config.json'), 'utf-8'));
|
|
82
|
+
if (!team?.name)
|
|
83
|
+
continue;
|
|
84
|
+
const project = team.projectIds?.map((id) => projects.get(id)).find((n) => !!n);
|
|
85
|
+
const info = { team: team.name, ...(project ? { project } : {}) };
|
|
86
|
+
for (const m of team.members ?? []) {
|
|
87
|
+
if (m.sessionName)
|
|
88
|
+
sessions.set(m.sessionName, info);
|
|
89
|
+
if (m.agentId)
|
|
90
|
+
sessions.set(m.agentId, info);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// unreadable team
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// no teams dir
|
|
100
|
+
}
|
|
101
|
+
this.sessions = sessions;
|
|
102
|
+
this.projects = projects;
|
|
103
|
+
this.loadedAt = this.now();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=team-directory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"team-directory.js","sourceRoot":"","sources":["../../../../../../backend/src/services/usage-ledger/team-directory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Puts tickets into categories at report time.
|
|
3
|
+
*
|
|
4
|
+
* Order of attempts, cheapest first:
|
|
5
|
+
* 1. Deterministic: keywords (or the comma-separated phrases of the
|
|
6
|
+
* description), team and project hints.
|
|
7
|
+
* 2. Only for tickets nothing matched, and only when a model is available: one
|
|
8
|
+
* small batched question to the default cheap model.
|
|
9
|
+
* 3. Otherwise `other`.
|
|
10
|
+
*
|
|
11
|
+
* With no definitions the category is the ticket's team (else its project).
|
|
12
|
+
*
|
|
13
|
+
* Results are cached per (category-set id, ticket); a fallback `other` that
|
|
14
|
+
* came from having no model is not cached, so it is retried later. The cache
|
|
15
|
+
* lives beside the ledger and is never part of the ticket facts.
|
|
16
|
+
*
|
|
17
|
+
* @module services/usage-ledger/unit-classifier
|
|
18
|
+
*/
|
|
19
|
+
import { type CategoryDef } from './usage-categories.store.js';
|
|
20
|
+
/** Category of tickets that match nothing. */
|
|
21
|
+
export declare const OTHER_CATEGORY = "other";
|
|
22
|
+
/** Category of usage that belongs to no ticket. */
|
|
23
|
+
export declare const NO_TICKET_CATEGORY = "(no ticket)";
|
|
24
|
+
/** File with the classification cache. */
|
|
25
|
+
export declare const CLASSIFICATION_CACHE_FILE = "classification-cache.json";
|
|
26
|
+
/** What the classifier reads about a ticket. */
|
|
27
|
+
export interface ClassifiableUnit {
|
|
28
|
+
unitId: string;
|
|
29
|
+
ticketId?: string;
|
|
30
|
+
title?: string;
|
|
31
|
+
excerpt?: string;
|
|
32
|
+
team?: string;
|
|
33
|
+
project?: string;
|
|
34
|
+
}
|
|
35
|
+
/** Asks a small model one question; null when none is available or it failed. */
|
|
36
|
+
export type CategoryLlm = (prompt: string) => Promise<string | null>;
|
|
37
|
+
/** Where tickets ended up. */
|
|
38
|
+
export interface ClassifyResult {
|
|
39
|
+
/** Ticket → category */
|
|
40
|
+
categories: Map<string, string>;
|
|
41
|
+
/** Tickets answered by the model in this call */
|
|
42
|
+
llmClassified: number;
|
|
43
|
+
/** Tickets that matched nothing and had no model to ask */
|
|
44
|
+
unclassified: number;
|
|
45
|
+
}
|
|
46
|
+
/** Default classification (no user categories): the ticket's team, else its project. */
|
|
47
|
+
export declare function defaultCategory(unit: ClassifiableUnit): string;
|
|
48
|
+
/**
|
|
49
|
+
* Keywords of a definition: its explicit keywords, else the comma / semicolon
|
|
50
|
+
* separated phrases of its description (so "Xiaohongshu, Substack, LinkedIn"
|
|
51
|
+
* works as typed).
|
|
52
|
+
*
|
|
53
|
+
* @param def - Category definition
|
|
54
|
+
* @returns Lower-case keywords
|
|
55
|
+
*/
|
|
56
|
+
export declare function keywordsOf(def: CategoryDef): string[];
|
|
57
|
+
/**
|
|
58
|
+
* Best category by keyword / team / project hints.
|
|
59
|
+
*
|
|
60
|
+
* @param unit - The ticket
|
|
61
|
+
* @param defs - Definitions
|
|
62
|
+
* @returns Category name, or null when nothing matched
|
|
63
|
+
*/
|
|
64
|
+
export declare function matchCategory(unit: ClassifiableUnit, defs: CategoryDef[]): string | null;
|
|
65
|
+
/**
|
|
66
|
+
* Classifies tickets for reports.
|
|
67
|
+
*/
|
|
68
|
+
export declare class UnitClassifier {
|
|
69
|
+
private readonly llm;
|
|
70
|
+
private readonly cacheFile;
|
|
71
|
+
private cache;
|
|
72
|
+
/**
|
|
73
|
+
* @param dir - Ledger directory
|
|
74
|
+
* @param llm - Optional model (null: deterministic only)
|
|
75
|
+
*/
|
|
76
|
+
constructor(dir: string, llm?: CategoryLlm | null);
|
|
77
|
+
private loadCache;
|
|
78
|
+
/**
|
|
79
|
+
* Classify tickets.
|
|
80
|
+
*
|
|
81
|
+
* @param units - Tickets (never the "(no ticket)" pseudo-units)
|
|
82
|
+
* @param defs - The user's categories; empty means the default (team / project)
|
|
83
|
+
* @returns Category per ticket
|
|
84
|
+
*/
|
|
85
|
+
classify(units: ClassifiableUnit[], defs: CategoryDef[]): Promise<ClassifyResult>;
|
|
86
|
+
private saveCache;
|
|
87
|
+
/**
|
|
88
|
+
* Ask the model about tickets nothing matched.
|
|
89
|
+
*
|
|
90
|
+
* @returns Ticket → category (only tickets the model answered validly)
|
|
91
|
+
*/
|
|
92
|
+
private askModel;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The default cheap model: a small Anthropic model when `ANTHROPIC_API_KEY`
|
|
96
|
+
* is set; none otherwise.
|
|
97
|
+
*
|
|
98
|
+
* @returns A question function, or null
|
|
99
|
+
*/
|
|
100
|
+
export declare function defaultCategoryLlm(): CategoryLlm | null;
|
|
101
|
+
//# sourceMappingURL=unit-classifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unit-classifier.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/usage-ledger/unit-classifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAE9E,8CAA8C;AAC9C,eAAO,MAAM,cAAc,UAAU,CAAC;AACtC,mDAAmD;AACnD,eAAO,MAAM,kBAAkB,gBAAgB,CAAC;AAEhD,0CAA0C;AAC1C,eAAO,MAAM,yBAAyB,8BAA8B,CAAC;AAQrE,gDAAgD;AAChD,MAAM,WAAW,gBAAgB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,iFAAiF;AACjF,MAAM,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAErE,8BAA8B;AAC9B,MAAM,WAAW,cAAc;IAC9B,wBAAwB;IACxB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,iDAAiD;IACjD,aAAa,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAC;CACrB;AAMD,wFAAwF;AACxF,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAE9D;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,EAAE,CAUrD;AAaD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,MAAM,GAAG,IAAI,CAaxF;AAED;;GAEG;AACH,qBAAa,cAAc;IAUzB,OAAO,CAAC,QAAQ,CAAC,GAAG;IATrB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,KAAK,CAA0B;IAEvC;;;OAGG;gBAEF,GAAG,EAAE,MAAM,EACM,GAAG,GAAE,WAAW,GAAG,IAAW;YAKlC,SAAS;IAWvB;;;;;;OAMG;IACG,QAAQ,CAAC,KAAK,EAAE,gBAAgB,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC;YA2CzE,SAAS;IAcvB;;;;OAIG;YACW,QAAQ;CAsCtB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,IAAI,WAAW,GAAG,IAAI,CAcvD"}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Puts tickets into categories at report time.
|
|
3
|
+
*
|
|
4
|
+
* Order of attempts, cheapest first:
|
|
5
|
+
* 1. Deterministic: keywords (or the comma-separated phrases of the
|
|
6
|
+
* description), team and project hints.
|
|
7
|
+
* 2. Only for tickets nothing matched, and only when a model is available: one
|
|
8
|
+
* small batched question to the default cheap model.
|
|
9
|
+
* 3. Otherwise `other`.
|
|
10
|
+
*
|
|
11
|
+
* With no definitions the category is the ticket's team (else its project).
|
|
12
|
+
*
|
|
13
|
+
* Results are cached per (category-set id, ticket); a fallback `other` that
|
|
14
|
+
* came from having no model is not cached, so it is retried later. The cache
|
|
15
|
+
* lives beside the ledger and is never part of the ticket facts.
|
|
16
|
+
*
|
|
17
|
+
* @module services/usage-ledger/unit-classifier
|
|
18
|
+
*/
|
|
19
|
+
import { promises as fs } from 'fs';
|
|
20
|
+
import * as path from 'path';
|
|
21
|
+
import { atomicWriteJson } from '../../utils/file-io.utils.js';
|
|
22
|
+
import { categorySetId } from './usage-categories.store.js';
|
|
23
|
+
/** Category of tickets that match nothing. */
|
|
24
|
+
export const OTHER_CATEGORY = 'other';
|
|
25
|
+
/** Category of usage that belongs to no ticket. */
|
|
26
|
+
export const NO_TICKET_CATEGORY = '(no ticket)';
|
|
27
|
+
/** File with the classification cache. */
|
|
28
|
+
export const CLASSIFICATION_CACHE_FILE = 'classification-cache.json';
|
|
29
|
+
/** Sets kept in the cache (oldest dropped). */
|
|
30
|
+
const MAX_CACHED_SETS = 6;
|
|
31
|
+
/** Tickets sent to the model in one call, and calls per report. */
|
|
32
|
+
const LLM_BATCH = 25;
|
|
33
|
+
const LLM_MAX_CALLS = 2;
|
|
34
|
+
/** Default classification (no user categories): the ticket's team, else its project. */
|
|
35
|
+
export function defaultCategory(unit) {
|
|
36
|
+
return unit.team?.trim() || unit.project?.trim() || OTHER_CATEGORY;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Keywords of a definition: its explicit keywords, else the comma / semicolon
|
|
40
|
+
* separated phrases of its description (so "Xiaohongshu, Substack, LinkedIn"
|
|
41
|
+
* works as typed).
|
|
42
|
+
*
|
|
43
|
+
* @param def - Category definition
|
|
44
|
+
* @returns Lower-case keywords
|
|
45
|
+
*/
|
|
46
|
+
export function keywordsOf(def) {
|
|
47
|
+
const explicit = def.keywords ?? [];
|
|
48
|
+
const phrases = explicit.length > 0
|
|
49
|
+
? []
|
|
50
|
+
: (def.description ?? '')
|
|
51
|
+
.split(/[,;,;、\n]+/)
|
|
52
|
+
.map((p) => p.trim())
|
|
53
|
+
.filter((p) => p.length >= 2 && p.length <= 40);
|
|
54
|
+
return [...new Set([...explicit, ...phrases].map((k) => k.toLowerCase()))];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Whether a lower-case text contains a keyword. Short ASCII keywords must
|
|
58
|
+
* match whole words ("ai" must not match "email").
|
|
59
|
+
*/
|
|
60
|
+
function hasKeyword(text, keyword) {
|
|
61
|
+
if (/^[a-z0-9 ]+$/.test(keyword) && keyword.length <= 3) {
|
|
62
|
+
return new RegExp(`(^|[^a-z0-9])${keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([^a-z0-9]|$)`).test(text);
|
|
63
|
+
}
|
|
64
|
+
return text.includes(keyword);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Best category by keyword / team / project hints.
|
|
68
|
+
*
|
|
69
|
+
* @param unit - The ticket
|
|
70
|
+
* @param defs - Definitions
|
|
71
|
+
* @returns Category name, or null when nothing matched
|
|
72
|
+
*/
|
|
73
|
+
export function matchCategory(unit, defs) {
|
|
74
|
+
const text = [unit.title, unit.excerpt, unit.ticketId, unit.team, unit.project].filter(Boolean).join(' \n ').toLowerCase();
|
|
75
|
+
const team = (unit.team ?? '').toLowerCase();
|
|
76
|
+
const project = (unit.project ?? '').toLowerCase();
|
|
77
|
+
let best = null;
|
|
78
|
+
for (const def of defs) {
|
|
79
|
+
let score = 0;
|
|
80
|
+
for (const k of keywordsOf(def))
|
|
81
|
+
if (hasKeyword(text, k))
|
|
82
|
+
score += 1;
|
|
83
|
+
if (team && def.teams?.some((t) => t.toLowerCase() === team))
|
|
84
|
+
score += 2;
|
|
85
|
+
if (project && def.projects?.some((p) => p.toLowerCase() === project))
|
|
86
|
+
score += 2;
|
|
87
|
+
if (score > 0 && (!best || score > best.score))
|
|
88
|
+
best = { name: def.name, score };
|
|
89
|
+
}
|
|
90
|
+
return best?.name ?? null;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Classifies tickets for reports.
|
|
94
|
+
*/
|
|
95
|
+
export class UnitClassifier {
|
|
96
|
+
llm;
|
|
97
|
+
cacheFile;
|
|
98
|
+
cache = null;
|
|
99
|
+
/**
|
|
100
|
+
* @param dir - Ledger directory
|
|
101
|
+
* @param llm - Optional model (null: deterministic only)
|
|
102
|
+
*/
|
|
103
|
+
constructor(dir, llm = null) {
|
|
104
|
+
this.llm = llm;
|
|
105
|
+
this.cacheFile = path.join(dir, CLASSIFICATION_CACHE_FILE);
|
|
106
|
+
}
|
|
107
|
+
async loadCache() {
|
|
108
|
+
if (this.cache)
|
|
109
|
+
return this.cache;
|
|
110
|
+
try {
|
|
111
|
+
const parsed = JSON.parse(await fs.readFile(this.cacheFile, 'utf-8'));
|
|
112
|
+
this.cache = parsed && typeof parsed.sets === 'object' && parsed.sets ? parsed : { sets: {} };
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
this.cache = { sets: {} };
|
|
116
|
+
}
|
|
117
|
+
return this.cache;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Classify tickets.
|
|
121
|
+
*
|
|
122
|
+
* @param units - Tickets (never the "(no ticket)" pseudo-units)
|
|
123
|
+
* @param defs - The user's categories; empty means the default (team / project)
|
|
124
|
+
* @returns Category per ticket
|
|
125
|
+
*/
|
|
126
|
+
async classify(units, defs) {
|
|
127
|
+
const categories = new Map();
|
|
128
|
+
if (defs.length === 0) {
|
|
129
|
+
for (const u of units)
|
|
130
|
+
categories.set(u.unitId, defaultCategory(u));
|
|
131
|
+
return { categories, llmClassified: 0, unclassified: 0 };
|
|
132
|
+
}
|
|
133
|
+
const setId = categorySetId(defs);
|
|
134
|
+
const cache = await this.loadCache();
|
|
135
|
+
const cached = cache.sets[setId] ?? {};
|
|
136
|
+
const fresh = {};
|
|
137
|
+
const unmatched = [];
|
|
138
|
+
for (const u of units) {
|
|
139
|
+
const known = cached[u.unitId];
|
|
140
|
+
if (known) {
|
|
141
|
+
categories.set(u.unitId, known);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const hit = matchCategory(u, defs);
|
|
145
|
+
if (hit) {
|
|
146
|
+
categories.set(u.unitId, hit);
|
|
147
|
+
fresh[u.unitId] = hit;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
unmatched.push(u);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
let llmClassified = 0;
|
|
154
|
+
let unclassified = 0;
|
|
155
|
+
const answers = this.llm ? await this.askModel(unmatched, defs) : new Map();
|
|
156
|
+
for (const u of unmatched) {
|
|
157
|
+
const answer = answers.get(u.unitId);
|
|
158
|
+
if (answer) {
|
|
159
|
+
categories.set(u.unitId, answer);
|
|
160
|
+
fresh[u.unitId] = answer;
|
|
161
|
+
if (answer !== OTHER_CATEGORY)
|
|
162
|
+
llmClassified += 1;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
categories.set(u.unitId, OTHER_CATEGORY);
|
|
166
|
+
unclassified += 1;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (Object.keys(fresh).length > 0)
|
|
170
|
+
await this.saveCache(setId, fresh);
|
|
171
|
+
return { categories, llmClassified, unclassified };
|
|
172
|
+
}
|
|
173
|
+
async saveCache(setId, fresh) {
|
|
174
|
+
const cache = await this.loadCache();
|
|
175
|
+
const merged = { ...(cache.sets[setId] ?? {}), ...fresh };
|
|
176
|
+
delete cache.sets[setId];
|
|
177
|
+
cache.sets[setId] = merged;
|
|
178
|
+
for (const key of Object.keys(cache.sets).slice(0, Math.max(0, Object.keys(cache.sets).length - MAX_CACHED_SETS)))
|
|
179
|
+
delete cache.sets[key];
|
|
180
|
+
try {
|
|
181
|
+
await fs.mkdir(path.dirname(this.cacheFile), { recursive: true });
|
|
182
|
+
await atomicWriteJson(this.cacheFile, cache);
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
// the cache is an optimisation
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Ask the model about tickets nothing matched.
|
|
190
|
+
*
|
|
191
|
+
* @returns Ticket → category (only tickets the model answered validly)
|
|
192
|
+
*/
|
|
193
|
+
async askModel(units, defs) {
|
|
194
|
+
const out = new Map();
|
|
195
|
+
if (!this.llm || units.length === 0)
|
|
196
|
+
return out;
|
|
197
|
+
const names = new Map(defs.map((d) => [d.name.toLowerCase(), d.name]));
|
|
198
|
+
const catalogue = defs.map((d) => `- ${d.name}${d.description ? `: ${d.description}` : ''}`).join('\n');
|
|
199
|
+
for (let call = 0; call < LLM_MAX_CALLS; call++) {
|
|
200
|
+
const batch = units.slice(call * LLM_BATCH, (call + 1) * LLM_BATCH);
|
|
201
|
+
if (batch.length === 0)
|
|
202
|
+
break;
|
|
203
|
+
const list = batch
|
|
204
|
+
.map((u, i) => `${i + 1}. ${[u.title, u.excerpt && u.excerpt !== u.title ? u.excerpt.slice(0, 160) : '', u.team ? `(team ${u.team})` : ''].filter(Boolean).join(' | ')}`)
|
|
205
|
+
.join('\n');
|
|
206
|
+
const prompt = `Assign each work item to exactly one category, or "${OTHER_CATEGORY}" if none fits.\n\nCategories:\n${catalogue}\n\nWork items:\n${list}\n\n` +
|
|
207
|
+
`Reply with only a JSON object mapping the item number to the category name, for example {"1":"${defs[0].name}","2":"${OTHER_CATEGORY}"}.`;
|
|
208
|
+
let reply = null;
|
|
209
|
+
try {
|
|
210
|
+
reply = await this.llm(prompt);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
reply = null;
|
|
214
|
+
}
|
|
215
|
+
if (!reply)
|
|
216
|
+
break;
|
|
217
|
+
const json = /\{[\s\S]*\}/.exec(reply)?.[0];
|
|
218
|
+
if (!json)
|
|
219
|
+
continue;
|
|
220
|
+
try {
|
|
221
|
+
const parsed = JSON.parse(json);
|
|
222
|
+
batch.forEach((u, i) => {
|
|
223
|
+
const value = parsed[String(i + 1)];
|
|
224
|
+
if (typeof value !== 'string')
|
|
225
|
+
return;
|
|
226
|
+
const name = value.trim().toLowerCase();
|
|
227
|
+
if (name === OTHER_CATEGORY)
|
|
228
|
+
out.set(u.unitId, OTHER_CATEGORY);
|
|
229
|
+
else if (names.has(name))
|
|
230
|
+
out.set(u.unitId, names.get(name));
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
// unparseable answer: those tickets stay unclassified
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return out;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* The default cheap model: a small Anthropic model when `ANTHROPIC_API_KEY`
|
|
242
|
+
* is set; none otherwise.
|
|
243
|
+
*
|
|
244
|
+
* @returns A question function, or null
|
|
245
|
+
*/
|
|
246
|
+
export function defaultCategoryLlm() {
|
|
247
|
+
if (!process.env.ANTHROPIC_API_KEY)
|
|
248
|
+
return null;
|
|
249
|
+
return async (prompt) => {
|
|
250
|
+
const { generateText } = await import('ai');
|
|
251
|
+
const { anthropic } = await import('@ai-sdk/anthropic');
|
|
252
|
+
const controller = new AbortController();
|
|
253
|
+
const timer = setTimeout(() => controller.abort(), 30_000);
|
|
254
|
+
try {
|
|
255
|
+
const result = await generateText({ model: anthropic('claude-haiku-4-5'), prompt, abortSignal: controller.signal, maxOutputTokens: 600 });
|
|
256
|
+
return result.text;
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
clearTimeout(timer);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
//# sourceMappingURL=unit-classifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unit-classifier.js","sourceRoot":"","sources":["../../../../../../backend/src/services/usage-ledger/unit-classifier.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The owner's own categories for usage reports ("self-media", "book", …).
|
|
3
|
+
*
|
|
4
|
+
* Categories are optional and never written into ticket facts: they only
|
|
5
|
+
* decide how a report groups tickets. Each definition has a natural-language
|
|
6
|
+
* description and optional hints (keywords, team names, project names). The
|
|
7
|
+
* set carries a version that changes whenever a definition changes; the
|
|
8
|
+
* classification cache is keyed on it.
|
|
9
|
+
*
|
|
10
|
+
* @module services/usage-ledger/usage-categories.store
|
|
11
|
+
*/
|
|
12
|
+
/** File holding the definitions. */
|
|
13
|
+
export declare const CATEGORIES_FILE = "categories.json";
|
|
14
|
+
/** One user-defined category. */
|
|
15
|
+
export interface CategoryDef {
|
|
16
|
+
/** Short label shown in reports */
|
|
17
|
+
name: string;
|
|
18
|
+
/** What belongs here, in plain words (also read by the language model) */
|
|
19
|
+
description?: string;
|
|
20
|
+
/** Words or phrases that put a ticket here (case-insensitive) */
|
|
21
|
+
keywords?: string[];
|
|
22
|
+
/** Team names that belong here */
|
|
23
|
+
teams?: string[];
|
|
24
|
+
/** Project names that belong here */
|
|
25
|
+
projects?: string[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Validate and normalise a definition.
|
|
29
|
+
*
|
|
30
|
+
* @param input - Candidate
|
|
31
|
+
* @returns The definition, or an error text
|
|
32
|
+
*/
|
|
33
|
+
export declare function normalizeCategory(input: unknown): CategoryDef | string;
|
|
34
|
+
/**
|
|
35
|
+
* Identity of a set of definitions: the same definitions always give the same
|
|
36
|
+
* id, so a classification cache survives a set being re-entered unchanged.
|
|
37
|
+
*
|
|
38
|
+
* @param defs - Definitions
|
|
39
|
+
* @returns `default` for none, else a short hash
|
|
40
|
+
*/
|
|
41
|
+
export declare function categorySetId(defs: CategoryDef[]): string;
|
|
42
|
+
/**
|
|
43
|
+
* File-backed category definitions.
|
|
44
|
+
*/
|
|
45
|
+
export declare class UsageCategoriesStore {
|
|
46
|
+
readonly dir: string;
|
|
47
|
+
private readonly file;
|
|
48
|
+
/**
|
|
49
|
+
* @param dir - Ledger directory (default `~/.crewly/usage-ledger`)
|
|
50
|
+
*/
|
|
51
|
+
constructor(dir?: string);
|
|
52
|
+
private read;
|
|
53
|
+
private write;
|
|
54
|
+
/**
|
|
55
|
+
* The current definitions.
|
|
56
|
+
*
|
|
57
|
+
* @returns Version, set id and definitions
|
|
58
|
+
*/
|
|
59
|
+
list(): Promise<{
|
|
60
|
+
version: number;
|
|
61
|
+
setId: string;
|
|
62
|
+
updatedAt: string;
|
|
63
|
+
categories: CategoryDef[];
|
|
64
|
+
}>;
|
|
65
|
+
/**
|
|
66
|
+
* Add or replace one category (matched by name, case-insensitive).
|
|
67
|
+
*
|
|
68
|
+
* @param input - The definition
|
|
69
|
+
* @returns The new state, or an error text
|
|
70
|
+
*/
|
|
71
|
+
set(input: unknown): Promise<{
|
|
72
|
+
version: number;
|
|
73
|
+
categories: CategoryDef[];
|
|
74
|
+
} | string>;
|
|
75
|
+
/**
|
|
76
|
+
* Replace every definition.
|
|
77
|
+
*
|
|
78
|
+
* @param inputs - The new set (empty clears it)
|
|
79
|
+
* @returns The new state, or an error text
|
|
80
|
+
*/
|
|
81
|
+
replaceAll(inputs: unknown[]): Promise<{
|
|
82
|
+
version: number;
|
|
83
|
+
categories: CategoryDef[];
|
|
84
|
+
} | string>;
|
|
85
|
+
/**
|
|
86
|
+
* Remove one category.
|
|
87
|
+
*
|
|
88
|
+
* @param name - Its name (case-insensitive)
|
|
89
|
+
* @returns The new state, or null when there was no such category
|
|
90
|
+
*/
|
|
91
|
+
remove(name: string): Promise<{
|
|
92
|
+
version: number;
|
|
93
|
+
categories: CategoryDef[];
|
|
94
|
+
} | null>;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=usage-categories.store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage-categories.store.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/usage-ledger/usage-categories.store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AASH,oCAAoC;AACpC,eAAO,MAAM,eAAe,oBAAoB,CAAC;AAEjD,iCAAiC;AACjC,MAAM,WAAW,WAAW;IAC3B,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,qCAAqC;IACrC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AA0BD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,GAAG,MAAM,CAiBtE;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,MAAM,CAIzD;AAED;;GAEG;AACH,qBAAa,oBAAoB;IAMpB,QAAQ,CAAC,GAAG,EAAE,MAAM;IALhC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAE9B;;OAEG;gBACkB,GAAG,GAAE,MAAwD;YAIpE,IAAI;YAYJ,KAAK;IAOnB;;;;OAIG;IACG,IAAI,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,WAAW,EAAE,CAAA;KAAE,CAAC;IAKvG;;;;;OAKG;IACG,GAAG,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,WAAW,EAAE,CAAA;KAAE,GAAG,MAAM,CAAC;IAU3F;;;;;OAKG;IACG,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,WAAW,EAAE,CAAA;KAAE,GAAG,MAAM,CAAC;IAYrG;;;;;OAKG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,WAAW,EAAE,CAAA;KAAE,GAAG,IAAI,CAAC;CAO1F"}
|