Files
citypulse/lib/services/mock_ai.dart
Zahar 6518df8ac1 feat: introduce FixMate Flutter app and React dashboard
- Add Flutter app shell (FixMateApp/MainScreen) with tabs: Report, Map,
  My Reports, Settings
- Implement capture and review flow (image_picker, geolocator, deterministic
  mock AI), and local storage (SharedPreferences + photo files on mobile)
- Build Map screen with flutter_map, marker clustering, filters, legend,
  marker details, and external maps deeplink
- Add My Reports list (view details, cycle status, delete) and Settings
  (language toggle via Provider, diagnostics, clear all data)
- Introduce JSON i18n loader and LocaleProvider; add EN/BM assets
- Define models (Report, enums) and UI badges (severity, status)

- Add static React dashboard (Leaflet map with clustering, heatmap toggle,
  filters incl. date range, queue, detail drawer), i18n (EN/BM), and
  demo data

- Update build/config and platform setup:
  - Extend pubspec with required packages and register i18n assets
  - Android: add CAMERA and location permissions; pin NDK version
  - iOS: add usage descriptions for camera, photo library, location
  - Gradle properties tuned for Windows/UNC stability
  - Register desktop plugins (Linux/macOS/Windows)
  - .gitignore: ignore .kilocode
  - Overhaul README and replace sample widget test
2025-09-25 18:38:18 +08:00

116 lines
3.6 KiB
Dart

import 'dart:math';
import 'package:flutter/foundation.dart' hide Category;
import '../models/enums.dart';
import '../models/report.dart';
/// Service for generating deterministic AI suggestions for reports
class MockAIService {
/// Generate a deterministic seed based on report parameters
static int _generateSeed(String id, String createdAt, double lat, double lng, int? photoSizeBytes) {
final combined = '$id$createdAt$lat$lng${photoSizeBytes ?? 0}';
var hash = 0;
for (var i = 0; i < combined.length; i++) {
hash = ((hash << 5) - hash) + combined.codeUnitAt(i);
hash = hash & hash; // Convert to 32-bit integer
}
return hash.abs();
}
/// Generate AI suggestion for a report
static AISuggestion generateSuggestion({
required String id,
required String createdAt,
required double lat,
required double lng,
int? photoSizeBytes,
}) {
final seed = _generateSeed(id, createdAt, lat, lng, photoSizeBytes);
final random = Random(seed);
// Category selection with weighted probabilities
final categoryWeights = {
Category.pothole: 0.35,
Category.trash: 0.25,
Category.streetlight: 0.15,
Category.signage: 0.10,
Category.drainage: 0.10,
Category.other: 0.05,
};
// Apply heuristics based on image dimensions (if available)
final aspectRatio = photoSizeBytes != null ? (random.nextDouble() * 2) : 1.0;
if (aspectRatio > 1.2) {
// Wide image - likely signage
categoryWeights[Category.signage] = categoryWeights[Category.signage]! * 2;
categoryWeights[Category.pothole] = categoryWeights[Category.pothole]! * 0.5;
}
// Select category based on weights
final categoryRand = random.nextDouble();
double cumulative = 0.0;
Category selectedCategory = Category.pothole;
for (final entry in categoryWeights.entries) {
cumulative += entry.value;
if (categoryRand <= cumulative) {
selectedCategory = entry.key;
break;
}
}
// Severity selection with weighted probabilities
final severityWeights = {
Severity.medium: 0.45,
Severity.high: 0.30,
Severity.low: 0.25,
};
// Apply location accuracy heuristic
final accuracy = random.nextDouble() * 50; // Simulate accuracy 0-50m
final isNight = random.nextBool(); // Simulate night time
if (accuracy <= 10 && isNight) {
// High accuracy at night - bump high severity
severityWeights[Severity.high] = severityWeights[Severity.high]! * 1.5;
severityWeights[Severity.medium] = severityWeights[Severity.medium]! * 0.8;
}
// Select severity based on weights
final severityRand = random.nextDouble();
cumulative = 0.0;
Severity selectedSeverity = Severity.medium;
for (final entry in severityWeights.entries) {
cumulative += entry.value;
if (severityRand <= cumulative) {
selectedSeverity = entry.key;
break;
}
}
// Generate confidence score (0.6 - 0.9)
final confidence = 0.6 + (random.nextDouble() * 0.3);
return AISuggestion(
category: selectedCategory,
severity: selectedSeverity,
confidence: confidence,
);
}
/// Check if the AI suggestion is reliable enough to use
static bool isSuggestionReliable(AISuggestion suggestion) {
return suggestion.confidence >= 0.7;
}
/// Get confidence level description
static String getConfidenceDescription(double confidence) {
if (confidence >= 0.8) {
return 'High confidence';
} else if (confidence >= 0.7) {
return 'Medium confidence';
} else {
return 'Low confidence';
}
}
}