import { ClsService } from "nestjs-cls"; import { AbstractRepository } from "../../../core/neo4j/abstracts/abstract.repository"; import { Neo4jService } from "../../../core/neo4j/services/neo4j.service"; import { SecurityService } from "../../../core/security/services/security.service"; import { TokenUsageDimension, TokenUsageTargetLabel } from "../common/tokenusage.target-labels"; import { TokenUsageAdminBreakdownEntity } from "../entities/tokenusage-admin-breakdown"; import { TokenUsageAdminSummaryEntity } from "../entities/tokenusage-admin-summary"; import { TokenUsageAdminTimelineEntity } from "../entities/tokenusage-admin-timeline"; import { TokenUsage, TokenUsageDescriptor } from "../entities/tokenusage"; /** * Cross-tenant reporting queries behind /tokenusages/administration/*. * * Extends AbstractRepository for the shared Neo4j/security/CLS plumbing and the * descriptor typing; none of the inherited finders are used, for the reasons in * the file header, and it deliberately does NOT override onModuleInit — the * :TokenUsage indexes belong to TokenUsageRepository. * * EVERY traversal constrains its target label — `->(:Company)`, `->(:User)`. * The write bug fixed in tokenusage.repository.ts left 12,237 label-less nodes * on exactly these two relationships; an unconstrained match returns them. */ export declare class TokenUsageAdminRepository extends AbstractRepository { private readonly targetLabels; protected readonly descriptor: import("../../..").EntityDescriptor; constructor(neo4j: Neo4jService, securityService: SecurityService, clsService: ClsService, targetLabels?: TokenUsageTargetLabel[]); /** * Six rows: {customer, platform, total} × {current, previous}. The previous * window is the equal-length span immediately preceding `from`, which is what * the KPI tiles' deltas are computed against. */ findSummary(params: { from: string; to: string; companyId?: string; }): Promise; findTimeline(params: { from: string; to: string; granularity: "day" | "week" | "month"; stackBy: "scope" | "type" | "company"; companyId?: string; }): Promise; /** * Ranked rows, descending by cost, truncated to `limit` with the exact * remainder folded into a single "other" row. The full ordered set is fetched * and sliced in JS so the remainder is exact rather than a second query's * approximation. Above ~1000 companies, move the LIMIT into Cypher and take * the remainder from findSummary() instead. */ findBreakdown(params: { from: string; to: string; dimension: TokenUsageDimension; targetLabel?: string; scope: "customer" | "platform"; companyId?: string; limit: number; }): Promise; /** Scalar rollup split by cost centre for one window. */ private _scopeTotals; /** * Guarantees both cost centres are present (zero-filled when a window has no * rows) and appends their sum as the "total" scope, so the tiles never have to * branch on a missing row. */ private _withTotal; /** * The query seed every aggregation in this file MUST use. * * It deliberately does NOT call `Neo4jService.initQuery()`. initQuery() * PREPENDS `MATCH (company:Company {id: $companyId})` (plus a currentUser * match hanging off it) whenever the CALLER's CLS session carries a * `companyId` — the same CLS scoping the file header explains this dashboard * must not have, reintroduced through a less obvious door than * buildDefaultMatch(). * * It is not merely redundant here, it is actively wrong: `$companyId` in this * file means "the company the admin is FILTERING BY", which is `null` in the * page's default state. Binding that to initQuery()'s prepended MATCH makes it * `MATCH (:Company {id: null})` — zero rows, and because it is a plain MATCH * it reduces the entire aggregation to zero rows. `_withTotal()` then * zero-fills, so the dashboard renders as a successful, empty page. That was * ADM-53..57: five e2e tests, no error anywhere, no data. * * Reachable only for an Administrator who ALSO belongs to a company (CLS has * no companyId otherwise), which is exactly the dual-role shape the platform * admin fixture — and real deployments — have. */ private _adminQuery; /** The equal-length span immediately preceding `from`. */ private _previousWindow; /** * Resolves a granularity to the `date.truncate` unit it may emit. Throws * rather than interpolating anything the table does not know about. */ private _truncationUnit; /** * Resolves the requested label against the host application's allowlist. * * Returns the ALLOWLIST'S OWN string, never the caller's — so even an exact * match is echoed from trusted memory rather than from the request. An empty * allowlist means the app did not opt in, and every target request is refused. */ private _resolveTarget; private _metrics; } //# sourceMappingURL=tokenusage.admin.repository.d.ts.map