Skip to content

Commit 02ee1e1

Browse files
authored
POC: triage for verification-request (#1981)
* poc: start new state machine to be used on triage * poc: added missing events and states to be part of the state machine on triage * remove logs * poc: remove logs * poc: MVP using state machine * poc: update trigger embed after creation * poc: update comments
1 parent 793e1ce commit 02ee1e1

11 files changed

+475
-22
lines changed

server/report/report.controller.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Controller, Param, Get } from "@nestjs/common";
22
import { ReportService } from "./report.service";
33
import { ApiTags } from "@nestjs/swagger";
4+
45
@Controller()
56
export class ReportController {
67
constructor(private reportService: ReportService) {}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
export enum SeverityEnum {
2+
"low_1",
3+
"low_2",
4+
"low_3",
5+
"medium_1",
6+
"medium_2",
7+
"medium_3",
8+
"high_1",
9+
"high_2",
10+
"high_3",
11+
"critical"
12+
}
13+
14+
export enum VerificationRequestStatus {
15+
PRE_TRIAGE = "Pre Triage",
16+
IN_TRIAGE = "In Triage",
17+
POSTED = "Posted",
18+
DECLINED = "Declined",
19+
}
20+
21+
export enum VerificationRequestStateMachineStates {
22+
REQUESTING = 'requesting',
23+
CREATING = 'creating',
24+
EMBEDDING = 'embedding',
25+
IDENTIFYING_DATA = 'identifyingData',
26+
IDENTIFYING_PERSONALITY = 'identifyingPersonality',
27+
IDENTIFYING_PERSONALITY_IMPACT = 'identifyingPersonalityImpact',
28+
IDENTIFYING_PERSONALITY_AREA = 'identifyingPersonalityArea',
29+
DEFINING_TOPICS = 'definingTopics',
30+
DEFINING_IMPACT_AREA = 'definingImpactArea',
31+
DEFINING_SEVERITY = 'definingSeverity',
32+
SIMILARITY_RESEARCH = 'similarityResearch',
33+
FINISHED = 'finished',
34+
}
35+
36+
export enum VerificationRequestStateMachineEvents {
37+
REQUEST = 'request',
38+
PRE_TRIAGE = 'preTriage',
39+
CREATE = 'create',
40+
EMBED = 'embed',
41+
IDENTIFY_DATA = 'identifyData',
42+
DEFINE_TOPICS = 'defineTopics',
43+
DEFINE_IMPACT_AREA = 'defineImpactArea',
44+
DEFINE_SEVERITY = 'defineSeverity',
45+
}
46+
47+
export const VerificationRequestMessages = {
48+
DESCRIPTIONS: {
49+
DEFAULT: 'default',
50+
CREATING: 'Verification is being created on the database',
51+
REQUESTING: 'Verification is being requested',
52+
REQUESTED: 'Verification has been requested',
53+
EMBED: 'Verification is being embedded',
54+
REVIEWING: 'Verification is being reviewed.',
55+
FINISHED: 'Verification has been finished.',
56+
CANCELLING: 'Verification is being cancelled. Status changes to Cancelled.',
57+
CANCELLED: 'Verification has been cancelled.',
58+
ERROR:
59+
'An unhandled error occurred on any of the steps of the request process.\n\nShould notify all entities associated and to inner channels.',
60+
},
61+
ERRORS: {
62+
LOCATION_SERVICE_PROVIDER_OR_USER_NOT_FOUND: 'Location, Service, Provider or User not found.',
63+
},
64+
}

server/verification-request/schemas/verification-request.schema.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
22
import * as mongoose from "mongoose";
33
import { Group } from "../../group/schemas/group.schema";
44
import { Topic } from "../../topic/schemas/topic.schema";
5+
import { SeverityEnum, VerificationRequestStatus } from "../dto/types";
56

67
export type VerificationRequestDocument = VerificationRequest &
78
mongoose.Document;
@@ -51,6 +52,18 @@ export class VerificationRequest {
5152

5253
@Prop({ type: Array, required: false })
5354
topics: Topic[];
55+
56+
@Prop({
57+
required: false,
58+
enum: SeverityEnum,
59+
})
60+
severity: string;
61+
62+
@Prop({
63+
required: true,
64+
enum: VerificationRequestStatus,
65+
})
66+
status: string;
5467
}
5568

5669
const VerificationRequestSchema =
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { createMachine, interpret, MachineConfig, assign, AnyEventObject } from 'xstate'
2+
import { waitFor } from 'xstate/lib/waitFor'
3+
import { Logger } from '@nestjs/common'
4+
5+
export interface StateMachineContext {
6+
result?: any
7+
error?: any
8+
timeout?: number
9+
}
10+
11+
export enum CommonStateMachineStates {
12+
REHYDRATE = 'rehydrateMachine',
13+
ERROR = 'errorMachine',
14+
WRAP_UP_EXECUTION = 'wrapUpMachine',
15+
}
16+
17+
export class StateMachineBase<Context extends StateMachineContext> {
18+
protected stateMachineService: any
19+
protected stateMachineConfig: (stateMachineService: any) => MachineConfig<any, any, any>
20+
protected readonly logger = new Logger(StateMachineBase.name)
21+
22+
constructor(stateMachineService: any) {
23+
this.stateMachineService = stateMachineService
24+
}
25+
26+
async createMachineAndWaitForResult(context: Context, triggerEvent?: string): Promise<any> {
27+
const stateMachine = createMachine(
28+
{
29+
...this.stateMachineConfig(this.stateMachineService),
30+
initial: await this.getEntityCurrentState(context),
31+
context: context,
32+
},
33+
{
34+
actions: {
35+
logInvalidEvent: (cont, event) => {
36+
this.logger.error(`Invalid event '${event.type}' in state '${JSON.stringify(cont)}'`)
37+
},
38+
},
39+
},
40+
)
41+
return this.interpretMachineAndWaitForResult(stateMachine, triggerEvent)
42+
}
43+
44+
private interpretMachineAndWaitForResult = async (stateMachine: any, triggerEventType?: string): Promise<any> => {
45+
const interpretedMachine = interpret(stateMachine).start()
46+
let result, error
47+
interpretedMachine.subscribe((state: any) => {
48+
switch (state.value) {
49+
case CommonStateMachineStates.WRAP_UP_EXECUTION:
50+
result = state.context.result
51+
case CommonStateMachineStates.ERROR:
52+
error = state.context.error
53+
}
54+
})
55+
if (triggerEventType) {
56+
interpretedMachine.send({ type: triggerEventType })
57+
}
58+
59+
try {
60+
await waitFor(
61+
interpretedMachine,
62+
(state) =>
63+
state.matches(CommonStateMachineStates.WRAP_UP_EXECUTION) || state.matches(CommonStateMachineStates.ERROR),
64+
{ timeout: stateMachine?.context?.timeout || 10000 },
65+
)
66+
} catch (error) {
67+
this.logger.error(`Error wait for xstate ${error}`)
68+
throw error
69+
}
70+
71+
if (error) {
72+
throw error
73+
}
74+
return result
75+
}
76+
77+
protected getEntityCurrentState(context: Context): string | Promise<string> {
78+
return CommonStateMachineStates.REHYDRATE
79+
}
80+
}
81+
82+
export const getOnDoneAction = () => {
83+
return {
84+
target: CommonStateMachineStates.WRAP_UP_EXECUTION,
85+
actions: assign({
86+
result: (context, event: AnyEventObject) => {
87+
return event.data
88+
},
89+
}),
90+
}
91+
}
92+
93+
export const getOnErrorAction = () => {
94+
return {
95+
target: CommonStateMachineStates.ERROR,
96+
actions: assign({
97+
error: (context, event: AnyEventObject) => {
98+
return event.data
99+
},
100+
}),
101+
}
102+
}
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import { getOnDoneAction, getOnErrorAction } from './base'
2+
import { AnyEventObject, assign, InvokeConfig } from 'xstate'
3+
4+
import { CommonStateMachineStates } from './base'
5+
import { VerificationRequestMessages, VerificationRequestStateMachineEvents, VerificationRequestStateMachineStates } from '../dto/types'
6+
import { VerificationRequestStateMachineContext, VerificationRequestStateMachineService } from './verification-request.state-machine.interface'
7+
import { VerificationRequestService } from '../verification-request.service'
8+
import { CreateAiTaskDto } from '../../ai-task/dto/create-ai-task.dto'
9+
import { AiTaskType, CallbackRoute, DEFAULT_EMBEDDING_MODEL } from '../../ai-task/constants/ai-task.constants'
10+
11+
12+
const verificationRequestStateMachineSchema = {
13+
context: {} as VerificationRequestStateMachineContext,
14+
events: {} as
15+
| { type: VerificationRequestStateMachineEvents.CREATE }
16+
| { type: VerificationRequestStateMachineEvents.EMBED }
17+
| { type: VerificationRequestStateMachineEvents.IDENTIFY_DATA }
18+
| { type: VerificationRequestStateMachineEvents.DEFINE_TOPICS }
19+
| { type: VerificationRequestStateMachineEvents.DEFINE_IMPACT_AREA }
20+
| { type: VerificationRequestStateMachineEvents.DEFINE_SEVERITY }
21+
}
22+
23+
const getStateInvokeSrc = (
24+
eventName: string,
25+
getVerificationRequestService: () => VerificationRequestService,
26+
): InvokeConfig<any, any>['src'] => {
27+
switch (eventName) {
28+
case VerificationRequestStateMachineEvents.CREATE:
29+
return async (context: VerificationRequestStateMachineContext, event, meta) => {
30+
return getVerificationRequestService().create(context.verificationRequest)
31+
}
32+
case VerificationRequestStateMachineEvents.EMBED:
33+
return async (context: VerificationRequestStateMachineContext, event, meta) => {
34+
const taskDto: CreateAiTaskDto = {
35+
type: AiTaskType.TEXT_EMBEDDING,
36+
content: {
37+
text: context.verificationRequest.content,
38+
model: DEFAULT_EMBEDDING_MODEL,
39+
},
40+
callbackRoute: CallbackRoute.VERIFICATION_UPDATE_EMBEDDING,
41+
callbackParams: {
42+
targetId: context.verificationRequest.id,
43+
field: "embedding",
44+
},
45+
};
46+
await getVerificationRequestService().createAiTask(taskDto)
47+
return context.result
48+
}
49+
default:
50+
return async (context: VerificationRequestStateMachineContext, event, meta) => {
51+
return
52+
}
53+
}
54+
}
55+
56+
type ConditionalFunc = (context: any, event: any) => boolean
57+
// TODO: add conditional functions
58+
59+
const getStateInvoke = (
60+
eventName: VerificationRequestStateMachineEvents,
61+
stateMachineService: VerificationRequestStateMachineService,
62+
): InvokeConfig<any, any> => {
63+
const getVerificationRequestService = () => stateMachineService.verificationRequestService
64+
switch (eventName) {
65+
case VerificationRequestStateMachineEvents.CREATE:
66+
return {
67+
id: eventName,
68+
src: getStateInvokeSrc(eventName, getVerificationRequestService),
69+
onDone: [
70+
{
71+
target: VerificationRequestStateMachineStates.EMBEDDING,
72+
actions: assign({
73+
result: (context, event: AnyEventObject) => {
74+
return event.data
75+
},
76+
verificationRequest: (context: any, event: AnyEventObject) => {
77+
return { id: event.data.id, ...context.verificationRequest }
78+
},
79+
}),
80+
},
81+
],
82+
onError: getOnErrorAction(),
83+
}
84+
default:
85+
return {
86+
id: eventName,
87+
src: getStateInvokeSrc(eventName, getVerificationRequestService),
88+
onDone: getOnDoneAction(),
89+
onError: getOnErrorAction(),
90+
}
91+
}
92+
}
93+
94+
const getStateTransitions = (originState: VerificationRequestStateMachineStates | CommonStateMachineStates) => {
95+
switch (originState) {
96+
case CommonStateMachineStates.REHYDRATE:
97+
return {
98+
[VerificationRequestStateMachineEvents.REQUEST]: {
99+
target: VerificationRequestStateMachineStates.REQUESTING,
100+
},
101+
[VerificationRequestStateMachineEvents.EMBED]: {
102+
target: VerificationRequestStateMachineStates.EMBEDDING,
103+
},
104+
}
105+
case VerificationRequestStateMachineStates.EMBEDDING:
106+
return {
107+
[VerificationRequestStateMachineEvents.EMBED]: {
108+
target: VerificationRequestStateMachineStates.EMBEDDING,
109+
},
110+
}
111+
case VerificationRequestStateMachineStates.IDENTIFYING_DATA:
112+
return {
113+
[VerificationRequestStateMachineEvents.IDENTIFY_DATA]: {
114+
target: VerificationRequestStateMachineStates.IDENTIFYING_DATA,
115+
},
116+
[VerificationRequestStateMachineEvents.DEFINE_TOPICS]: {
117+
target: VerificationRequestStateMachineStates.DEFINING_TOPICS,
118+
},
119+
[VerificationRequestStateMachineEvents.DEFINE_IMPACT_AREA]: {
120+
target: VerificationRequestStateMachineStates.DEFINING_IMPACT_AREA,
121+
},
122+
[VerificationRequestStateMachineEvents.DEFINE_SEVERITY]: {
123+
target: VerificationRequestStateMachineStates.DEFINING_SEVERITY,
124+
},
125+
}
126+
default:
127+
return {
128+
on: {},
129+
}
130+
}
131+
}
132+
/**
133+
* TODO: after creation we need trigger other AI tasks to be executed
134+
*/
135+
export const getVerificationRequestStateMachineConfig = (stateMachineService: VerificationRequestStateMachineService) => {
136+
return {
137+
id: 'verificationRequest',
138+
initial: CommonStateMachineStates.REHYDRATE,
139+
states: {
140+
[CommonStateMachineStates.REHYDRATE]: {
141+
description: VerificationRequestMessages.DESCRIPTIONS.REQUESTING,
142+
on: getStateTransitions(CommonStateMachineStates.REHYDRATE),
143+
},
144+
[VerificationRequestStateMachineStates.IDENTIFYING_DATA]: {
145+
invoke: getStateInvoke(VerificationRequestStateMachineEvents.IDENTIFY_DATA, stateMachineService),
146+
description: VerificationRequestMessages.DESCRIPTIONS.DEFAULT,
147+
},
148+
[VerificationRequestStateMachineStates.CREATING]: {
149+
invoke: getStateInvoke(VerificationRequestStateMachineEvents.CREATE, stateMachineService),
150+
description: VerificationRequestMessages.DESCRIPTIONS.CREATING,
151+
},
152+
[VerificationRequestStateMachineStates.REQUESTING]: {
153+
invoke: getStateInvoke(VerificationRequestStateMachineEvents.CREATE, stateMachineService),
154+
description: VerificationRequestMessages.DESCRIPTIONS.REQUESTING,
155+
},
156+
[VerificationRequestStateMachineStates.EMBEDDING]: {
157+
invoke: getStateInvoke(VerificationRequestStateMachineEvents.EMBED, stateMachineService),
158+
description: VerificationRequestMessages.DESCRIPTIONS.EMBED,
159+
},
160+
[CommonStateMachineStates.ERROR]: {
161+
description: VerificationRequestMessages.DESCRIPTIONS.ERROR,
162+
type: 'final' as const,
163+
},
164+
[CommonStateMachineStates.WRAP_UP_EXECUTION]: {
165+
type: 'final' as const,
166+
},
167+
},
168+
on: {
169+
'*': {
170+
actions: 'logInvalidEvent',
171+
},
172+
},
173+
schema: verificationRequestStateMachineSchema,
174+
context: {},
175+
predictableActionArguments: true,
176+
preserveActionOrder: true,
177+
}
178+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { StateMachineContext } from './base'
2+
import { VerificationRequestService } from '../verification-request.service'
3+
4+
export interface VerificationRequestStateMachineContext extends StateMachineContext {
5+
verificationRequest: (
6+
| any // TODO: improve this type
7+
) & {
8+
id?: string
9+
}
10+
}
11+
12+
export interface VerificationRequestStateMachineService {
13+
verificationRequestService: VerificationRequestService
14+
}

0 commit comments

Comments
 (0)