@attlaz/client 1.108.0 → 1.110.0

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/dist/Client.d.ts CHANGED
@@ -35,6 +35,7 @@ import { ProductMatchEndpoint } from './MarketPulse/Service/ProductMatchEndpoint
35
35
  import { PulseCatalogProductEndpoint } from './MarketPulse/Service/PulseCatalogProductEndpoint.js';
36
36
  import { PriceAlertEndpoint } from './MarketPulse/Service/PriceAlertEndpoint.js';
37
37
  import { PricingEndpoint } from './MarketPulse/Service/PricingEndpoint.js';
38
+ import { OpportunityEndpoint } from './MarketPulse/Service/OpportunityEndpoint.js';
38
39
  import { ProductGroupEndpoint } from './MarketPulse/Service/ProductGroupEndpoint.js';
39
40
  import { CatalogProductEndpoint } from './Catalog/Service/CatalogProductEndpoint.js';
40
41
  import { GenerateContentEndpoint } from './DataEnrichment/Service/GenerateContentEndpoint.js';
@@ -147,6 +148,7 @@ export declare class Client {
147
148
  getCatalogProductEndpoint(): CatalogProductEndpoint;
148
149
  getPriceAlertEndpoint(): PriceAlertEndpoint;
149
150
  getPricingEndpoint(): PricingEndpoint;
151
+ getOpportunityEndpoint(): OpportunityEndpoint;
150
152
  getProductGroupEndpoint(): ProductGroupEndpoint;
151
153
  getGenerateContentEndpoint(): GenerateContentEndpoint;
152
154
  getCompetitorEndpoint(): CompetitorEndpoint;
package/dist/Client.js CHANGED
@@ -35,6 +35,7 @@ import { ProductMatchEndpoint } from './MarketPulse/Service/ProductMatchEndpoint
35
35
  import { PulseCatalogProductEndpoint } from './MarketPulse/Service/PulseCatalogProductEndpoint.js';
36
36
  import { PriceAlertEndpoint } from './MarketPulse/Service/PriceAlertEndpoint.js';
37
37
  import { PricingEndpoint } from './MarketPulse/Service/PricingEndpoint.js';
38
+ import { OpportunityEndpoint } from './MarketPulse/Service/OpportunityEndpoint.js';
38
39
  import { ProductGroupEndpoint } from './MarketPulse/Service/ProductGroupEndpoint.js';
39
40
  import { CatalogProductEndpoint } from './Catalog/Service/CatalogProductEndpoint.js';
40
41
  import { GenerateContentEndpoint } from './DataEnrichment/Service/GenerateContentEndpoint.js';
@@ -105,6 +106,7 @@ export class Client {
105
106
  PulseCatalogProductEndpoint,
106
107
  PriceAlertEndpoint,
107
108
  PricingEndpoint,
109
+ OpportunityEndpoint,
108
110
  ProductGroupEndpoint,
109
111
  CatalogProductEndpoint,
110
112
  GenerateContentEndpoint,
@@ -339,6 +341,9 @@ export class Client {
339
341
  getPricingEndpoint() {
340
342
  return this.getEndpoint('pricing', this.Store.PricingEndpoint);
341
343
  }
344
+ getOpportunityEndpoint() {
345
+ return this.getEndpoint('opportunity', this.Store.OpportunityEndpoint);
346
+ }
342
347
  getProductGroupEndpoint() {
343
348
  return this.getEndpoint('product-group', this.Store.ProductGroupEndpoint);
344
349
  }
@@ -0,0 +1,41 @@
1
+ import { ApiRecord } from '../../Model/ApiRecord.js';
2
+ /**
3
+ * What the detection engine found wrong with a product's price.
4
+ *
5
+ * Duplicated from apps-lib rather than imported, per the client's enum-duplication convention: the
6
+ * client must not depend on a server package.
7
+ */
8
+ export type OpportunityType = 'OVERPRICED_WITH_ROOM' | 'NEEDLESSLY_CHEAP' | 'UNCOMPETITIVE' | 'JUST_CHANGED';
9
+ /**
10
+ * One detected pricing opportunity on one catalog product — the `/pulse/…/opportunities` resource.
11
+ *
12
+ * A derived cache, not history: every recompute delete-and-replaces the whole catalog, so a product
13
+ * that stops qualifying simply stops appearing. A product can hold two of these, because
14
+ * `JUST_CHANGED` co-occurs with one of the price types — which is why `id` is the (product, type)
15
+ * pair rather than the product id.
16
+ *
17
+ * The reference price throughout is the **median** of the product's matched, in-stock competitors,
18
+ * never the average. `NEEDLESSLY_CHEAP` is the one type judged against the cheapest rival instead.
19
+ */
20
+ export declare class ProductOpportunity {
21
+ id: string;
22
+ catalogProductId: string;
23
+ type: OpportunityType;
24
+ /** Money at stake. Ranks the worklist within one type; not comparable across types. */
25
+ score: number;
26
+ priceVsMedianPct: number | null;
27
+ marginPct: number | null;
28
+ medianCompetitorPrice: number | null;
29
+ competitorCount: number;
30
+ computedAt: Date | null;
31
+ /** Which threshold set produced this row, so rows from an older configuration are identifiable. */
32
+ configVersion: number;
33
+ productName: string;
34
+ productImage: string | null;
35
+ productBrand: string | null;
36
+ ownPrice: number;
37
+ /** The product's own currency. Never assume one — see DECISIONS.md (2026-08-08). */
38
+ currency: string | null;
39
+ constructor(id: string, catalogProductId: string, type: OpportunityType);
40
+ static parse(raw: ApiRecord): ProductOpportunity;
41
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * One detected pricing opportunity on one catalog product — the `/pulse/…/opportunities` resource.
3
+ *
4
+ * A derived cache, not history: every recompute delete-and-replaces the whole catalog, so a product
5
+ * that stops qualifying simply stops appearing. A product can hold two of these, because
6
+ * `JUST_CHANGED` co-occurs with one of the price types — which is why `id` is the (product, type)
7
+ * pair rather than the product id.
8
+ *
9
+ * The reference price throughout is the **median** of the product's matched, in-stock competitors,
10
+ * never the average. `NEEDLESSLY_CHEAP` is the one type judged against the cheapest rival instead.
11
+ */
12
+ export class ProductOpportunity {
13
+ id;
14
+ catalogProductId;
15
+ type;
16
+ /** Money at stake. Ranks the worklist within one type; not comparable across types. */
17
+ score = 0;
18
+ priceVsMedianPct = null;
19
+ marginPct = null;
20
+ medianCompetitorPrice = null;
21
+ competitorCount = 0;
22
+ computedAt = null;
23
+ /** Which threshold set produced this row, so rows from an older configuration are identifiable. */
24
+ configVersion = 0;
25
+ // The own product, carried on the row so a worklist renders without a call per product.
26
+ productName = '';
27
+ productImage = null;
28
+ productBrand = null;
29
+ ownPrice = 0;
30
+ /** The product's own currency. Never assume one — see DECISIONS.md (2026-08-08). */
31
+ currency = null;
32
+ constructor(id, catalogProductId, type) {
33
+ this.id = id;
34
+ this.catalogProductId = catalogProductId;
35
+ this.type = type;
36
+ }
37
+ static parse(raw) {
38
+ const opportunity = new ProductOpportunity(raw.id, raw.catalog_product, raw.type);
39
+ opportunity.score = Number(raw.score ?? 0);
40
+ opportunity.priceVsMedianPct = raw.price_vs_median_pct === null || raw.price_vs_median_pct === undefined ? null : Number(raw.price_vs_median_pct);
41
+ opportunity.marginPct = raw.margin_pct === null || raw.margin_pct === undefined ? null : Number(raw.margin_pct);
42
+ opportunity.medianCompetitorPrice = raw.median_competitor_price === null || raw.median_competitor_price === undefined ? null : Number(raw.median_competitor_price);
43
+ opportunity.competitorCount = Number(raw.competitor_count ?? 0);
44
+ opportunity.computedAt = raw.computed_at ? new Date(raw.computed_at) : null;
45
+ opportunity.configVersion = Number(raw.config_version ?? 0);
46
+ opportunity.productName = raw.product_name ?? '';
47
+ opportunity.productImage = raw.product_image ?? null;
48
+ opportunity.productBrand = raw.product_brand ?? null;
49
+ opportunity.ownPrice = Number(raw.own_price ?? 0);
50
+ opportunity.currency = raw.currency ?? null;
51
+ return opportunity;
52
+ }
53
+ }
@@ -20,15 +20,14 @@ export declare class CrawlJobEndpoint extends Endpoint {
20
20
  create(vendorId: string, isPartial?: boolean): Promise<CrawlJob>;
21
21
  /** Update a crawl job's status ('started', 'finished', 'cancelled'). */
22
22
  updateStatus(crawlJobId: string, status: CrawlJobStatus): Promise<CrawlJob>;
23
- /** A vendor's crawl jobs (newest first), each with progress (`total` / `processed`). */
24
- getByVendor(vendorId: string, pagination?: CursorPagination | null): Promise<CollectionResult<CrawlJob>>;
25
23
  /**
26
- * Crawl jobs across every competitor of a catalog (newest first).
24
+ * Crawl jobs across every competitor of a catalog (newest first), each with progress
25
+ * (`total` / `processed`). Pass `vendorId` to narrow to one competitor.
27
26
  *
28
27
  * A crawl job belongs to a vendor, not to a catalog — the API resolves the catalog's competitors
29
- * and returns the jobs of their vendors.
28
+ * and returns the jobs of their vendors. A `vendorId` outside this catalog is rejected.
30
29
  */
31
- getByCatalog(catalogId: CatalogId | string, pagination?: CursorPagination | null): Promise<CollectionResult<CrawlJob>>;
30
+ getByCatalog(catalogId: CatalogId | string, pagination?: CursorPagination | null, vendorId?: string | null): Promise<CollectionResult<CrawlJob>>;
32
31
  /** The crawl job with this id, or null. */
33
32
  getById(crawlJobId: string): Promise<CrawlJob | null>;
34
33
  /** Register the pages this crawl will process (idempotent server-side). */
@@ -33,21 +33,19 @@ export class CrawlJobEndpoint extends Endpoint {
33
33
  }
34
34
  return job;
35
35
  }
36
- /** A vendor's crawl jobs (newest first), each with progress (`total` / `processed`). */
37
- async getByVendor(vendorId, pagination = null) {
38
- const queryString = new QueryString(path('/pulse/vendors/:vendorId/crawl-jobs', { vendorId }));
39
- queryString.addPagination(pagination);
40
- return await this.requestCollection(queryString, CrawlJob.parse);
41
- }
42
36
  /**
43
- * Crawl jobs across every competitor of a catalog (newest first).
37
+ * Crawl jobs across every competitor of a catalog (newest first), each with progress
38
+ * (`total` / `processed`). Pass `vendorId` to narrow to one competitor.
44
39
  *
45
40
  * A crawl job belongs to a vendor, not to a catalog — the API resolves the catalog's competitors
46
- * and returns the jobs of their vendors.
41
+ * and returns the jobs of their vendors. A `vendorId` outside this catalog is rejected.
47
42
  */
48
- async getByCatalog(catalogId, pagination = null) {
43
+ async getByCatalog(catalogId, pagination = null, vendorId = null) {
49
44
  const queryString = new QueryString(path('/pulse/catalogs/:catalogId/crawl-jobs', { catalogId: catalogId.toString() }));
50
45
  queryString.addPagination(pagination);
46
+ if (vendorId !== null) {
47
+ queryString.set('vendor', vendorId);
48
+ }
51
49
  return await this.requestCollection(queryString, CrawlJob.parse);
52
50
  }
53
51
  /** The crawl job with this id, or null. */
@@ -9,10 +9,11 @@ import { VendorStatus } from '../Model/VendorStatus.js';
9
9
  * can be matched — and the resulting per-vendor freshness status.
10
10
  */
11
11
  export declare class IndexJobEndpoint extends Endpoint {
12
- /** Index jobs across every competitor of a catalog, newest first. */
13
- getByCatalog(catalogId: CatalogId | string, pagination?: CursorPagination | null): Promise<CollectionResult<IndexJob>>;
14
- /** Index jobs of one competitor. */
15
- getByVendor(catalogId: CatalogId | string, vendorId: string, pagination?: CursorPagination | null): Promise<CollectionResult<IndexJob>>;
12
+ /**
13
+ * Index jobs across every competitor of a catalog, newest first. Pass `vendorId` to narrow to one
14
+ * competitor; a vendor outside this catalog is rejected.
15
+ */
16
+ getByCatalog(catalogId: CatalogId | string, pagination?: CursorPagination | null, vendorId?: string | null): Promise<CollectionResult<IndexJob>>;
16
17
  /** Queue a re-index of a competitor's products, so later matching sees current data. */
17
18
  requestReindex(catalogId: CatalogId | string, vendorId: string): Promise<void>;
18
19
  /** How fresh a competitor's index and matches are, plus any match job running right now. */
@@ -8,16 +8,16 @@ import { VendorStatus } from '../Model/VendorStatus.js';
8
8
  * can be matched — and the resulting per-vendor freshness status.
9
9
  */
10
10
  export class IndexJobEndpoint extends Endpoint {
11
- /** Index jobs across every competitor of a catalog, newest first. */
12
- async getByCatalog(catalogId, pagination = null) {
11
+ /**
12
+ * Index jobs across every competitor of a catalog, newest first. Pass `vendorId` to narrow to one
13
+ * competitor; a vendor outside this catalog is rejected.
14
+ */
15
+ async getByCatalog(catalogId, pagination = null, vendorId = null) {
13
16
  const queryString = new QueryString(path('/pulse/catalogs/:catalogId/index-jobs', { catalogId: catalogId.toString() }));
14
17
  queryString.addPagination(pagination);
15
- return await this.requestCollection(queryString, IndexJob.parse);
16
- }
17
- /** Index jobs of one competitor. */
18
- async getByVendor(catalogId, vendorId, pagination = null) {
19
- const queryString = new QueryString(path('/pulse/catalogs/:catalogId/vendors/:vendorId/index-jobs', { catalogId: catalogId.toString(), vendorId }));
20
- queryString.addPagination(pagination);
18
+ if (vendorId !== null) {
19
+ queryString.set('vendor', vendorId);
20
+ }
21
21
  return await this.requestCollection(queryString, IndexJob.parse);
22
22
  }
23
23
  /** Queue a re-index of a competitor's products, so later matching sees current data. */
@@ -0,0 +1,17 @@
1
+ import { CatalogId } from '../../Catalog/Model/CatalogId.js';
2
+ import { CursorPagination } from '../../Model/Pagination/CursorPagination.js';
3
+ import { CollectionResult } from '../../Model/Result/CollectionResult.js';
4
+ import { Endpoint } from '../../Service/Endpoint.js';
5
+ import { OpportunityType, ProductOpportunity } from '../Model/ProductOpportunity.js';
6
+ /**
7
+ * The pricing opportunity worklist — `/pulse/catalogs/:catalogId/opportunities`.
8
+ */
9
+ export declare class OpportunityEndpoint extends Endpoint {
10
+ /**
11
+ * Opportunities in a catalog, ranked by money at stake.
12
+ *
13
+ * Pass a `type`: the score means something different per type, so an unfiltered read interleaves
14
+ * four scales into one order. Unfiltered is an overview of what exists, not a ranked worklist.
15
+ */
16
+ getOpportunities(catalogId: CatalogId | string, pagination?: CursorPagination | null, type?: OpportunityType | null): Promise<CollectionResult<ProductOpportunity>>;
17
+ }
@@ -0,0 +1,23 @@
1
+ import { path } from '../../Http/Data/Path.js';
2
+ import { QueryString } from '../../Http/Data/QueryString.js';
3
+ import { Endpoint } from '../../Service/Endpoint.js';
4
+ import { ProductOpportunity } from '../Model/ProductOpportunity.js';
5
+ /**
6
+ * The pricing opportunity worklist — `/pulse/catalogs/:catalogId/opportunities`.
7
+ */
8
+ export class OpportunityEndpoint extends Endpoint {
9
+ /**
10
+ * Opportunities in a catalog, ranked by money at stake.
11
+ *
12
+ * Pass a `type`: the score means something different per type, so an unfiltered read interleaves
13
+ * four scales into one order. Unfiltered is an overview of what exists, not a ranked worklist.
14
+ */
15
+ async getOpportunities(catalogId, pagination = null, type = null) {
16
+ const queryString = new QueryString(path('/pulse/catalogs/:catalogId/opportunities', { catalogId: catalogId.toString() }));
17
+ if (type !== null) {
18
+ queryString.set('type', type);
19
+ }
20
+ queryString.addPagination(pagination);
21
+ return await this.requestCollection(queryString, ProductOpportunity.parse);
22
+ }
23
+ }
@@ -8,10 +8,11 @@ import { Endpoint } from '../../Service/Endpoint.js';
8
8
  * so they share {@link ProductMatchJob}.
9
9
  */
10
10
  export declare class ProductMatchJobEndpoint extends Endpoint {
11
- /** Every match job of a catalog, newest first. */
12
- getByCatalog(catalogId: CatalogId | string, pagination?: CursorPagination | null): Promise<CollectionResult<ProductMatchJob>>;
13
- /** The match jobs of one of the catalog's competitors. */
14
- getByVendor(catalogId: CatalogId | string, vendorId: string, pagination?: CursorPagination | null): Promise<CollectionResult<ProductMatchJob>>;
11
+ /**
12
+ * Every match job of a catalog, newest first. Pass `vendorId` to narrow to one of its
13
+ * competitors; a vendor outside this catalog is rejected.
14
+ */
15
+ getByCatalog(catalogId: CatalogId | string, pagination?: CursorPagination | null, vendorId?: string | null): Promise<CollectionResult<ProductMatchJob>>;
15
16
  /** Queue a matching run of the catalog against one competitor. */
16
17
  requestMatching(catalogId: CatalogId | string, vendorId: string): Promise<void>;
17
18
  /** The match job with this id, or null. */
@@ -7,16 +7,16 @@ import { Endpoint } from '../../Service/Endpoint.js';
7
7
  * so they share {@link ProductMatchJob}.
8
8
  */
9
9
  export class ProductMatchJobEndpoint extends Endpoint {
10
- /** Every match job of a catalog, newest first. */
11
- async getByCatalog(catalogId, pagination = null) {
10
+ /**
11
+ * Every match job of a catalog, newest first. Pass `vendorId` to narrow to one of its
12
+ * competitors; a vendor outside this catalog is rejected.
13
+ */
14
+ async getByCatalog(catalogId, pagination = null, vendorId = null) {
12
15
  const queryString = new QueryString(path('/pulse/catalogs/:catalogId/match-jobs', { catalogId: catalogId.toString() }));
13
16
  queryString.addPagination(pagination);
14
- return await this.requestCollection(queryString, ProductMatchJob.parse);
15
- }
16
- /** The match jobs of one of the catalog's competitors. */
17
- async getByVendor(catalogId, vendorId, pagination = null) {
18
- const queryString = new QueryString(path('/pulse/catalogs/:catalogId/vendors/:vendorId/match-jobs', { catalogId: catalogId.toString(), vendorId }));
19
- queryString.addPagination(pagination);
17
+ if (vendorId !== null) {
18
+ queryString.set('vendor', vendorId);
19
+ }
20
20
  return await this.requestCollection(queryString, ProductMatchJob.parse);
21
21
  }
22
22
  /** Queue a matching run of the catalog against one competitor. */
@@ -16,8 +16,8 @@ export declare class AdapterConnectionEndpoint extends Endpoint {
16
16
  * it is a partial update. A configuration the API does not let a client set (a provider token)
17
17
  * is untouched rather than cleared.
18
18
  *
19
- * Needs an API that serves the PATCH route; it is also still registered as POST for clients
20
- * released before the verb changed.
19
+ * Needs an API that serves the PATCH route. The POST alias this once relied on has since been
20
+ * removed from Core-Api, so a client older than 1.104.0 can no longer write this at all.
21
21
  */
22
22
  saveConnectionConfiguration(connectionId: string, configuration: AdapterConnectionConfigurationValue[]): Promise<boolean>;
23
23
  getConnectionEvents(connectionId: string, pagination: CursorPagination): Promise<CollectionResult<AdapterConnectionEvent>>;
@@ -84,8 +84,8 @@ export class AdapterConnectionEndpoint extends Endpoint {
84
84
  * it is a partial update. A configuration the API does not let a client set (a provider token)
85
85
  * is untouched rather than cleared.
86
86
  *
87
- * Needs an API that serves the PATCH route; it is also still registered as POST for clients
88
- * released before the verb changed.
87
+ * Needs an API that serves the PATCH route. The POST alias this once relied on has since been
88
+ * removed from Core-Api, so a client older than 1.104.0 can no longer write this at all.
89
89
  */
90
90
  async saveConnectionConfiguration(connectionId, configuration) {
91
91
  try {
package/dist/index.d.ts CHANGED
@@ -122,6 +122,8 @@ export { PricingRule } from './MarketPulse/Model/PricingRule.js';
122
122
  export { PriceSuggestion, PriceSuggestionStatus } from './MarketPulse/Model/PriceSuggestion.js';
123
123
  export { PricingRuleData } from './MarketPulse/Model/PricingRuleData.js';
124
124
  export { PricingEndpoint } from './MarketPulse/Service/PricingEndpoint.js';
125
+ export { ProductOpportunity, OpportunityType } from './MarketPulse/Model/ProductOpportunity.js';
126
+ export { OpportunityEndpoint } from './MarketPulse/Service/OpportunityEndpoint.js';
125
127
  export { ProductGroup, ProductGroupType } from './MarketPulse/Model/ProductGroup.js';
126
128
  export { ProductGroupData } from './MarketPulse/Model/ProductGroupData.js';
127
129
  export { ProductGroupEndpoint } from './MarketPulse/Service/ProductGroupEndpoint.js';
package/dist/index.js CHANGED
@@ -99,6 +99,8 @@ export { PriceAlertEndpoint } from './MarketPulse/Service/PriceAlertEndpoint.js'
99
99
  export { PricingRule } from './MarketPulse/Model/PricingRule.js';
100
100
  export { PriceSuggestion } from './MarketPulse/Model/PriceSuggestion.js';
101
101
  export { PricingEndpoint } from './MarketPulse/Service/PricingEndpoint.js';
102
+ export { ProductOpportunity } from './MarketPulse/Model/ProductOpportunity.js';
103
+ export { OpportunityEndpoint } from './MarketPulse/Service/OpportunityEndpoint.js';
102
104
  export { ProductGroup } from './MarketPulse/Model/ProductGroup.js';
103
105
  export { ProductGroupEndpoint } from './MarketPulse/Service/ProductGroupEndpoint.js';
104
106
  export { CatalogProduct } from './Catalog/Model/CatalogProduct.js';
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "1.107.0";
1
+ export declare const VERSION = "1.109.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "1.107.0";
1
+ export const VERSION = "1.109.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@attlaz/client",
3
- "version": "1.108.0",
3
+ "version": "1.110.0",
4
4
  "description": "Javascript Client to access Attlaz API",
5
5
  "types": "./dist/index.d.ts",
6
6
  "main": "./dist/index.js",