Skip to content

Repository files navigation

🧠 Mental Journal Vocal — Vertex AI Pipeline

AI-powered mental health journal analysis using Google Vertex AI. Transform weekly voice recordings into actionable insights with Speech-to-Text v2 and Gemini 2.0.

GCP Python Gemini Status


✅ System Status

FULLY OPERATIONAL - All components tested end-to-end (October 27, 2025)

  • ✅ Audio upload (no time limit)
  • ✅ Automatic transcription (95-99% accuracy)
  • ✅ Emotion & event extraction
  • ✅ Weekly report generation (HTML/PDF)

📊 View Full System Status


🚀 Quick Start

Upload & Process Audio

# Upload a session (recommended method)
./scripts/upload_session_simple.sh my_journal.wav 2025-W44 session_001

# Generate weekly report
bash scripts/run_pipeline.sh 2025-W44

# View report
gsutil cp gs://mj-reports-mental-journal-dev/2025-W44/weekly_report.html .
open weekly_report.html

For detailed instructions, see User Guide


📚 Documentation

Document Description
USER_GUIDE.md Complete step-by-step usage guide
SYSTEM_STATUS.md Current system status & test results
SIGNED_URL_ISSUE.md Known issue with signed URLs & workaround
ARCHITECTURE.md System architecture details
QUICKSTART.md Fast deployment guide

🌍 Regions & Names (exemples)

  • Region: europe-west1 (Belgium)

  • PROJECT_ID: mental-journal-dev

  • GCS buckets :

    • mj-audio-raw-<PROJECT_ID> (source WAV/MP3/FLAC)
    • mj-audio-processed-<PROJECT_ID> (normalized wav + features)
    • mj-analytics-<PROJECT_ID> (JSON artifacts)
    • mj-reports-<PROJECT_ID> (weekly HTML/PDF)
  • BQ dataset (optionnel Looker Studio) : journaling

  • Service account : pipeline-sa@<PROJECT_ID>.iam.gserviceaccount.com


🎯 What It Does (overview)

Voice Recording (≤ 15 min)
    ↓ Speech-to-Text v2          → transcript.json (+ word timestamps)
    ↓ Librosa (Prosody)          → prosody_features.json (pitch, energy, pauses)
    ↓ Gemini (NLU)               → events_emotions.json (events, emotions, themes)
    ↓ Jinja2 + WeasyPrint        → weekly_report.json + weekly_report.{html,pdf}

🚀 Quick Start

Prerequisites

# Authenticate with GCP
gcloud auth login
gcloud config set project mental-journal-dev

# Enable required APIs
gcloud services enable \
  run.googleapis.com \
  cloudbuild.googleapis.com \
  secretmanager.googleapis.com \
  aiplatform.googleapis.com \
  speech.googleapis.com \
  cloudkms.googleapis.com \
  workflows.googleapis.com \
  logging.googleapis.com \
  pubsub.googleapis.com \
  bigquery.googleapis.com

1) Setup Infrastructure

./scripts/setup.sh

Crée les buckets GCS, le service account, KMS (CMEK) et active les APIs.

2) Deploy Pipeline

./scripts/deploy.sh

Construit l’image Docker et déploie le Cloud Run Job.

3) Run Analysis

# Upload audio (ex. semaine 2025-W42)
gsutil cp audio.wav gs://mj-audio-raw-mental-journal-dev/2025-W42/session_001.wav

# Execute pipeline for that week
gcloud run jobs execute mj-weekly-pipeline --region=europe-west1 --args=2025-W42

Vérifier les résultats :

./scripts/check_results.sh 2025-W42

📁 Project Structure

vertex/ ├── pipeline/ # Main analysis pipeline │ ├── main.py # Orchestration script │ ├── Dockerfile │ └── requirements.txt ├── api/ # FastAPI REST API │ ├── main.py # API entry point │ ├── routers/ # API routes (health, upload, sessions, reports, orchestration) │ ├── Dockerfile │ └── .env.example ├── templates/ # HTML report templates ├── scripts/ # Bash automation scripts │ ├── deploy.sh # Deploy pipeline (Cloud Run Job) │ ├── deploy_api.sh # Deploy API (Cloud Run Service) │ ├── test_api.sh # Test all API routes │ └── ... └── docs/ # Documentation ├── INDEX.md # Documentation index ├── ACCOMPLISSEMENT.md ├── RESUME_EXECUTIF.md ├── GEMINI_2X_MIGRATION.md ├── PROJECT_ROADMAP.md ├── API_GUIDE.md # API usage guide ├── API_ROUTES.md # API routes reference └── MCP_SETUP.md


