Include voice recording payload in analysis
Build and deploy Worker / build (push) Successful in 53s

This commit is contained in:
Ruslan Bakiev
2026-06-12 21:09:07 +07:00
parent 321eab6d6d
commit 5021b55537
8 changed files with 2599 additions and 167 deletions
+23
View File
@@ -24,7 +24,16 @@ model Place {
longitude Float
googlePrimaryType String?
googleTypes String[] @default([])
googleBusinessStatus String?
googleRating Float?
googleUserRatingCount Int?
googleRegularOpeningHours Json?
googleCurrentOpeningHours Json?
googlePhotos Json?
googlePayload Json?
googlePayloadFetchedAt DateTime?
experiences VoiceExperience[]
favoriteUsers UserFavoritePlace[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
@@ -39,10 +48,22 @@ model User {
languageCode String?
isAdmin Boolean @default(false)
voiceExperiences VoiceExperience[]
favoritePlaces UserFavoritePlace[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model UserFavoritePlace {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id])
placeId String
place Place @relation(fields: [placeId], references: [id])
createdAt DateTime @default(now())
@@unique([userId, placeId])
}
model VoiceExperience {
id String @id @default(cuid())
placeId String
@@ -57,6 +78,8 @@ model VoiceExperience {
status VoiceExperienceStatus @default(UPLOADED)
transcript String?
analysis Json?
recordingPayload Json?
promptHintsShown String[] @default([])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
File diff suppressed because one or more lines are too long
+23 -5
View File
@@ -128,6 +128,14 @@ exports.Prisma.PlaceScalarFieldEnum = {
longitude: 'longitude',
googlePrimaryType: 'googlePrimaryType',
googleTypes: 'googleTypes',
googleBusinessStatus: 'googleBusinessStatus',
googleRating: 'googleRating',
googleUserRatingCount: 'googleUserRatingCount',
googleRegularOpeningHours: 'googleRegularOpeningHours',
googleCurrentOpeningHours: 'googleCurrentOpeningHours',
googlePhotos: 'googlePhotos',
googlePayload: 'googlePayload',
googlePayloadFetchedAt: 'googlePayloadFetchedAt',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
@@ -145,6 +153,13 @@ exports.Prisma.UserScalarFieldEnum = {
updatedAt: 'updatedAt'
};
exports.Prisma.UserFavoritePlaceScalarFieldEnum = {
id: 'id',
userId: 'userId',
placeId: 'placeId',
createdAt: 'createdAt'
};
exports.Prisma.VoiceExperienceScalarFieldEnum = {
id: 'id',
placeId: 'placeId',
@@ -157,6 +172,8 @@ exports.Prisma.VoiceExperienceScalarFieldEnum = {
status: 'status',
transcript: 'transcript',
analysis: 'analysis',
recordingPayload: 'recordingPayload',
promptHintsShown: 'promptHintsShown',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
@@ -176,16 +193,16 @@ exports.Prisma.QueryMode = {
insensitive: 'insensitive'
};
exports.Prisma.NullsOrder = {
first: 'first',
last: 'last'
};
exports.Prisma.JsonNullValueFilter = {
DbNull: Prisma.DbNull,
JsonNull: Prisma.JsonNull,
AnyNull: Prisma.AnyNull
};
exports.Prisma.NullsOrder = {
first: 'first',
last: 'last'
};
exports.VoiceExperienceStatus = exports.$Enums.VoiceExperienceStatus = {
UPLOADED: 'UPLOADED',
TRANSCRIBING: 'TRANSCRIBING',
@@ -198,6 +215,7 @@ exports.VoiceExperienceStatus = exports.$Enums.VoiceExperienceStatus = {
exports.Prisma.ModelName = {
Place: 'Place',
User: 'User',
UserFavoritePlace: 'UserFavoritePlace',
VoiceExperience: 'VoiceExperience'
};
+2433 -118
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "prisma-client-404088dc715856cb4ed2e7f2d4b8eb35c87427ce32c0e67bc04d6ff67961fc2b",
"name": "prisma-client-baa0475e783dd87e65df5f828f4a43807c3762e0c66b9d15b841a12e00ad0bd4",
"main": "index.js",
"types": "index.d.ts",
"browser": "default.js",
+23
View File
@@ -24,7 +24,16 @@ model Place {
longitude Float
googlePrimaryType String?
googleTypes String[] @default([])
googleBusinessStatus String?
googleRating Float?
googleUserRatingCount Int?
googleRegularOpeningHours Json?
googleCurrentOpeningHours Json?
googlePhotos Json?
googlePayload Json?
googlePayloadFetchedAt DateTime?
experiences VoiceExperience[]
favoriteUsers UserFavoritePlace[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
@@ -39,10 +48,22 @@ model User {
languageCode String?
isAdmin Boolean @default(false)
voiceExperiences VoiceExperience[]
favoritePlaces UserFavoritePlace[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model UserFavoritePlace {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id])
placeId String
place Place @relation(fields: [placeId], references: [id])
createdAt DateTime @default(now())
@@unique([userId, placeId])
}
model VoiceExperience {
id String @id @default(cuid())
placeId String
@@ -57,6 +78,8 @@ model VoiceExperience {
status VoiceExperienceStatus @default(UPLOADED)
transcript String?
analysis Json?
recordingPayload Json?
promptHintsShown String[] @default([])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
+17
View File
@@ -26,6 +26,11 @@ type PlaceAnalysis = {
summary: string;
};
type RecordingContext = {
promptHintsShown: string[];
recordingPayload: unknown;
};
export async function analyzeVoiceExperience(experienceId: string) {
const experience = await prisma.voiceExperience.findUnique({
where: { id: experienceId },
@@ -48,6 +53,8 @@ export async function analyzeVoiceExperience(experienceId: string) {
const analysis = await buildPlaceAnalysis({
placeName: experience.place.name,
transcript: experience.transcript,
promptHintsShown: experience.promptHintsShown,
recordingPayload: experience.recordingPayload,
});
await prisma.voiceExperience.update({
@@ -64,6 +71,8 @@ export async function analyzeVoiceExperience(experienceId: string) {
async function buildPlaceAnalysis(input: {
placeName: string;
transcript: string;
promptHintsShown: string[];
recordingPayload: unknown;
}): Promise<PlaceAnalysis> {
if (!config.openRouterApiKey) {
throw new Error('OPENROUTER_API_KEY is required for voice analysis.');
@@ -146,6 +155,7 @@ async function buildPlaceAnalysis(input: {
},
placeName: input.placeName,
ontology,
recordingContext: recordingContext(input),
transcript: input.transcript,
}),
},
@@ -174,6 +184,13 @@ async function buildPlaceAnalysis(input: {
return analysis;
}
function recordingContext(input: RecordingContext) {
return {
promptHintsShown: input.promptHintsShown,
recordingPayload: input.recordingPayload,
};
}
function assertValidAnalysis(analysis: PlaceAnalysis) {
const allowedTags = new Set(
placeOntology.flatMap((axis) =>