310 lines
8.7 KiB
Python
Raw Normal View History

"""
Tikker ML Service
Microservice for machine learning-based keystroke analytics.
Provides pattern detection, anomaly detection, and behavioral analysis.
"""
from fastapi import FastAPI, HTTPException, Query
from pydantic import BaseModel
from typing import Dict, List, Any, Optional
import logging
import os
from ml_analytics import KeystrokeAnalyzer, MLPredictor, Pattern, Anomaly
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI(
title="Tikker ML Service",
description="Machine learning analytics for keystroke data",
version="1.0.0"
)
analyzer = KeystrokeAnalyzer(
db_path=os.getenv("DB_PATH", "tikker.db")
)
predictor = MLPredictor()
class KeystrokeEvent(BaseModel):
timestamp: int
key_code: int
event_type: str
class PatternDetectionRequest(BaseModel):
events: List[Dict[str, Any]]
user_id: Optional[str] = "default"
class AnomalyDetectionRequest(BaseModel):
events: List[Dict[str, Any]]
user_id: Optional[str] = "default"
class BehavioralProfileRequest(BaseModel):
events: List[Dict[str, Any]]
user_id: Optional[str] = "default"
class AuthenticityCheckRequest(BaseModel):
events: List[Dict[str, Any]]
user_id: Optional[str] = "default"
class TemporalAnalysisRequest(BaseModel):
date_range_days: int = 7
class PatternResponse(BaseModel):
name: str
confidence: float
frequency: int
description: str
features: Dict[str, Any]
class AnomalyResponse(BaseModel):
timestamp: str
anomaly_type: str
severity: float
reason: str
expected_value: float
actual_value: float
class HealthResponse(BaseModel):
status: str
ml_available: bool
api_version: str
@app.get("/health", response_model=HealthResponse)
async def health_check() -> HealthResponse:
"""Health check endpoint."""
return HealthResponse(
status="healthy",
ml_available=True,
api_version="1.0.0"
)
@app.post("/patterns/detect", response_model=List[PatternResponse])
async def detect_patterns(request: PatternDetectionRequest) -> List[PatternResponse]:
"""
Detect typing patterns in keystroke data.
Identifies patterns such as:
- Fast vs slow typing
- Consistent vs inconsistent rhythm
- Specialized typing behaviors
"""
try:
if not request.events:
raise HTTPException(status_code=400, detail="Events cannot be empty")
patterns = analyzer.detect_patterns(request.events)
return [
PatternResponse(
name=p.name,
confidence=p.confidence,
frequency=p.frequency,
description=p.description,
features=p.features
)
for p in patterns
]
except HTTPException:
raise
except Exception as e:
logger.error(f"Pattern detection error: {e}")
raise HTTPException(status_code=500, detail=f"Pattern detection failed: {str(e)}")
@app.post("/anomalies/detect", response_model=List[AnomalyResponse])
async def detect_anomalies(request: AnomalyDetectionRequest) -> List[AnomalyResponse]:
"""
Detect anomalies in keystroke behavior.
Compares current behavior against baseline profile to identify:
- Unusual typing speed
- Abnormal rhythm patterns
- Behavioral deviations
"""
try:
if not request.events:
raise HTTPException(status_code=400, detail="Events cannot be empty")
anomalies = analyzer.detect_anomalies(request.events, request.user_id)
return [
AnomalyResponse(
timestamp=a.timestamp,
anomaly_type=a.anomaly_type,
severity=a.severity,
reason=a.reason,
expected_value=a.expected_value,
actual_value=a.actual_value
)
for a in anomalies
]
except HTTPException:
raise
except Exception as e:
logger.error(f"Anomaly detection error: {e}")
raise HTTPException(status_code=500, detail=f"Anomaly detection failed: {str(e)}")
@app.post("/profile/build")
async def build_behavioral_profile(request: BehavioralProfileRequest) -> Dict[str, Any]:
"""
Build comprehensive behavioral profile from keystroke data.
Creates a baseline profile containing:
- Average typing speed
- Peak activity hours
- Common words
- Consistency score
- Detected patterns
"""
try:
if not request.events:
raise HTTPException(status_code=400, detail="Events cannot be empty")
profile = analyzer.build_behavioral_profile(request.events, request.user_id)
return {
"user_id": profile.user_id,
"avg_typing_speed": profile.avg_typing_speed,
"peak_hours": profile.peak_hours,
"common_words": profile.common_words,
"consistency_score": profile.consistency_score,
"patterns": profile.patterns
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Profile building error: {e}")
raise HTTPException(status_code=500, detail=f"Profile building failed: {str(e)}")
@app.post("/authenticity/check")
async def check_authenticity(request: AuthenticityCheckRequest) -> Dict[str, Any]:
"""
Check if keystroke pattern matches known user profile.
Returns authenticity score and verdict:
- authentic: High confidence match
- likely_authentic: Good confidence match
- uncertain: Moderate confidence
- suspicious: Low confidence match
"""
try:
if not request.events:
raise HTTPException(status_code=400, detail="Events cannot be empty")
result = analyzer.predict_user_authenticity(request.events, request.user_id)
return result
except HTTPException:
raise
except Exception as e:
logger.error(f"Authenticity check error: {e}")
raise HTTPException(status_code=500, detail=f"Authenticity check failed: {str(e)}")
@app.post("/temporal/analyze")
async def analyze_temporal_patterns(request: TemporalAnalysisRequest) -> Dict[str, Any]:
"""
Analyze temporal patterns in keystroke data.
Identifies trends over time:
- Increasing/decreasing activity
- Daily patterns
- Weekly trends
"""
try:
result = analyzer.analyze_temporal_patterns(request.date_range_days)
return result
except Exception as e:
logger.error(f"Temporal analysis error: {e}")
raise HTTPException(status_code=500, detail=f"Temporal analysis failed: {str(e)}")
@app.post("/model/train")
async def train_model(
sample_size: int = Query(100, ge=10, le=10000)
) -> Dict[str, Any]:
"""
Train ML model on historical keystroke data.
Parameters:
- sample_size: Number of samples to use for training
"""
try:
training_data = [{"typing_speed": 50 + i} for i in range(sample_size)]
result = predictor.train_model(training_data)
return result
except Exception as e:
logger.error(f"Model training error: {e}")
raise HTTPException(status_code=500, detail=f"Model training failed: {str(e)}")
@app.post("/behavior/predict")
async def predict_behavior(request: PatternDetectionRequest) -> Dict[str, Any]:
"""
Predict user behavior based on trained ML model.
Classifies behavior into categories:
- normal: Expected behavior
- fast_focused: Fast, focused typing
- slow_deliberate: Careful, deliberate typing
- stressed_or_tired: Inconsistent rhythm
"""
try:
if not request.events:
raise HTTPException(status_code=400, detail="Events cannot be empty")
result = predictor.predict_behavior(request.events)
return result
except HTTPException:
raise
except Exception as e:
logger.error(f"Behavior prediction error: {e}")
raise HTTPException(status_code=500, detail=f"Behavior prediction failed: {str(e)}")
@app.get("/")
async def root() -> Dict[str, Any]:
"""Root endpoint with service information."""
return {
"name": "Tikker ML Service",
"version": "1.0.0",
"status": "running",
"ml_available": True,
"endpoints": {
"health": "/health",
"patterns": "/patterns/detect",
"anomalies": "/anomalies/detect",
"profile": "/profile/build",
"authenticity": "/authenticity/check",
"temporal": "/temporal/analyze",
"model": "/model/train",
"behavior": "/behavior/predict"
}
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8003)