@dhyasama/totem-models 12.19.0 → 12.21.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/index.js +1 -0
- package/lib/Brief.js +140 -0
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -53,6 +53,7 @@ var bootstrap = function(mongoose, config) {
|
|
|
53
53
|
require('./lib/Account.js')(mongoose, config);
|
|
54
54
|
require('./lib/Activity.js')(mongoose, config);
|
|
55
55
|
require('./lib/ApiKey.js')(mongoose, config);
|
|
56
|
+
require('./lib/Brief.js')(mongoose, config);
|
|
56
57
|
require('./lib/CalendarEvent.js')(mongoose, config);
|
|
57
58
|
require('./lib/CapTable.js')(mongoose, config);
|
|
58
59
|
require('./lib/DiffbotArticle')(mongoose, config);
|
package/lib/Brief.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = function(mongoose, config) {
|
|
4
|
+
|
|
5
|
+
const Schema = mongoose.Schema;
|
|
6
|
+
|
|
7
|
+
// Set of models a brief can be generated for. Mirrors the polymorphic
|
|
8
|
+
// entity pattern used elsewhere in the codebase (Folder, Note, etc.).
|
|
9
|
+
const ALLOWED_ENTITY_MODELS = ['Organization', 'Fund', 'LimitedPartner'];
|
|
10
|
+
|
|
11
|
+
const Brief = new Schema({
|
|
12
|
+
|
|
13
|
+
customer: { type: Schema.ObjectId, ref: 'Account', index: true, required: true },
|
|
14
|
+
|
|
15
|
+
// What this brief is *about*. The model picks which loader runs to
|
|
16
|
+
// collect source material (org has emails/meetings/financials; fund has
|
|
17
|
+
// portfolio rollups; LP has communications/campaigns; etc.).
|
|
18
|
+
entity: {
|
|
19
|
+
// refPath lets `.populate('entity.id')` resolve to the correct
|
|
20
|
+
// collection based on the sibling `entity.model` field.
|
|
21
|
+
id: { type: Schema.ObjectId, required: true, refPath: 'entity.model' },
|
|
22
|
+
model: { type: String, enum: ALLOWED_ENTITY_MODELS, required: true }
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
// Lifecycle: 'generating' while the Lambda is producing the next brief,
|
|
26
|
+
// 'ready' once new content lands, 'error' if generation failed. Any prior
|
|
27
|
+
// 'ready' html is preserved while regenerating.
|
|
28
|
+
status: { type: String, enum: ['generating', 'ready', 'error'], default: 'generating', required: true, index: true },
|
|
29
|
+
|
|
30
|
+
// The AI-generated brief, as sanitized HTML. The model picks the
|
|
31
|
+
// layout, sections, ordering, and visualizations that best tell the story
|
|
32
|
+
// of this particular entity. May include <canvas data-chart='{...}'>
|
|
33
|
+
// blocks that the client hydrates with Chart.js, and
|
|
34
|
+
// <a class="cite" data-cite-type data-cite-id> anchors that resolve
|
|
35
|
+
// against the `references` array below.
|
|
36
|
+
html: { type: String },
|
|
37
|
+
|
|
38
|
+
// Deduped list of source records cited anywhere in `html`. Each entry
|
|
39
|
+
// appears once regardless of how many times its id is referenced in the
|
|
40
|
+
// body. Known types: 'email', 'meeting', 'financials' (more added as
|
|
41
|
+
// additional loaders are wired up). The client uses (type, id) to fetch
|
|
42
|
+
// the underlying record when a citation is opened.
|
|
43
|
+
references: {
|
|
44
|
+
type: [{
|
|
45
|
+
_id: false,
|
|
46
|
+
type: { type: String, required: true },
|
|
47
|
+
id: { type: Schema.ObjectId, required: true }
|
|
48
|
+
}],
|
|
49
|
+
default: []
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// When the latest 'ready' html was produced.
|
|
53
|
+
generatedOn: { type: Date }
|
|
54
|
+
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// One brief per (customer, entity). Generations upsert in place.
|
|
58
|
+
Brief.index({ customer: 1, 'entity.id': 1, 'entity.model': 1 }, { unique: true });
|
|
59
|
+
|
|
60
|
+
const validateCustomerId = function(customerId) {
|
|
61
|
+
if (!customerId) throw new Error('customerId is required');
|
|
62
|
+
if (!mongoose.Types.ObjectId.isValid(customerId)) throw new Error('customerId is not a valid ObjectId');
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const validateEntity = function(entity) {
|
|
66
|
+
if (!entity || typeof entity !== 'object') throw new Error('entity is required');
|
|
67
|
+
if (!entity.id) throw new Error('entity.id is required');
|
|
68
|
+
if (!mongoose.Types.ObjectId.isValid(entity.id)) throw new Error('entity.id is not a valid ObjectId');
|
|
69
|
+
if (!entity.model) throw new Error('entity.model is required');
|
|
70
|
+
if (ALLOWED_ENTITY_MODELS.indexOf(entity.model) === -1) {
|
|
71
|
+
throw new Error('entity.model must be one of: ' + ALLOWED_ENTITY_MODELS.join(', '));
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const entityFilter = function(customerId, entity) {
|
|
76
|
+
return {
|
|
77
|
+
customer: customerId,
|
|
78
|
+
'entity.id': entity.id,
|
|
79
|
+
'entity.model': entity.model
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
Brief.statics.getByEntity = async function(customerId, entity) {
|
|
84
|
+
validateCustomerId(customerId);
|
|
85
|
+
validateEntity(entity);
|
|
86
|
+
return this.findOne(entityFilter(customerId, entity));
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Upserts the brief and sets its status without touching html. Preserves
|
|
90
|
+
// any prior 'ready' html so the UI keeps showing the previous brief while
|
|
91
|
+
// the next one is generating (or after an error).
|
|
92
|
+
const setStatus = async function(Model, customerId, entity, status) {
|
|
93
|
+
validateCustomerId(customerId);
|
|
94
|
+
validateEntity(entity);
|
|
95
|
+
|
|
96
|
+
return Model.findOneAndUpdate(
|
|
97
|
+
entityFilter(customerId, entity),
|
|
98
|
+
{
|
|
99
|
+
$set: { status },
|
|
100
|
+
$setOnInsert: { customer: customerId, entity: { id: entity.id, model: entity.model } }
|
|
101
|
+
},
|
|
102
|
+
{ upsert: true, new: true, setDefaultsOnInsert: true }
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
Brief.statics.markGenerating = function(customerId, entity) {
|
|
107
|
+
return setStatus(this, customerId, entity, 'generating');
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
Brief.statics.markError = function(customerId, entity) {
|
|
111
|
+
return setStatus(this, customerId, entity, 'error');
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Writes the finished html + references and flips status to 'ready'.
|
|
115
|
+
Brief.statics.saveResult = async function(customerId, entity, html, references) {
|
|
116
|
+
validateCustomerId(customerId);
|
|
117
|
+
validateEntity(entity);
|
|
118
|
+
if (!html || typeof html !== 'string') throw new Error('html is required');
|
|
119
|
+
if (!Array.isArray(references)) throw new Error('references must be an array');
|
|
120
|
+
|
|
121
|
+
return this.findOneAndUpdate(
|
|
122
|
+
entityFilter(customerId, entity),
|
|
123
|
+
{
|
|
124
|
+
$set: {
|
|
125
|
+
status: 'ready',
|
|
126
|
+
html,
|
|
127
|
+
references,
|
|
128
|
+
generatedOn: new Date()
|
|
129
|
+
},
|
|
130
|
+
$setOnInsert: { customer: customerId, entity: { id: entity.id, model: entity.model } }
|
|
131
|
+
},
|
|
132
|
+
{ upsert: true, new: true, setDefaultsOnInsert: true }
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
Brief.set('autoIndex', false);
|
|
137
|
+
|
|
138
|
+
mongoose.model('Brief', Brief);
|
|
139
|
+
|
|
140
|
+
};
|