StatisticsService.track porte isVoice, qui pose "voice": true dans VisitEvent.Metadata. Le flux lunettes n'émettait aucun VisitEvent : une visite vocale ne produisait ni session, ni section vue, ni durée. Le sectionView vocal part après la synthèse, pas avant : un déclenchement dont le TTS échoue n'a rien fait entendre, le compter serait faux. Aucun événement par question posée — elles sont déjà dans VisitorQuestion et remontent dans l'onglet Guide IA ; les compter ici serait le même fait dans deux écrans. isAutoTriggered suit le renommage serveur en isVisitorQuestion. flutter analyze lib sans erreur, flutter build apk --debug --flavor dev vert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
69 lines
2.3 KiB
Dart
69 lines
2.3 KiB
Dart
import 'dart:convert';
|
|
import 'dart:math';
|
|
|
|
import 'package:manager_api_new/api.dart';
|
|
import 'package:mymuseum_visitapp/client.dart';
|
|
|
|
class StatisticsService {
|
|
final Client clientAPI;
|
|
final String? instanceId;
|
|
final String? configurationId;
|
|
final String? appType; // "Mobile" or "Tablet"
|
|
final String? language;
|
|
final String sessionId;
|
|
|
|
StatisticsService({
|
|
required this.clientAPI,
|
|
required this.instanceId,
|
|
required this.configurationId,
|
|
this.appType = 'Mobile',
|
|
this.language,
|
|
}) : sessionId = _generateSessionId();
|
|
|
|
static String _generateSessionId() {
|
|
final rand = Random();
|
|
final bytes = List<int>.generate(16, (_) => rand.nextInt(256));
|
|
return bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
|
|
}
|
|
|
|
/// [isVoice] marque un événement **consommé à la voix** — le guide a lu la section
|
|
/// à haute voix, par déclenchement proactif ou en réponse à une question.
|
|
///
|
|
/// ⚠️ C'est un attribut de l'événement, **pas un `appType`**. Un visiteur passe des
|
|
/// lunettes à l'écran dans une même session — c'est tout l'objet du miroir — et
|
|
/// `AppTypeDistribution` ne compte qu'une entrée par session, tranchée sur
|
|
/// l'événement le plus ancien : commencer au doigt puis mettre les lunettes aurait
|
|
/// rendu le vocal invisible, l'inverse aurait compté la navigation tactile comme
|
|
/// vocale. Le KPI serveur devient « part des sessions ayant utilisé le vocal ».
|
|
///
|
|
/// Même conception que `VisitorQuestion`, qui porte un booléen `IsVoice` **à côté**
|
|
/// de son `AppType`.
|
|
Future<void> track(
|
|
String eventType, {
|
|
String? sectionId,
|
|
int? durationSeconds,
|
|
Map<String, dynamic>? metadata,
|
|
bool isVoice = false,
|
|
}) async {
|
|
final payload = isVoice
|
|
? {...?metadata, 'voice': true}
|
|
: metadata;
|
|
try {
|
|
await clientAPI.statsApi!.statsTrackEvent(VisitEventDTO(
|
|
instanceId: instanceId ?? '',
|
|
configurationId: configurationId,
|
|
sectionId: sectionId,
|
|
sessionId: sessionId,
|
|
eventType: eventType,
|
|
appType: appType,
|
|
language: language,
|
|
durationSeconds: durationSeconds,
|
|
metadata: payload != null ? jsonEncode(payload) : null,
|
|
timestamp: DateTime.now(),
|
|
));
|
|
} catch (_) {
|
|
// fire-and-forget — never block the UI on stats errors
|
|
}
|
|
}
|
|
}
|