import { CueApi } from './api'; import { ReadonlySignal } from './signal'; import { EntityDetailedData, EntityRelationships, ProjectEntitiesData, SummaryGraphData } from './models'; /** * Manages entity data for a single project. * * ### Data model * - **`entityInfoMap`** — merged per-entity signal. Recomputes reactively as * any of the five underlying slices (core data, documents, relationships, * OSM map, OSM WKT) change. * - **`entityGraph`** — project-level category-to-category relationship graph, * fetched once on construction. * * ### Lazy loading * All public `request*` / `fetch*` methods are no-ops for UUIDs / IRIs already * in the cache. Only the delta is fetched. Data is merged into the * `entityInfoMap` signal incrementally — callers just react to the signal. * * ### Language * Entity labels (`qcy:value`) are not language-tagged in the triplestore, so * entity data itself is language-independent. The `language` parameter is * carried here for future-proofing and forwards to the schema-level queries * where language filtering does apply. * * ### Lifecycle * Call `reset()` when the project changes. The Angular adapter calls this from * the effect that watches `projectId`. * * @example * ```ts * const entities = new CueProjectEntities(cue.api, projectId); * entities.requestEntityData(['uuid1', 'uuid2']); * const info = entities.entityInfoMap.get()['uuid1']; * ``` */ export declare class CueProjectEntities { private readonly _api; private readonly _projectId; private readonly _graphType?; private readonly _verbose; /** Full RDF base URL for this project, e.g. `https://cue.qaecy.com/r/{pid}/` */ readonly baseURL: string; private readonly _entityDetails; private readonly _entityDocuments; private readonly _entityRelationships; private readonly _entityOSMMap; private readonly _osmWKTMap; private readonly _fetchingOSMIds; /** Cumulative unique entity UUIDs ever passed to request methods (survives cache hits). */ private readonly _seenIds; private readonly _entityGraph; /** * Memoizes the raw category-summary rows per neighbourhood (keyed by the * expanded `entityIRI`, or `''` for the full graph) so that repeat/concurrent * `buildSummaryGraph` calls for the same neighbourhood — e.g. re-rendering a * view, or requesting both `'graph'` and `'md'` formats — share one fetch * instead of re-querying. Cleared on `reset()`. */ private readonly _summaryGraphCache; /** * Memoizes per-category entity counts (project-wide, not neighbourhood-scoped * — unlike `_summaryGraphCache` there's only ever one of these per project). * Cleared on `reset()`. */ private _categoryCountsCache; private readonly _entityInfoMapComputed; /** Merged per-entity detail map. Updated reactively as data arrives. */ readonly entityInfoMap: ReadonlySignal>; /** Project-level category graph (fetched once per project). */ readonly entityGraph: ReadonlySignal; constructor(_api: CueApi, _projectId: string, /** Override the RDF resource base URL. Defaults to `https://cue.qaecy.com/r/`. */ rdfBase?: string, _graphType?: string | undefined, _verbose?: boolean); /** * Constructs the full RDF IRI for the given entity UUID. * Use this to bridge the UUID-based batch APIs and the IRI-based per-entity APIs. */ entityIri(uuid: string): string; /** @internal Builds a full resource IRI from a UUID without a SPARQL round-trip. */ private _resourceIri; private _log; /** * Resets all entity state and re-fetches the entity graph. * Call when the active project changes. */ reset(): void; /** * Lazily batch-fetches core data (label + categories) for the given entity * UUIDs. Already-fetched UUIDs are skipped. * * Data is merged into `entityInfoMap` once the SPARQL response arrives. */ requestEntityData(uuids: string[], includeMentionCount?: boolean): void; /** * Lazily fetches OSM location data for the given entity UUIDs. * Already-fetched UUIDs are skipped. * * OSM WKT geometry is fetched in a second pass via a federated SPARQL SERVICE * query and merged into `entityInfoMap` reactively once it arrives. */ requestEntityLocations(uuids: string[]): Promise; /** * Fetches incoming and outgoing relationships for a single entity IRI. * Neighbouring entity core data is opportunistically populated into * `entityInfoMap` as a side-effect. * * The result is stored in `entityInfoMap[uuid].relationshipData` and also * returned directly for callers that need an immediate value. */ fetchEntityRelationships(iri: string): Promise; /** * Fetches UUIDs of documents that reference the given entity IRI. * Also triggers a core-data fetch for the entity itself if not yet loaded. * * Returns document UUIDs. Full document data will be populated by the * document service (future `CueProjectDocuments`). */ fetchEntityDocuments(iri: string): Promise; /** * Fetches all `qcy:EntityCategory` IRIs and their preferred labels for this * project. Uses `api.language` (default `'en'`); * falls back to an untagged label when no match is found. */ contentCategoriesInProject(orderByOccurences?: boolean): Promise<{ iri: string; label: string; }[]>; /** * Fetches all `qcy:CanonicalEntity` instances that belong to at least one of * the given category IRIs. * * Accepts both full HTTP IRIs and prefixed forms, e.g.: * - `"qcy:Building"` * - `"https://cue.qaecy.com/ontology#Building"` * * By default returns `{ iri, uuid }[]` — the IRI is built locally from the * project base URL so no extra data is fetched from the endpoint. * Pass `includeMetadata: true` to also get `value` and `categories`. * Use the default form when you intend to lazy-load details via `requestEntityData`. */ entitiesByCategory(categoryIris: string[], includeMetadata?: false): Promise>; entitiesByCategory(categoryIris: string[], includeMetadata: true): Promise>; /** * Fetches a summary graph of entity category relationships for the project. * * Each row describes how many times entities of `sourceCat` point to * entities of `targetCat` via a given `predicate`, ordered by descending * occurrence count. * * @param format * - `undefined` — raw SPARQL JSON result * - `'graph'` — structured `{ entities, relations }` with predicates and weights * - `'md'` — compact aligned text table * * @param entityIRI Optional category IRI (prefixed, e.g. `qcy:Building`, or * full, e.g. `https://…Building`). When supplied, only edges where either * the source or target category equals this IRI are returned — i.e. the * one-hop neighbourhood of that category. * * @example * ```ts * // Structured graph (nodes + edges) * const g = await entities.buildSummaryGraph('graph'); * // g.entities → [{ iri: 'https://…FloorPlanDrawing' }, …] * // g.relations → [{ sourceID, predicate, targetID, weight }, …] * * // Neighbourhood of a single category (prefixed IRI) * const g2 = await entities.buildSummaryGraph('graph', 'qcy:Building'); * * // Markdown table * const md = await entities.buildSummaryGraph('md'); * // qcy:FloorPlanDrawing -> qcy:includesBuildingEntity -> qcy:BuildingZone (5652) * ``` */ buildSummaryGraph(format: 'graph', entityIRI?: string): Promise; buildSummaryGraph(format: 'md', entityIRI?: string): Promise; buildSummaryGraph(format?: undefined, entityIRI?: string): Promise; /** * Fetches the category-summary rows. On QLever the pre-computed * `entity-summary` materialized view is tried first; if it yields no rows we * warn and fall back to the live aggregation query. Fuseki always uses the * live query. */ private _fetchSummaryData; /** * Fetches per-category entity counts (project-wide — there's no neighbourhood * scoping for this one). Cached for the lifetime of this instance since it's * a cheap, single-aggregate view that doesn't vary per `buildSummaryGraph` call. */ private _fetchCategoryCounts; /** * Tries the named materialized view first (QLever only); falls back to the * live query if the view yields no rows, or on Fuseki (which has no views). */ private _fetchViewBacked; private _computeEntityInfoMap; private _fetchOutgoingRelationships; private _fetchIncomingRelationships; private _fetchEntityGraph; /** Detects new OSM IRIs in the OSM map that don't yet have WKT and fetches them. */ private _checkPendingOSMFetches; private _fetchOSMLocations; }