feat: base code

This commit is contained in:
2024-11-23 23:48:25 +01:00
parent 7d962abaaa
commit 13b5beeb7e
22 changed files with 5294 additions and 1 deletions

2
.env.example Normal file
View File

@@ -0,0 +1,2 @@
DATABASE_URL="postgresql://postgres:postgres@localhost:5433/persona?schema=public&connect_timeout=300"
ANTHROPIC_API_KEY=

24
.eslintrc.json Normal file
View File

@@ -0,0 +1,24 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"next/core-web-vitals",
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["@typescript-eslint"],
"rules": {
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/consistent-type-definitions": "error",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "error"
}
}

4
.gitattributes vendored Normal file
View File

@@ -0,0 +1,4 @@
/.yarn/** linguist-vendored
/.yarn/releases/* binary
/.yarn/plugins/**/* binary
/.pnp.* binary linguist-generated

4
.gitignore vendored
View File

@@ -128,3 +128,7 @@ dist
.yarn/build-state.yml .yarn/build-state.yml
.yarn/install-state.gz .yarn/install-state.gz
.pnp.* .pnp.*
.editorconfig
personas/

9
.prettierrc Normal file
View File

@@ -0,0 +1,9 @@
{
"semi": true,
"trailingComma": "none",
"singleQuote": true,
"printWidth": 80,
"jsxSingleQuote": true,
"tabWidth": 2,
"arrowParens": "avoid"
}

5
.yarnrc.yml Normal file
View File

@@ -0,0 +1,5 @@
compressionLevel: mixed
enableGlobalCache: false
nodeLinker: node-modules

View File

@@ -1 +1 @@
# purchases-personas # purchases-personas

3
commitlint.config.ts Normal file
View File

@@ -0,0 +1,3 @@
module.exports = {
extends: ['@commitlint/config-conventional']
};

17
docker-compose.yml Normal file
View File

@@ -0,0 +1,17 @@
version: "3.8"
services:
postgres:
container_name: personadb
image: postgres:15
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: persona
ports:
- "5433:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:

40
package.json Normal file
View File

@@ -0,0 +1,40 @@
{
"name": "purchases-personas",
"scripts": {
"start": "prisma generate && node dist/index.js",
"dev": "nodemon src/index.ts",
"build": "tsc",
"lint": "next lint",
"format": "prettier --config .prettierrc '**/*.{ts,json,md}' --write",
"typecheck": "tsc --noEmit",
"prepare": "husky install",
"audit": "audit-ci",
"generate": "prisma generate",
"migrate": "prisma migrate dev"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.32.1",
"@prisma/client": "^5.22.0",
"crypto": "^1.0.1",
"dotenv": "^16.4.5",
"express": "^4.21.1"
},
"devDependencies": {
"@commitlint/cli": "^18.4.3",
"@commitlint/config-conventional": "^18.4.3",
"@types/express": "^4.17.21",
"@types/node": "^20.10.0",
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "^6.12.0",
"audit-ci": "^6.6.1",
"eslint": "^8",
"eslint-config-prettier": "^9.0.0",
"husky": "^8.0.3",
"lint-staged": "^15.1.0",
"nodemon": "^3.0.2",
"prettier": "^3.1.0",
"prisma": "^5.8.0",
"ts-node": "^10.9.2",
"typescript": "^5.3.0"
}
}

View File

@@ -0,0 +1,29 @@
-- CreateTable
CREATE TABLE "users" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"age" INTEGER NOT NULL,
"persona" JSONB NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "items" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"amount" DOUBLE PRECISION NOT NULL,
"datetime" TIMESTAMP(3) NOT NULL,
"location" TEXT NOT NULL,
"notes" TEXT,
"userId" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "items_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "items" ADD CONSTRAINT "items_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"

35
prisma/schema.prisma Normal file
View File

@@ -0,0 +1,35 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
name String
age Int
persona Json
items Item[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("users")
}
model Item {
id Int @id @default(autoincrement())
name String
amount Float
datetime DateTime
location String
notes String?
user User @relation(fields: [userId], references: [id])
userId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("items")
}

11
src/index.ts Normal file
View File