🛠️ Tech Stack

  • Cloud : Cloud Run, Cloud Storage (CMEK), Vertex AI, Workflows, Cloud Scheduler

  • AI Models :

    • Speech-to-Text v2 (location=global, support long audios via LRO)
    • Gemini 2.x (Flash/Pro selon besoins)
  • Python : librosa, scipy, numpy, jinja2, weasyprint

  • Container : Docker (Python 3.11 + ffmpeg + deps WeasyPrint)


📊 Outputs

Pour chaque semaine (ex. 2025-W42) :

Analytics (JSON)

gs://mj-analytics-mental-journal-dev/2025-W42/
├── session_001/
│   ├── transcript.json          # STT transcription
│   ├── prosody_features.json    # Vocal features
│   └── events_emotions.json     # NLU analysis
└── weekly_report.json           # Aggregated report

Reports (HTML/PDF)

gs://mj-reports-mental-journal-dev/2025-W42/
├── weekly_report.html
└── weekly_report.pdf

💰 Cost (approx.)

**~ €0.30 / semaine** (≈ €1.20 / mois)

  • Speech-to-Text v2 : ~€0.10
  • Gemini (Flash/Pro) : ~€0.15
  • Storage + Compute : ~€0.05

(Dépend de la durée d’audio, du modèle Gemini et de la fréquence d’exécution.)


📝 Configuration (Cloud Run Job — env vars)

PROJECT_ID=mental-journal-dev
REGION=europe-west1
GOOGLE_CLOUD_LOCATION=global
GEMINI_MODEL=gemini-2.0-flash-exp   # ou gemini-2.5-pro
BUCKET_RAW=mj-audio-raw-mental-journal-dev
BUCKET_PROC=mj-audio-processed-mental-journal-dev
BUCKET_ANALYTICS=mj-analytics-mental-journal-dev
BUCKET_REPORTS=mj-reports-mental-journal-dev

🔐 Security (CMEK, IAM, Logs)

  • CMEK activé sur tous les buckets.
  • IAM minimal : pipeline-sa pour écrire/lire là où nécessaire, lecteur Looker si export BQ.
  • Logs d’accès : Cloud Audit Logging (optionnel : sink vers BigQuery).

Exemple de sink :

bq --location=$REGION mk --dataset $PROJECT_ID:logsink
LOG_SINK=access-logs
gcloud logging sinks create $LOG_SINK \
  bigquery.googleapis.com/projects/$PROJECT_ID/datasets/logsink \
  --log-filter='resource.type="gcs_bucket"'

📚 Schemas (extraits)

schemas/transcript.schema.json

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Transcript",
  "type": "object",
  "properties": {
    "session_id": {"type": "string"},
    "audio_uri": {"type": "string"},
    "language_code": {"type": "string"},
    "created_at": {"type": "string", "format": "date-time"},
    "transcript": {"type": "string"},
    "words": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "start": {"type": "number"},
          "end": {"type": "number"},
          "word": {"type": "string"},
          "confidence": {"type": "number"}
        },
        "required": ["start", "end", "word"]
      }
    }
  },
  "required": ["session_id", "audio_uri", "language_code", "created_at", "transcript"]
}

schemas/prosody_features.schema.json

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "ProsodyFeatures",
  "type": "object",
  "properties": {
    "session_id": {"type": "string"},
    "created_at": {"type": "string", "format": "date-time"},
    "sr": {"type": "integer"},
    "duration_sec": {"type": "number"},
    "pitch_mean": {"type": "number"},
    "pitch_std": {"type": "number"},
    "energy_mean": {"type": "number"},
    "energy_std": {"type": "number"},
    "pause_count": {"type": "integer"},
    "pause_total_sec": {"type": "number"}
  },
  "required": ["session_id", "created_at", "sr", "duration_sec"]
}

schemas/events_emotions.schema.json

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "EventsEmotions",
  "type": "object",
  "properties": {
    "session_id": {"type": "string"},
    "created_at": {"type": "string", "format": "date-time"},
    "events": {"type": "array", "items": {"type": "string"}},
    "emotions": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "label": {"type": "string"},
          "confidence": {"type": "number"}
        },
        "required": ["label"]
      }
    },
    "themes": {"type": "array", "items": {"type": "string"}}
  },
  "required": ["session_id", "created_at", "events", "emotions"]
}

