Phase 3: Initial commit - Nextcloud Analytics Hub Project
Nextcloud Analytics Hub complete: - Nextcloud PHP app (analytics-hub/) - All phases (1-3) complete - Go client tool (nextcloud-analytics) - Full CLI implementation - Documentation (PRD, README, STATUS, SKILL.md) - Production-ready for deployment to https://cloud.shortcutsolutions.net Repository: git.teamworkapps.com/shortcut/nextcloud-analytics Workspace: /home/molt/.openclaw/workspace
This commit is contained in:
725
PRD.md
Normal file
725
PRD.md
Normal file
@@ -0,0 +1,725 @@
|
||||
# Nextcloud "Mini-CMO" Analytics Hub - Product Requirements Document (PRD)
|
||||
|
||||
**Version**: 3.0 (PHP App Architecture)
|
||||
**Project**: nextcloud-google-analytics-integration
|
||||
**User**: Mike (Shortcut Solutions)
|
||||
**Goal**: Nextcloud internal PHP app with AI-generated analytics reports
|
||||
**Status**: READY FOR IMPLEMENTATION
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Nextcloud internal PHP application providing Google Analytics 4 reporting with AI-generated client reports. Exposes REST APIs for agent access via nextcloud-integration project. Scheduled internal jobs for daily processing.
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
**Technology Stack**: PHP 8.0+ (Nextcloud App Framework)
|
||||
**Target Nextcloud**: 25.0+ (https://cloud.shortcutsolutions.net)
|
||||
**Dependencies**: Google Analytics Data API v1, Anthropic Claude API
|
||||
**Agent Access**: REST APIs exposed via Nextcloud-integration tools
|
||||
**Scheduling**: Nextcloud cron system
|
||||
|
||||
---
|
||||
|
||||
## Technical Architecture & Strategy
|
||||
|
||||
### The "Nextcloud Internal App" Strategy (REVISED)
|
||||
|
||||
**Approach**: Nextcloud PHP application running inside Nextcloud server, exposing APIs for agent consumption.
|
||||
|
||||
**Core Principles**:
|
||||
1. **Embedded in Nextcloud**: Runs as PHP app, not external service
|
||||
2. **Agent Integration**: Exposes Nextcloud API for nextcloud-integration tool access
|
||||
3. **Secure Credentials**: Uses Nextcloud app password for authentication
|
||||
4. **Fail-Fast**: Halt on errors rather than propagate bad data
|
||||
5. **Observable**: Every run generates logs and status notifications
|
||||
|
||||
---
|
||||
|
||||
## Data Flow Architecture (REVISED)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ PHASE 1: App Installation (One-Time Setup) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ 1. Install Nextcloud app (analytics-hub) │
|
||||
│ 2. Configure Google OAuth credentials │
|
||||
│ 3. Configure Anthropic API key │
|
||||
│ 4. Set client configurations (clients.json) │
|
||||
│ 5. Enable cron job (Mon-Fri 7:00 AM) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ PHASE 2: Agent Integration (OpenClaw) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ OpenClaw calls nextcloud-integration tools: │
|
||||
│ │
|
||||
│ 1. Trigger report generation │
|
||||
│ → POST /apps/analytics-hub/api/generate │
|
||||
│ → Client slug + date range │
|
||||
│ │
|
||||
│ 2. List available reports │
|
||||
│ → GET /apps/analytics-hub/api/reports │
|
||||
│ │
|
||||
│ 3. Download specific report │
|
||||
│ → GET /apps/analytics-hub/api/report/<id> │
|
||||
│ │
|
||||
│ 4. Configure client settings │
|
||||
│ → POST /apps/analytics-hub/api/config │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ PHASE 3: Internal Processing (Nextcloud App) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Nextcloud app runs scheduled job (Mon-Fri 7:00 AM): │
|
||||
│ │
|
||||
│ 1. Refresh Google access token │
|
||||
│ → IF 401: Email alert + HALT │
|
||||
│ │
|
||||
│ 2. For each client in clients.json: │
|
||||
│ a. Fetch GA4 data (last 7 days) │
|
||||
│ b. Validate completeness │
|
||||
│ c. Calculate deltas with smart thresholds │
|
||||
│ d. Generate Report via Anthropic API │
|
||||
│ e. Store in Nextcloud files (WebDAV internal) │
|
||||
│ f. Expose via API for agent access │
|
||||
│ │
|
||||
│ 3. Log success summary │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**KEY CHANGES**:
|
||||
- Nextcloud PHP app (not external Python)
|
||||
- Exposes REST APIs for agent integration
|
||||
- Uses nextcloud-integration tools for access
|
||||
- Scheduled internal jobs for daily processing
|
||||
|
||||
---
|
||||
|
||||
## Feature Specifications
|
||||
|
||||
### Feature 1: Nextcloud App Architecture
|
||||
|
||||
**Goal**: PHP application running inside Nextcloud
|
||||
|
||||
**Implementation**:
|
||||
|
||||
#### App Structure
|
||||
```
|
||||
analytics-hub/
|
||||
├── lib/
|
||||
│ ├── Controller/
|
||||
│ │ ├── ApiV1Controller.php # Main API endpoints
|
||||
│ │ └── ReportController.php # Report generation logic
|
||||
│ ├── Service/
|
||||
│ │ ├── GoogleAnalyticsService.php # GA4 API wrapper
|
||||
│ │ ├── LLMService.php # Anthropic API wrapper
|
||||
│ │ └── DataProcessor.php # Delta calculations
|
||||
│ ├── Model/
|
||||
│ │ ├── ClientConfig.php # Client entity
|
||||
│ │ └── Report.php # Report entity
|
||||
│ └── AppInfo.php # App metadata
|
||||
├── templates/
|
||||
│ └── admin.php # Configuration UI
|
||||
├── css/
|
||||
│ └── style.css
|
||||
├── js/
|
||||
│ └── admin.js
|
||||
├── config/
|
||||
│ └── clients.json # Client configurations
|
||||
├── info.xml # Nextcloud app info
|
||||
├── appinfo/info.xml # App metadata
|
||||
└── cron.php # Scheduled jobs
|
||||
```
|
||||
|
||||
#### Nextcloud Integration
|
||||
|
||||
```php
|
||||
// Use Nextcloud APIs
|
||||
use OCP\AppFramework\App;
|
||||
use OCP\IL10N\IFactory;
|
||||
use OCP\Files\Node;
|
||||
|
||||
// Access Nextcloud user
|
||||
$user = $this->getUser();
|
||||
|
||||
// Access Nextcloud files (internal WebDAV)
|
||||
$files = \OC::$server->getWebDavRoot();
|
||||
```
|
||||
|
||||
#### Authentication
|
||||
|
||||
- **Method**: Nextcloud App Password authentication
|
||||
- **Storage**: Nextcloud's built-in app password system
|
||||
- **Access**: App password stored in Nextcloud settings (encrypted)
|
||||
- **Agent Access**: Nextcloud-integration tools use app password for API access
|
||||
|
||||
---
|
||||
|
||||
### Feature 2: REST API Endpoints for Agent Integration
|
||||
|
||||
**Goal**: Expose analytics functions via Nextcloud API
|
||||
|
||||
#### API Endpoints
|
||||
|
||||
| Endpoint | Method | Description | Agent Tool |
|
||||
|-----------|--------|-------------|--------------|
|
||||
| `/apps/analytics-hub/api/reports` | GET | List all available reports | nextcloud-client (custom) |
|
||||
| `/apps/analytics-hub/api/report/<id>` | GET | Download specific report | nextcloud-client (custom) |
|
||||
| `/apps/analytics-hub/api/generate` | POST | Trigger report generation | nextcloud-client (custom) |
|
||||
| `/apps/analytics-hub/api/config` | GET/POST | List/update client config | nextcloud-client (custom) |
|
||||
| `/apps/analytics-hub/api/status` | GET | App health/status | nextcloud-client (custom) |
|
||||
|
||||
#### API Authentication
|
||||
|
||||
```php
|
||||
// Verify Nextcloud app password
|
||||
$userSession = $this->getUserSession();
|
||||
$appPassword = $this->request->getHeader('Authorization');
|
||||
|
||||
if (!$userSession->verifyPassword($appPassword)) {
|
||||
return new JSONResponse(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
// Allow only authorized agents
|
||||
if (!$this->getAppConfig()->getAgentAuthorized()) {
|
||||
return new JSONResponse(['error' => 'Agent access disabled'], 403);
|
||||
}
|
||||
```
|
||||
|
||||
#### API Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"reports": [
|
||||
{
|
||||
"id": 123,
|
||||
"client_name": "Logos School",
|
||||
"report_date": "2026-02-12",
|
||||
"file_path": "/files/Analytics/LogosSchool/Report_2026-02-12.md"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Feature 3: Secure Authentication & Token Health Monitoring
|
||||
|
||||
**Goal**: Zero-touch operation with proactive failure detection
|
||||
|
||||
**Implementation**:
|
||||
|
||||
#### Initial Setup (auth.py)
|
||||
- Runs locally on Mike's machine
|
||||
- Opens browser for Google OAuth consent
|
||||
- Saves `refresh_token` to server `.env` file
|
||||
- Sets file permissions: `chmod 600 .env` (owner read/write only)
|
||||
|
||||
#### Token Health Checks (NEW)
|
||||
```python
|
||||
token_age_days = (now - token_created_date).days
|
||||
|
||||
if token_age_days > 150: # Warning at 5 months
|
||||
send_email("⚠️ Refresh token nearing expiry (180 days)")
|
||||
|
||||
if 401 error on token refresh:
|
||||
send_email("🔴 URGENT: Token expired - run auth.py")
|
||||
sys.exit(1) # Halt execution
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Feature 2: Intelligent Data Pipeline
|
||||
|
||||
**Goal**: Mathematical accuracy + data quality validation
|
||||
|
||||
#### GA4 API Configuration
|
||||
- **Date Range**: Last 7 days (enables Week-over-Week comparison)
|
||||
- **Dimensions**: `date`, `sessionDefaultChannelGroup`, `pagePath`
|
||||
- **Metrics**: `sessions`, `totalUsers`, `conversions`, `eventCount`
|
||||
|
||||
#### Smart Processing Logic (NEW)
|
||||
|
||||
```python
|
||||
def calculate_delta(current, previous):
|
||||
"""Handles edge cases that break naive % calculations"""
|
||||
|
||||
# Edge case 1: Division by zero
|
||||
if previous == 0:
|
||||
if current == 0:
|
||||
return {"change_pct": 0, "label": "No change", "is_significant": False}
|
||||
else:
|
||||
return {"change_pct": None, "label": f"New activity (+{current})", "is_significant": True}
|
||||
|
||||
# Edge case 2: Small numbers creating misleading %
|
||||
change_pct = ((current - previous) / previous) * 100
|
||||
abs_change = current - previous
|
||||
|
||||
# Require both % threshold AND minimum absolute change
|
||||
is_significant = (abs(change_pct) > 20 AND abs(abs_change) > 5)
|
||||
|
||||
return {
|
||||
"change_pct": round(change_pct, 1),
|
||||
"abs_change": abs_change,
|
||||
"is_significant": is_significant,
|
||||
"label": format_delta_label(change_pct, abs_change)
|
||||
}
|
||||
```
|
||||
|
||||
#### Data Validation (NEW)
|
||||
|
||||
```python
|
||||
def validate_data(response):
|
||||
"""Ensure data completeness before processing"""
|
||||
|
||||
# Check 1: Expected date range present
|
||||
expected_dates = get_last_7_days()
|
||||
actual_dates = extract_dates(response)
|
||||
missing_dates = expected_dates - actual_dates
|
||||
|
||||
if missing_dates:
|
||||
raise DataIncompleteError(f"Missing data for: {missing_dates}")
|
||||
|
||||
# Check 2: No null metrics
|
||||
if any_null_metrics(response):
|
||||
raise DataIncompleteError("Null values in metrics")
|
||||
|
||||
return True
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Feature 3: Client Context Configuration
|
||||
|
||||
**Goal**: Maintainable client-specific settings with validation
|
||||
|
||||
#### Storage
|
||||
- **File**: `config/clients.json` (version controlled)
|
||||
|
||||
#### Structure (ENHANCED)
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.0",
|
||||
"last_updated": "2026-02-12",
|
||||
"clients": [
|
||||
{
|
||||
"property_id": "123456789",
|
||||
"name": "Logos School",
|
||||
"slug": "logos_school",
|
||||
"active": true,
|
||||
"context": {
|
||||
"business_type": "Therapeutic school",
|
||||
"key_metrics": ["admissions", "parent_inquiries"],
|
||||
"tone": "professional",
|
||||
"focus_areas": "Focus on Admission funnel traffic and conversion quality"
|
||||
},
|
||||
"webdav_config": {
|
||||
"base_path": "/AIGeneratedReports/LogosSchool",
|
||||
"auto_create": true
|
||||
},
|
||||
"thresholds": {
|
||||
"significant_change_pct": 20,
|
||||
"significant_change_abs": 5
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Validation Schema
|
||||
|
||||
```python
|
||||
def validate_clients_config(config):
|
||||
"""Ensure config is well-formed before using"""
|
||||
|
||||
required_fields = ["property_id", "name", "slug", "context", "webdav_config"]
|
||||
|
||||
for client in config["clients"]:
|
||||
# Check required fields exist
|
||||
missing = [f for f in required_fields if f not in client]
|
||||
if missing:
|
||||
raise ConfigError(f"Missing fields for {client.get('name')}: {missing}")
|
||||
|
||||
# Validate property_id format
|
||||
if not client["property_id"].isdigit():
|
||||
raise ConfigError(f"Invalid property_id for {client['name']}")
|
||||
|
||||
# Check WebDAV path doesn't have spaces
|
||||
if " " in client["webdav_config"]["base_path"]:
|
||||
raise ConfigError(f"WebDAV path cannot contain spaces")
|
||||
|
||||
return True
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Feature 4: The "Mini-CMO" Report Generator
|
||||
|
||||
**Goal**: Consistent, high-quality narrative reports via Claude API
|
||||
|
||||
#### LLM Integration (SPECIFIED)
|
||||
|
||||
- **Provider**: Anthropic Claude API
|
||||
- **Model**: `claude-sonnet-4-5-20250929` (cost-effective, high quality)
|
||||
- **API Key Storage**: Server `.env` file (separate from Google credentials)
|
||||
- **Rate Limiting**: Max 5 reports/minute (Anthropic tier limits)
|
||||
- **Cost Control**: ~$0.015 per report (3K tokens @ $3/M input, $15/M output)
|
||||
|
||||
#### Prompt Template
|
||||
|
||||
```python
|
||||
SYSTEM_PROMPT = """
|
||||
You are a Mini-CMO analyst for {client_name}, a {business_type}.
|
||||
|
||||
Your role: Transform analytics data into clear, actionable insights for business owner.
|
||||
|
||||
TONE: {tone} but conversational - avoid jargon.
|
||||
FOCUS: {focus_areas}
|
||||
|
||||
OUTPUT FORMAT (strict):
|
||||
# Weekly Analytics Snapshot - {report_date}
|
||||
|
||||
## 📊 Headline
|
||||
[One sentence summary of the biggest story this week]
|
||||
|
||||
## ✅ Key Wins
|
||||
[2-3 bullet points on positive movements or validations]
|
||||
|
||||
## 💡 Recommendation
|
||||
[One specific action based on the data]
|
||||
|
||||
## 📈 The Numbers
|
||||
[Formatted table of metrics with context]
|
||||
|
||||
RULES:
|
||||
- Be specific (use actual numbers from data)
|
||||
- Focus on "why this matters" not just "what changed"
|
||||
- If a change is statistically insignificant, note it
|
||||
- Avoid phrases like "optimization" or "synergy"
|
||||
"""
|
||||
|
||||
USER_PROMPT = """
|
||||
DATA SUMMARY: {processed_data_json}
|
||||
|
||||
Generate weekly report following the system format exactly.
|
||||
"""
|
||||
```
|
||||
|
||||
#### Retry Logic (NEW)
|
||||
|
||||
```python
|
||||
def call_llm_with_retry(prompt, max_retries=3):
|
||||
"""Robust API calling with exponential backoff"""
|
||||
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
response = anthropic_client.messages.create(
|
||||
model="claude-sonnet-4-5-20250929",
|
||||
max_tokens=2000,
|
||||
system=SYSTEM_PROMPT,
|
||||
messages=[{"role": "user", "content": USER_PROMPT}],
|
||||
timeout=30.0
|
||||
)
|
||||
|
||||
# Validate response quality
|
||||
content = response.content[0].text
|
||||
if len(content) < 200:
|
||||
raise ValueError("Response too short - likely error")
|
||||
if "# Weekly Analytics Snapshot" not in content:
|
||||
raise ValueError("Missing required format")
|
||||
|
||||
return content
|
||||
|
||||
except anthropic.RateLimitError:
|
||||
wait_time = 2 ** attempt # Exponential backoff
|
||||
log.warning(f"Rate limited, waiting {wait_time}s")
|
||||
time.sleep(wait_time)
|
||||
|
||||
except anthropic.APIError as e:
|
||||
if attempt == max_retries - 1:
|
||||
raise # Give up after max retries
|
||||
log.error(f"API error (attempt {attempt + 1}): {e}")
|
||||
time.sleep(5)
|
||||
|
||||
raise Exception(f"Failed after {max_retries} attempts")
|
||||
```
|
||||
|
||||
#### Delivery (ENHANCED)
|
||||
|
||||
```python
|
||||
def upload_to_nextcloud(markdown_content, client_config, report_date):
|
||||
"""Upload report with folder creation and error handling"""
|
||||
|
||||
# Build full path
|
||||
base_path = client_config["webdav_config"]["base_path"]
|
||||
filename = f"Report_{report_date}.md"
|
||||
full_path = f"{base_path}/{filename}"
|
||||
|
||||
# Step 1: Ensure folder exists (NEW)
|
||||
if client_config["webdav_config"]["auto_create"]:
|
||||
create_webdav_folder_if_not_exists(base_path)
|
||||
|
||||
# Step 2: Upload with retry
|
||||
webdav_url = f"https://cloud.shortcutsolutions.net/remote.php/dav/files/mike{full_path}"
|
||||
|
||||
response = requests.put(
|
||||
webdav_url,
|
||||
auth=("mike", NEXTCLOUD_APP_PASSWORD),
|
||||
data=markdown_content.encode('utf-8'),
|
||||
headers={"Content-Type": "text/markdown"},
|
||||
timeout=30
|
||||
)
|
||||
|
||||
# Step 3: Validate upload
|
||||
if response.status_code == 201: # Created
|
||||
log.info(f"✅ Uploaded: {filename}")
|
||||
return full_path
|
||||
|
||||
elif response.status_code == 204: # Updated existing
|
||||
log.info(f"✅ Updated: {filename}")
|
||||
return full_path
|
||||
|
||||
else:
|
||||
raise WebDAVError(f"Upload failed: {response.status_code} - {response.text}")
|
||||
|
||||
def create_webdav_folder_if_not_exists(folder_path):
|
||||
"""Create nested folders as needed"""
|
||||
|
||||
webdav_url = f"https://cloud.shortcutsolutions.net/remote.php/dav/files/mike{folder_path}"
|
||||
|
||||
# MKCOL creates folder
|
||||
response = requests.request(
|
||||
"MKCOL",
|
||||
webdav_url,
|
||||
auth=("mike", NEXTCLOUD_APP_PASSWORD)
|
||||
)
|
||||
|
||||
if response.status_code in [201, 405]: # 405 = already exists
|
||||
return True
|
||||
else:
|
||||
raise WebDAVError(f"Folder creation failed: {response.status_code}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Plan (REVISED)
|
||||
|
||||
### Phase 1: Nextcloud App Development (Days 1-3)
|
||||
|
||||
#### Nextcloud App Structure
|
||||
Create Nextcloud app directory structure with PHP MVC pattern.
|
||||
|
||||
#### info.xml
|
||||
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<info>
|
||||
<id>analytics-hub</id>
|
||||
<name>Mini-CMO Analytics Hub</name>
|
||||
<description>AI-powered Google Analytics 4 reporting</description>
|
||||
<licence>AGPL</licence>
|
||||
<author>Shortcut Solutions</author>
|
||||
<version>1.0.0</version>
|
||||
<namespace>AnalyticsHub</namespace>
|
||||
<category>integration</category>
|
||||
<dependencies>
|
||||
<nextcloud min-version="25" max-version="26"/>
|
||||
</dependencies>
|
||||
<settings>
|
||||
<admin>OCA\AnalyticsHub\Settings\Admin</admin>
|
||||
</settings>
|
||||
</info>
|
||||
```
|
||||
|
||||
#### Routes Configuration
|
||||
|
||||
```php
|
||||
// lib/AppInfo/Application.php
|
||||
$this->registerRoutes($this, [
|
||||
[
|
||||
'name' => 'api#v1',
|
||||
'url' => '/api',
|
||||
'verb' => 'POST',
|
||||
],
|
||||
[
|
||||
'name' => 'api#reports',
|
||||
'url' => '/api/reports',
|
||||
'verb' => 'GET',
|
||||
],
|
||||
[
|
||||
'name' => 'api#generate',
|
||||
'url' => '/api/generate',
|
||||
'verb' => 'POST',
|
||||
],
|
||||
]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Core Application (Days 4-7)
|
||||
|
||||
#### Google Cloud Configuration
|
||||
- Create Google Cloud Project: `nextcloud-analytics-hub`
|
||||
- Enable "Google Analytics Data API v1"
|
||||
- Configure OAuth Consent Screen
|
||||
- Scopes: `https://www.googleapis.com/auth/analytics.readonly`
|
||||
- Create OAuth 2.0 Client ID
|
||||
|
||||
#### App Settings Page
|
||||
|
||||
```php
|
||||
// templates/admin.php
|
||||
<form method="POST" action="/apps/analytics-hub/settings/save">
|
||||
<input type="text" name="google_client_id" placeholder="Google Client ID">
|
||||
<input type="password" name="google_client_secret" placeholder="Google Client Secret">
|
||||
<textarea name="anthropic_api_key" placeholder="Anthropic API Key"></textarea>
|
||||
<input type="file" name="credentials_json" accept=".json">
|
||||
<button type="submit">Save Configuration</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
#### Cron Job Setup
|
||||
|
||||
```php
|
||||
// cron.php
|
||||
$app = \OC::$server->getAppManager()->getApp('analytics-hub');
|
||||
|
||||
// Run Mon-Fri at 7:00 AM
|
||||
if (date('N') >= 1 && date('N') <= 5 && date('H') === '07') {
|
||||
$clients = $app->getConfig()->getClients();
|
||||
|
||||
foreach ($clients as $client) {
|
||||
try {
|
||||
$data = $app->getGoogleAnalyticsService()->fetchData($client);
|
||||
$processed = $app->getDataProcessor()->process($data, $client);
|
||||
$markdown = $app->getLLMService()->generate($processed, $client);
|
||||
$app->getNextcloudFiles()->saveReport($markdown, $client);
|
||||
} catch (Exception $e) {
|
||||
\OCP\Util::writeLog("Failed for {$client['name']}: {$e->getMessage()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: Agent Integration (Days 8-10)
|
||||
|
||||
#### Nextcloud-Integration Tool Updates
|
||||
|
||||
Add custom API access to nextcloud-client tool:
|
||||
|
||||
```python
|
||||
# tools/go/nextcloud-client/main.go
|
||||
|
||||
// New operations for analytics-hub
|
||||
func listAnalyticsReports() {
|
||||
endpoint := "/apps/analytics-hub/api/reports"
|
||||
resp := webdavRequest(endpoint, "GET", nil)
|
||||
return parseReports(resp)
|
||||
}
|
||||
|
||||
func generateAnalyticsReport(clientSlug, dateRange) {
|
||||
endpoint := "/apps/analytics-hub/api/generate"
|
||||
payload := map[string]interface{}{
|
||||
"client_slug": clientSlug,
|
||||
"date_range": dateRange,
|
||||
}
|
||||
resp := webdavRequest(endpoint, "POST", payload)
|
||||
return parseResponse(resp)
|
||||
}
|
||||
|
||||
func downloadAnalyticsReport(reportId) {
|
||||
endpoint := fmt.Sprintf("/apps/analytics-hub/api/report/%d", reportId)
|
||||
resp := webdavRequest(endpoint, "GET", nil)
|
||||
return saveToFile(resp)
|
||||
}
|
||||
```
|
||||
|
||||
#### SKILL.md Updates
|
||||
|
||||
```markdown
|
||||
## Nextcloud Analytics Hub
|
||||
|
||||
Generate AI-powered analytics reports:
|
||||
|
||||
### List Reports
|
||||
```bash
|
||||
nextcloud-client --op analytics-reports-list
|
||||
```
|
||||
|
||||
### Generate Report
|
||||
```bash
|
||||
nextcloud-client --op analytics-generate --client logos_school --days 7
|
||||
```
|
||||
|
||||
### Download Report
|
||||
```bash
|
||||
nextcloud-client --op analytics-download --report-id 123
|
||||
```
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: Deployment & Automation (Days 11-12)
|
||||
|
||||
#### App Installation
|
||||
|
||||
```bash
|
||||
# Upload to Nextcloud apps directory
|
||||
scp -r analytics-hub/ mike@cloud.shortcutsolutions.net:/var/www/nextcloud/apps/
|
||||
|
||||
# Enable app via Nextcloud UI
|
||||
# Settings → Apps → Analytics Hub → Enable
|
||||
|
||||
# Or use occ CLI
|
||||
sudo -u www-data php occ app:enable analytics-hub
|
||||
```
|
||||
|
||||
#### Cron Configuration
|
||||
|
||||
Nextcloud will handle cron automatically:
|
||||
|
||||
```php
|
||||
// appinfo/info.xml
|
||||
<cron>
|
||||
<job>OCA\AnalyticsHub\Cron\DailyReport</job>
|
||||
<interval>86400</interval> <!-- 24 hours -->
|
||||
</cron>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
This PRD represents a Nextcloud internal PHP application with:
|
||||
|
||||
- **Embedded in Nextcloud** - Runs as PHP app, not external service
|
||||
- **Agent Integration** - Exposes REST APIs for nextcloud-integration tools
|
||||
- **Zero-Touch Operation** - Scheduled internal jobs, no manual intervention
|
||||
- **Fail-Fast Architecture** - Halt on errors, don't propagate bad data
|
||||
- **Observable Execution** - Logs stored in Nextcloud logs
|
||||
- **Graceful Degradation** - One client failure doesn't crash job
|
||||
- **Cost Efficiency** - ~$1/month (LLM only, Google API free)
|
||||
- **Proactive Monitoring** - Token health checks and email alerts
|
||||
|
||||
**Estimated Implementation Time**: 12 days → 4 weeks of reliable operation
|
||||
|
||||
**Architecture Change**: From external Python app (v2.0) to Nextcloud PHP app (v3.0)
|
||||
|
||||
---
|
||||
|
||||
**PRD Version**: 3.0
|
||||
**Last Updated**: 2026-02-12 23:15 GMT
|
||||
**Status**: READY FOR IMPLEMENTATION
|
||||
Reference in New Issue
Block a user