@@ -0,0 +1,11 @@
import { makeRequest } from './utils/anthropicClient';
import { savePersonaJson } from './utils/savePersonaJson';
import { savePersonaDb } from './utils/savePersonaDb';
const personaPromise = makeRequest();
personaPromise.then(persona => {
savePersonaDb(persona.text).then(id => savePersonaJson(persona.text, id));
console.log('New persona:', persona.text);
});

View File

@@ -0,0 +1,105 @@
import 'dotenv/config';
import Anthropic from '@anthropic-ai/sdk';
/*
THIS IS THE PROMPT IN HUMAN-READABLE FORMAT:
Generate a JSON object for a persona with the following schema:
{
"core": {
"age": <number>,
"name": <string>,
"occupation": {
"title": <string>,
"level": <string>,
"income": <number>,
"location": <string>,
"schedule": <array of strings>
},
"home": {
"type": <string>,
"ownership": <string>,
"location": <string>,
"commute_distance_km": <number>
},
"household": {
"status": <string>,
"members": <array of strings>,
"pets": <array of objects>
}
},
"routines": {
"weekday": <object with 24 hour timeline>,
"weekend": <array of regular activities>,
"commute": {
"method": <string>,
"route": <array of locations>,
"regular_stops": <array of objects>
}
},
"preferences": {
"diet": <array of strings>,
"brands": <array of objects with loyalty scores>,
"price_sensitivity": <number 1-10>,
"payment_methods": <array of strings>,
"shopping": {
"grocery_stores": <array of objects with frequency>,
"coffee_shops": <array of objects with frequency>,
"restaurants": <array of objects with frequency>,
"retail": <array of objects with frequency>
}
},
"finances": {
"subscriptions": <array of objects with amounts and dates>,
"regular_bills": <array of objects with amounts and dates>,
"spending_patterns": {
"impulsive_score": <number 1-10>,
"categories": <object with category preferences>
}
},
"habits": {
"exercise": <array of objects with schedule>,
"social": <array of objects with frequency>,
"entertainment": <array of objects with frequency>,
"vices": <array of objects with frequency>
},
"context": {
"stress_triggers": <array of strings>,
"reward_behaviors": <array of strings>,
"upcoming_events": <array of objects with dates>,
"recent_changes": <array of strings>
}
}
Generate realistic values for all fields, including specific store names, brands, and locations. All temporal data should be relative to the current week. Frequencies should be specified as times per week. Include coordinates for locations. Use ISO timestamps for dates.
*/
const personaCreationPrompt =
'Generate a detailed JSON object for a realistic persona following this schema, populating all fields with specific real-world values including store names, brands, coordinates, and locations. Use frequencies per week, ISO timestamps relative to the current week, and include coordinates for all locations. The output should be a complete JSON object with core demographic details (age, job, housing, household), daily routines, shopping preferences with actual store names and visit frequencies, brand loyalties with scores, diet, payment preferences, exercise habits, social patterns, entertainment choices, upcoming events, and stress factors. The data should reflect the current week for all temporal values. Schema: {"core":{age:<number>,"name": <string>,occupation:{title:<string>,level:<string>,income:<number>,location:<string>,schedule:<array of strings>},home:{type:<string>,ownership:<string>,location:<string>,commute_distance_km:<number>},household:{status:<string>,members:<array of strings>,pets:<array of objects>}},"routines":{weekday:<object with 24 hour timeline>,weekend:<array of regular activities>,commute:{method:<string>,route:<array of locations>,regular_stops:<array of objects>}},"preferences":{diet:<array of strings>,brands:<array of objects with loyalty scores>,price_sensitivity:<number 1-10>,payment_methods:<array of strings>,shopping:{grocery_stores:<array of objects with frequency>,coffee_shops:<array of objects with frequency>,restaurants:<array of objects with frequency>,retail:<array of objects with frequency>}},"finances":{subscriptions:<array of objects with amounts and dates>,regular_bills:<array of objects with amounts and dates>,spending_patterns:{impulsive_score:<number 1-10>,categories:<object with category preferences>}},"habits":{exercise:<array of objects with schedule>,social:<array of objects with frequency>,entertainment:<array of objects with frequency>,vices:<array of objects with frequency>},"context":{stress_triggers:<array of strings>,reward_behaviors:<array of strings>,upcoming_events:<array of objects with dates>,recent_changes:<array of strings>}}';
export async function makeRequest() {
if (!process.env.ANTHROPIC_API_KEY) {
throw Error('Anthropic API key missing.');
}
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY
});
const response = await anthropic.messages.create({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 2000,
messages: [{ role: 'user', content: personaCreationPrompt }]
});
if (
response.stop_reason &&
response.stop_reason !== 'end_turn' &&
response.stop_reason !== 'stop_sequence'
) {
throw Error(response.stop_reason);
}
console.log('Message ID:', response.id);
return response.content[0] as { text: string };
}