schemas/weekly_report.schema.json

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "WeeklyReport",
  "type": "object",
  "properties": {
    "week": {"type": "string"},
    "user_tz": {"type": "string"},
    "sessions": {"type": "integer"},
    "emotion_index": {"type": "number"},
    "trend": {"type": "string", "enum": ["up", "down", "flat"]},
    "highlights": {"type": "array", "items": {"type": "string"}},
    "prosody_summary": {
      "type": "object",
      "properties": {
        "pitch_mean": {"type": "number"},
        "energy_mean": {"type": "number"},
        "pause_rate": {"type": "number"}
      }
    }
  },
  "required": ["week", "sessions", "emotion_index", "trend"]
}

🧩 Build & Deploy (Cloud Run Job)

# Build image
gcloud builds submit --tag gcr.io/$PROJECT_ID/mj-pipeline:latest pipeline/

# Create Cloud Run Job
gcloud run jobs create mj-weekly-pipeline \
  --image gcr.io/$PROJECT_ID/mj-pipeline:latest \
  --region=$REGION \
  --service-account=pipeline-sa@$PROJECT_ID.iam.gserviceaccount.com \
  --set-env-vars=PROJECT_ID=$PROJECT_ID,REGION=$REGION,BUCKET_RAW=mj-audio-raw-$PROJECT_ID,BUCKET_PROC=mj-audio-processed-$PROJECT_ID,BUCKET_ANALYTICS=mj-analytics-$PROJECT_ID,BUCKET_REPORTS=mj-reports-$PROJECT_ID,USER_TZ=Europe/Paris \
  --max-retries=1 \
  --task-timeout=3600s

# Manual execution for current week
WEEK=$(date +"%G-W%V")
gcloud run jobs execute mj-weekly-pipeline --region=$REGION --args=$WEEK

⏱️ Scheduling (Cloud Scheduler → Workflows → Run Job)

workflows/trigger_job.yaml

main:
  params: [week]
  steps:
  - callRunJob:
      call: http.post
      args:
        url: https://run.googleapis.com/apis/run.googleapis.com/v1/projects/${sys.get_env("GOOGLE_CLOUD_PROJECT")}/locations/europe-west1/jobs/mj-weekly-pipeline:run
        auth:
          type: OAuth2
        body:
          overrides:
            containerOverrides:
              - args: ["${week}"]
      result: r
  - returnResult:
      return: ${r.body}

Déploiement & autorisations :

WORKFLOW=mj-run-job
gcloud workflows deploy $WORKFLOW --source=workflows/trigger_job.yaml --location=$REGION

# Autoriser le SA à invoquer le workflow
gcloud workflows add-iam-policy-binding $WORKFLOW \
  --location=$REGION \
  --member=serviceAccount:pipeline-sa@$PROJECT_ID.iam.gserviceaccount.com \
  --role=roles/workflows.invoker

Cloud Scheduler (dimanche 23:55 Europe/Paris) :

CRON="55 23 * * SUN"
PAYLOAD=$(jq -n --arg w "$(date +"%G-W%V")" '{argument: {week: $w}}')

gcloud scheduler jobs create http mj-weekly \
  --schedule="$CRON" \
  --time-zone="Europe/Paris" \
  --http-method=POST \
  --uri="https://workflowexecutions.googleapis.com/v1/projects/$PROJECT_ID/locations/$REGION/workflows/$WORKFLOW/executions" \
  --oauth-service-account-email=pipeline-sa@$PROJECT_ID.iam.gserviceaccount.com \
  --headers="Content-Type=application/json" \
  --message-body="$PAYLOAD"

Variante Pub/Sub : publier {week:"YYYY-Www"} et déclencher une petite Cloud Run Service/Workflow. La version HTTP ci-dessus est la plus simple.


🧪 Useful Commands

# Execute for current week
gcloud run jobs execute mj-weekly-pipeline --args=$(date +'%G-W%V') --region=$REGION

# View logs
gcloud logging read 'resource.type=cloud_run_job' --limit=20

# List outputs
gsutil ls gs://mj-analytics-mental-journal-dev/

# Download report
gsutil cp gs://mj-reports-mental-journal-dev/2025-W42/weekly_report.pdf ./

📚 Documentation

Toute la documentation est dans /docs :


🤝 Contributing

This project was developed for the GCPU Hackathon 2025 – Mental Health Journal.


GCP Project: mental-journal-dev Region: europe-west1 Contact: queriauxrobin@gmail.com

About

🧠 AI-powered mental health journal analysis pipeline using Google Vertex AI (Speech-to-Text v2 + Gemini 2.0 Flash)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages