Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copy this file to .env and provide your private OpenAI API key.
OPENAI_API_KEY=replace-with-your-openai-api-key
# Optionally override the default model (defaults to gpt-4.1-mini)
# OPENAI_MODEL=gpt-4.1-mini
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
__pycache__/
*.pyc
.env
node_modules/
crm-ui/dist/
.DS_Store
103 changes: 102 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,102 @@
# beta
# Insight Navigator CRM

Insight Navigator CRM is a local-first revenue intelligence workspace that fuses data
management, predictive analytics, risk mitigation, AI advisory, and document comparison
workflows. The backend exposes a lightweight Flask API that brokers secure calls to your
ChatGPT connection, while the front-end delivers an immersive CRM experience built with
Vite + React.

## Feature highlights

- **Unified prediction engine** – blend revenue, expense, pipeline, and retention metrics to
generate forward-looking guidance, confidence notes, and quarter-by-quarter outlooks.
- **Data management diagnostics** – surface data quality gaps, integration opportunities, and
curation actions from uploaded CRM exports or notes.
- **Risk and compliance radar** – quantify risk appetite, exposure hotspots, and mitigation playbooks.
- **AI advice console** – drive follow-up strategy conversations with context-aware prompts.
- **Deep document comparison** – upload two strategic artefacts and receive AI-aligned
alignment, conflict, and integration recommendations alongside structural diffs.
- **Advanced React CRM shell** – responsive dashboards, rich charts, flexible layout primitives,
and quick navigation across modules.

## Prerequisites

- Python 3.10+
- Node.js 18+
- An OpenAI API key with access to the Responses API (`OPENAI_API_KEY`)

## Installation

1. Clone the repository and move into the project directory.
2. Run the Python dependency check (skips libraries that are already installed):

```bash
python install.py
```

On Windows you can execute `install.bat` for the same flow.

3. Copy the environment template and add your OpenAI credentials:

```bash
cp .env.example .env
# edit .env and set OPENAI_API_KEY (and optionally OPENAI_MODEL)
```

4. Install backend packages:

```bash
python -m pip install -r requirements.txt
```

5. Install the React workspace dependencies:

```bash
cd crm-ui
npm install
```

## Running the full stack

In one terminal start the Flask API:

```bash
python app.py
```

In another terminal launch the Vite development server:

```bash
cd crm-ui
npm run dev
```

By default the React app is served at http://localhost:5173 and proxies API calls to
http://localhost:5000. Update `VITE_API_BASE_URL` in `.env` if you need a different host.

## Environment variables

The root `.env` file is read by the Flask backend:

- `OPENAI_API_KEY` – required private key for the ChatGPT Responses API
- `OPENAI_MODEL` – optional override (defaults to `gpt-4.1-mini`)

The React client can be configured with `crm-ui/.env` using the same
`VITE_API_BASE_URL` value shown in `.env.example`.

## Project structure

```
app/ Flask blueprint and ChatGPT client helpers
crm-ui/ Vite React CRM workspace
src/api/ Typed API clients and React Query hooks
src/components/ UI primitives used across CRM pages
src/pages/ Dashboard modules (prediction, risk, data, AI advisor, comparison)
install.py Convenience installer for backend dependencies
app.py Flask entry point
```

## Security

The application only reads your API key from environment variables at runtime and never
persists it. Do not commit real keys to version control.
7 changes: 7 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from app import create_app

app = create_app()


if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
29 changes: 29 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from __future__ import annotations

import logging
from pathlib import Path

from dotenv import load_dotenv
from flask import Flask
from flask_cors import CORS

from .routes import bp


def create_app() -> Flask:
"""Application factory for the CRM intelligence API."""
env_path = Path('.') / '.env'
if env_path.exists():
load_dotenv(env_path)

app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024 # 5 MB payload cap

# Enable CORS so the Vite React workspace can communicate with the API during dev.
CORS(app, resources={r"/api/*": {"origins": "*"}})

logging.basicConfig(level=logging.INFO)

app.register_blueprint(bp)

return app
234 changes: 234 additions & 0 deletions app/chatgpt_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
from __future__ import annotations

import json
import os
from typing import Any, Dict

from openai import OpenAI


class ChatGPTClient:
"""Wrapper around the OpenAI Responses API tailored for CRM intelligence."""

def __init__(self) -> None:
api_key = os.getenv('OPENAI_API_KEY')
if not api_key:
raise RuntimeError(
'OPENAI_API_KEY is not configured. Set it in your environment or .env file.'
)
self._client = OpenAI(api_key=api_key)
self._model = os.getenv('OPENAI_MODEL', 'gpt-4.1-mini')

