Update network-app docs: new stack (Flutter, Elysia, Bun, Postgres)
This commit is contained in:
@@ -2,8 +2,9 @@
|
|||||||
|
|
||||||
**Client:** David DePoyster
|
**Client:** David DePoyster
|
||||||
**Company:** NWM (Wealth Management Firm)
|
**Company:** NWM (Wealth Management Firm)
|
||||||
**Status:** Planning Phase
|
**Developer:** Donovan Kelly
|
||||||
**Last Updated:** 2025-06-26
|
**Status:** Development Phase
|
||||||
|
**Last Updated:** 2026-01-27
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -14,39 +15,67 @@ The Network App is an AI-powered CRM mobile application designed specifically fo
|
|||||||
Wealth managers juggling 100+ high-net-worth clients struggle to:
|
Wealth managers juggling 100+ high-net-worth clients struggle to:
|
||||||
- Remember personal details (interests, family, preferences)
|
- Remember personal details (interests, family, preferences)
|
||||||
- Send timely, personalized communications
|
- Send timely, personalized communications
|
||||||
- Identify networking opportunities between clients
|
|
||||||
- Track important dates (birthdays, anniversaries)
|
- Track important dates (birthdays, anniversaries)
|
||||||
- Maintain GDPR/HIPAA compliance while staying personal
|
- Maintain compliance while staying personal
|
||||||
|
|
||||||
## Solution
|
## Solution
|
||||||
|
|
||||||
A mobile-first CRM that uses AI (Claude) to:
|
A mobile-first CRM that uses AI to:
|
||||||
1. **Enrich client profiles** with deep personal context
|
1. **Enrich client profiles** with deep personal context
|
||||||
2. **Generate personalized communications** that feel authentic
|
2. **Generate personalized communications** that feel authentic
|
||||||
3. **Match clients** who would benefit from knowing each other
|
3. **Automate thoughtful touches** for important dates
|
||||||
4. **Automate thoughtful touches** for important dates
|
4. **Ensure compliance** with financial industry regulations
|
||||||
5. **Ensure compliance** with financial industry regulations
|
|
||||||
|
|
||||||
## Key Differentiators
|
|
||||||
|
|
||||||
- **Relationship-first design** vs transaction-tracking
|
|
||||||
- **AI-powered introductions** connecting clients meaningfully
|
|
||||||
- **Mobile-native** for advisors on the go
|
|
||||||
- **Compliance-built-in** for wealth management industry
|
|
||||||
|
|
||||||
## Tech Stack
|
## Tech Stack
|
||||||
|
|
||||||
- **Frontend:** Swift/SwiftUI (iOS)
|
| Layer | Technology |
|
||||||
- **Backend:** Firebase (Firestore, Auth, Functions)
|
|-------|------------|
|
||||||
- **AI:** Claude API (Anthropic)
|
| Frontend | Flutter (Dart) |
|
||||||
- **Initial Scale:** 100+ clients
|
| Backend | Elysia + Bun (TypeScript) |
|
||||||
|
| Database | PostgreSQL |
|
||||||
|
| ORM | Drizzle |
|
||||||
|
| Auth | BetterAuth |
|
||||||
|
| AI | LangChain.js (model-agnostic) |
|
||||||
|
| Email | Resend |
|
||||||
|
| Jobs | pg-boss |
|
||||||
|
| Deploy | Dokploy (self-hosted VM) |
|
||||||
|
|
||||||
|
### Why This Stack
|
||||||
|
|
||||||
|
- **Flutter** — Cross-platform (iOS + Android) from one codebase
|
||||||
|
- **Elysia + Bun** — Fast, type-safe, excellent DX
|
||||||
|
- **Drizzle** — Lightweight ORM, great TypeScript support
|
||||||
|
- **BetterAuth** — Modern TypeScript-native auth
|
||||||
|
- **LangChain.js** — Swap AI models (Claude, GPT, Gemini) without code changes
|
||||||
|
- **Self-hosted** — Full control, predictable costs
|
||||||
|
|
||||||
|
## MVP Scope
|
||||||
|
|
||||||
|
### Phase 1: Core (Build First)
|
||||||
|
- [ ] User authentication (login/logout)
|
||||||
|
- [ ] Client CRUD (add, edit, view, delete)
|
||||||
|
- [ ] Client search and filtering
|
||||||
|
- [ ] AI email generation
|
||||||
|
- [ ] Birthday/event tracking
|
||||||
|
|
||||||
|
### Deferred (Post-MVP)
|
||||||
|
- Client matching/introductions
|
||||||
|
- Push notifications
|
||||||
|
- Offline mode
|
||||||
|
- Multi-advisor/team features
|
||||||
|
- File attachments
|
||||||
|
|
||||||
## Project Documents
|
## Project Documents
|
||||||
|
|
||||||
- [Requirements](./requirements.md) - Full feature specification
|
- [Requirements](./requirements.md) — Full feature specification
|
||||||
- [Competitors](./competitors.md) - Market research and analysis
|
- [Architecture](./architecture.md) — Technical design
|
||||||
- [Feasibility](./feasibility.md) - Technical assessment
|
- [Competitors](./competitors.md) — Market research
|
||||||
- [Blueprint](./blueprint.md) - Architecture and development plan
|
- [API Spec](./api-spec.md) — Backend endpoints
|
||||||
|
|
||||||
|
## Repositories
|
||||||
|
|
||||||
|
- Frontend: `network-app-mobile`
|
||||||
|
- Backend: `network-app-api`
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
|
|
||||||
|
|||||||
476
projects/network-app/architecture.md
Normal file
476
projects/network-app/architecture.md
Normal file
@@ -0,0 +1,476 @@
|
|||||||
|
# The Network App — Architecture
|
||||||
|
|
||||||
|
**Last Updated:** 2026-01-27
|
||||||
|
|
||||||
|
## System Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||||
|
│ Flutter App │────▶│ Elysia API │────▶│ PostgreSQL │
|
||||||
|
│ (iOS/Android) │ │ (Bun runtime) │ │ │
|
||||||
|
└─────────────────┘ └────────┬────────┘ └─────────────────┘
|
||||||
|
│
|
||||||
|
┌────────────┼────────────┐
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||||
|
│ LangChain│ │ Resend │ │ pg-boss │
|
||||||
|
│ (AI) │ │ (Email) │ │ (Jobs) │
|
||||||
|
└──────────┘ └──────────┘ └──────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backend Architecture
|
||||||
|
|
||||||
|
### Framework: Elysia + Bun
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Example structure
|
||||||
|
src/
|
||||||
|
├── index.ts // Entry point
|
||||||
|
├── routes/
|
||||||
|
│ ├── auth.ts // BetterAuth routes
|
||||||
|
│ ├── clients.ts // Client CRUD
|
||||||
|
│ ├── emails.ts // AI email generation
|
||||||
|
│ └── events.ts // Birthday/event tracking
|
||||||
|
├── services/
|
||||||
|
│ ├── ai.ts // LangChain integration
|
||||||
|
│ ├── email.ts // Resend integration
|
||||||
|
│ └── jobs.ts // pg-boss job definitions
|
||||||
|
├── db/
|
||||||
|
│ ├── schema.ts // Drizzle schema
|
||||||
|
│ ├── migrations/ // SQL migrations
|
||||||
|
│ └── index.ts // DB connection
|
||||||
|
├── lib/
|
||||||
|
│ ├── auth.ts // BetterAuth config
|
||||||
|
│ └── validation.ts // Zod schemas
|
||||||
|
└── types/
|
||||||
|
└── index.ts // Shared types
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Schema (Drizzle)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// db/schema.ts
|
||||||
|
import { pgTable, text, timestamp, uuid, boolean, jsonb } from 'drizzle-orm/pg-core';
|
||||||
|
|
||||||
|
export const users = pgTable('users', {
|
||||||
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
|
email: text('email').notNull().unique(),
|
||||||
|
name: text('name').notNull(),
|
||||||
|
createdAt: timestamp('created_at').defaultNow(),
|
||||||
|
updatedAt: timestamp('updated_at').defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const clients = pgTable('clients', {
|
||||||
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
|
userId: uuid('user_id').references(() => users.id).notNull(),
|
||||||
|
|
||||||
|
// Basic info
|
||||||
|
firstName: text('first_name').notNull(),
|
||||||
|
lastName: text('last_name').notNull(),
|
||||||
|
email: text('email'),
|
||||||
|
phone: text('phone'),
|
||||||
|
|
||||||
|
// Professional
|
||||||
|
company: text('company'),
|
||||||
|
role: text('role'),
|
||||||
|
industry: text('industry'),
|
||||||
|
|
||||||
|
// Personal
|
||||||
|
birthday: timestamp('birthday'),
|
||||||
|
anniversary: timestamp('anniversary'),
|
||||||
|
interests: jsonb('interests').$type<string[]>().default([]),
|
||||||
|
family: jsonb('family').$type<{
|
||||||
|
spouse?: string;
|
||||||
|
children?: string[];
|
||||||
|
}>(),
|
||||||
|
notes: text('notes'),
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
tags: jsonb('tags').$type<string[]>().default([]),
|
||||||
|
lastContactedAt: timestamp('last_contacted_at'),
|
||||||
|
createdAt: timestamp('created_at').defaultNow(),
|
||||||
|
updatedAt: timestamp('updated_at').defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const events = pgTable('events', {
|
||||||
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
|
userId: uuid('user_id').references(() => users.id).notNull(),
|
||||||
|
clientId: uuid('client_id').references(() => clients.id).notNull(),
|
||||||
|
|
||||||
|
type: text('type').notNull(), // 'birthday' | 'anniversary' | 'followup' | 'custom'
|
||||||
|
title: text('title').notNull(),
|
||||||
|
date: timestamp('date').notNull(),
|
||||||
|
recurring: boolean('recurring').default(false),
|
||||||
|
reminderDays: integer('reminder_days').default(7),
|
||||||
|
|
||||||
|
createdAt: timestamp('created_at').defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const communications = pgTable('communications', {
|
||||||
|
id: uuid('id').primaryKey().defaultRandom(),
|
||||||
|
userId: uuid('user_id').references(() => users.id).notNull(),
|
||||||
|
clientId: uuid('client_id').references(() => clients.id).notNull(),
|
||||||
|
|
||||||
|
type: text('type').notNull(), // 'email' | 'birthday' | 'followup'
|
||||||
|
subject: text('subject'),
|
||||||
|
content: text('content').notNull(),
|
||||||
|
aiGenerated: boolean('ai_generated').default(false),
|
||||||
|
status: text('status').default('draft'), // 'draft' | 'sent'
|
||||||
|
sentAt: timestamp('sent_at'),
|
||||||
|
|
||||||
|
createdAt: timestamp('created_at').defaultNow(),
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Routes
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// routes/clients.ts
|
||||||
|
import { Elysia, t } from 'elysia';
|
||||||
|
import { db } from '../db';
|
||||||
|
import { clients } from '../db/schema';
|
||||||
|
import { eq, and, ilike, or } from 'drizzle-orm';
|
||||||
|
|
||||||
|
export const clientRoutes = new Elysia({ prefix: '/clients' })
|
||||||
|
.get('/', async ({ query, user }) => {
|
||||||
|
const { search, tag } = query;
|
||||||
|
|
||||||
|
let conditions = [eq(clients.userId, user.id)];
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
conditions.push(
|
||||||
|
or(
|
||||||
|
ilike(clients.firstName, `%${search}%`),
|
||||||
|
ilike(clients.lastName, `%${search}%`),
|
||||||
|
ilike(clients.company, `%${search}%`)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.select().from(clients).where(and(...conditions));
|
||||||
|
})
|
||||||
|
.get('/:id', async ({ params, user }) => {
|
||||||
|
const client = await db.select()
|
||||||
|
.from(clients)
|
||||||
|
.where(and(eq(clients.id, params.id), eq(clients.userId, user.id)))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!client[0]) throw new Error('Client not found');
|
||||||
|
return client[0];
|
||||||
|
})
|
||||||
|
.post('/', async ({ body, user }) => {
|
||||||
|
const [client] = await db.insert(clients)
|
||||||
|
.values({ ...body, userId: user.id })
|
||||||
|
.returning();
|
||||||
|
return client;
|
||||||
|
})
|
||||||
|
.put('/:id', async ({ params, body, user }) => {
|
||||||
|
const [client] = await db.update(clients)
|
||||||
|
.set({ ...body, updatedAt: new Date() })
|
||||||
|
.where(and(eq(clients.id, params.id), eq(clients.userId, user.id)))
|
||||||
|
.returning();
|
||||||
|
return client;
|
||||||
|
})
|
||||||
|
.delete('/:id', async ({ params, user }) => {
|
||||||
|
await db.delete(clients)
|
||||||
|
.where(and(eq(clients.id, params.id), eq(clients.userId, user.id)));
|
||||||
|
return { success: true };
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### AI Integration (LangChain)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// services/ai.ts
|
||||||
|
import { ChatOpenAI } from '@langchain/openai';
|
||||||
|
import { ChatAnthropic } from '@langchain/anthropic';
|
||||||
|
import { PromptTemplate } from '@langchain/core/prompts';
|
||||||
|
|
||||||
|
// Model-agnostic setup
|
||||||
|
const getModel = (provider: 'openai' | 'anthropic' = 'anthropic') => {
|
||||||
|
if (provider === 'anthropic') {
|
||||||
|
return new ChatAnthropic({
|
||||||
|
modelName: 'claude-3-5-sonnet-20241022',
|
||||||
|
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return new ChatOpenAI({
|
||||||
|
modelName: 'gpt-4-turbo',
|
||||||
|
openAIApiKey: process.env.OPENAI_API_KEY,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const emailPrompt = PromptTemplate.fromTemplate(`
|
||||||
|
You are a professional wealth advisor writing to a valued client.
|
||||||
|
Maintain a warm but professional tone. Incorporate personal details naturally.
|
||||||
|
|
||||||
|
Advisor: {advisorName}
|
||||||
|
Client: {clientName}
|
||||||
|
Their interests: {interests}
|
||||||
|
Recent notes: {notes}
|
||||||
|
Purpose: {purpose}
|
||||||
|
|
||||||
|
Generate a personalized email that feels genuine, not templated.
|
||||||
|
Keep it concise (3-4 paragraphs max).
|
||||||
|
`);
|
||||||
|
|
||||||
|
export async function generateEmail(params: {
|
||||||
|
advisorName: string;
|
||||||
|
clientName: string;
|
||||||
|
interests: string[];
|
||||||
|
notes: string;
|
||||||
|
purpose: string;
|
||||||
|
provider?: 'openai' | 'anthropic';
|
||||||
|
}) {
|
||||||
|
const model = getModel(params.provider);
|
||||||
|
const chain = emailPrompt.pipe(model);
|
||||||
|
|
||||||
|
const response = await chain.invoke({
|
||||||
|
advisorName: params.advisorName,
|
||||||
|
clientName: params.clientName,
|
||||||
|
interests: params.interests.join(', '),
|
||||||
|
notes: params.notes,
|
||||||
|
purpose: params.purpose,
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.content;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Email Service (Resend)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// services/email.ts
|
||||||
|
import { Resend } from 'resend';
|
||||||
|
|
||||||
|
const resend = new Resend(process.env.RESEND_API_KEY);
|
||||||
|
|
||||||
|
export async function sendEmail(params: {
|
||||||
|
to: string;
|
||||||
|
subject: string;
|
||||||
|
content: string;
|
||||||
|
from?: string;
|
||||||
|
}) {
|
||||||
|
const { data, error } = await resend.emails.send({
|
||||||
|
from: params.from || 'David <david@nwm.com>',
|
||||||
|
to: params.to,
|
||||||
|
subject: params.subject,
|
||||||
|
text: params.content,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) throw error;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Background Jobs (pg-boss)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// services/jobs.ts
|
||||||
|
import PgBoss from 'pg-boss';
|
||||||
|
import { db } from '../db';
|
||||||
|
import { events, clients } from '../db/schema';
|
||||||
|
import { generateEmail } from './ai';
|
||||||
|
import { sendEmail } from './email';
|
||||||
|
|
||||||
|
const boss = new PgBoss(process.env.DATABASE_URL);
|
||||||
|
|
||||||
|
export async function initJobs() {
|
||||||
|
await boss.start();
|
||||||
|
|
||||||
|
// Check for upcoming birthdays daily
|
||||||
|
await boss.schedule('check-birthdays', '0 8 * * *'); // 8am daily
|
||||||
|
|
||||||
|
boss.work('check-birthdays', async () => {
|
||||||
|
const upcomingBirthdays = await db.query.events.findMany({
|
||||||
|
where: (events, { eq, and, between }) => and(
|
||||||
|
eq(events.type, 'birthday'),
|
||||||
|
// Check next 7 days
|
||||||
|
between(events.date, new Date(), addDays(new Date(), 7))
|
||||||
|
),
|
||||||
|
with: { client: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const event of upcomingBirthdays) {
|
||||||
|
await boss.send('send-birthday-email', {
|
||||||
|
clientId: event.clientId,
|
||||||
|
eventId: event.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
boss.work('send-birthday-email', async ({ data }) => {
|
||||||
|
// Generate and queue birthday email
|
||||||
|
const client = await db.query.clients.findFirst({
|
||||||
|
where: eq(clients.id, data.clientId),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!client) return;
|
||||||
|
|
||||||
|
const content = await generateEmail({
|
||||||
|
advisorName: 'David',
|
||||||
|
clientName: client.firstName,
|
||||||
|
interests: client.interests || [],
|
||||||
|
notes: client.notes || '',
|
||||||
|
purpose: 'birthday wishes',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save as draft for review (don't auto-send)
|
||||||
|
await db.insert(communications).values({
|
||||||
|
userId: client.userId,
|
||||||
|
clientId: client.id,
|
||||||
|
type: 'birthday',
|
||||||
|
subject: `Happy Birthday, ${client.firstName}!`,
|
||||||
|
content,
|
||||||
|
aiGenerated: true,
|
||||||
|
status: 'draft',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Frontend Architecture (Flutter)
|
||||||
|
|
||||||
|
### Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
lib/
|
||||||
|
├── main.dart
|
||||||
|
├── app/
|
||||||
|
│ ├── app.dart // MaterialApp setup
|
||||||
|
│ └── router.dart // GoRouter config
|
||||||
|
├── features/
|
||||||
|
│ ├── auth/
|
||||||
|
│ │ ├── data/ // Repository, API
|
||||||
|
│ │ ├── domain/ // Models
|
||||||
|
│ │ └── presentation/ // Screens, widgets
|
||||||
|
│ ├── clients/
|
||||||
|
│ │ ├── data/
|
||||||
|
│ │ ├── domain/
|
||||||
|
│ │ └── presentation/
|
||||||
|
│ ├── emails/
|
||||||
|
│ │ ├── data/
|
||||||
|
│ │ ├── domain/
|
||||||
|
│ │ └── presentation/
|
||||||
|
│ └── events/
|
||||||
|
│ ├── data/
|
||||||
|
│ ├── domain/
|
||||||
|
│ └── presentation/
|
||||||
|
├── shared/
|
||||||
|
│ ├── providers/ // Riverpod providers
|
||||||
|
│ ├── services/ // HTTP client, storage
|
||||||
|
│ └── widgets/ // Shared components
|
||||||
|
└── config/
|
||||||
|
└── env.dart // Environment config
|
||||||
|
```
|
||||||
|
|
||||||
|
### State Management (Riverpod)
|
||||||
|
|
||||||
|
```dart
|
||||||
|
// providers/clients_provider.dart
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'clients_provider.g.dart';
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
class ClientsNotifier extends _$ClientsNotifier {
|
||||||
|
@override
|
||||||
|
Future<List<Client>> build() async {
|
||||||
|
return ref.read(clientRepositoryProvider).getClients();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addClient(CreateClientDto dto) async {
|
||||||
|
final client = await ref.read(clientRepositoryProvider).createClient(dto);
|
||||||
|
state = AsyncData([...state.value ?? [], client]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateClient(String id, UpdateClientDto dto) async {
|
||||||
|
final updated = await ref.read(clientRepositoryProvider).updateClient(id, dto);
|
||||||
|
state = AsyncData([
|
||||||
|
for (final c in state.value ?? [])
|
||||||
|
if (c.id == id) updated else c
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteClient(String id) async {
|
||||||
|
await ref.read(clientRepositoryProvider).deleteClient(id);
|
||||||
|
state = AsyncData([
|
||||||
|
for (final c in state.value ?? [])
|
||||||
|
if (c.id != id) c
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment (Dokploy)
|
||||||
|
|
||||||
|
### Docker Compose
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# docker-compose.yml
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
api:
|
||||||
|
build: ./api
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
- DATABASE_URL=postgresql://postgres:password@db:5432/networkapp
|
||||||
|
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
||||||
|
- RESEND_API_KEY=${RESEND_API_KEY}
|
||||||
|
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET}
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=postgres
|
||||||
|
- POSTGRES_PASSWORD=password
|
||||||
|
- POSTGRES_DB=networkapp
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Dockerfile
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
# api/Dockerfile
|
||||||
|
FROM oven/bun:1 AS base
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
FROM base AS install
|
||||||
|
COPY package.json bun.lockb ./
|
||||||
|
RUN bun install --frozen-lockfile
|
||||||
|
|
||||||
|
FROM base AS release
|
||||||
|
COPY --from=install /app/node_modules ./node_modules
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["bun", "run", "src/index.ts"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
- **Auth:** BetterAuth handles sessions, CSRF, secure cookies
|
||||||
|
- **API:** All routes require authentication (middleware)
|
||||||
|
- **Data:** User can only access their own clients
|
||||||
|
- **Secrets:** All API keys in environment variables
|
||||||
|
- **HTTPS:** Handled by Dokploy/reverse proxy
|
||||||
|
|
||||||
|
## Future Considerations (Post-MVP)
|
||||||
|
|
||||||
|
- Redis for caching/sessions
|
||||||
|
- Push notifications (FCM/APNs)
|
||||||
|
- File storage (MinIO)
|
||||||
|
- Offline support (SQLite + sync)
|
||||||
|
- Multi-advisor teams
|
||||||
|
- Audit logging
|
||||||
@@ -1,869 +0,0 @@
|
|||||||
# The Network App - Project Blueprint
|
|
||||||
|
|
||||||
**Date:** 2025-06-26
|
|
||||||
**Version:** 1.0
|
|
||||||
**Purpose:** Technical architecture, screens, data models, and development plan
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. App Architecture Diagram
|
|
||||||
|
|
||||||
```
|
|
||||||
┌────────────────────────────────────────────────────────────────────┐
|
|
||||||
│ iOS APPLICATION │
|
|
||||||
├────────────────────────────────────────────────────────────────────┤
|
|
||||||
│ │
|
|
||||||
│ ┌──────────────────────────────────────────────────────────────┐ │
|
|
||||||
│ │ PRESENTATION LAYER │ │
|
|
||||||
│ │ │ │
|
|
||||||
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌────────┐ │ │
|
|
||||||
│ │ │Dashboard│ │ Clients │ │ Matches │ │Messages │ │Settings│ │ │
|
|
||||||
│ │ │ View │ │ View │ │ View │ │ View │ │ View │ │ │
|
|
||||||
│ │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ └────┬───┘ │ │
|
|
||||||
│ │ │ │ │ │ │ │ │
|
|
||||||
│ │ ┌────▼───────────▼───────────▼───────────▼───────────▼───┐ │ │
|
|
||||||
│ │ │ VIEW MODELS │ │ │
|
|
||||||
│ │ │ DashboardVM │ ClientsVM │ MatchesVM │ CommsVM │ SettingsVM│ │
|
|
||||||
│ │ └─────────────────────────┬──────────────────────────────┘ │ │
|
|
||||||
│ └────────────────────────────│───────────────────────────────┘ │
|
|
||||||
│ │ │
|
|
||||||
│ ┌────────────────────────────▼───────────────────────────────┐ │
|
|
||||||
│ │ DOMAIN LAYER │ │
|
|
||||||
│ │ │ │
|
|
||||||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
|
||||||
│ │ │ Use Cases │ │ Models │ │ Repository │ │ │
|
|
||||||
│ │ │ │ │ │ │ Protocols │ │ │
|
|
||||||
│ │ │ - GetClients│ │ - Client │ │ │ │ │
|
|
||||||
│ │ │ - SaveClient│ │ - Match │ │ - ClientRepo│ │ │
|
|
||||||
│ │ │ - GenEmail │ │ - Message │ │ - MatchRepo │ │ │
|
|
||||||
│ │ │ - FindMatch │ │ - Event │ │ - AIService │ │ │
|
|
||||||
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
|
|
||||||
│ └─────────────────────────────┬────────────────────────────────┘ │
|
|
||||||
│ │ │
|
|
||||||
│ ┌─────────────────────────────▼────────────────────────────────┐ │
|
|
||||||
│ │ DATA LAYER │ │
|
|
||||||
│ │ │ │
|
|
||||||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
|
||||||
│ │ │ Firebase │ │ Claude │ │ Local │ │ │
|
|
||||||
│ │ │ Service │ │ Service │ │ Cache │ │ │
|
|
||||||
│ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │
|
|
||||||
│ └─────────│────────────────│────────────────│──────────────────┘ │
|
|
||||||
└────────────│────────────────│────────────────│──────────────────────┘
|
|
||||||
│ │ │
|
|
||||||
▼ ▼ ▼
|
|
||||||
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
|
|
||||||
│ FIRESTORE │ │ CLOUD FUNCTION │ │ CORE DATA │
|
|
||||||
│ DATABASE │ │ (AI PROXY) │ │ (OFFLINE) │
|
|
||||||
└────────────────┘ └───────┬────────┘ └────────────────┘
|
|
||||||
│
|
|
||||||
▼
|
|
||||||
┌────────────────┐
|
|
||||||
│ CLAUDE API │
|
|
||||||
│ (ANTHROPIC) │
|
|
||||||
└────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Key Screens & Wireframes
|
|
||||||
|
|
||||||
### 2.1 Dashboard (Home)
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ ◀ The Network App 👤 ⚙️ │
|
|
||||||
├─────────────────────────────────────┤
|
|
||||||
│ │
|
|
||||||
│ Good morning, David │
|
|
||||||
│ │
|
|
||||||
│ ┌─────────────────────────────┐ │
|
|
||||||
│ │ 🎂 UPCOMING THIS WEEK │ │
|
|
||||||
│ │ │ │
|
|
||||||
│ │ Today John Smith (60th) │ │
|
|
||||||
│ │ Wed Mary Johnson Anniv │ │
|
|
||||||
│ │ Fri Follow-up: Bob Lee │ │
|
|
||||||
│ │ │ │
|
|
||||||
│ │ [View All Events] │ │
|
|
||||||
│ └─────────────────────────────┘ │
|
|
||||||
│ │
|
|
||||||
│ ┌─────────────────────────────┐ │
|
|
||||||
│ │ 🤝 SUGGESTED CONNECTIONS │ │
|
|
||||||
│ │ │ │
|
|
||||||
│ │ ┌────┐ ┌────┐ │ │
|
|
||||||
│ │ │ JS │──│ TW │ Both golf │ │
|
|
||||||
│ │ └────┘ └────┘ enthusiasts │ │
|
|
||||||
│ │ │ │
|
|
||||||
│ │ [Review Match] │ │
|
|
||||||
│ └─────────────────────────────┘ │
|
|
||||||
│ │
|
|
||||||
│ ┌─────────────────────────────┐ │
|
|
||||||
│ │ 📝 DRAFT MESSAGES (3) │ │
|
|
||||||
│ │ │ │
|
|
||||||
│ │ Birthday wish for John... │ │
|
|
||||||
│ │ Newsletter intro for... │ │
|
|
||||||
│ │ │ │
|
|
||||||
│ └─────────────────────────────┘ │
|
|
||||||
│ │
|
|
||||||
├─────────────────────────────────────┤
|
|
||||||
│ 🏠 👥 🤝 ✉️ ⚙️ │
|
|
||||||
│ Home Clients Matches Messages Set │
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 Client List
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ ◀ Clients ➕ 🔍 │
|
|
||||||
├─────────────────────────────────────┤
|
|
||||||
│ ┌─────────────────────────────────┐ │
|
|
||||||
│ │ 🔍 Search clients... │ │
|
|
||||||
│ └─────────────────────────────────┘ │
|
|
||||||
│ │
|
|
||||||
│ [All] [VIP] [Recent] [Birthdays] │
|
|
||||||
│ │
|
|
||||||
│ ┌─────────────────────────────────┐ │
|
|
||||||
│ │ 👤 John Smith 🎂 ▶ │ │
|
|
||||||
│ │ CEO, Smith Ventures │ │
|
|
||||||
│ │ Last contact: 2 days ago │ │
|
|
||||||
│ └─────────────────────────────────┘ │
|
|
||||||
│ │
|
|
||||||
│ ┌─────────────────────────────────┐ │
|
|
||||||
│ │ 👤 Mary Johnson ▶ │ │
|
|
||||||
│ │ Retired, Real Estate │ │
|
|
||||||
│ │ Last contact: 1 week ago │ │
|
|
||||||
│ └─────────────────────────────────┘ │
|
|
||||||
│ │
|
|
||||||
│ ┌─────────────────────────────────┐ │
|
|
||||||
│ │ 👤 Robert Lee 🔔 ▶ │ │
|
|
||||||
│ │ Attorney, Lee & Partners │ │
|
|
||||||
│ │ Follow-up due │ │
|
|
||||||
│ └─────────────────────────────────┘ │
|
|
||||||
│ │
|
|
||||||
│ ... more clients ... │
|
|
||||||
│ │
|
|
||||||
├─────────────────────────────────────┤
|
|
||||||
│ 🏠 👥 🤝 ✉️ ⚙️ │
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.3 Client Profile
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ ◀ Back ✏️ ••• │
|
|
||||||
├─────────────────────────────────────┤
|
|
||||||
│ │
|
|
||||||
│ ┌───────────┐ │
|
|
||||||
│ │ 👤 │ │
|
|
||||||
│ │ Photo │ │
|
|
||||||
│ └───────────┘ │
|
|
||||||
│ John Smith │
|
|
||||||
│ CEO, Smith Ventures │
|
|
||||||
│ │
|
|
||||||
│ 📞 (555) 123-4567 ✉️ Email │
|
|
||||||
│ │
|
|
||||||
├─────────────────────────────────────┤
|
|
||||||
│ [Overview] [Notes] [History] [AI] │
|
|
||||||
├─────────────────────────────────────┤
|
|
||||||
│ │
|
|
||||||
│ 📅 IMPORTANT DATES │
|
|
||||||
│ Birthday: March 15 (turns 60) │
|
|
||||||
│ Client since: 2018 (7 years) │
|
|
||||||
│ Anniversary: June 22 │
|
|
||||||
│ │
|
|
||||||
│ 💼 PROFESSIONAL │
|
|
||||||
│ CEO at Smith Ventures │
|
|
||||||
│ Industry: Private Equity │
|
|
||||||
│ Previously: Goldman Sachs │
|
|
||||||
│ │
|
|
||||||
│ ❤️ INTERESTS │
|
|
||||||
│ Golf • Wine collecting • Jazz │
|
|
||||||
│ Stanford alumni • Board member │
|
|
||||||
│ │
|
|
||||||
│ 👨👩👧👦 FAMILY │
|
|
||||||
│ Spouse: Sarah │
|
|
||||||
│ Kids: Emma (28), Michael (25) │
|
|
||||||
│ │
|
|
||||||
│ 🏷️ TAGS │
|
|
||||||
│ [VIP] [Referral Source] [Golf] │
|
|
||||||
│ │
|
|
||||||
├─────────────────────────────────────┤
|
|
||||||
│ [📧 Generate Email] [🤝 Find Match]│
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.4 AI Message Generation
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ ◀ Generate Message │
|
|
||||||
├─────────────────────────────────────┤
|
|
||||||
│ │
|
|
||||||
│ To: John Smith │
|
|
||||||
│ │
|
|
||||||
│ Purpose: │
|
|
||||||
│ ┌─────────────────────────────┐ │
|
|
||||||
│ │ ● Birthday Wish │ │
|
|
||||||
│ │ ○ Check-in │ │
|
|
||||||
│ │ ○ Meeting Follow-up │ │
|
|
||||||
│ │ ○ Introduction │ │
|
|
||||||
│ │ ○ Custom │ │
|
|
||||||
│ └─────────────────────────────┘ │
|
|
||||||
│ │
|
|
||||||
│ Additional context (optional): │
|
|
||||||
│ ┌─────────────────────────────┐ │
|
|
||||||
│ │ Mention the wine we │ │
|
|
||||||
│ │ discussed last month... │ │
|
|
||||||
│ └─────────────────────────────┘ │
|
|
||||||
│ │
|
|
||||||
│ [✨ Generate with AI] │
|
|
||||||
│ │
|
|
||||||
├─────────────────────────────────────┤
|
|
||||||
│ AI GENERATED DRAFT │
|
|
||||||
│ ┌─────────────────────────────┐ │
|
|
||||||
│ │ Dear John, │ │
|
|
||||||
│ │ │ │
|
|
||||||
│ │ Happy 60th birthday! I hope │ │
|
|
||||||
│ │ you're celebrating with │ │
|
|
||||||
│ │ Sarah and that bottle of │ │
|
|
||||||
│ │ '82 Margaux we talked │ │
|
|
||||||
│ │ about at the club... │ │
|
|
||||||
│ │ │ │
|
|
||||||
│ │ [Edit] │ │
|
|
||||||
│ └─────────────────────────────┘ │
|
|
||||||
│ │
|
|
||||||
│ [🔄 Regenerate] [✅ Approve & Send]│
|
|
||||||
│ │
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.5 Match Suggestions
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────┐
|
|
||||||
│ ◀ Network Matches 🔄 │
|
|
||||||
├─────────────────────────────────────┤
|
|
||||||
│ │
|
|
||||||
│ AI-suggested connections between │
|
|
||||||
│ your clients who may benefit from │
|
|
||||||
│ knowing each other. │
|
|
||||||
│ │
|
|
||||||
│ ┌─────────────────────────────────┐ │
|
|
||||||
│ │ ┌────┐ ┌────┐ │ │
|
|
||||||
│ │ │ JS │──────────│ TW │ │ │
|
|
||||||
│ │ └────┘ └────┘ │ │
|
|
||||||
│ │ John Smith Tom Wilson │ │
|
|
||||||
│ │ │ │
|
|
||||||
│ │ MATCH SCORE: 87% │ │
|
|
||||||
│ │ │ │
|
|
||||||
│ │ 🎯 Why they'd connect: │ │
|
|
||||||
│ │ • Both avid golfers (single │ │
|
|
||||||
│ │ digit handicaps) │ │
|
|
||||||
│ │ • Both Stanford MBA alumni │ │
|
|
||||||
│ │ • John looking for PE deals; │ │
|
|
||||||
│ │ Tom has portfolio co exits │ │
|
|
||||||
│ │ │ │
|
|
||||||
│ │ [❌ Not a Fit] [✅ Introduce] │ │
|
|
||||||
│ └─────────────────────────────────┘ │
|
|
||||||
│ │
|
|
||||||
│ ┌─────────────────────────────────┐ │
|
|
||||||
│ │ ┌────┐ ┌────┐ │ │
|
|
||||||
│ │ │ MJ │──────────│ SL │ │ │
|
|
||||||
│ │ └────┘ └────┘ │ │
|
|
||||||
│ │ Mary Johnson Susan Lee │ │
|
|
||||||
│ │ │ │
|
|
||||||
│ │ MATCH SCORE: 72% │ │
|
|
||||||
│ │ ... │ │
|
|
||||||
│ └─────────────────────────────────┘ │
|
|
||||||
│ │
|
|
||||||
├─────────────────────────────────────┤
|
|
||||||
│ 🏠 👥 🤝 ✉️ ⚙️ │
|
|
||||||
└─────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Data Models
|
|
||||||
|
|
||||||
### 3.1 Swift Models
|
|
||||||
|
|
||||||
```swift
|
|
||||||
// MARK: - Client Model
|
|
||||||
|
|
||||||
struct Client: Identifiable, Codable {
|
|
||||||
let id: String
|
|
||||||
var userId: String
|
|
||||||
|
|
||||||
// Basic Info
|
|
||||||
var firstName: String
|
|
||||||
var lastName: String
|
|
||||||
var email: String?
|
|
||||||
var phone: String?
|
|
||||||
var address: Address?
|
|
||||||
var photoURL: String?
|
|
||||||
|
|
||||||
// Professional
|
|
||||||
var company: String?
|
|
||||||
var role: String?
|
|
||||||
var industry: String?
|
|
||||||
var previousCompanies: [String]?
|
|
||||||
|
|
||||||
// Personal
|
|
||||||
var birthday: Date?
|
|
||||||
var anniversary: Date?
|
|
||||||
var interests: [String]
|
|
||||||
var family: FamilyInfo?
|
|
||||||
var preferences: [String: String]
|
|
||||||
|
|
||||||
// Metadata
|
|
||||||
var tags: [String]
|
|
||||||
var notes: [Note]
|
|
||||||
var clientSince: Date
|
|
||||||
var lastContactedAt: Date?
|
|
||||||
var matchPreferences: MatchPreferences
|
|
||||||
|
|
||||||
var createdAt: Date
|
|
||||||
var updatedAt: Date
|
|
||||||
|
|
||||||
var fullName: String {
|
|
||||||
"\(firstName) \(lastName)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Address: Codable {
|
|
||||||
var street: String?
|
|
||||||
var city: String?
|
|
||||||
var state: String?
|
|
||||||
var zip: String?
|
|
||||||
var country: String?
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FamilyInfo: Codable {
|
|
||||||
var spouseName: String?
|
|
||||||
var children: [FamilyMember]?
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FamilyMember: Codable {
|
|
||||||
var name: String
|
|
||||||
var age: Int?
|
|
||||||
var relationship: String
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Note: Identifiable, Codable {
|
|
||||||
let id: String
|
|
||||||
var content: String
|
|
||||||
var createdAt: Date
|
|
||||||
var isPrivate: Bool
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MatchPreferences: Codable {
|
|
||||||
var excludeFromMatching: Bool
|
|
||||||
var excludeClientIds: [String]
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Match Model
|
|
||||||
|
|
||||||
struct Match: Identifiable, Codable {
|
|
||||||
let id: String
|
|
||||||
var userId: String
|
|
||||||
var client1Id: String
|
|
||||||
var client2Id: String
|
|
||||||
|
|
||||||
var score: Double // 0.0 - 1.0
|
|
||||||
var aiReasoning: String
|
|
||||||
var sharedInterests: [String]
|
|
||||||
var matchType: MatchType
|
|
||||||
|
|
||||||
var status: MatchStatus
|
|
||||||
var introducedAt: Date?
|
|
||||||
var outcome: String?
|
|
||||||
|
|
||||||
var createdAt: Date
|
|
||||||
var updatedAt: Date
|
|
||||||
}
|
|
||||||
|
|
||||||
enum MatchType: String, Codable {
|
|
||||||
case professional
|
|
||||||
case personal
|
|
||||||
case both
|
|
||||||
}
|
|
||||||
|
|
||||||
enum MatchStatus: String, Codable {
|
|
||||||
case suggested
|
|
||||||
case approved
|
|
||||||
case introduced
|
|
||||||
case rejected
|
|
||||||
case successful
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Communication Model
|
|
||||||
|
|
||||||
struct Communication: Identifiable, Codable {
|
|
||||||
let id: String
|
|
||||||
var userId: String
|
|
||||||
var clientId: String
|
|
||||||
|
|
||||||
var type: CommunicationType
|
|
||||||
var purpose: String
|
|
||||||
var status: CommunicationStatus
|
|
||||||
|
|
||||||
var aiGenerated: AIContent?
|
|
||||||
var finalContent: String?
|
|
||||||
|
|
||||||
var sentAt: Date?
|
|
||||||
var createdAt: Date
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CommunicationType: String, Codable {
|
|
||||||
case email
|
|
||||||
case birthday
|
|
||||||
case anniversary
|
|
||||||
case introduction
|
|
||||||
case newsletter
|
|
||||||
case custom
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CommunicationStatus: String, Codable {
|
|
||||||
case draft
|
|
||||||
case approved
|
|
||||||
case sent
|
|
||||||
case failed
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AIContent: Codable {
|
|
||||||
var content: String
|
|
||||||
var generatedAt: Date
|
|
||||||
var promptContext: String
|
|
||||||
var modelVersion: String
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Event Model
|
|
||||||
|
|
||||||
struct ClientEvent: Identifiable, Codable {
|
|
||||||
let id: String
|
|
||||||
var userId: String
|
|
||||||
var clientId: String
|
|
||||||
|
|
||||||
var type: EventType
|
|
||||||
var title: String
|
|
||||||
var date: Date
|
|
||||||
var isRecurring: Bool
|
|
||||||
var reminderDays: Int
|
|
||||||
|
|
||||||
var lastTriggeredAt: Date?
|
|
||||||
var createdAt: Date
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EventType: String, Codable {
|
|
||||||
case birthday
|
|
||||||
case anniversary
|
|
||||||
case followUp
|
|
||||||
case custom
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - User Model
|
|
||||||
|
|
||||||
struct AppUser: Identifiable, Codable {
|
|
||||||
let id: String
|
|
||||||
var email: String
|
|
||||||
var displayName: String
|
|
||||||
var firmName: String?
|
|
||||||
|
|
||||||
var settings: UserSettings
|
|
||||||
var subscription: Subscription
|
|
||||||
|
|
||||||
var createdAt: Date
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UserSettings: Codable {
|
|
||||||
var defaultReminderDays: Int
|
|
||||||
var communicationTone: String // "formal", "casual", "warm"
|
|
||||||
var signatureTemplate: String?
|
|
||||||
var notificationsEnabled: Bool
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Subscription: Codable {
|
|
||||||
var tier: SubscriptionTier
|
|
||||||
var expiresAt: Date?
|
|
||||||
var aiCreditsRemaining: Int?
|
|
||||||
}
|
|
||||||
|
|
||||||
enum SubscriptionTier: String, Codable {
|
|
||||||
case free
|
|
||||||
case pro
|
|
||||||
case enterprise
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 Firestore Collections Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
firestore/
|
|
||||||
├── users/
|
|
||||||
│ └── {userId}/
|
|
||||||
│ ├── profile (document fields)
|
|
||||||
│ ├── settings (document fields)
|
|
||||||
│ └── subscription (document fields)
|
|
||||||
│
|
|
||||||
├── clients/
|
|
||||||
│ └── {clientId}/
|
|
||||||
│ ├── (all client fields)
|
|
||||||
│ └── notes[] (embedded array)
|
|
||||||
│
|
|
||||||
├── matches/
|
|
||||||
│ └── {matchId}/
|
|
||||||
│ └── (all match fields)
|
|
||||||
│
|
|
||||||
├── communications/
|
|
||||||
│ └── {communicationId}/
|
|
||||||
│ └── (all communication fields)
|
|
||||||
│
|
|
||||||
├── events/
|
|
||||||
│ └── {eventId}/
|
|
||||||
│ └── (all event fields)
|
|
||||||
│
|
|
||||||
└── auditLog/
|
|
||||||
└── {logId}/
|
|
||||||
├── userId
|
|
||||||
├── action
|
|
||||||
├── resourceType
|
|
||||||
├── resourceId
|
|
||||||
├── timestamp
|
|
||||||
└── metadata
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. API Endpoints (Cloud Functions)
|
|
||||||
|
|
||||||
### 4.1 AI Endpoints
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /api/ai/generateEmail
|
|
||||||
Request:
|
|
||||||
{
|
|
||||||
"clientId": "abc123",
|
|
||||||
"purpose": "birthday",
|
|
||||||
"additionalContext": "mention wine discussion"
|
|
||||||
}
|
|
||||||
Response:
|
|
||||||
{
|
|
||||||
"content": "Dear John, ...",
|
|
||||||
"generatedAt": "2025-06-26T10:00:00Z",
|
|
||||||
"communicationId": "comm123"
|
|
||||||
}
|
|
||||||
|
|
||||||
POST /api/ai/suggestMatches
|
|
||||||
Request:
|
|
||||||
{
|
|
||||||
"clientId": "abc123", // optional, for specific client
|
|
||||||
"limit": 10
|
|
||||||
}
|
|
||||||
Response:
|
|
||||||
{
|
|
||||||
"matches": [
|
|
||||||
{
|
|
||||||
"client1Id": "abc123",
|
|
||||||
"client2Id": "def456",
|
|
||||||
"score": 0.87,
|
|
||||||
"reasoning": "Both avid golfers...",
|
|
||||||
"sharedInterests": ["golf", "stanford"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
POST /api/ai/generateIntroduction
|
|
||||||
Request:
|
|
||||||
{
|
|
||||||
"matchId": "match123"
|
|
||||||
}
|
|
||||||
Response:
|
|
||||||
{
|
|
||||||
"emailToClient1": "...",
|
|
||||||
"emailToClient2": "...",
|
|
||||||
"communicationIds": ["comm1", "comm2"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.2 Data Endpoints
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /api/clients
|
|
||||||
GET /api/clients/{clientId}
|
|
||||||
POST /api/clients
|
|
||||||
PUT /api/clients/{clientId}
|
|
||||||
DELETE /api/clients/{clientId}
|
|
||||||
|
|
||||||
GET /api/events/upcoming?days=7
|
|
||||||
POST /api/events
|
|
||||||
|
|
||||||
GET /api/matches?status=suggested
|
|
||||||
PUT /api/matches/{matchId}/status
|
|
||||||
|
|
||||||
POST /api/communications/{commId}/send
|
|
||||||
|
|
||||||
GET /api/export/client/{clientId} // GDPR
|
|
||||||
DELETE /api/data/client/{clientId} // GDPR right to deletion
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.3 Cloud Functions Structure
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// functions/index.js
|
|
||||||
|
|
||||||
const functions = require('firebase-functions');
|
|
||||||
const admin = require('firebase-admin');
|
|
||||||
const Anthropic = require('@anthropic-ai/sdk');
|
|
||||||
|
|
||||||
admin.initializeApp();
|
|
||||||
|
|
||||||
// AI Email Generation
|
|
||||||
exports.generateEmail = functions.https.onCall(async (data, context) => {
|
|
||||||
// Verify authentication
|
|
||||||
if (!context.auth) {
|
|
||||||
throw new functions.https.HttpsError('unauthenticated', 'Must be logged in');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get client data
|
|
||||||
const client = await getClient(data.clientId, context.auth.uid);
|
|
||||||
|
|
||||||
// Build prompt
|
|
||||||
const prompt = buildEmailPrompt(client, data.purpose, data.context);
|
|
||||||
|
|
||||||
// Call Claude
|
|
||||||
const anthropic = new Anthropic();
|
|
||||||
const response = await anthropic.messages.create({
|
|
||||||
model: 'claude-3-5-sonnet-20241022',
|
|
||||||
max_tokens: 1024,
|
|
||||||
messages: [{ role: 'user', content: prompt }]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Save draft and return
|
|
||||||
const commId = await saveDraft(context.auth.uid, data.clientId, response);
|
|
||||||
|
|
||||||
// Audit log
|
|
||||||
await logAction(context.auth.uid, 'ai_generate', 'communication', commId);
|
|
||||||
|
|
||||||
return { content: response.content[0].text, communicationId: commId };
|
|
||||||
});
|
|
||||||
|
|
||||||
// Scheduled: Daily event check
|
|
||||||
exports.checkDailyEvents = functions.pubsub
|
|
||||||
.schedule('0 8 * * *')
|
|
||||||
.timeZone('America/New_York')
|
|
||||||
.onRun(async (context) => {
|
|
||||||
const events = await getUpcomingEvents(7);
|
|
||||||
await sendEventNotifications(events);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Development Phases
|
|
||||||
|
|
||||||
### Phase 1: Foundation (Weeks 1-4)
|
|
||||||
|
|
||||||
**Week 1-2: Project Setup**
|
|
||||||
- [ ] Create Xcode project with SwiftUI
|
|
||||||
- [ ] Set up project architecture (folders, protocols)
|
|
||||||
- [ ] Configure Firebase project
|
|
||||||
- [ ] Implement Firebase Authentication
|
|
||||||
- [ ] Set up CI/CD (GitHub Actions + TestFlight)
|
|
||||||
|
|
||||||
**Week 3-4: Core Data Layer**
|
|
||||||
- [ ] Create all Swift models
|
|
||||||
- [ ] Implement Firestore service
|
|
||||||
- [ ] Build repository layer
|
|
||||||
- [ ] Enable offline persistence
|
|
||||||
- [ ] Implement basic CRUD for clients
|
|
||||||
|
|
||||||
**Deliverables:**
|
|
||||||
- App shell with authentication
|
|
||||||
- Can create/read clients (no UI polish)
|
|
||||||
- Offline storage working
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Phase 2: Core Features (Weeks 5-8)
|
|
||||||
|
|
||||||
**Week 5-6: Client Management UI**
|
|
||||||
- [ ] Client list view with search
|
|
||||||
- [ ] Client detail view (full profile)
|
|
||||||
- [ ] Client edit/create forms
|
|
||||||
- [ ] Notes management
|
|
||||||
- [ ] Tags and filtering
|
|
||||||
|
|
||||||
**Week 7-8: Events & Notifications**
|
|
||||||
- [ ] Event model and storage
|
|
||||||
- [ ] Birthday/anniversary detection
|
|
||||||
- [ ] Local notifications setup
|
|
||||||
- [ ] Push notifications (Firebase Cloud Messaging)
|
|
||||||
- [ ] Dashboard with upcoming events
|
|
||||||
|
|
||||||
**Deliverables:**
|
|
||||||
- Fully functional CRM (without AI)
|
|
||||||
- Can manage clients, notes, events
|
|
||||||
- Receives reminders for birthdays
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Phase 3: AI Integration (Weeks 9-12)
|
|
||||||
|
|
||||||
**Week 9-10: AI Infrastructure**
|
|
||||||
- [ ] Set up Cloud Functions
|
|
||||||
- [ ] Claude API integration
|
|
||||||
- [ ] Prompt engineering and testing
|
|
||||||
- [ ] Rate limiting and error handling
|
|
||||||
|
|
||||||
**Week 11-12: AI Features**
|
|
||||||
- [ ] Email generation UI
|
|
||||||
- [ ] Review/edit/approve workflow
|
|
||||||
- [ ] Match suggestion algorithm
|
|
||||||
- [ ] Match review UI
|
|
||||||
- [ ] Introduction email generation
|
|
||||||
|
|
||||||
**Deliverables:**
|
|
||||||
- AI email generation working
|
|
||||||
- Match suggestions with explanations
|
|
||||||
- Full AI workflow complete
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Phase 4: Polish & Compliance (Weeks 13-16)
|
|
||||||
|
|
||||||
**Week 13-14: Compliance**
|
|
||||||
- [ ] Data export feature (GDPR)
|
|
||||||
- [ ] Account deletion flow
|
|
||||||
- [ ] Audit logging
|
|
||||||
- [ ] Security hardening
|
|
||||||
- [ ] Biometric authentication
|
|
||||||
|
|
||||||
**Week 15-16: Polish**
|
|
||||||
- [ ] UI refinements
|
|
||||||
- [ ] Animation and transitions
|
|
||||||
- [ ] Edge case handling
|
|
||||||
- [ ] Performance optimization
|
|
||||||
- [ ] Comprehensive testing
|
|
||||||
- [ ] App Store assets
|
|
||||||
- [ ] TestFlight beta
|
|
||||||
|
|
||||||
**Deliverables:**
|
|
||||||
- Production-ready application
|
|
||||||
- App Store submission ready
|
|
||||||
- Documentation complete
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. File Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
NetworkApp/
|
|
||||||
├── App/
|
|
||||||
│ ├── NetworkApp.swift
|
|
||||||
│ └── AppDelegate.swift
|
|
||||||
│
|
|
||||||
├── Core/
|
|
||||||
│ ├── Models/
|
|
||||||
│ │ ├── Client.swift
|
|
||||||
│ │ ├── Match.swift
|
|
||||||
│ │ ├── Communication.swift
|
|
||||||
│ │ ├── Event.swift
|
|
||||||
│ │ └── User.swift
|
|
||||||
│ │
|
|
||||||
│ ├── Services/
|
|
||||||
│ │ ├── FirebaseService.swift
|
|
||||||
│ │ ├── AuthService.swift
|
|
||||||
│ │ ├── AIService.swift
|
|
||||||
│ │ └── NotificationService.swift
|
|
||||||
│ │
|
|
||||||
│ ├── Repositories/
|
|
||||||
│ │ ├── ClientRepository.swift
|
|
||||||
│ │ ├── MatchRepository.swift
|
|
||||||
│ │ └── EventRepository.swift
|
|
||||||
│ │
|
|
||||||
│ └── Utilities/
|
|
||||||
│ ├── Extensions/
|
|
||||||
│ ├── Helpers/
|
|
||||||
│ └── Constants.swift
|
|
||||||
│
|
|
||||||
├── Features/
|
|
||||||
│ ├── Authentication/
|
|
||||||
│ │ ├── Views/
|
|
||||||
│ │ └── ViewModels/
|
|
||||||
│ │
|
|
||||||
│ ├── Dashboard/
|
|
||||||
│ │ ├── Views/
|
|
||||||
│ │ └── ViewModels/
|
|
||||||
│ │
|
|
||||||
│ ├── Clients/
|
|
||||||
│ │ ├── Views/
|
|
||||||
│ │ │ ├── ClientListView.swift
|
|
||||||
│ │ │ ├── ClientDetailView.swift
|
|
||||||
│ │ │ └── ClientEditView.swift
|
|
||||||
│ │ └── ViewModels/
|
|
||||||
│ │ └── ClientsViewModel.swift
|
|
||||||
│ │
|
|
||||||
│ ├── Matches/
|
|
||||||
│ │ ├── Views/
|
|
||||||
│ │ └── ViewModels/
|
|
||||||
│ │
|
|
||||||
│ ├── Communications/
|
|
||||||
│ │ ├── Views/
|
|
||||||
│ │ └── ViewModels/
|
|
||||||
│ │
|
|
||||||
│ └── Settings/
|
|
||||||
│ ├── Views/
|
|
||||||
│ └── ViewModels/
|
|
||||||
│
|
|
||||||
├── Resources/
|
|
||||||
│ ├── Assets.xcassets
|
|
||||||
│ ├── Localizable.strings
|
|
||||||
│ └── Info.plist
|
|
||||||
│
|
|
||||||
└── Tests/
|
|
||||||
├── UnitTests/
|
|
||||||
└── UITests/
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Testing Strategy
|
|
||||||
|
|
||||||
### Unit Tests
|
|
||||||
- Model encoding/decoding
|
|
||||||
- Repository logic
|
|
||||||
- AI prompt building
|
|
||||||
- Date/event calculations
|
|
||||||
|
|
||||||
### Integration Tests
|
|
||||||
- Firebase read/write
|
|
||||||
- AI service calls
|
|
||||||
- Authentication flows
|
|
||||||
|
|
||||||
### UI Tests
|
|
||||||
- Critical user journeys
|
|
||||||
- Client CRUD operations
|
|
||||||
- AI generation flow
|
|
||||||
|
|
||||||
### Manual Testing
|
|
||||||
- Various client data scenarios
|
|
||||||
- Offline/online transitions
|
|
||||||
- Edge cases
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Launch Checklist
|
|
||||||
|
|
||||||
- [ ] All features tested on device
|
|
||||||
- [ ] Performance profiled (Instruments)
|
|
||||||
- [ ] Memory leaks checked
|
|
||||||
- [ ] Crash-free for 48+ hours
|
|
||||||
- [ ] App Store screenshots (6.5" & 5.5")
|
|
||||||
- [ ] App Store description
|
|
||||||
- [ ] Privacy policy URL
|
|
||||||
- [ ] Support URL
|
|
||||||
- [ ] App Review notes
|
|
||||||
- [ ] TestFlight beta feedback addressed
|
|
||||||
- [ ] Analytics events verified
|
|
||||||
- [ ] Crashlytics configured
|
|
||||||
@@ -1,460 +1,73 @@
|
|||||||
# The Network App - Technical Feasibility Assessment
|
# The Network App — Feasibility Assessment
|
||||||
|
|
||||||
**Date:** 2025-06-26
|
**Last Updated:** 2026-01-27
|
||||||
**Purpose:** Evaluate technical approach, architecture, and development requirements
|
**Status:** ✅ Feasible — Proceed with MVP
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Executive Summary
|
## Stack Validation
|
||||||
|
|
||||||
The Network App is **technically feasible** with the proposed stack (Swift/SwiftUI, Firebase, Claude AI). The project is moderate complexity with well-understood patterns. Main challenges are AI integration quality and compliance implementation. Estimated timeline: **12-16 weeks for MVP**.
|
| Component | Choice | Maturity | Risk |
|
||||||
|
|-----------|--------|----------|------|
|
||||||
|
| Flutter | Stable | Production-ready | Low |
|
||||||
|
| Elysia + Bun | Growing | v1.0+, stable | Low |
|
||||||
|
| PostgreSQL | Mature | Industry standard | Low |
|
||||||
|
| Drizzle | Stable | Production-ready | Low |
|
||||||
|
| BetterAuth | Newer | v1.0+, active dev | Low-Medium |
|
||||||
|
| LangChain.js | Mature | Widely adopted | Low |
|
||||||
|
| Dokploy | Stable | Self-hosted PaaS | Low |
|
||||||
|
|
||||||
|
**Overall Risk:** Low — All core technologies are production-ready.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Swift/SwiftUI Architecture Recommendations
|
## MVP Timeline Estimate
|
||||||
|
|
||||||
### Platform Choice: Native iOS ✅
|
| Phase | Scope | Duration |
|
||||||
|
|-------|-------|----------|
|
||||||
|
| 1. Setup | Repos, auth, DB schema | 1 week |
|
||||||
|
| 2. Client CRUD | Backend + Flutter UI | 2 weeks |
|
||||||
|
| 3. Search & Events | Filters, birthday tracking | 1 week |
|
||||||
|
| 4. AI Integration | LangChain email generation | 1 week |
|
||||||
|
| 5. Polish | Testing, bug fixes, deploy | 1 week |
|
||||||
|
|
||||||
**Why Native:**
|
**Total: 6-8 weeks for MVP**
|
||||||
- Best performance for data-heavy CRM
|
|
||||||
- Full access to iOS features (biometrics, contacts, notifications)
|
|
||||||
- SwiftUI is mature and production-ready (iOS 16+)
|
|
||||||
- Offline-first capabilities easier to implement
|
|
||||||
- Better for sensitive data handling
|
|
||||||
|
|
||||||
**Why NOT Cross-Platform:**
|
|
||||||
- Single platform requirement (iOS first)
|
|
||||||
- Native gives compliance advantages
|
|
||||||
- No need to compromise on UX
|
|
||||||
|
|
||||||
### Recommended Architecture: MVVM + Clean Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────┐
|
|
||||||
│ Presentation │
|
|
||||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
||||||
│ │ Views │ │ ViewModels │ │ Router │ │
|
|
||||||
│ │ (SwiftUI) │ │ (ObsObj) │ │ (Coordinat) │ │
|
|
||||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
|
||||||
├─────────────────────────────────────────────────────┤
|
|
||||||
│ Domain │
|
|
||||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
||||||
│ │ Use Cases │ │ Entities │ │ Repositories│ │
|
|
||||||
│ │ │ │ (Models) │ │ (Protocols) │ │
|
|
||||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
|
||||||
├─────────────────────────────────────────────────────┤
|
|
||||||
│ Data │
|
|
||||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
||||||
│ │ Firebase │ │ Claude │ │ Local │ │
|
|
||||||
│ │ Service │ │ Service │ │ Cache │ │
|
|
||||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
|
||||||
└─────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### Key SwiftUI Patterns
|
|
||||||
|
|
||||||
1. **State Management:** Combine + ObservableObject
|
|
||||||
2. **Navigation:** NavigationStack (iOS 16+)
|
|
||||||
3. **Data Flow:** Single source of truth in ViewModels
|
|
||||||
4. **Dependency Injection:** Protocol-based for testability
|
|
||||||
|
|
||||||
### iOS Version Target
|
|
||||||
|
|
||||||
- **Minimum:** iOS 16.0
|
|
||||||
- **Reason:** NavigationStack, modern SwiftUI features
|
|
||||||
- **Coverage:** ~95% of active iPhones
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Firebase Structure for Client Data
|
## Cost Estimate (Monthly)
|
||||||
|
|
||||||
### Why Firebase ✅
|
| Service | Cost |
|
||||||
|
|---------|------|
|
||||||
**Advantages:**
|
| VM (Dokploy host) | Already owned |
|
||||||
- Real-time sync built-in
|
| PostgreSQL | Included (self-hosted) |
|
||||||
- Offline persistence automatic
|
| Claude API (~200 emails) | ~$3-5 |
|
||||||
- Authentication included
|
| Resend (3k emails free) | $0 |
|
||||||
- Cloud Functions for backend logic
|
| **Total** | ~$5/month |
|
||||||
- Good security rules system
|
|
||||||
- Scales automatically
|
|
||||||
|
|
||||||
**Considerations:**
|
|
||||||
- NoSQL requires careful data modeling
|
|
||||||
- Compliance certifications available (SOC 2, ISO 27001)
|
|
||||||
- Data residency options exist
|
|
||||||
|
|
||||||
### Firestore Data Model
|
|
||||||
|
|
||||||
```
|
|
||||||
users/
|
|
||||||
{userId}/
|
|
||||||
profile: { name, email, settings }
|
|
||||||
subscription: { tier, expires }
|
|
||||||
|
|
||||||
clients/
|
|
||||||
{clientId}/
|
|
||||||
userId: string (owner reference)
|
|
||||||
basic: {
|
|
||||||
firstName, lastName, email, phone
|
|
||||||
address: { street, city, state, zip }
|
|
||||||
}
|
|
||||||
professional: {
|
|
||||||
company, role, industry
|
|
||||||
}
|
|
||||||
personal: {
|
|
||||||
birthday, anniversary
|
|
||||||
interests: []
|
|
||||||
family: { spouse, children: [] }
|
|
||||||
preferences: {}
|
|
||||||
}
|
|
||||||
notes: [] // Array of timestamped notes
|
|
||||||
tags: []
|
|
||||||
matchPreferences: {
|
|
||||||
excludeFromMatching: boolean
|
|
||||||
excludeClientIds: []
|
|
||||||
}
|
|
||||||
metadata: {
|
|
||||||
createdAt, updatedAt, lastContactedAt
|
|
||||||
}
|
|
||||||
|
|
||||||
communications/
|
|
||||||
{communicationId}/
|
|
||||||
userId: string
|
|
||||||
clientId: string
|
|
||||||
type: "email" | "newsletter" | "birthday"
|
|
||||||
status: "draft" | "approved" | "sent"
|
|
||||||
aiGenerated: {
|
|
||||||
content: string
|
|
||||||
generatedAt: timestamp
|
|
||||||
promptUsed: string
|
|
||||||
}
|
|
||||||
finalContent: string
|
|
||||||
sentAt: timestamp
|
|
||||||
|
|
||||||
matches/
|
|
||||||
{matchId}/
|
|
||||||
userId: string
|
|
||||||
client1Id: string
|
|
||||||
client2Id: string
|
|
||||||
aiReasoning: string
|
|
||||||
score: number
|
|
||||||
status: "suggested" | "approved" | "introduced" | "rejected"
|
|
||||||
introducedAt: timestamp
|
|
||||||
outcome: string
|
|
||||||
|
|
||||||
events/
|
|
||||||
{eventId}/
|
|
||||||
userId: string
|
|
||||||
clientId: string
|
|
||||||
type: "birthday" | "anniversary" | "followup" | "custom"
|
|
||||||
date: timestamp
|
|
||||||
recurring: boolean
|
|
||||||
reminderDays: number
|
|
||||||
lastTriggered: timestamp
|
|
||||||
```
|
|
||||||
|
|
||||||
### Security Rules Strategy
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Firestore Security Rules (simplified)
|
|
||||||
rules_version = '2';
|
|
||||||
service cloud.firestore {
|
|
||||||
match /databases/{database}/documents {
|
|
||||||
// Users can only access their own data
|
|
||||||
match /clients/{clientId} {
|
|
||||||
allow read, write: if request.auth != null
|
|
||||||
&& resource.data.userId == request.auth.uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Audit log - write only
|
|
||||||
match /auditLog/{logId} {
|
|
||||||
allow create: if request.auth != null;
|
|
||||||
allow read: if false; // Only via admin SDK
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Offline Strategy
|
|
||||||
|
|
||||||
1. **Firestore Persistence:** Enable by default
|
|
||||||
2. **Optimistic Updates:** UI updates immediately
|
|
||||||
3. **Conflict Resolution:** Last-write-wins for most fields
|
|
||||||
4. **Sync Indicator:** Show pending changes to user
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Claude AI Integration Approach
|
## Key Decisions
|
||||||
|
|
||||||
### API Integration Architecture
|
1. **Cross-platform (Flutter)** over iOS-only — Serves both platforms from one codebase
|
||||||
|
2. **Self-hosted** over Firebase — Full control, predictable costs, no vendor lock-in
|
||||||
```
|
3. **LangChain** over direct API — Model flexibility for future
|
||||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
4. **Lean MVP** — Core features only, iterate based on David's feedback
|
||||||
│ iOS App │────▶│ Firebase │────▶│ Claude API │
|
|
||||||
│ │ │ Functions │ │ │
|
|
||||||
└─────────────┘ └─────────────┘ └─────────────┘
|
|
||||||
│
|
|
||||||
API Key secured
|
|
||||||
in Cloud Functions
|
|
||||||
```
|
|
||||||
|
|
||||||
**Why Cloud Functions Proxy:**
|
|
||||||
- API key never on device
|
|
||||||
- Rate limiting and cost control
|
|
||||||
- Audit logging for compliance
|
|
||||||
- Can cache/optimize requests
|
|
||||||
|
|
||||||
### Claude Use Cases & Prompts
|
|
||||||
|
|
||||||
#### 1. Personalized Email Generation
|
|
||||||
|
|
||||||
```
|
|
||||||
System: You are a professional wealth advisor writing to a valued client.
|
|
||||||
Maintain a warm but professional tone. Incorporate personal details naturally.
|
|
||||||
|
|
||||||
Context:
|
|
||||||
- Advisor name: {advisorName}
|
|
||||||
- Client: {clientName}
|
|
||||||
- Their interests: {interests}
|
|
||||||
- Recent notes: {recentNotes}
|
|
||||||
- Purpose: {emailPurpose}
|
|
||||||
|
|
||||||
Generate a personalized email that feels genuine, not templated.
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. Client Match Suggestions
|
|
||||||
|
|
||||||
```
|
|
||||||
System: You are analyzing client profiles to identify valuable networking
|
|
||||||
opportunities. Only suggest matches that would genuinely benefit both parties.
|
|
||||||
|
|
||||||
Client A: {profileA}
|
|
||||||
Client B: {profileB}
|
|
||||||
|
|
||||||
Evaluate potential match:
|
|
||||||
1. What shared interests or complementary needs exist?
|
|
||||||
2. How might they benefit from knowing each other?
|
|
||||||
3. Confidence score (1-10) with reasoning
|
|
||||||
4. Suggested introduction approach
|
|
||||||
|
|
||||||
Output as JSON: { score, reasoning, introductionSuggestion }
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. Birthday/Event Messages
|
|
||||||
|
|
||||||
```
|
|
||||||
System: Generate a thoughtful birthday message from a wealth advisor to
|
|
||||||
their client. Should feel personal, not generic.
|
|
||||||
|
|
||||||
Client: {clientName}
|
|
||||||
Relationship tenure: {yearsSinceClient}
|
|
||||||
Interests: {interests}
|
|
||||||
Last interaction: {lastNote}
|
|
||||||
|
|
||||||
Keep it brief (2-3 sentences) and sincere.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Cost Estimation
|
|
||||||
|
|
||||||
| Use Case | Tokens/Request | Requests/Month | Monthly Cost |
|
|
||||||
|----------|----------------|----------------|--------------|
|
|
||||||
| Email generation | ~800 | 200 | ~$2.40 |
|
|
||||||
| Match analysis | ~1200 | 100 | ~$1.80 |
|
|
||||||
| Birthday messages | ~300 | 50 | ~$0.45 |
|
|
||||||
| **Total estimated** | | | **~$5-10/user** |
|
|
||||||
|
|
||||||
*Based on Claude 3.5 Sonnet pricing ($3/M input, $15/M output)*
|
|
||||||
|
|
||||||
### Error Handling
|
|
||||||
|
|
||||||
1. **Timeout:** 30-second limit, show "AI thinking..."
|
|
||||||
2. **Failure:** Graceful fallback to templates
|
|
||||||
3. **Rate Limits:** Queue and retry with exponential backoff
|
|
||||||
4. **Content Filtering:** Review AI output before displaying
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Security & Compliance Considerations
|
## Risks & Mitigations
|
||||||
|
|
||||||
### GDPR Compliance
|
| Risk | Probability | Mitigation |
|
||||||
|
|------|-------------|------------|
|
||||||
| Requirement | Implementation |
|
| BetterAuth edge cases | Low | Active community, can fallback to Lucia |
|
||||||
|-------------|----------------|
|
| AI response quality | Medium | Prompt iteration, human review before send |
|
||||||
| Right to Access | Export all client data as JSON/PDF |
|
| Bun compatibility issues | Low | Mature ecosystem, fallback to Node if needed |
|
||||||
| Right to Deletion | Hard delete with cascade (Firebase Admin SDK) |
|
|
||||||
| Data Portability | Standard export format |
|
|
||||||
| Consent Tracking | Store consent timestamps per client |
|
|
||||||
| Data Minimization | Only collect necessary fields |
|
|
||||||
|
|
||||||
### HIPAA Considerations
|
|
||||||
|
|
||||||
If storing health-related notes (e.g., "client mentioned health concerns"):
|
|
||||||
|
|
||||||
1. **BAA with Firebase:** Google offers BAA for Cloud/Firebase
|
|
||||||
2. **Encryption:** AES-256 at rest (Firebase default)
|
|
||||||
3. **Access Logging:** Audit trail for all data access
|
|
||||||
4. **Employee Training:** Document handling procedures
|
|
||||||
|
|
||||||
### Encryption Strategy
|
|
||||||
|
|
||||||
| Layer | Method |
|
|
||||||
|-------|--------|
|
|
||||||
| In Transit | TLS 1.3 (Firebase default) |
|
|
||||||
| At Rest | AES-256 (Firebase default) |
|
|
||||||
| Sensitive Fields | Additional client-side encryption (optional) |
|
|
||||||
| Local Cache | iOS Keychain for sensitive data |
|
|
||||||
|
|
||||||
### Authentication Flow
|
|
||||||
|
|
||||||
```
|
|
||||||
┌──────────────┐ ┌─────────────┐ ┌──────────────┐
|
|
||||||
│ App Launch │───▶│ Biometric │───▶│ Firebase │
|
|
||||||
│ │ │ (FaceID) │ │ Auth │
|
|
||||||
└──────────────┘ └─────────────┘ └──────────────┘
|
|
||||||
│
|
|
||||||
Fallback to PIN
|
|
||||||
```
|
|
||||||
|
|
||||||
### Audit Logging
|
|
||||||
|
|
||||||
Log these events to a write-only collection:
|
|
||||||
|
|
||||||
- User login/logout
|
|
||||||
- Client record created/modified/deleted
|
|
||||||
- AI generation requested
|
|
||||||
- Export performed
|
|
||||||
- Data deletion requested
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Complexity Assessment
|
## Recommendation
|
||||||
|
|
||||||
### Feature Complexity Breakdown
|
**Proceed with development.** Stack is solid, timeline is reasonable, costs are minimal.
|
||||||
|
|
||||||
| Feature | Complexity | Effort (days) | Risk |
|
Next step: Set up repos and scaffold projects.
|
||||||
|---------|------------|---------------|------|
|
|
||||||
| Client CRUD | Low | 5 | Low |
|
|
||||||
| Search & Filters | Medium | 4 | Low |
|
|
||||||
| Firebase Auth | Low | 2 | Low |
|
|
||||||
| Client Profile UI | Medium | 6 | Low |
|
|
||||||
| AI Email Generation | Medium | 5 | Medium |
|
|
||||||
| AI Matching | High | 8 | Medium |
|
|
||||||
| Birthday Tracking | Low | 3 | Low |
|
|
||||||
| Push Notifications | Medium | 3 | Low |
|
|
||||||
| Offline Support | Medium | 4 | Medium |
|
|
||||||
| Compliance Features | Medium | 5 | Medium |
|
|
||||||
| Settings & Preferences | Low | 2 | Low |
|
|
||||||
|
|
||||||
### Technical Risks
|
|
||||||
|
|
||||||
| Risk | Probability | Impact | Mitigation |
|
|
||||||
|------|-------------|--------|------------|
|
|
||||||
| AI quality inconsistent | Medium | High | Prompt iteration, human review |
|
|
||||||
| Firebase scaling costs | Low | Medium | Monitor usage, set alerts |
|
|
||||||
| iOS approval delays | Low | Medium | Follow guidelines strictly |
|
|
||||||
| Offline sync conflicts | Medium | Medium | Clear conflict resolution UX |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Timeline Estimate
|
|
||||||
|
|
||||||
### Phase 1: Foundation (Weeks 1-4)
|
|
||||||
- Project setup, architecture
|
|
||||||
- Firebase configuration
|
|
||||||
- Authentication flow
|
|
||||||
- Basic client CRUD
|
|
||||||
- **Deliverable:** App shell with auth and data persistence
|
|
||||||
|
|
||||||
### Phase 2: Core Features (Weeks 5-8)
|
|
||||||
- Client profile UI (full)
|
|
||||||
- Search and filtering
|
|
||||||
- Notes and tags
|
|
||||||
- Event/birthday tracking
|
|
||||||
- Push notifications
|
|
||||||
- **Deliverable:** Functional CRM without AI
|
|
||||||
|
|
||||||
### Phase 3: AI Integration (Weeks 9-12)
|
|
||||||
- Cloud Functions setup
|
|
||||||
- AI email generation
|
|
||||||
- AI matching algorithm
|
|
||||||
- Review/approval workflows
|
|
||||||
- **Deliverable:** Full AI features working
|
|
||||||
|
|
||||||
### Phase 4: Polish & Compliance (Weeks 13-16)
|
|
||||||
- Compliance features (export, delete)
|
|
||||||
- Audit logging
|
|
||||||
- UI polish and edge cases
|
|
||||||
- Testing and bug fixes
|
|
||||||
- App Store preparation
|
|
||||||
- **Deliverable:** Production-ready app
|
|
||||||
|
|
||||||
### Total Estimate: 12-16 weeks
|
|
||||||
|
|
||||||
**With buffer for unknowns:** 16-20 weeks
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Technology Recommendations
|
|
||||||
|
|
||||||
### Recommended Stack
|
|
||||||
|
|
||||||
| Component | Technology | Rationale |
|
|
||||||
|-----------|------------|-----------|
|
|
||||||
| Language | Swift 5.9+ | Modern, safe, performant |
|
|
||||||
| UI | SwiftUI | Declarative, less code |
|
|
||||||
| Architecture | MVVM | SwiftUI natural fit |
|
|
||||||
| Backend | Firebase | Real-time, offline, scales |
|
|
||||||
| Database | Firestore | Flexible schema, good sync |
|
|
||||||
| Auth | Firebase Auth | Turnkey, secure |
|
|
||||||
| Functions | Cloud Functions | Secure AI proxy |
|
|
||||||
| AI | Claude API | Quality, context length |
|
|
||||||
| Analytics | Firebase Analytics | Integrated, free |
|
|
||||||
| Crash Reporting | Firebase Crashlytics | Integrated, free |
|
|
||||||
|
|
||||||
### Development Tools
|
|
||||||
|
|
||||||
- **Xcode 15+** - Latest SwiftUI features
|
|
||||||
- **Swift Package Manager** - Dependency management
|
|
||||||
- **SwiftLint** - Code quality
|
|
||||||
- **Firebase Emulator** - Local development
|
|
||||||
|
|
||||||
### Third-Party Libraries
|
|
||||||
|
|
||||||
Keep minimal:
|
|
||||||
- Firebase iOS SDK
|
|
||||||
- (Optional) Kingfisher for image caching
|
|
||||||
- (Optional) SwiftUI introspect for edge cases
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Conclusion
|
|
||||||
|
|
||||||
### Feasibility: ✅ HIGH
|
|
||||||
|
|
||||||
The project is well within established technology patterns. No experimental tech required.
|
|
||||||
|
|
||||||
### Key Success Factors
|
|
||||||
|
|
||||||
1. **AI Prompt Engineering** - Invest time in quality prompts
|
|
||||||
2. **UX for AI Review** - Make human approval frictionless
|
|
||||||
3. **Offline-First** - Critical for mobile advisor use
|
|
||||||
4. **Compliance Documentation** - Start early, not late
|
|
||||||
|
|
||||||
### Recommended Next Steps
|
|
||||||
|
|
||||||
1. Finalize scope and budget
|
|
||||||
2. Set up Firebase project
|
|
||||||
3. Create Figma designs for key screens
|
|
||||||
4. Begin Phase 1 development
|
|
||||||
|
|
||||||
### Cost Summary
|
|
||||||
|
|
||||||
| Item | One-Time | Monthly |
|
|
||||||
|------|----------|---------|
|
|
||||||
| Development (16 wks) | $15-40K* | - |
|
|
||||||
| Firebase (100 users) | - | $25-50 |
|
|
||||||
| Claude API (per user) | - | $5-10 |
|
|
||||||
| Apple Developer | $99/year | - |
|
|
||||||
| **Total Year 1** | ~$15-40K | ~$75-150 |
|
|
||||||
|
|
||||||
*Development cost varies significantly based on who builds it
|
|
||||||
|
|||||||
Reference in New Issue
Block a user