chore: استيراد أولي من سيرو (ecfe7568) — بلا أي تعديل
نسخة كاملة من مستودع سيرو عند ecfe7568 لتكون أساس تطبيق «انطلق». نُسخ المتعقَّب في git فقط (12,509 ملفاً / 302 م.ب) بـ git archive، لا `cp -r` — فاستُثنيت تلقائياً مخلفات البناء (build · node_modules · .dart_tool · .gradle · Pods ≈ 10.7 غ.ب) وكل ما يستثنيه .gitignore. هذا الكوميت **بلا أي تعديل عمداً** حتى يكون كل ما يليه فرقاً مقروءاً مقابل سيرو الأصلي. سيرو نفسه لم يُمسّ. ⚠️ لا يبني بعد: `.env` و`lib/env/env.g.dart` غير متعقَّبين في سيرو (وهذا صحيح — أسرار لكل مستأجر). كل تطبيق فلاتر هنا يحتاج .env خاصاً بانطلق ثم توليد env.g.dart عبر build_runner. لا تُنسخ أسرار سيرو. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,336 @@
|
||||
import 'package:get/get.dart';
|
||||
import '../../constant/links.dart';
|
||||
import '../../views/widgets/snackbar.dart';
|
||||
import '../functions/crud.dart';
|
||||
|
||||
class MarketingController extends GetxController {
|
||||
bool isLoading = false;
|
||||
List priceAnomalies = [];
|
||||
List campaignsLog = [];
|
||||
|
||||
// --- Dynamic Dashboard Configurations ---
|
||||
String selectedCountry = 'All'; // 'All', 'SY', 'JO', 'EG', 'IQ'
|
||||
bool isAutopilotEnabled = true;
|
||||
String systemPrompt = "أنت المحلل الذكي لتطبيق Siro لخدمات نقل الركاب. قمنا برصد قراءات أسعار المنافسين لتقديم عروض وحملات تسويقية ملائمة ومخصصة.";
|
||||
|
||||
// --- AI API Token & Cost Telemetry ---
|
||||
int apiRequestsCount = 0;
|
||||
int totalTokensUsed = 0;
|
||||
double estimatedCostUSD = 0.0;
|
||||
|
||||
// --- Price Comparison Data ---
|
||||
List hourlyPriceData = [];
|
||||
List pciRegions = [];
|
||||
Map siroBasePrices = {};
|
||||
|
||||
// --- Country Selection Handler ---
|
||||
void changeCountry(String countryCode) {
|
||||
selectedCountry = countryCode;
|
||||
fetchAnomalies();
|
||||
fetchCampaignsLog();
|
||||
fetchPriceComparison();
|
||||
fetchPriceGapHeatmap();
|
||||
fetchMarketShareAnalytics();
|
||||
fetchAiPricePrediction();
|
||||
fetchWinbackTargets();
|
||||
}
|
||||
|
||||
// --- Autopilot Status Toggle ---
|
||||
void toggleAutopilot(bool value) {
|
||||
isAutopilotEnabled = value;
|
||||
update();
|
||||
mySnackbarInfo(value ? "Full Autopilot mode enabled".tr : "Approval Mode enabled".tr);
|
||||
}
|
||||
|
||||
// --- System Prompt Configuration Saver ---
|
||||
void savePrompt(String newPrompt) {
|
||||
systemPrompt = newPrompt;
|
||||
update();
|
||||
mySnackbarSuccess("Gemini system instructions updated successfully".tr);
|
||||
}
|
||||
|
||||
Future<void> fetchAnomalies() async {
|
||||
isLoading = true;
|
||||
update();
|
||||
try {
|
||||
Map<String, dynamic> params = {};
|
||||
if (selectedCountry != 'All') {
|
||||
params['country_code'] = selectedCountry;
|
||||
}
|
||||
|
||||
var res = await CRUD().post(
|
||||
link: AppLink.getMarketAnomalies,
|
||||
payload: params,
|
||||
);
|
||||
if (res is Map && res['status'] == 'success') {
|
||||
priceAnomalies = res['message'] ?? [];
|
||||
} else {
|
||||
mySnackbarError("Failed to fetch price anomalies");
|
||||
}
|
||||
} catch (e) {
|
||||
mySnackbarError("Network error while loading anomalies");
|
||||
} finally {
|
||||
isLoading = false;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchTelemetry() async {
|
||||
try {
|
||||
Map<String, dynamic> params = {};
|
||||
if (selectedCountry != 'All') {
|
||||
params['country_code'] = selectedCountry;
|
||||
}
|
||||
|
||||
var res = await CRUD().post(
|
||||
link: AppLink.getTelemetry,
|
||||
payload: params,
|
||||
);
|
||||
if (res is Map && res['status'] == 'success') {
|
||||
final data = res['message'];
|
||||
if (data is Map) {
|
||||
apiRequestsCount = data['api_requests_count'] ?? 0;
|
||||
totalTokensUsed = data['total_tokens_used'] ?? 0;
|
||||
estimatedCostUSD = (data['estimated_cost_usd'] ?? 0.0).toDouble();
|
||||
update();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Silently fail — telemetry is non-critical
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchCampaignsLog() async {
|
||||
isLoading = true;
|
||||
update();
|
||||
try {
|
||||
Map<String, dynamic> params = {};
|
||||
if (selectedCountry != 'All') {
|
||||
params['country_code'] = selectedCountry;
|
||||
}
|
||||
|
||||
var res = await CRUD().post(
|
||||
link: AppLink.getCampaignsLog,
|
||||
payload: params,
|
||||
);
|
||||
if (res is Map && res['status'] == 'success') {
|
||||
campaignsLog = res['message'] ?? [];
|
||||
} else {
|
||||
mySnackbarError("Failed to fetch campaign logs");
|
||||
}
|
||||
} catch (e) {
|
||||
mySnackbarError("Network error while loading campaign logs");
|
||||
} finally {
|
||||
isLoading = false;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> fetchPriceComparison() async {
|
||||
try {
|
||||
Map<String, dynamic> params = {};
|
||||
if (selectedCountry != 'All') {
|
||||
params['country_code'] = selectedCountry;
|
||||
}
|
||||
|
||||
var res = await CRUD().post(
|
||||
link: AppLink.getPriceComparison,
|
||||
payload: params,
|
||||
);
|
||||
if (res is Map && res['status'] == 'success') {
|
||||
final data = res['message'];
|
||||
if (data is Map) {
|
||||
hourlyPriceData = data['hourly_competitor_prices'] ?? [];
|
||||
pciRegions = data['pci_regions'] ?? [];
|
||||
siroBasePrices = data['siro_base_prices'] is Map
|
||||
? data['siro_base_prices'] as Map
|
||||
: {};
|
||||
update();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Silently fail
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> triggerAICampaign() async {
|
||||
isLoading = true;
|
||||
update();
|
||||
try {
|
||||
Map<String, dynamic> params = {};
|
||||
if (selectedCountry != 'All') {
|
||||
params['country_code'] = selectedCountry;
|
||||
}
|
||||
|
||||
var res = await CRUD().post(
|
||||
link: AppLink.triggerCampaign,
|
||||
payload: params,
|
||||
);
|
||||
if (res is Map) {
|
||||
if (res['status'] == 'success') {
|
||||
mySnackbarSuccess("AI campaign triggered successfully! Promos sent.");
|
||||
fetchCampaignsLog();
|
||||
fetchTelemetry();
|
||||
} else {
|
||||
mySnackbarWarning(res['message'] ?? "Campaign rate limited.");
|
||||
}
|
||||
} else {
|
||||
mySnackbarError("Failed to trigger AI campaign");
|
||||
}
|
||||
} catch (e) {
|
||||
mySnackbarError("Network error while triggering campaign");
|
||||
} finally {
|
||||
isLoading = false;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
// --- What-If Simulator State ---
|
||||
double? simulatedPci;
|
||||
double? simulatedMarketShare;
|
||||
String? simulatorRecommendationStatus;
|
||||
String? simulatorRecommendationMessage;
|
||||
|
||||
Future<void> runWhatIfSimulation(String proposedSpeedPrice) async {
|
||||
isLoading = true;
|
||||
update();
|
||||
try {
|
||||
Map<String, dynamic> params = {
|
||||
'speed_price': proposedSpeedPrice
|
||||
};
|
||||
if (selectedCountry != 'All') {
|
||||
params['country_code'] = selectedCountry;
|
||||
} else {
|
||||
params['country_code'] = 'SY'; // Fallback
|
||||
}
|
||||
|
||||
var res = await CRUD().post(
|
||||
link: AppLink.whatIfSimulator,
|
||||
payload: params,
|
||||
);
|
||||
if (res is Map && res['status'] == 'success') {
|
||||
final data = res['message'];
|
||||
if (data is Map) {
|
||||
simulatedPci = (data['simulated_pci'] ?? 0.0).toDouble();
|
||||
simulatedMarketShare = (data['simulated_market_share_percent'] ?? 0.0).toDouble();
|
||||
simulatorRecommendationStatus = data['recommendation_status'];
|
||||
simulatorRecommendationMessage = data['recommendation_message'];
|
||||
}
|
||||
} else {
|
||||
mySnackbarError(res['message'] ?? "Failed to run simulation");
|
||||
}
|
||||
} catch (e) {
|
||||
mySnackbarError("Network error during simulation");
|
||||
} finally {
|
||||
isLoading = false;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
// --- Heatmap State ---
|
||||
List heatmapData = [];
|
||||
double currentSiroPriceHeatmap = 0.0;
|
||||
|
||||
Future<void> fetchPriceGapHeatmap() async {
|
||||
try {
|
||||
Map<String, dynamic> params = {};
|
||||
if (selectedCountry != 'All') {
|
||||
params['country_code'] = selectedCountry;
|
||||
} else {
|
||||
params['country_code'] = 'SY'; // Fallback
|
||||
}
|
||||
|
||||
var res = await CRUD().post(
|
||||
link: AppLink.getPriceGapHeatmap,
|
||||
payload: params,
|
||||
);
|
||||
if (res is Map && res['status'] == 'success') {
|
||||
final data = res['message'];
|
||||
if (data is Map) {
|
||||
heatmapData = data['heatmap_data'] ?? [];
|
||||
currentSiroPriceHeatmap = (data['current_siro_price_per_km'] ?? 0.0).toDouble();
|
||||
update();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Silently fail
|
||||
}
|
||||
}
|
||||
|
||||
// --- Market Share Analytics ---
|
||||
List marketShareData = [];
|
||||
Future<void> fetchMarketShareAnalytics() async {
|
||||
try {
|
||||
Map<String, dynamic> params = {};
|
||||
if (selectedCountry != 'All') {
|
||||
params['country_code'] = selectedCountry;
|
||||
} else {
|
||||
params['country_code'] = 'SY';
|
||||
}
|
||||
|
||||
var res = await CRUD().post(
|
||||
link: AppLink.getMarketShareAnalytics,
|
||||
payload: params,
|
||||
);
|
||||
if (res is Map && res['status'] == 'success') {
|
||||
marketShareData = res['historical_data'] ?? [];
|
||||
update();
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
// --- AI Price Prediction ---
|
||||
String? aiPredictionMessage;
|
||||
List predictedSurgeHours = [];
|
||||
Future<void> fetchAiPricePrediction() async {
|
||||
try {
|
||||
Map<String, dynamic> params = {};
|
||||
if (selectedCountry != 'All') {
|
||||
params['country_code'] = selectedCountry;
|
||||
} else {
|
||||
params['country_code'] = 'SY';
|
||||
}
|
||||
|
||||
var res = await CRUD().post(
|
||||
link: AppLink.aiPricePrediction,
|
||||
payload: params,
|
||||
);
|
||||
if (res is Map && res['status'] == 'success') {
|
||||
aiPredictionMessage = res['ai_analysis_message'];
|
||||
predictedSurgeHours = res['predicted_surge_hours'] ?? [];
|
||||
update();
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
// --- Win-Back Targets ---
|
||||
List winbackTargets = [];
|
||||
int winbackTotalCount = 0;
|
||||
Future<void> fetchWinbackTargets() async {
|
||||
try {
|
||||
Map<String, dynamic> params = {};
|
||||
if (selectedCountry != 'All') {
|
||||
params['country_code'] = selectedCountry;
|
||||
} else {
|
||||
params['country_code'] = 'SY';
|
||||
}
|
||||
|
||||
var res = await CRUD().post(
|
||||
link: AppLink.winbackHotspotTargets,
|
||||
payload: params,
|
||||
);
|
||||
if (res is Map && res['status'] == 'success') {
|
||||
winbackTargets = res['targets'] ?? [];
|
||||
winbackTotalCount = res['total_targets'] ?? 0;
|
||||
update();
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user