def synthesize_crm_intelligence(self, payload: Dict[str, Any]) -> Dict[str, Any]:
"""Generate CRM-ready insights across data, prediction, risk, and guidance."""
system_prompt = (
"You are a principal CRM strategist fusing data science with go-to-market leadership. "
"You receive structured telemetry about a customer portfolio and must produce an actionable "
"intelligence brief for revenue teams. Always respond with well-structured JSON that adheres "
"to the provided schema."
)
user_prompt = (
"Create an enterprise-grade briefing that includes sections for data management, predictive outlook, "
"risk posture, AI-powered recommendations, and CRM workflow actions. Anchor your commentary in the "
"metrics, document summaries, and comparison context supplied below."
f"\n\nPayload:\n{json.dumps(payload, indent=2)}"
)
request_arguments: Dict[str, Any] = {
'model': self._model,
'input': [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
'temperature': 0.25,
'max_output_tokens': 1100,
}
try:
response = self._client.responses.create(
**request_arguments,
response_format={"type": "json_schema", "json_schema": self._crm_schema()},
)
except TypeError:
response = self._client.responses.create(**request_arguments)
return self._parse_response(response)

def compare_documents(self, payload: Dict[str, Any]) -> Dict[str, Any]:
"""Derive alignment and conflicts between two artefacts."""
system_prompt = (
"You are an enterprise transformation advisor reconciling two strategic documents. "
"Provide a candid comparison with highlights of convergence, divergence, and integration "
"opportunities. Return JSON that complies with the schema."
)
user_prompt = (
"Two documents plus optional focus guidance are provided. Produce a deep comparison, surfacing "
"key differences, risks, and recommended unifying actions."
f"\n\nPayload:\n{json.dumps(payload, indent=2)}"
)
request_arguments: Dict[str, Any] = {
'model': self._model,
'input': [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
'temperature': 0.2,
'max_output_tokens': 800,
}
try:
response = self._client.responses.create(
**request_arguments,
response_format={"type": "json_schema", "json_schema": self._comparison_schema()},
)
except TypeError:
response = self._client.responses.create(**request_arguments)
return self._parse_response(response)

def continue_conversation(self, conversation: str, prompt: str) -> str:
"""Send a free-form prompt to ChatGPT for additional assistance."""
system_prompt = (
"You are a senior revenue strategist who references previous CRM analyses to answer follow-up "
"questions with precision. Provide structured, next-best-action advice."
)
user_prompt = (
"Historical context:\n"
f"{conversation}\n\n"
"New request:\n"
f"{prompt}"
)
response = self._client.responses.create(
model=self._model,
input=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
temperature=0.35,
max_output_tokens=700,
)
return response.output_text.strip()

@staticmethod
def _crm_schema() -> Dict[str, Any]:
return {
"name": "crm_intelligence_report",
"schema": {
"type": "object",
"properties": {
"data_management": {
"type": "object",
"properties": {
"quality_assessment": {"type": "string"},
"integration_opportunities": {"type": "string"},
"gaps": {"type": "string"},
},
"required": [
"quality_assessment",
"integration_opportunities",
"gaps",
],
},
"predictive_outlook": {
"type": "object",
"properties": {
"growth_projection": {"type": "string"},
"leading_indicators": {"type": "string"},
"confidence": {"type": "string"},
"next_quarters": {"type": "string"},
},
"required": [
"growth_projection",
"leading_indicators",
"confidence",
"next_quarters",
],
},
"risk_analysis": {
"type": "object",
"properties": {
"exposures": {"type": "string"},
"mitigations": {"type": "string"},
"heatmap": {"type": "string"},
},
"required": ["exposures", "mitigations", "heatmap"],
},
"ai_advice": {
"type": "object",
"properties": {
"strategic_guidance": {"type": "string"},
"tactical_steps": {"type": "string"},
"automation_ideas": {"type": "string"},
},
"required": [
"strategic_guidance",
"tactical_steps",
"automation_ideas",
],
},
"crm_actions": {
"type": "object",
"properties": {
"priority_accounts": {"type": "string"},
"engagement_plan": {"type": "string"},
"workflow_enhancements": {"type": "string"},
},
"required": [
"priority_accounts",
"engagement_plan",
"workflow_enhancements",
],
},
},
"required": [
"data_management",
"predictive_outlook",
"risk_analysis",
"ai_advice",
"crm_actions",
],
"additionalProperties": False,
},
}

@staticmethod
def _comparison_schema() -> Dict[str, Any]:
return {
"name": "document_comparison",
"schema": {
"type": "object",
"properties": {
"alignment": {"type": "string"},
"contradictions": {"type": "string"},
"integration_paths": {"type": "string"},
"risk_flags": {"type": "string"},
},
"required": [
"alignment",
"contradictions",
"integration_paths",
"risk_flags",
],
"additionalProperties": False,
},
}

@staticmethod
def _parse_response(response: Any) -> Dict[str, Any]:
raw_payload = _extract_response_text(response)
try:
parsed = json.loads(raw_payload)
except json.JSONDecodeError as exc:
raise RuntimeError('Unable to parse response from ChatGPT.') from exc
if not isinstance(parsed, dict):
raise RuntimeError('Unexpected response format from ChatGPT.')
return parsed


def _extract_response_text(response: Any) -> str:
"""Return the textual payload from an OpenAI Responses result."""

for attribute in ('output_text', 'text'):
value = getattr(response, attribute, None)
if isinstance(value, str) and value.strip():
return value

try:
return response.output[0].content[0].text # type: ignore[index]
except (AttributeError, IndexError, KeyError) as exc:
raise RuntimeError('Unable to read response payload from ChatGPT.') from exc
Loading