feat: integrate OpenAI API for intelligent task suggestions

Implement real OpenAI integration for the AI task suggestions feature:

- Create aiService.js with GPT-3.5/GPT-4 integration
- Add context-aware prompt engineering (street data, past tasks, weather, priorities)
- Implement automatic fallback to high-quality mock suggestions
- Add graceful degradation when API key not configured
- Create comprehensive error handling and timeout protection
- Add input validation with aiValidator middleware
- Implement /api/ai/status endpoint for service monitoring
- Add 12 passing test cases covering all functionality
- Document OpenAI model configuration in .env.example
- Create detailed AI_SERVICE.md documentation

Key features:
- Uses axios to call OpenAI API directly (no SDK dependency)
- Analyzes street conditions and past 30 days of completed tasks
- Generates structured JSON responses with task metadata
- 10-second timeout with automatic fallback
- Comprehensive logging using centralized logger service
- Returns warnings when running in mock mode

All tests pass (12/12). Ready for production use with or without API key.

🤖 Generated with AI Assistant

Co-Authored-By: AI Assistant <noreply@ai-assistant.com>
This commit is contained in:
William Valentin
2025-11-03 13:19:00 -08:00
parent a12519aa41
commit 771d39a52b
6 changed files with 1005 additions and 12 deletions

View File

@@ -0,0 +1,35 @@
const { query, validationResult } = require("express-validator");
const validate = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array().map((err) => ({
field: err.path,
message: err.msg,
})),
});
}
next();
};
/**
* Task suggestions validation
*/
const taskSuggestionsValidation = [
query("streetId")
.optional()
.isString()
.withMessage("Street ID must be a string"),
query("count")
.optional()
.isInt({ min: 1, max: 10 })
.withMessage("Count must be between 1 and 10"),
validate,
];
module.exports = {
taskSuggestionsValidation,
validate,
};