@memberjunction/ai-recommendations 3.4.0 → 4.1.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/README.md +376 -0
- package/dist/Engine.d.ts +1 -1
- package/dist/Engine.js +19 -23
- package/dist/Engine.js.map +1 -1
- package/dist/ProviderBase.d.ts +1 -1
- package/dist/ProviderBase.js +5 -9
- package/dist/ProviderBase.js.map +1 -1
- package/dist/generic/types.js +2 -7
- package/dist/generic/types.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -19
- package/dist/index.js.map +1 -1
- package/package.json +8 -7
- package/readme.md +315 -176
package/README.md
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# @memberjunction/ai-recommendations
|
|
2
|
+
|
|
3
|
+
A provider-based recommendation engine for MemberJunction. Manages recommendation runs, delegates to pluggable providers via the class factory, and tracks results through Recommendation, Recommendation Run, and Recommendation Item entities.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```mermaid
|
|
8
|
+
graph TD
|
|
9
|
+
subgraph Engine["@memberjunction/ai-recommendations"]
|
|
10
|
+
REB["RecommendationEngineBase<br/>(singleton BaseEngine)"]
|
|
11
|
+
RPB["RecommendationProviderBase<br/>(abstract)"]
|
|
12
|
+
RR["RecommendationRequest<T>"]
|
|
13
|
+
RRES["RecommendationResult"]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
subgraph Providers["Registered Providers"]
|
|
17
|
+
P1["Provider A"]
|
|
18
|
+
P2["Provider B"]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
subgraph MJEntities["MemberJunction Entities"]
|
|
22
|
+
RP["Recommendation Providers"]
|
|
23
|
+
RUN["Recommendation Runs"]
|
|
24
|
+
REC["Recommendations"]
|
|
25
|
+
RI["Recommendation Items"]
|
|
26
|
+
LIST["Lists / List Details"]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
subgraph MJCore["MemberJunction Core"]
|
|
30
|
+
BE["BaseEngine"]
|
|
31
|
+
CF["ClassFactory"]
|
|
32
|
+
MD["Metadata"]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
REB -->|extends| BE
|
|
36
|
+
REB -->|discovers| CF
|
|
37
|
+
CF -->|creates| P1
|
|
38
|
+
CF -->|creates| P2
|
|
39
|
+
P1 -->|extends| RPB
|
|
40
|
+
P2 -->|extends| RPB
|
|
41
|
+
REB --> RP
|
|
42
|
+
REB --> RUN
|
|
43
|
+
RPB --> REC
|
|
44
|
+
RPB --> RI
|
|
45
|
+
REB --> LIST
|
|
46
|
+
|
|
47
|
+
style Engine fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
48
|
+
style Providers fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
49
|
+
style MJEntities fill:#b8762f,stroke:#8a5722,color:#fff
|
|
50
|
+
style MJCore fill:#7c5295,stroke:#563a6b,color:#fff
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install @memberjunction/ai-recommendations
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Overview
|
|
60
|
+
|
|
61
|
+
This package provides the framework for running recommendations in MemberJunction. It follows the engine/provider pattern used throughout the platform:
|
|
62
|
+
|
|
63
|
+
1. **RecommendationEngineBase** -- a singleton engine (extending `BaseEngine`) that loads provider metadata, selects a provider, creates Recommendation Run tracking records, and delegates the actual recommendation logic
|
|
64
|
+
2. **RecommendationProviderBase** -- an abstract class that concrete providers implement to generate recommendations for each source record
|
|
65
|
+
3. **RecommendationRequest/RecommendationResult** -- typed request and response objects that flow through the pipeline
|
|
66
|
+
|
|
67
|
+
Providers are discovered at runtime through MemberJunction's `ClassFactory` using `@RegisterClass(RecommendationProviderBase, 'ProviderName')`.
|
|
68
|
+
|
|
69
|
+
## Recommendation Flow
|
|
70
|
+
|
|
71
|
+
```mermaid
|
|
72
|
+
sequenceDiagram
|
|
73
|
+
participant Caller
|
|
74
|
+
participant Engine as RecommendationEngineBase
|
|
75
|
+
participant CF as ClassFactory
|
|
76
|
+
participant Provider as RecommendationProvider
|
|
77
|
+
participant DB as MJ Database
|
|
78
|
+
|
|
79
|
+
Caller->>Engine: Recommend(request)
|
|
80
|
+
Engine->>Engine: TryThrowIfNotLoaded()
|
|
81
|
+
|
|
82
|
+
alt Provider specified
|
|
83
|
+
Engine->>Engine: Use request.Provider
|
|
84
|
+
else No provider
|
|
85
|
+
Engine->>Engine: Use first available
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
Engine->>Engine: GetRecommendationEntities(request)
|
|
89
|
+
|
|
90
|
+
alt From List
|
|
91
|
+
Engine->>DB: Load List + List Details
|
|
92
|
+
Engine->>DB: Load entity records by IDs
|
|
93
|
+
else From EntityAndRecordsInfo
|
|
94
|
+
Engine->>DB: Load records by entity name + IDs
|
|
95
|
+
else Pre-built
|
|
96
|
+
Engine->>Engine: Validate Recommendations array
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
Engine->>DB: Create Recommendation Run (Status: In Progress)
|
|
100
|
+
|
|
101
|
+
opt CreateErrorList = true
|
|
102
|
+
Engine->>DB: Create error tracking List
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
Engine->>CF: CreateInstance(provider.Name)
|
|
106
|
+
CF-->>Engine: Provider instance
|
|
107
|
+
|
|
108
|
+
Engine->>Provider: Recommend(request)
|
|
109
|
+
|
|
110
|
+
loop For each recommendation
|
|
111
|
+
Provider->>Provider: Call external API
|
|
112
|
+
Provider->>DB: SaveRecommendation + Items
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
Provider-->>Engine: RecommendationResult
|
|
116
|
+
|
|
117
|
+
Engine->>DB: Update Run (Completed/Error)
|
|
118
|
+
Engine-->>Caller: RecommendationResult
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Core Components
|
|
122
|
+
|
|
123
|
+
### RecommendationEngineBase
|
|
124
|
+
|
|
125
|
+
A singleton engine that manages the recommendation lifecycle.
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { RecommendationEngineBase } from '@memberjunction/ai-recommendations';
|
|
129
|
+
|
|
130
|
+
// Access the singleton
|
|
131
|
+
const engine = RecommendationEngineBase.Instance;
|
|
132
|
+
|
|
133
|
+
// Initialize (loads Recommendation Providers metadata)
|
|
134
|
+
await engine.Config(false, contextUser);
|
|
135
|
+
|
|
136
|
+
// Run recommendations
|
|
137
|
+
const result = await engine.Recommend(request);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Key properties and methods:**
|
|
141
|
+
|
|
142
|
+
| Member | Description |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `Instance` | Static getter for the singleton instance |
|
|
145
|
+
| `RecommendationProviders` | Array of `RecommendationProviderEntity` loaded from metadata |
|
|
146
|
+
| `Config(forceRefresh?, contextUser?, provider?)` | Loads provider metadata into cache |
|
|
147
|
+
| `Recommend<T>(request)` | Runs the full recommendation pipeline |
|
|
148
|
+
|
|
149
|
+
### RecommendationProviderBase
|
|
150
|
+
|
|
151
|
+
Abstract base class for implementing recommendation providers.
|
|
152
|
+
|
|
153
|
+
```mermaid
|
|
154
|
+
classDiagram
|
|
155
|
+
class RecommendationProviderBase {
|
|
156
|
+
<<abstract>>
|
|
157
|
+
-_md : Metadata
|
|
158
|
+
-_ContextUser : UserInfo
|
|
159
|
+
+ContextUser : UserInfo
|
|
160
|
+
+Recommend(request)* RecommendationResult
|
|
161
|
+
#SaveRecommendation(rec, runID, items) boolean
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
class ConcreteProvider {
|
|
165
|
+
+Recommend(request) RecommendationResult
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
RecommendationProviderBase <|-- ConcreteProvider
|
|
169
|
+
|
|
170
|
+
style RecommendationProviderBase fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
171
|
+
style ConcreteProvider fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The `SaveRecommendation` helper method handles:
|
|
175
|
+
1. Setting the `RecommendationRunID` on the recommendation entity
|
|
176
|
+
2. Saving the recommendation record
|
|
177
|
+
3. Linking and saving all `RecommendationItemEntity` records
|
|
178
|
+
|
|
179
|
+
### RecommendationRequest\<T\>
|
|
180
|
+
|
|
181
|
+
The request object supports three ways to specify source records:
|
|
182
|
+
|
|
183
|
+
```mermaid
|
|
184
|
+
graph TD
|
|
185
|
+
RR["RecommendationRequest"]
|
|
186
|
+
OPT1["Recommendations[]<br/>Pre-built entities"]
|
|
187
|
+
OPT2["EntityAndRecordsInfo<br/>Entity name + Record IDs"]
|
|
188
|
+
OPT3["ListID<br/>MJ List reference"]
|
|
189
|
+
|
|
190
|
+
RR --> OPT1
|
|
191
|
+
RR --> OPT2
|
|
192
|
+
RR --> OPT3
|
|
193
|
+
|
|
194
|
+
style RR fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
195
|
+
style OPT1 fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
196
|
+
style OPT2 fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
197
|
+
style OPT3 fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
| Field | Type | Description |
|
|
201
|
+
|---|---|---|
|
|
202
|
+
| `Recommendations` | `RecommendationEntity[]` | Pre-built unsaved recommendation entities |
|
|
203
|
+
| `EntityAndRecordsInfo` | `{ EntityName, RecordIDs }` | Entity name and array of record IDs to process |
|
|
204
|
+
| `ListID` | `string` | ID of a MJ List whose details become the source records |
|
|
205
|
+
| `Provider` | `RecommendationProviderEntity` | Specific provider to use (defaults to first available) |
|
|
206
|
+
| `CurrentUser` | `UserInfo` | User context |
|
|
207
|
+
| `Options` | `T` | Generic additional options passed to the provider |
|
|
208
|
+
| `CreateErrorList` | `boolean` | Whether to create an error tracking list |
|
|
209
|
+
| `RunID` | `string` | Set automatically by the engine |
|
|
210
|
+
| `ErrorListID` | `string` | Set automatically if error list is created |
|
|
211
|
+
|
|
212
|
+
### RecommendationResult
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
class RecommendationResult {
|
|
216
|
+
Request: RecommendationRequest;
|
|
217
|
+
RecommendationRun?: RecommendationRunEntity;
|
|
218
|
+
RecommendationItems?: RecommendationItemEntity[];
|
|
219
|
+
Success: boolean;
|
|
220
|
+
ErrorMessage: string;
|
|
221
|
+
|
|
222
|
+
AppendWarning(message: string): void; // Adds warning without setting Success=false
|
|
223
|
+
AppendError(message: string): void; // Adds error and sets Success=false
|
|
224
|
+
GetErrorMessages(): string[]; // Splits ErrorMessage into array
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Usage
|
|
229
|
+
|
|
230
|
+
### Running Recommendations from a List
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
import { RecommendationEngineBase } from '@memberjunction/ai-recommendations';
|
|
234
|
+
import { RecommendationRequest } from '@memberjunction/ai-recommendations';
|
|
235
|
+
|
|
236
|
+
const engine = RecommendationEngineBase.Instance;
|
|
237
|
+
await engine.Config(false, contextUser);
|
|
238
|
+
|
|
239
|
+
const request = new RecommendationRequest();
|
|
240
|
+
request.ListID = 'list-uuid';
|
|
241
|
+
request.CurrentUser = contextUser;
|
|
242
|
+
request.CreateErrorList = true;
|
|
243
|
+
|
|
244
|
+
const result = await engine.Recommend(request);
|
|
245
|
+
|
|
246
|
+
if (result.Success) {
|
|
247
|
+
console.log(`Generated ${result.RecommendationItems?.length ?? 0} items`);
|
|
248
|
+
} else {
|
|
249
|
+
console.error(result.ErrorMessage);
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Running Recommendations by Entity and Record IDs
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
const request = new RecommendationRequest();
|
|
257
|
+
request.EntityAndRecordsInfo = {
|
|
258
|
+
EntityName: 'Products',
|
|
259
|
+
RecordIDs: ['id-1', 'id-2', 'id-3']
|
|
260
|
+
};
|
|
261
|
+
request.CurrentUser = contextUser;
|
|
262
|
+
|
|
263
|
+
const result = await engine.Recommend(request);
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Implementing a Provider
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
import { RecommendationProviderBase } from '@memberjunction/ai-recommendations';
|
|
270
|
+
import { RecommendationRequest, RecommendationResult } from '@memberjunction/ai-recommendations';
|
|
271
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
272
|
+
import { Metadata } from '@memberjunction/core';
|
|
273
|
+
import { RecommendationItemEntity } from '@memberjunction/core-entities';
|
|
274
|
+
|
|
275
|
+
@RegisterClass(RecommendationProviderBase, 'My Recommendation Provider')
|
|
276
|
+
export class MyProvider extends RecommendationProviderBase {
|
|
277
|
+
async Recommend(request: RecommendationRequest): Promise<RecommendationResult> {
|
|
278
|
+
const result = new RecommendationResult(request);
|
|
279
|
+
const md = new Metadata();
|
|
280
|
+
|
|
281
|
+
for (const rec of request.Recommendations) {
|
|
282
|
+
// Call your recommendation API/algorithm
|
|
283
|
+
const suggestions = await this.getSuggestions(rec.SourceEntityRecordID);
|
|
284
|
+
|
|
285
|
+
const items: RecommendationItemEntity[] = [];
|
|
286
|
+
for (const suggestion of suggestions) {
|
|
287
|
+
const item = await md.GetEntityObject<RecommendationItemEntity>(
|
|
288
|
+
'Recommendation Items', request.CurrentUser
|
|
289
|
+
);
|
|
290
|
+
item.NewRecord();
|
|
291
|
+
item.DestinationEntityID = suggestion.entityID;
|
|
292
|
+
item.DestinationEntityRecordID = suggestion.recordID;
|
|
293
|
+
item.MatchProbability = suggestion.score;
|
|
294
|
+
items.push(item);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
await this.SaveRecommendation(rec, request.RunID, items);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return result;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private async getSuggestions(recordID: string): Promise<Suggestion[]> {
|
|
304
|
+
// Your recommendation logic here
|
|
305
|
+
return [];
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Database Entities
|
|
311
|
+
|
|
312
|
+
```mermaid
|
|
313
|
+
erDiagram
|
|
314
|
+
RECOMMENDATION_PROVIDERS {
|
|
315
|
+
string ID PK
|
|
316
|
+
string Name
|
|
317
|
+
string Description
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
RECOMMENDATION_RUNS {
|
|
321
|
+
string ID PK
|
|
322
|
+
string RecommendationProviderID FK
|
|
323
|
+
string RunByUserID FK
|
|
324
|
+
datetime StartDate
|
|
325
|
+
string Status
|
|
326
|
+
string Description
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
RECOMMENDATIONS {
|
|
330
|
+
string ID PK
|
|
331
|
+
string RecommendationRunID FK
|
|
332
|
+
string SourceEntityID FK
|
|
333
|
+
string SourceEntityRecordID
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
RECOMMENDATION_ITEMS {
|
|
337
|
+
string ID PK
|
|
338
|
+
string RecommendationID FK
|
|
339
|
+
string DestinationEntityID FK
|
|
340
|
+
string DestinationEntityRecordID
|
|
341
|
+
float MatchProbability
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
LISTS {
|
|
345
|
+
string ID PK
|
|
346
|
+
string Name
|
|
347
|
+
string EntityID FK
|
|
348
|
+
string UserID FK
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
RECOMMENDATION_PROVIDERS ||--o{ RECOMMENDATION_RUNS : has
|
|
352
|
+
RECOMMENDATION_RUNS ||--o{ RECOMMENDATIONS : contains
|
|
353
|
+
RECOMMENDATIONS ||--o{ RECOMMENDATION_ITEMS : produces
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## Dependencies
|
|
357
|
+
|
|
358
|
+
| Package | Purpose |
|
|
359
|
+
|---|---|
|
|
360
|
+
| `@memberjunction/core` | `BaseEngine`, `Metadata`, `RunView`, `UserInfo`, `LogStatus` |
|
|
361
|
+
| `@memberjunction/core-entities` | `RecommendationEntity`, `RecommendationRunEntity`, `RecommendationItemEntity`, `RecommendationProviderEntity`, `ListEntity` |
|
|
362
|
+
| `@memberjunction/global` | `MJGlobal` class factory for provider discovery |
|
|
363
|
+
|
|
364
|
+
## Development
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
# Build
|
|
368
|
+
npm run build
|
|
369
|
+
|
|
370
|
+
# Development mode
|
|
371
|
+
npm run start
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## License
|
|
375
|
+
|
|
376
|
+
ISC
|
package/dist/Engine.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BaseEngine, UserInfo, IMetadataProvider } from '@memberjunction/core';
|
|
2
2
|
import { RecommendationProviderEntity } from '@memberjunction/core-entities';
|
|
3
|
-
import { RecommendationRequest, RecommendationResult } from './generic/types';
|
|
3
|
+
import { RecommendationRequest, RecommendationResult } from './generic/types.js';
|
|
4
4
|
/**
|
|
5
5
|
* Engine class to be used for running all recommendation requests
|
|
6
6
|
*/
|
package/dist/Engine.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const core_1 = require("@memberjunction/core");
|
|
5
|
-
const global_1 = require("@memberjunction/global");
|
|
6
|
-
const ProviderBase_1 = require("./ProviderBase");
|
|
1
|
+
import { BaseEngine, Metadata, LogStatus, RunView, LogError } from '@memberjunction/core';
|
|
2
|
+
import { MJGlobal } from '@memberjunction/global';
|
|
3
|
+
import { RecommendationProviderBase } from './ProviderBase.js';
|
|
7
4
|
/**
|
|
8
5
|
* Engine class to be used for running all recommendation requests
|
|
9
6
|
*/
|
|
10
|
-
class RecommendationEngineBase extends
|
|
7
|
+
export class RecommendationEngineBase extends BaseEngine {
|
|
11
8
|
constructor() {
|
|
12
9
|
super(...arguments);
|
|
13
10
|
this._RecommendationProviders = [];
|
|
@@ -39,14 +36,14 @@ class RecommendationEngineBase extends core_1.BaseEngine {
|
|
|
39
36
|
provider = this.RecommendationProviders[0];
|
|
40
37
|
}
|
|
41
38
|
}
|
|
42
|
-
|
|
39
|
+
LogStatus(`Recommendation Engine is using provider: ${provider.Name}`);
|
|
43
40
|
// get the driver
|
|
44
|
-
const driver =
|
|
41
|
+
const driver = MJGlobal.Instance.ClassFactory.CreateInstance(RecommendationProviderBase, provider.Name);
|
|
45
42
|
if (!driver) {
|
|
46
43
|
throw new Error(`Could not find driver for provider: ${provider.Name}`);
|
|
47
44
|
}
|
|
48
45
|
const recommendations = await this.GetRecommendationEntities(request);
|
|
49
|
-
|
|
46
|
+
LogStatus(`Processing ${recommendations.length} recommendations`);
|
|
50
47
|
if (recommendations.length == 0) {
|
|
51
48
|
return {
|
|
52
49
|
Success: true,
|
|
@@ -55,7 +52,7 @@ class RecommendationEngineBase extends core_1.BaseEngine {
|
|
|
55
52
|
}
|
|
56
53
|
request.Recommendations = recommendations;
|
|
57
54
|
// load the run
|
|
58
|
-
const recommendationRunEntity = await new
|
|
55
|
+
const recommendationRunEntity = await new Metadata().GetEntityObject('Recommendation Runs', request.CurrentUser);
|
|
59
56
|
recommendationRunEntity.NewRecord();
|
|
60
57
|
// update status for current run
|
|
61
58
|
recommendationRunEntity.Status = 'In Progress';
|
|
@@ -64,7 +61,7 @@ class RecommendationEngineBase extends core_1.BaseEngine {
|
|
|
64
61
|
recommendationRunEntity.RunByUserID = request.CurrentUser ? request.CurrentUser.ID : super.ContextUser.ID;
|
|
65
62
|
const saveResult = await recommendationRunEntity.Save();
|
|
66
63
|
if (!saveResult) {
|
|
67
|
-
|
|
64
|
+
LogStatus(`Error saving RecommendationRun entity: `, undefined, recommendationRunEntity.LatestResult);
|
|
68
65
|
throw new Error('Error creating Recommendation Run entity');
|
|
69
66
|
}
|
|
70
67
|
if (request.CreateErrorList) {
|
|
@@ -79,7 +76,7 @@ class RecommendationEngineBase extends core_1.BaseEngine {
|
|
|
79
76
|
recommendationRunEntity.Description = recommendResult.ErrorMessage;
|
|
80
77
|
const postRunSaveResult = await recommendationRunEntity.Save();
|
|
81
78
|
if (!postRunSaveResult) {
|
|
82
|
-
|
|
79
|
+
LogStatus(`Error saving RecommendationRun entity: `, undefined, recommendationRunEntity.LatestResult);
|
|
83
80
|
throw new Error('Error updating Recommendation Run entity');
|
|
84
81
|
}
|
|
85
82
|
return recommendResult;
|
|
@@ -108,8 +105,8 @@ class RecommendationEngineBase extends core_1.BaseEngine {
|
|
|
108
105
|
}
|
|
109
106
|
}
|
|
110
107
|
async GetRecommendationsByListID(listID, currentUser) {
|
|
111
|
-
const rv = new
|
|
112
|
-
const md = new
|
|
108
|
+
const rv = new RunView();
|
|
109
|
+
const md = new Metadata();
|
|
113
110
|
const rvListDetailsResult = await rv.RunViews([
|
|
114
111
|
{
|
|
115
112
|
EntityName: 'Lists',
|
|
@@ -132,7 +129,7 @@ class RecommendationEngineBase extends core_1.BaseEngine {
|
|
|
132
129
|
}
|
|
133
130
|
const list = listViewResult.Results[0];
|
|
134
131
|
const entityName = list.Entity;
|
|
135
|
-
|
|
132
|
+
LogStatus(`Getting recommendations for list: ${list.Name}. Entity: ${entityName}`);
|
|
136
133
|
const entityID = list.EntityID;
|
|
137
134
|
const entity = md.Entities.find((e) => e.ID == entityID);
|
|
138
135
|
const needsQuotes = entity.FirstPrimaryKey.NeedsQuotes ? "'" : '';
|
|
@@ -164,13 +161,13 @@ class RecommendationEngineBase extends core_1.BaseEngine {
|
|
|
164
161
|
return recommendations;
|
|
165
162
|
}
|
|
166
163
|
async GetRecommendationsByRecordIDs(entityName, recordIDs, currentUser) {
|
|
167
|
-
const md = new
|
|
168
|
-
const rv = new
|
|
164
|
+
const md = new Metadata();
|
|
165
|
+
const rv = new RunView();
|
|
169
166
|
const entity = md.Entities.find((e) => e.Name.toLowerCase() == entityName.toLowerCase());
|
|
170
167
|
if (!entity) {
|
|
171
168
|
throw new Error(`Unable to get recommendations by entity info: Entity not found with name: ${entityName}`);
|
|
172
169
|
}
|
|
173
|
-
|
|
170
|
+
LogStatus(`Getting recommendations for entity: ${entityName}`);
|
|
174
171
|
const needsQuotes = entity.FirstPrimaryKey.NeedsQuotes ? "'" : '';
|
|
175
172
|
const recordIDsFilter = recordIDs.map((id) => `${needsQuotes}${id}${needsQuotes}`).join(',');
|
|
176
173
|
const rvEntityResult = await rv.RunView({
|
|
@@ -191,18 +188,18 @@ class RecommendationEngineBase extends core_1.BaseEngine {
|
|
|
191
188
|
return recommendations;
|
|
192
189
|
}
|
|
193
190
|
async CreateRecommendationErrorList(recommendationRunID, entityID, currentUser) {
|
|
194
|
-
const md = new
|
|
191
|
+
const md = new Metadata();
|
|
195
192
|
const list = await md.GetEntityObject('Lists', currentUser);
|
|
196
193
|
list.Name = `Recommendation Run ${recommendationRunID} Errors`;
|
|
197
194
|
list.EntityID = entityID;
|
|
198
195
|
list.UserID = currentUser ? currentUser.ID : super.ContextUser.ID;
|
|
199
196
|
const saveResult = await list.Save();
|
|
200
197
|
if (!saveResult) {
|
|
201
|
-
|
|
198
|
+
LogError(`Error saving Recommendation Error List entity: `, undefined, list.LatestResult);
|
|
202
199
|
return null;
|
|
203
200
|
}
|
|
204
201
|
else {
|
|
205
|
-
|
|
202
|
+
LogStatus(`Error list created for recommendation run: ${recommendationRunID}. List ID: ${list.ID}`);
|
|
206
203
|
}
|
|
207
204
|
return list;
|
|
208
205
|
}
|
|
@@ -210,5 +207,4 @@ class RecommendationEngineBase extends core_1.BaseEngine {
|
|
|
210
207
|
return value === null || value === undefined;
|
|
211
208
|
}
|
|
212
209
|
}
|
|
213
|
-
exports.RecommendationEngineBase = RecommendationEngineBase;
|
|
214
210
|
//# sourceMappingURL=Engine.js.map
|
package/dist/Engine.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Engine.js","sourceRoot":"","sources":["../src/Engine.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Engine.js","sourceRoot":"","sources":["../src/Engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAY,SAAS,EAAE,OAAO,EAAc,QAAQ,EAA+C,MAAM,sBAAsB,CAAC;AAE7J,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAG5D;;GAEG;AACH,MAAM,OAAO,wBAAyB,SAAQ,UAAoC;IAAlF;;QACU,6BAAwB,GAAmC,EAAE,CAAC;IAoPxE,CAAC;IAlPQ,MAAM,KAAK,QAAQ;QACxB,OAAO,KAAK,CAAC,WAAW,EAA4B,CAAC;IACvD,CAAC;IAED,IAAW,uBAAuB;QAChC,OAAO,IAAI,CAAC,wBAAwB,CAAC;IACvC,CAAC;IAEM,KAAK,CAAC,MAAM,CAAC,YAAsB,EAAE,WAAsB,EAAE,QAA4B;QAC9F,MAAM,MAAM,GAA6C;YACvD;gBACE,YAAY,EAAE,0BAA0B;gBACxC,UAAU,EAAE,0BAA0B;gBACtC,UAAU,EAAE,IAAI;aACjB;SACF,CAAC;QAEF,OAAO,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;IACtE,CAAC;IAEM,KAAK,CAAC,SAAS,CAAI,OAAiC;QACzD,KAAK,CAAC,mBAAmB,EAAE,CAAC;QAE5B,IAAI,QAAQ,GAAiC,OAAO,CAAC,QAAQ,CAAC;QAC9D,IAAI,CAAC,QAAQ,EAAC,CAAC;YACb,IAAG,IAAI,CAAC,uBAAuB,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;gBAC5C,MAAM,IAAI,KAAK,CAAC,uEAAuE,CAAC,CAAC;YAC3F,CAAC;iBACG,CAAC;gBACH,QAAQ,GAAG,IAAI,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC;YAC7C,CAAC;QACH,CAAC;QAED,SAAS,CAAC,4CAA4C,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QAEvE,iBAAiB;QACjB,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,cAAc,CAA6B,0BAA0B,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;QAEpI,IAAG,CAAC,MAAM,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,uCAAuC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QAC1E,CAAC;QAED,MAAM,eAAe,GAA2B,MAAM,IAAI,CAAC,yBAAyB,CAAC,OAAO,CAAC,CAAC;QAC9F,SAAS,CAAC,cAAc,eAAe,CAAC,MAAM,kBAAkB,CAAC,CAAC;QAElE,IAAG,eAAe,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YAC/B,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,YAAY,EAAE,6CAA6C;aACrD,CAAC;QACX,CAAC;QAED,OAAO,CAAC,eAAe,GAAG,eAAe,CAAC;QAE1C,eAAe;QACf,MAAM,uBAAuB,GAAG,MAAM,IAAI,QAAQ,EAAE,CAAC,eAAe,CAA0B,qBAAqB,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;QAC1I,uBAAuB,CAAC,SAAS,EAAE,CAAC;QAEpC,gCAAgC;QAChC,uBAAuB,CAAC,MAAM,GAAG,aAAa,CAAC;QAC/C,uBAAuB,CAAC,wBAAwB,GAAG,QAAQ,CAAC,EAAE,CAAC;QAC/D,uBAAuB,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC;QAC/C,uBAAuB,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;QAE1G,MAAM,UAAU,GAAY,MAAM,uBAAuB,CAAC,IAAI,EAAE,CAAC;QACjE,IAAG,CAAC,UAAU,EAAE,CAAC;YACf,SAAS,CAAC,yCAAyC,EAAE,SAAS,EAAE,uBAAuB,CAAC,YAAY,CAAC,CAAC;YACtG,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;QAC9D,CAAC;QAED,IAAG,OAAO,CAAC,eAAe,EAAC,CAAC;YAC1B,MAAM,SAAS,GAAsB,MAAM,IAAI,CAAC,6BAA6B,CAAC,uBAAuB,CAAC,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,cAAc,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;YAClK,IAAG,SAAS,EAAC,CAAC;gBACZ,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,EAAE,CAAC;YACrC,CAAC;QACH,CAAC;QAED,OAAO,CAAC,KAAK,GAAG,uBAAuB,CAAC,EAAE,CAAC;QAC3C,MAAM,eAAe,GAAyB,MAAM,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAC9E,uBAAuB,CAAC,MAAM,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC;QACjF,uBAAuB,CAAC,WAAW,GAAG,eAAe,CAAC,YAAY,CAAC;QAEnE,MAAM,iBAAiB,GAAY,MAAM,uBAAuB,CAAC,IAAI,EAAE,CAAC;QACxE,IAAG,CAAC,iBAAiB,EAAE,CAAC;YACtB,SAAS,CAAC,yCAAyC,EAAE,SAAS,EAAE,uBAAuB,CAAC,YAAY,CAAC,CAAC;YACtG,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;QAC9D,CAAC;QAED,OAAO,eAAe,CAAC;IACzB,CAAC;IAEO,KAAK,CAAC,yBAAyB,CAAC,OAA8B;QACpE,IAAG,OAAO,CAAC,eAAe,EAAC,CAAC;YAC1B,MAAM,eAAe,GAA2B,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC;YACnJ,IAAG,eAAe,CAAC,MAAM,GAAG,CAAC,EAAC,CAAC;gBAC7B,MAAM,IAAI,KAAK,CAAC,gHAAgH,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChL,CAAC;YAED,OAAO,OAAO,CAAC,eAAe,CAAC;QACjC,CAAC;aACI,IAAG,OAAO,CAAC,MAAM,EAAC,CAAC;YACtB,OAAO,MAAM,IAAI,CAAC,0BAA0B,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;QACpF,CAAC;aACI,IAAG,OAAO,CAAC,oBAAoB,EAAC,CAAC;YACpC,MAAM,UAAU,GAAG,OAAO,CAAC,oBAAoB,CAAC,UAAU,CAAC;YAC3D,MAAM,SAAS,GAAG,OAAO,CAAC,oBAAoB,CAAC,SAAS,CAAC;YAEzD,IAAG,CAAC,UAAU,EAAC,CAAC;gBACd,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;YACrE,CAAC;YAED,IAAG,CAAC,SAAS,EAAC,CAAC;gBACb,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;YACpE,CAAC;YAED,OAAO,MAAM,IAAI,CAAC,6BAA6B,CAAC,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;QAC9F,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,0BAA0B,CAAC,MAAc,EAAE,WAAsB;QAC7E,MAAM,EAAE,GAAY,IAAI,OAAO,EAAE,CAAC;QAClC,MAAM,EAAE,GAAa,IAAI,QAAQ,EAAE,CAAC;QAEpC,MAAM,mBAAmB,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC;YAC5C;gBACE,UAAU,EAAE,OAAO;gBACnB,WAAW,EAAE,SAAS,MAAM,GAAG;gBAC/B,UAAU,EAAE,QAAQ;aACrB;YACD;gBACE,UAAU,EAAE,cAAc;gBAC1B,WAAW,EAAE,aAAa,MAAM,GAAG;gBACnC,UAAU,EAAE,QAAQ;gBACpB,aAAa,EAAE,IAAI;aACpB;SACF,EAAE,WAAW,CAAC,CAAC;QAEhB,MAAM,cAAc,GAAG,mBAAmB,CAAC,CAAC,CAAC,CAAC;QAC9C,IAAG,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,+BAA+B,MAAM,KAAK,cAAc,CAAC,YAAY,EAAE,CAAC,CAAC;QAC3F,CAAC;QAED,IAAG,cAAc,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YACtC,MAAM,IAAI,KAAK,CAAC,0BAA0B,MAAM,EAAE,CAAC,CAAC;QACtD,CAAC;QAED,MAAM,IAAI,GAAmB,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACvD,MAAM,UAAU,GAAW,IAAI,CAAC,MAAM,CAAC;QACvC,SAAS,CAAC,qCAAqC,IAAI,CAAC,IAAI,aAAa,UAAU,EAAE,CAAC,CAAC;QAEnF,MAAM,QAAQ,GAAW,IAAI,CAAC,QAAQ,CAAC;QACvC,MAAM,MAAM,GAAe,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,QAAQ,CAAC,CAAC;QACrE,MAAM,WAAW,GAAW,MAAM,CAAC,eAAe,CAAC,WAAW,CAAA,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAEzE,MAAM,iBAAiB,GAAG,mBAAmB,CAAC,CAAC,CAAC,CAAC;QACjD,IAAG,CAAC,iBAAiB,CAAC,OAAO,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,0CAA0C,MAAM,KAAK,iBAAiB,CAAC,YAAY,EAAE,CAAC,CAAC;QACzG,CAAC;QAED,gCAAgC;QAChC,IAAG,iBAAiB,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YACzC,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,SAAS,GAAW,iBAAiB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAwB,EAAE,EAAE,CAAC,GAAG,WAAW,GAAG,EAAE,CAAC,QAAQ,GAAG,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC9I,MAAM,cAAc,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC;YACtC,UAAU,EAAE,UAAU;YACtB,WAAW,EAAE,GAAG,MAAM,CAAC,eAAe,CAAC,IAAI,QAAQ,SAAS,GAAG;YAC/D,aAAa,EAAE,IAAI;SACpB,EAAE,WAAW,CAAC,CAAC;QAEhB,IAAG,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,4CAA4C,MAAM,KAAK,cAAc,CAAC,YAAY,EAAE,CAAC,CAAC;QACxG,CAAC;QAED,IAAI,eAAe,GAA2B,EAAE,CAAC;QACjD,KAAI,MAAM,MAAM,IAAI,cAAc,CAAC,OAAO,EAAE,CAAC;YAC3C,MAAM,oBAAoB,GAAyB,MAAM,EAAE,CAAC,eAAe,CAAuB,iBAAiB,EAAE,WAAW,CAAC,CAAC;YAClI,oBAAoB,CAAC,SAAS,EAAE,CAAC;YACjC,oBAAoB,CAAC,cAAc,GAAG,QAAQ,CAAC;YAC/C,oBAAoB,CAAC,oBAAoB,GAAG,MAAM,CAAC,EAAE,CAAC;YACtD,eAAe,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAC7C,CAAC;QAED,OAAO,eAAe,CAAC;IACzB,CAAC;IAEO,KAAK,CAAC,6BAA6B,CAAC,UAAkB,EAAE,SAAiC,EAAE,WAAsB;QACvH,MAAM,EAAE,GAAa,IAAI,QAAQ,EAAE,CAAC;QACpC,MAAM,EAAE,GAAY,IAAI,OAAO,EAAE,CAAC;QAElC,MAAM,MAAM,GAAe,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC;QACrG,IAAG,CAAC,MAAM,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,6EAA6E,UAAU,EAAE,CAAC,CAAC;QAC7G,CAAC;QAED,SAAS,CAAC,uCAAuC,UAAU,EAAE,CAAC,CAAC;QAC/D,MAAM,WAAW,GAAW,MAAM,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1E,MAAM,eAAe,GAAW,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,WAAW,GAAG,EAAE,GAAG,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrG,MAAM,cAAc,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC;YACtC,UAAU,EAAE,UAAU;YACtB,WAAW,EAAE,GAAG,MAAM,CAAC,eAAe,CAAC,IAAI,QAAQ,eAAe,GAAG;YACrE,aAAa,EAAE,IAAI;SACpB,EAAE,WAAW,CAAC,CAAC;QAEhB,IAAG,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,4CAA4C,UAAU,KAAK,cAAc,CAAC,YAAY,EAAE,CAAC,CAAC;QAC5G,CAAC;QAED,IAAI,eAAe,GAA2B,EAAE,CAAC;QACjD,KAAI,MAAM,MAAM,IAAI,cAAc,CAAC,OAAO,EAAE,CAAC;YAC3C,MAAM,oBAAoB,GAAyB,MAAM,EAAE,CAAC,eAAe,CAAuB,iBAAiB,EAAE,WAAW,CAAC,CAAC;YAClI,oBAAoB,CAAC,SAAS,EAAE,CAAC;YACjC,oBAAoB,CAAC,cAAc,GAAG,MAAM,CAAC,EAAE,CAAC;YAChD,eAAe,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAC7C,CAAC;QAED,OAAO,eAAe,CAAC;IACzB,CAAC;IAEO,KAAK,CAAC,6BAA6B,CAAC,mBAA2B,EAAE,QAAgB,EAAE,WAAsB;QAC/G,MAAM,EAAE,GAAa,IAAI,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,GAAe,MAAM,EAAE,CAAC,eAAe,CAAa,OAAO,EAAE,WAAW,CAAC,CAAC;QACpF,IAAI,CAAC,IAAI,GAAG,sBAAsB,mBAAmB,SAAS,CAAC;QAC/D,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;QAElE,MAAM,UAAU,GAAY,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC9C,IAAG,CAAC,UAAU,EAAE,CAAC;YACf,QAAQ,CAAC,iDAAiD,EAAE,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;YAC1F,OAAO,IAAI,CAAC;QACd,CAAC;aACG,CAAC;YACH,SAAS,CAAC,8CAA8C,mBAAmB,cAAc,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;QACtG,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,iBAAiB,CAAC,KAAc;QACtC,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,CAAC;IAC/C,CAAC;CACF"}
|
package/dist/ProviderBase.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RecommendationEntity, RecommendationItemEntity } from '@memberjunction/core-entities';
|
|
2
2
|
import { UserInfo } from '@memberjunction/core';
|
|
3
|
-
import { RecommendationRequest, RecommendationResult } from './generic/types';
|
|
3
|
+
import { RecommendationRequest, RecommendationResult } from './generic/types.js';
|
|
4
4
|
/**
|
|
5
5
|
* Base class for all recommendation providers
|
|
6
6
|
*/
|
package/dist/ProviderBase.js
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RecommendationProviderBase = void 0;
|
|
4
|
-
const core_1 = require("@memberjunction/core");
|
|
1
|
+
import { LogError, Metadata } from '@memberjunction/core';
|
|
5
2
|
/**
|
|
6
3
|
* Base class for all recommendation providers
|
|
7
4
|
*/
|
|
8
|
-
class RecommendationProviderBase {
|
|
5
|
+
export class RecommendationProviderBase {
|
|
9
6
|
constructor(ContextUser) {
|
|
10
7
|
this._ContextUser = ContextUser;
|
|
11
|
-
this._md = new
|
|
8
|
+
this._md = new Metadata();
|
|
12
9
|
}
|
|
13
10
|
get ContextUser() {
|
|
14
11
|
return this._ContextUser;
|
|
@@ -23,7 +20,7 @@ class RecommendationProviderBase {
|
|
|
23
20
|
recommendation.RecommendationRunID = RunID;
|
|
24
21
|
const recommendationSaveResult = await recommendation.Save();
|
|
25
22
|
if (!recommendationSaveResult) {
|
|
26
|
-
|
|
23
|
+
LogError(`Error saving recommendation for ${recommendation.ID}`, undefined, recommendation.LatestResult);
|
|
27
24
|
return false;
|
|
28
25
|
}
|
|
29
26
|
let allSaved = true;
|
|
@@ -31,12 +28,11 @@ class RecommendationProviderBase {
|
|
|
31
28
|
item.RecommendationID = recommendation.ID;
|
|
32
29
|
const saveResult = await item.Save();
|
|
33
30
|
if (!saveResult) {
|
|
34
|
-
|
|
31
|
+
LogError(`Error saving recommendation item for recommendation ${recommendation.ID}`, undefined, item.LatestResult);
|
|
35
32
|
allSaved = false;
|
|
36
33
|
}
|
|
37
34
|
}
|
|
38
35
|
return allSaved;
|
|
39
36
|
}
|
|
40
37
|
}
|
|
41
|
-
exports.RecommendationProviderBase = RecommendationProviderBase;
|
|
42
38
|
//# sourceMappingURL=ProviderBase.js.map
|
package/dist/ProviderBase.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProviderBase.js","sourceRoot":"","sources":["../src/ProviderBase.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ProviderBase.js","sourceRoot":"","sources":["../src/ProviderBase.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAY,MAAM,sBAAsB,CAAC;AAGpE;;GAEG;AACH,MAAM,OAAgB,0BAA0B;IAI9C,YAAmB,WAAqB;QACtC,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC;QAChC,IAAI,CAAC,GAAG,GAAG,IAAI,QAAQ,EAAE,CAAC;IAC5B,CAAC;IAED,IAAW,WAAW;QACpB,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAUD;;;;;OAKG;IACO,KAAK,CAAC,kBAAkB,CAAC,cAAoC,EAAE,KAAa,EAAE,KAAiC;QACvH,cAAc,CAAC,mBAAmB,GAAG,KAAK,CAAC;QAC3C,MAAM,wBAAwB,GAAY,MAAM,cAAc,CAAC,IAAI,EAAE,CAAC;QACtE,IAAG,CAAC,wBAAwB,EAAE,CAAC;YAC7B,QAAQ,CAAC,mCAAmC,cAAc,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,cAAc,CAAC,YAAY,CAAC,CAAC;YACzG,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,QAAQ,GAAY,IAAI,CAAC;QAC7B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC,gBAAgB,GAAG,cAAc,CAAC,EAAE,CAAC;YAC1C,MAAM,UAAU,GAAY,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;YAC9C,IAAG,CAAC,UAAU,EAAE,CAAC;gBACf,QAAQ,CAAC,uDAAuD,cAAc,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;gBACnH,QAAQ,GAAG,KAAK,CAAC;YACnB,CAAC;QACH,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF"}
|
package/dist/generic/types.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RecommendationResult = exports.RecommendationRequest = void 0;
|
|
4
1
|
/**
|
|
5
2
|
* Used to make requests to Recommendation providers
|
|
6
3
|
*/
|
|
7
|
-
class RecommendationRequest {
|
|
4
|
+
export class RecommendationRequest {
|
|
8
5
|
constructor() {
|
|
9
6
|
/**
|
|
10
7
|
* Array of the requested recommendations. When preparing a batch of recommendations to request, do NOT set the RecommendationRunID or attempt
|
|
@@ -14,11 +11,10 @@ class RecommendationRequest {
|
|
|
14
11
|
this.Recommendations = [];
|
|
15
12
|
}
|
|
16
13
|
}
|
|
17
|
-
exports.RecommendationRequest = RecommendationRequest;
|
|
18
14
|
/**
|
|
19
15
|
* This response is generated for each Recommend() request
|
|
20
16
|
*/
|
|
21
|
-
class RecommendationResult {
|
|
17
|
+
export class RecommendationResult {
|
|
22
18
|
constructor(request) {
|
|
23
19
|
/**
|
|
24
20
|
* The Recommendation Item Entities that were created by the recommendation provider
|
|
@@ -54,5 +50,4 @@ class RecommendationResult {
|
|
|
54
50
|
return this.ErrorMessage.split('\n');
|
|
55
51
|
}
|
|
56
52
|
}
|
|
57
|
-
exports.RecommendationResult = RecommendationResult;
|
|
58
53
|
//# sourceMappingURL=types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/generic/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/generic/types.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,OAAO,qBAAqB;IAAlC;QAQI;;;;WAIG;QACH,oBAAe,GAA4B,EAAE,CAAC;IA6ClD,CAAC;CAAA;AAED;;GAEG;AACH,MAAM,OAAO,oBAAoB;IAa7B,YAAY,OAA8B;QAP1C;;WAEG;QACH,wBAAmB,GAAgC,EAAE,CAAC;QAKlD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,YAAY,GAAG,EAAE,CAAC;IAC3B,CAAC;IAED;;;;OAIG;IACH,aAAa,CAAC,cAAsB;QAChC,IAAI,CAAC,YAAY,IAAI,YAAY,cAAc,KAAK,CAAC;IACzD,CAAC;IAED;;;;OAIG;IACH,WAAW,CAAC,YAAoB;QAC5B,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,IAAI,CAAC,YAAY,IAAI,UAAU,YAAY,KAAK,CAAC;IACrD,CAAC;IAED;;;;OAIG;IACH,gBAAgB;QACZ,OAAO,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;CACJ"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export * from './Engine';
|
|
2
|
-
export * from './ProviderBase';
|
|
3
|
-
export * from './generic/types';
|
|
1
|
+
export * from './Engine.js';
|
|
2
|
+
export * from './ProviderBase.js';
|
|
3
|
+
export * from './generic/types.js';
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,20 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
-
};
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./Engine"), exports);
|
|
18
|
-
__exportStar(require("./ProviderBase"), exports);
|
|
19
|
-
__exportStar(require("./generic/types"), exports);
|
|
1
|
+
export * from './Engine.js';
|
|
2
|
+
export * from './ProviderBase.js';
|
|
3
|
+
export * from './generic/types.js';
|
|
20
4
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ai-recommendations",
|
|
3
|
-
"
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "4.1.0",
|
|
4
5
|
"description": "MemberJunction Recommendations Engine",
|
|
5
6
|
"main": "dist/index.js",
|
|
6
7
|
"types": "dist/index.d.ts",
|
|
@@ -9,20 +10,20 @@
|
|
|
9
10
|
],
|
|
10
11
|
"scripts": {
|
|
11
12
|
"start": "ts-node-dev src/index.ts",
|
|
12
|
-
"build": "tsc",
|
|
13
|
+
"build": "tsc && tsc-alias -f",
|
|
13
14
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
14
15
|
},
|
|
15
16
|
"author": "MemberJunction.com",
|
|
16
17
|
"license": "ISC",
|
|
17
18
|
"devDependencies": {
|
|
18
19
|
"ts-node-dev": "^2.0.0",
|
|
19
|
-
"typescript": "^5.
|
|
20
|
+
"typescript": "^5.9.3"
|
|
20
21
|
},
|
|
21
22
|
"dependencies": {
|
|
22
|
-
"@memberjunction/global": "
|
|
23
|
-
"@memberjunction/core": "
|
|
24
|
-
"@memberjunction/core-entities": "
|
|
25
|
-
"@memberjunction/ai": "
|
|
23
|
+
"@memberjunction/global": "4.1.0",
|
|
24
|
+
"@memberjunction/core": "4.1.0",
|
|
25
|
+
"@memberjunction/core-entities": "4.1.0",
|
|
26
|
+
"@memberjunction/ai": "4.1.0"
|
|
26
27
|
},
|
|
27
28
|
"repository": {
|
|
28
29
|
"type": "git",
|
package/readme.md
CHANGED
|
@@ -1,16 +1,54 @@
|
|
|
1
1
|
# @memberjunction/ai-recommendations
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
A provider-based recommendation engine for MemberJunction. Manages recommendation runs, delegates to pluggable providers via the class factory, and tracks results through Recommendation, Recommendation Run, and Recommendation Item entities.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```mermaid
|
|
8
|
+
graph TD
|
|
9
|
+
subgraph Engine["@memberjunction/ai-recommendations"]
|
|
10
|
+
REB["RecommendationEngineBase<br/>(singleton BaseEngine)"]
|
|
11
|
+
RPB["RecommendationProviderBase<br/>(abstract)"]
|
|
12
|
+
RR["RecommendationRequest<T>"]
|
|
13
|
+
RRES["RecommendationResult"]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
subgraph Providers["Registered Providers"]
|
|
17
|
+
P1["Provider A"]
|
|
18
|
+
P2["Provider B"]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
subgraph MJEntities["MemberJunction Entities"]
|
|
22
|
+
RP["Recommendation Providers"]
|
|
23
|
+
RUN["Recommendation Runs"]
|
|
24
|
+
REC["Recommendations"]
|
|
25
|
+
RI["Recommendation Items"]
|
|
26
|
+
LIST["Lists / List Details"]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
subgraph MJCore["MemberJunction Core"]
|
|
30
|
+
BE["BaseEngine"]
|
|
31
|
+
CF["ClassFactory"]
|
|
32
|
+
MD["Metadata"]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
REB -->|extends| BE
|
|
36
|
+
REB -->|discovers| CF
|
|
37
|
+
CF -->|creates| P1
|
|
38
|
+
CF -->|creates| P2
|
|
39
|
+
P1 -->|extends| RPB
|
|
40
|
+
P2 -->|extends| RPB
|
|
41
|
+
REB --> RP
|
|
42
|
+
REB --> RUN
|
|
43
|
+
RPB --> REC
|
|
44
|
+
RPB --> RI
|
|
45
|
+
REB --> LIST
|
|
46
|
+
|
|
47
|
+
style Engine fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
48
|
+
style Providers fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
49
|
+
style MJEntities fill:#b8762f,stroke:#8a5722,color:#fff
|
|
50
|
+
style MJCore fill:#7c5295,stroke:#563a6b,color:#fff
|
|
51
|
+
```
|
|
14
52
|
|
|
15
53
|
## Installation
|
|
16
54
|
|
|
@@ -18,220 +56,321 @@ The MemberJunction Recommendations Engine provides a flexible and extensible fra
|
|
|
18
56
|
npm install @memberjunction/ai-recommendations
|
|
19
57
|
```
|
|
20
58
|
|
|
59
|
+
## Overview
|
|
60
|
+
|
|
61
|
+
This package provides the framework for running recommendations in MemberJunction. It follows the engine/provider pattern used throughout the platform:
|
|
62
|
+
|
|
63
|
+
1. **RecommendationEngineBase** -- a singleton engine (extending `BaseEngine`) that loads provider metadata, selects a provider, creates Recommendation Run tracking records, and delegates the actual recommendation logic
|
|
64
|
+
2. **RecommendationProviderBase** -- an abstract class that concrete providers implement to generate recommendations for each source record
|
|
65
|
+
3. **RecommendationRequest/RecommendationResult** -- typed request and response objects that flow through the pipeline
|
|
66
|
+
|
|
67
|
+
Providers are discovered at runtime through MemberJunction's `ClassFactory` using `@RegisterClass(RecommendationProviderBase, 'ProviderName')`.
|
|
68
|
+
|
|
69
|
+
## Recommendation Flow
|
|
70
|
+
|
|
71
|
+
```mermaid
|
|
72
|
+
sequenceDiagram
|
|
73
|
+
participant Caller
|
|
74
|
+
participant Engine as RecommendationEngineBase
|
|
75
|
+
participant CF as ClassFactory
|
|
76
|
+
participant Provider as RecommendationProvider
|
|
77
|
+
participant DB as MJ Database
|
|
78
|
+
|
|
79
|
+
Caller->>Engine: Recommend(request)
|
|
80
|
+
Engine->>Engine: TryThrowIfNotLoaded()
|
|
81
|
+
|
|
82
|
+
alt Provider specified
|
|
83
|
+
Engine->>Engine: Use request.Provider
|
|
84
|
+
else No provider
|
|
85
|
+
Engine->>Engine: Use first available
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
Engine->>Engine: GetRecommendationEntities(request)
|
|
89
|
+
|
|
90
|
+
alt From List
|
|
91
|
+
Engine->>DB: Load List + List Details
|
|
92
|
+
Engine->>DB: Load entity records by IDs
|
|
93
|
+
else From EntityAndRecordsInfo
|
|
94
|
+
Engine->>DB: Load records by entity name + IDs
|
|
95
|
+
else Pre-built
|
|
96
|
+
Engine->>Engine: Validate Recommendations array
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
Engine->>DB: Create Recommendation Run (Status: In Progress)
|
|
100
|
+
|
|
101
|
+
opt CreateErrorList = true
|
|
102
|
+
Engine->>DB: Create error tracking List
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
Engine->>CF: CreateInstance(provider.Name)
|
|
106
|
+
CF-->>Engine: Provider instance
|
|
107
|
+
|
|
108
|
+
Engine->>Provider: Recommend(request)
|
|
109
|
+
|
|
110
|
+
loop For each recommendation
|
|
111
|
+
Provider->>Provider: Call external API
|
|
112
|
+
Provider->>DB: SaveRecommendation + Items
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
Provider-->>Engine: RecommendationResult
|
|
116
|
+
|
|
117
|
+
Engine->>DB: Update Run (Completed/Error)
|
|
118
|
+
Engine-->>Caller: RecommendationResult
|
|
119
|
+
```
|
|
120
|
+
|
|
21
121
|
## Core Components
|
|
22
122
|
|
|
23
123
|
### RecommendationEngineBase
|
|
24
124
|
|
|
25
|
-
|
|
125
|
+
A singleton engine that manages the recommendation lifecycle.
|
|
26
126
|
|
|
27
127
|
```typescript
|
|
28
|
-
import { RecommendationEngineBase
|
|
128
|
+
import { RecommendationEngineBase } from '@memberjunction/ai-recommendations';
|
|
29
129
|
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// Create a recommendation request
|
|
34
|
-
const request = new RecommendationRequest();
|
|
130
|
+
// Access the singleton
|
|
131
|
+
const engine = RecommendationEngineBase.Instance;
|
|
35
132
|
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
EntityName: 'Customers',
|
|
39
|
-
RecordIDs: ['CUST001', 'CUST002']
|
|
40
|
-
};
|
|
133
|
+
// Initialize (loads Recommendation Providers metadata)
|
|
134
|
+
await engine.Config(false, contextUser);
|
|
41
135
|
|
|
42
|
-
//
|
|
43
|
-
const result = await
|
|
136
|
+
// Run recommendations
|
|
137
|
+
const result = await engine.Recommend(request);
|
|
44
138
|
```
|
|
45
139
|
|
|
46
|
-
|
|
140
|
+
**Key properties and methods:**
|
|
47
141
|
|
|
48
|
-
|
|
142
|
+
| Member | Description |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `Instance` | Static getter for the singleton instance |
|
|
145
|
+
| `RecommendationProviders` | Array of `RecommendationProviderEntity` loaded from metadata |
|
|
146
|
+
| `Config(forceRefresh?, contextUser?, provider?)` | Loads provider metadata into cache |
|
|
147
|
+
| `Recommend<T>(request)` | Runs the full recommendation pipeline |
|
|
49
148
|
|
|
50
|
-
|
|
51
|
-
import { RecommendationProviderBase, RecommendationRequest, RecommendationResult } from '@memberjunction/ai-recommendations';
|
|
52
|
-
import { UserInfo } from '@memberjunction/core';
|
|
53
|
-
import { RecommendationItemEntity } from '@memberjunction/core-entities';
|
|
149
|
+
### RecommendationProviderBase
|
|
54
150
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// Generate items for this recommendation
|
|
67
|
-
const items: RecommendationItemEntity[] = [];
|
|
68
|
-
|
|
69
|
-
// Your recommendation logic here
|
|
70
|
-
// ...
|
|
71
|
-
|
|
72
|
-
// Save the recommendation and its items
|
|
73
|
-
await this.SaveRecommendation(recommendation, request.RunID, items);
|
|
74
|
-
}
|
|
75
|
-
} catch (error) {
|
|
76
|
-
result.AppendError(error.message);
|
|
151
|
+
Abstract base class for implementing recommendation providers.
|
|
152
|
+
|
|
153
|
+
```mermaid
|
|
154
|
+
classDiagram
|
|
155
|
+
class RecommendationProviderBase {
|
|
156
|
+
<<abstract>>
|
|
157
|
+
-_md : Metadata
|
|
158
|
+
-_ContextUser : UserInfo
|
|
159
|
+
+ContextUser : UserInfo
|
|
160
|
+
+Recommend(request)* RecommendationResult
|
|
161
|
+
#SaveRecommendation(rec, runID, items) boolean
|
|
77
162
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
163
|
+
|
|
164
|
+
class ConcreteProvider {
|
|
165
|
+
+Recommend(request) RecommendationResult
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
RecommendationProviderBase <|-- ConcreteProvider
|
|
169
|
+
|
|
170
|
+
style RecommendationProviderBase fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
171
|
+
style ConcreteProvider fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
82
172
|
```
|
|
83
173
|
|
|
84
|
-
|
|
174
|
+
The `SaveRecommendation` helper method handles:
|
|
175
|
+
1. Setting the `RecommendationRunID` on the recommendation entity
|
|
176
|
+
2. Saving the recommendation record
|
|
177
|
+
3. Linking and saving all `RecommendationItemEntity` records
|
|
85
178
|
|
|
86
|
-
###
|
|
179
|
+
### RecommendationRequest\<T\>
|
|
87
180
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// Execute the recommendation
|
|
106
|
-
const result = await RecommendationEngineBase.Instance.Recommend(request);
|
|
107
|
-
|
|
108
|
-
if (result.Success) {
|
|
109
|
-
console.log('Recommendations generated successfully!');
|
|
110
|
-
return result.RecommendationItems;
|
|
111
|
-
} else {
|
|
112
|
-
console.error('Error generating recommendations:', result.ErrorMessage);
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
181
|
+
The request object supports three ways to specify source records:
|
|
182
|
+
|
|
183
|
+
```mermaid
|
|
184
|
+
graph TD
|
|
185
|
+
RR["RecommendationRequest"]
|
|
186
|
+
OPT1["Recommendations[]<br/>Pre-built entities"]
|
|
187
|
+
OPT2["EntityAndRecordsInfo<br/>Entity name + Record IDs"]
|
|
188
|
+
OPT3["ListID<br/>MJ List reference"]
|
|
189
|
+
|
|
190
|
+
RR --> OPT1
|
|
191
|
+
RR --> OPT2
|
|
192
|
+
RR --> OPT3
|
|
193
|
+
|
|
194
|
+
style RR fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
195
|
+
style OPT1 fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
196
|
+
style OPT2 fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
197
|
+
style OPT3 fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
116
198
|
```
|
|
117
199
|
|
|
118
|
-
|
|
200
|
+
| Field | Type | Description |
|
|
201
|
+
|---|---|---|
|
|
202
|
+
| `Recommendations` | `RecommendationEntity[]` | Pre-built unsaved recommendation entities |
|
|
203
|
+
| `EntityAndRecordsInfo` | `{ EntityName, RecordIDs }` | Entity name and array of record IDs to process |
|
|
204
|
+
| `ListID` | `string` | ID of a MJ List whose details become the source records |
|
|
205
|
+
| `Provider` | `RecommendationProviderEntity` | Specific provider to use (defaults to first available) |
|
|
206
|
+
| `CurrentUser` | `UserInfo` | User context |
|
|
207
|
+
| `Options` | `T` | Generic additional options passed to the provider |
|
|
208
|
+
| `CreateErrorList` | `boolean` | Whether to create an error tracking list |
|
|
209
|
+
| `RunID` | `string` | Set automatically by the engine |
|
|
210
|
+
| `ErrorListID` | `string` | Set automatically if error list is created |
|
|
211
|
+
|
|
212
|
+
### RecommendationResult
|
|
119
213
|
|
|
120
214
|
```typescript
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
// Execute the recommendation
|
|
132
|
-
const result = await RecommendationEngineBase.Instance.Recommend(request);
|
|
133
|
-
|
|
134
|
-
if (result.Success) {
|
|
135
|
-
console.log('Recommendations generated successfully!');
|
|
136
|
-
console.log('Error list ID (if needed):', result.Request.ErrorListID);
|
|
137
|
-
return result.RecommendationItems;
|
|
138
|
-
} else {
|
|
139
|
-
console.error('Error generating recommendations:', result.ErrorMessage);
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
215
|
+
class RecommendationResult {
|
|
216
|
+
Request: RecommendationRequest;
|
|
217
|
+
RecommendationRun?: RecommendationRunEntity;
|
|
218
|
+
RecommendationItems?: RecommendationItemEntity[];
|
|
219
|
+
Success: boolean;
|
|
220
|
+
ErrorMessage: string;
|
|
221
|
+
|
|
222
|
+
AppendWarning(message: string): void; // Adds warning without setting Success=false
|
|
223
|
+
AppendError(message: string): void; // Adds error and sets Success=false
|
|
224
|
+
GetErrorMessages(): string[]; // Splits ErrorMessage into array
|
|
142
225
|
}
|
|
143
226
|
```
|
|
144
227
|
|
|
145
|
-
|
|
228
|
+
## Usage
|
|
229
|
+
|
|
230
|
+
### Running Recommendations from a List
|
|
146
231
|
|
|
147
232
|
```typescript
|
|
148
|
-
import { RecommendationEngineBase
|
|
233
|
+
import { RecommendationEngineBase } from '@memberjunction/ai-recommendations';
|
|
234
|
+
import { RecommendationRequest } from '@memberjunction/ai-recommendations';
|
|
149
235
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
236
|
+
const engine = RecommendationEngineBase.Instance;
|
|
237
|
+
await engine.Config(false, contextUser);
|
|
238
|
+
|
|
239
|
+
const request = new RecommendationRequest();
|
|
240
|
+
request.ListID = 'list-uuid';
|
|
241
|
+
request.CurrentUser = contextUser;
|
|
242
|
+
request.CreateErrorList = true;
|
|
156
243
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
EntityName: 'Customers',
|
|
164
|
-
RecordIDs: [customerId]
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
// Add provider-specific options
|
|
168
|
-
request.Options = {
|
|
169
|
-
similarityThreshold: 0.75,
|
|
170
|
-
maxRecommendations: 5,
|
|
171
|
-
includeRatings: true
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
// Execute the recommendation
|
|
175
|
-
return await RecommendationEngineBase.Instance.Recommend(request);
|
|
244
|
+
const result = await engine.Recommend(request);
|
|
245
|
+
|
|
246
|
+
if (result.Success) {
|
|
247
|
+
console.log(`Generated ${result.RecommendationItems?.length ?? 0} items`);
|
|
248
|
+
} else {
|
|
249
|
+
console.error(result.ErrorMessage);
|
|
176
250
|
}
|
|
177
251
|
```
|
|
178
252
|
|
|
179
|
-
|
|
253
|
+
### Running Recommendations by Entity and Record IDs
|
|
180
254
|
|
|
181
|
-
|
|
255
|
+
```typescript
|
|
256
|
+
const request = new RecommendationRequest();
|
|
257
|
+
request.EntityAndRecordsInfo = {
|
|
258
|
+
EntityName: 'Products',
|
|
259
|
+
RecordIDs: ['id-1', 'id-2', 'id-3']
|
|
260
|
+
};
|
|
261
|
+
request.CurrentUser = contextUser;
|
|
182
262
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
3. **Provider Selection**: The appropriate recommendation provider is selected
|
|
186
|
-
4. **Recommendation Generation**: The provider generates recommendations for each requested record
|
|
187
|
-
5. **Result Storage**: Recommendations and items are saved to the database
|
|
188
|
-
6. **Status Update**: The run status is updated (completed or error)
|
|
189
|
-
7. **Result Return**: The `RecommendationResult` is returned to the caller
|
|
263
|
+
const result = await engine.Recommend(request);
|
|
264
|
+
```
|
|
190
265
|
|
|
191
|
-
|
|
266
|
+
### Implementing a Provider
|
|
192
267
|
|
|
193
|
-
|
|
268
|
+
```typescript
|
|
269
|
+
import { RecommendationProviderBase } from '@memberjunction/ai-recommendations';
|
|
270
|
+
import { RecommendationRequest, RecommendationResult } from '@memberjunction/ai-recommendations';
|
|
271
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
272
|
+
import { Metadata } from '@memberjunction/core';
|
|
273
|
+
import { RecommendationItemEntity } from '@memberjunction/core-entities';
|
|
194
274
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
275
|
+
@RegisterClass(RecommendationProviderBase, 'My Recommendation Provider')
|
|
276
|
+
export class MyProvider extends RecommendationProviderBase {
|
|
277
|
+
async Recommend(request: RecommendationRequest): Promise<RecommendationResult> {
|
|
278
|
+
const result = new RecommendationResult(request);
|
|
279
|
+
const md = new Metadata();
|
|
280
|
+
|
|
281
|
+
for (const rec of request.Recommendations) {
|
|
282
|
+
// Call your recommendation API/algorithm
|
|
283
|
+
const suggestions = await this.getSuggestions(rec.SourceEntityRecordID);
|
|
284
|
+
|
|
285
|
+
const items: RecommendationItemEntity[] = [];
|
|
286
|
+
for (const suggestion of suggestions) {
|
|
287
|
+
const item = await md.GetEntityObject<RecommendationItemEntity>(
|
|
288
|
+
'Recommendation Items', request.CurrentUser
|
|
289
|
+
);
|
|
290
|
+
item.NewRecord();
|
|
291
|
+
item.DestinationEntityID = suggestion.entityID;
|
|
292
|
+
item.DestinationEntityRecordID = suggestion.recordID;
|
|
293
|
+
item.MatchProbability = suggestion.score;
|
|
294
|
+
items.push(item);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
await this.SaveRecommendation(rec, request.RunID, items);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return result;
|
|
301
|
+
}
|
|
199
302
|
|
|
200
|
-
|
|
303
|
+
private async getSuggestions(recordID: string): Promise<Suggestion[]> {
|
|
304
|
+
// Your recommendation logic here
|
|
305
|
+
return [];
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
201
309
|
|
|
202
|
-
|
|
310
|
+
## Database Entities
|
|
203
311
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
312
|
+
```mermaid
|
|
313
|
+
erDiagram
|
|
314
|
+
RECOMMENDATION_PROVIDERS {
|
|
315
|
+
string ID PK
|
|
316
|
+
string Name
|
|
317
|
+
string Description
|
|
318
|
+
}
|
|
207
319
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
320
|
+
RECOMMENDATION_RUNS {
|
|
321
|
+
string ID PK
|
|
322
|
+
string RecommendationProviderID FK
|
|
323
|
+
string RunByUserID FK
|
|
324
|
+
datetime StartDate
|
|
325
|
+
string Status
|
|
326
|
+
string Description
|
|
327
|
+
}
|
|
212
328
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
329
|
+
RECOMMENDATIONS {
|
|
330
|
+
string ID PK
|
|
331
|
+
string RecommendationRunID FK
|
|
332
|
+
string SourceEntityID FK
|
|
333
|
+
string SourceEntityRecordID
|
|
334
|
+
}
|
|
219
335
|
|
|
220
|
-
|
|
336
|
+
RECOMMENDATION_ITEMS {
|
|
337
|
+
string ID PK
|
|
338
|
+
string RecommendationID FK
|
|
339
|
+
string DestinationEntityID FK
|
|
340
|
+
string DestinationEntityRecordID
|
|
341
|
+
float MatchProbability
|
|
342
|
+
}
|
|
221
343
|
|
|
222
|
-
|
|
344
|
+
LISTS {
|
|
345
|
+
string ID PK
|
|
346
|
+
string Name
|
|
347
|
+
string EntityID FK
|
|
348
|
+
string UserID FK
|
|
349
|
+
}
|
|
223
350
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
351
|
+
RECOMMENDATION_PROVIDERS ||--o{ RECOMMENDATION_RUNS : has
|
|
352
|
+
RECOMMENDATION_RUNS ||--o{ RECOMMENDATIONS : contains
|
|
353
|
+
RECOMMENDATIONS ||--o{ RECOMMENDATION_ITEMS : produces
|
|
354
|
+
```
|
|
227
355
|
|
|
228
356
|
## Dependencies
|
|
229
357
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
358
|
+
| Package | Purpose |
|
|
359
|
+
|---|---|
|
|
360
|
+
| `@memberjunction/core` | `BaseEngine`, `Metadata`, `RunView`, `UserInfo`, `LogStatus` |
|
|
361
|
+
| `@memberjunction/core-entities` | `RecommendationEntity`, `RecommendationRunEntity`, `RecommendationItemEntity`, `RecommendationProviderEntity`, `ListEntity` |
|
|
362
|
+
| `@memberjunction/global` | `MJGlobal` class factory for provider discovery |
|
|
363
|
+
|
|
364
|
+
## Development
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
# Build
|
|
368
|
+
npm run build
|
|
369
|
+
|
|
370
|
+
# Development mode
|
|
371
|
+
npm run start
|
|
372
|
+
```
|
|
234
373
|
|
|
235
374
|
## License
|
|
236
375
|
|
|
237
|
-
ISC
|
|
376
|
+
ISC
|