View File

@@ -0,0 +1,5 @@
import { Persona } from './types';
export function parsePersona(persona: string) {
return JSON.parse(persona) as Persona;
}

View File

@@ -0,0 +1,3 @@
import { PrismaClient } from '@prisma/client';
export const prisma = new PrismaClient();

View File

@@ -0,0 +1,17 @@
import { prisma } from './prismaClient';
export async function savePersonaDb(persona: string) {
const personaObject = JSON.parse(persona);
const result = await prisma.user.create({
data: {
name: personaObject['core']['name'],
age: personaObject['core']['age'],
persona: JSON.stringify(persona)
}
});
console.log(`Persona ${result.name} inserted with ID ${result.id}`);
return result.id;
}

View File

@@ -0,0 +1,10 @@
import fs from 'fs';
import { parsePersona } from './parsePersona';
export function savePersonaJson(persona: string, id: number) {
fs.promises.writeFile(`personas/${id}.json`, persona, 'utf8');
const personaObject = parsePersona(persona);
console.log(`Persona ${personaObject.core.name} saved as persona/${id}.json`);
}

118
src/utils/types.ts Normal file
View File

@@ -0,0 +1,118 @@
type Pet = {
[key: string]: any;
};
type FrequencyObject = {
name: string;
frequency: number;
};
type SubscriptionBill = {
name: string;
amount: number;
date: string | Date;
};
type ActivityObject = {
name: string;
frequency: number;
schedule?: string[];
};
type BrandLoyalty = {
name: string;
loyaltyScore: number;
};
type Event = {
name: string;
date: string | Date;
details?: string;
};
type TimelineActivity = {
activity: string;
duration: string;
location?: string;
};
type RegularStop = {
location: string;
purpose: string;
frequency: string;
};
type SpendingCategories = {
[category: string]: {
preference: number;
frequency: number;
};
};
export type Persona = {
core: {
age: number;
name: string;
occupation: {
title: string;
level: string;
income: number;
location: string;
schedule: string[];
};
home: {
type: string;
ownership: string;
location: string;
commute_distance_km: number;
};
household: {
status: string;
members: string[];
pets: Pet[];
};
};
routines: {
weekday: {
[hour: string]: TimelineActivity;
};
weekend: ActivityObject[];
commute: {
method: string;
route: string[];
regular_stops: RegularStop[];
};
};
preferences: {
diet: string[];
brands: BrandLoyalty[];
price_sensitivity: number;
payment_methods: string[];
shopping: {
grocery_stores: FrequencyObject[];
coffee_shops: FrequencyObject[];
restaurants: FrequencyObject[];
retail: FrequencyObject[];
};
};
finances: {
subscriptions: SubscriptionBill[];
regular_bills: SubscriptionBill[];
spending_patterns: {
impulsive_score: number;
categories: SpendingCategories;
};
};
habits: {
exercise: ActivityObject[];
social: ActivityObject[];
entertainment: ActivityObject[];
vices: ActivityObject[];
};
context: {
stress_triggers: string[];
reward_behaviors: string[];
upcoming_events: Event[];
recent_changes: string[];
};
};

18
tsconfig.json Normal file
View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "es2017",
"module": "commonjs",
"lib": ["es6"],
"allowJs": true,
"outDir": "dist",
"rootDir": "src",
"strict": true,
"noImplicitAny": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"types": ["node"],
"typeRoots": ["./node_modules/@types"]
},
"include": ["src/**/*", "utils/anthropicClient.ts"],
"exclude": ["node_modules", "dist"]
}

4831
yarn.lock Normal file

File diff suppressed because it is too large Load Diff