CulicidaeLab Ecosystem Integration Guide¶
Overview¶
The CulicidaeLab mobile application is part of a comprehensive ecosystem designed for mosquito research, species identification, and vector-borne disease surveillance. This guide provides detailed information on how the mobile app integrates with the broader CulicidaeLab ecosystem and how researchers and developers can extend its functionality.
Ecosystem Architecture¶
Complete System Overview¶
The CulicidaeLab ecosystem consists of four main layers working together to provide comprehensive mosquito research capabilities:
flowchart TD
subgraph L1 ["🗄️ Data Layer"]
DS1["🦟 mosquito_dataset_46_3139<br/>Base Diversity Dataset<br/>(46 species, 3139 unique images)<br/>📄 CC-BY-SA-4.0"]
DS2["📊 mosquito-species-<br/>classification-dataset<br/>📄 CC-BY-SA-4.0"]
DS3["🔍 mosquito-species-<br/>detection-dataset<br/>📄 CC-BY-SA-4.0"]
DS4["✂️ mosquito-species-<br/>segmentation-dataset<br/>📄 CC-BY-SA-4.0"]
end
subgraph L2 ["🤖 AI Model Layer"]
subgraph M_COLLECTION ["Top-5 Model Collection"]
M4["📊 exp_7_new_bg_simple-subs_1_v_5<br/>pvt_v2_b0.in1k_ep_60<br/>(Classification)<br/>📄 Apache 2.0"]
end
subgraph M_DEFAULT ["Default Models"]
M1["📊 culico-net-cls-v1<br/>(Classification)<br/>📄 Apache 2.0"]
M2["🔍 culico-net-det-v1<br/>(Detection)<br/>📄 AGPL-3.0"]
M3["✂️ culico-net-segm-v1-nano<br/>(Segmentation)<br/>📄 Apache 2.0"]
end
end
subgraph L3 ["💻 Application Layer"]
APP1["🐍 culicidaelab<br/>Python Library<br/>(Core ML functionality)<br/>📄 AGPL-3.0"]
APP2["🌐 culicidaelab-server<br/>Web Application<br/>(API services)<br/>📄 AGPL-3.0"]
APP3["📱 culicidaelab-mobile<br/>Mobile Application<br/>(This App)<br/>📄 AGPL-3.0"]
end
subgraph L4 ["🔌 API Service Layer"]
S1["🗲 Prediction Service<br/>(ML inference)"]
S2["💾 Observation Service<br/>(Data storage & retrieval)"]
S3["🗺️ Map Service<br/>(Geospatial visualization)"]
S4["🦟 Mosquito Gallery Service"]
S5["💊 Diseases Gallery Service"]
end
%% Data flow connections
DS1 -.->|"derives"| DS2
DS1 -.->|"derives"| DS3
DS1 -.->|"derives"| DS4
DS2 -->|"trains"| M1
DS3 -->|"trains"| M2
DS4 -->|"trains"| M3
DS2 -->|"trains"| M4
%% Model integration
M1 -->|"integrated"| APP1
M2 -->|"integrated"| APP1
M3 -->|"integrated"| APP1
M4 -->|"mobile optimized"| APP3
%% Gallery data
DS1 -->|"provides photos"| APP2
DS1 -->|"provides photos"| APP3
%% Core library integration
APP1 -->|"powers"| APP2
%% Service hosting
APP2 -->|"hosts"| S1
APP2 -->|"hosts"| S2
APP2 -->|"hosts"| S3
APP2 -->|"hosts"| S4
APP2 -->|"hosts"| S5
%% Mobile app integration
APP3 <-->|"API calls"| S1
APP3 <-->|"API calls"| S2
APP3 -->|"WebView"| S3
APP3 -->|"consumes"| S4
APP3 -->|"consumes"| S5
Component Relationships¶
Data Layer: - Base Dataset: 46 species, 3139 unique images under CC-BY-SA-4.0 - Specialized Datasets: Classification, detection, and segmentation derivatives - Open Access: All datasets available on Hugging Face
AI Model Layer: - Production Models: Default models used by the ecosystem - Research Models: Top-5 collection with >90% accuracy for 17 species - Mobile Optimization: Specialized models for mobile deployment
Application Layer: - Python Library: Core ML functionality and research tools - Web Server: API services and web interface - Mobile App: Educational and field research tool
Service Layer: - Prediction Service: Real-time species identification - Observation Service: Data collection and storage - Map Service: Geospatial visualization - Gallery Services: Educational content delivery
Mobile App Integration Points¶
1. Server API Integration¶
The mobile app integrates with the CulicidaeLab server through RESTful APIs:
// Core API endpoints
class CulicidaeLabAPI {
static const String baseUrl = "https://culicidealab.ru";
// Prediction service integration
static const String predictionEndpoint = "/api/predict";
// Observation service integration
static const String observationsEndpoint = "/api/observations";
// Map service integration
static const String mapEndpoint = "/map";
// Gallery service integration
static const String speciesGalleryEndpoint = "/api/species";
static const String diseasesGalleryEndpoint = "/api/diseases";
}
2. Data Synchronization¶
Observation Data Flow:
sequenceDiagram
participant User
participant MobileApp
participant LocalDB
participant ServerAPI
participant ResearchDB
User->>MobileApp: Capture mosquito image
MobileApp->>MobileApp: Local AI classification
MobileApp->>ServerAPI: Request server prediction
ServerAPI-->>MobileApp: Enhanced prediction result
MobileApp->>User: Display combined results
User->>MobileApp: Submit observation
MobileApp->>LocalDB: Store locally
MobileApp->>ServerAPI: Sync to server
ServerAPI->>ResearchDB: Store for research
ResearchDB-->>ServerAPI: Confirmation
ServerAPI-->>MobileApp: Sync confirmation
3. Model Integration¶
Hybrid Classification Approach:
class HybridClassificationService {
final ClassificationService _localService;
final ClassificationRepository _repository;
Future<ClassificationResult> classifyImage(File imageFile) async {
// Local classification for immediate results
final localResult = await _localService.classifyImage(imageFile);
// Server classification for enhanced accuracy
try {
final webResult = await _repository.getWebPrediction(imageFile);
// Combine results for comprehensive analysis
return _combineResults(localResult, webResult);
} catch (e) {
// Fallback to local result if server unavailable
return localResult;
}
}
ClassificationResult _combineResults(
ClassificationResult local,
WebPredictionResult web
) {
// Implementation combines local and server predictions
// Provides confidence comparison and alternative suggestions
return ClassificationResult(
species: web.confidence > local.confidence ?
_getSpeciesFromWeb(web) : local.species,
confidence: math.max(local.confidence, web.confidence),
inferenceTime: local.inferenceTime,
relatedDiseases: local.relatedDiseases,
imageFile: local.imageFile,
metadata: {
'local_prediction': local.species.name,
'local_confidence': local.confidence,
'web_prediction': web.scientificName,
'web_confidence': web.confidence,
'prediction_agreement': local.species.name == web.scientificName,
},
);
}
}
Extending the Mobile App¶
1. Plugin Architecture¶
The mobile app supports extensions through a plugin-based architecture:
// Plugin interface for extending functionality
abstract class CulicidaeLabPlugin {
String get name;
String get version;
List<String> get supportedFeatures;
Future<void> initialize();
Future<Map<String, dynamic>> processData(Map<String, dynamic> input);
Future<void> cleanup();
}
// Example: Environmental data plugin
class EnvironmentalDataPlugin implements CulicidaeLabPlugin {
@override
String get name => 'Environmental Data Collector';
@override
String get version => '1.0.0';
@override
List<String> get supportedFeatures => [
'temperature_measurement',
'humidity_measurement',
'weather_integration'
];
@override
Future<void> initialize() async {
// Initialize weather API connections
// Set up sensor integrations
}
@override
Future<Map<String, dynamic>> processData(Map<String, dynamic> input) async {
final location = input['location'] as Location;
return {
'temperature': await _getTemperature(location),
'humidity': await _getHumidity(location),
'weather_conditions': await _getWeatherConditions(location),
'timestamp': DateTime.now().toIso8601String(),
};
}
Future<double> _getTemperature(Location location) async {
// Implementation for temperature data collection
return 25.0; // Example value
}
Future<double> _getHumidity(Location location) async {
// Implementation for humidity data collection
return 65.0; // Example value
}
Future<String> _getWeatherConditions(Location location) async {
// Implementation for weather condition detection
return 'partly_cloudy'; // Example value
}
@override
Future<void> cleanup() async {
// Cleanup resources
}
}
2. Custom Model Integration¶
Adding New Classification Models:
// Interface for custom classification models
abstract class ClassificationModel {
String get modelId;
String get modelVersion;
List<String> get supportedSpecies;
Future<void> loadModel();
Future<ClassificationResult> classify(File imageFile);
Future<void> unloadModel();
}
// Example: Custom TensorFlow Lite model
class CustomTFLiteModel implements ClassificationModel {
late Interpreter _interpreter;
@override
String get modelId => 'custom_tflite_v1';
@override
String get modelVersion => '1.0.0';
@override
List<String> get supportedSpecies => [
'Aedes aegypti',
'Aedes albopictus',
'Culex pipiens',
// ... additional species
];
@override
Future<void> loadModel() async {
final modelFile = await _loadModelFile('assets/models/custom_model.tflite');
_interpreter = Interpreter.fromFile(modelFile);
}
@override
Future<ClassificationResult> classify(File imageFile) async {
// Preprocess image
final input = await _preprocessImage(imageFile);
// Run inference
final output = List.filled(supportedSpecies.length, 0.0);
_interpreter.run(input, output);
// Post-process results
return _postprocessResults(output, imageFile);
}
Future<List<List<List<List<double>>>>> _preprocessImage(File imageFile) async {
// Image preprocessing implementation
// Resize, normalize, convert to tensor format
return []; // Placeholder
}
ClassificationResult _postprocessResults(
List<double> output,
File imageFile
) {
// Find highest confidence prediction
final maxIndex = output.indexOf(output.reduce(math.max));
final confidence = output[maxIndex];
final speciesName = supportedSpecies[maxIndex];
// Create species object and related diseases
final species = _createSpeciesFromName(speciesName);
final diseases = _getRelatedDiseases(species);
return ClassificationResult(
species: species,
confidence: confidence,
inferenceTime: 0, // Would measure actual inference time
relatedDiseases: diseases,
imageFile: imageFile,
);
}
@override
Future<void> unloadModel() async {
_interpreter.close();
}
}
3. Data Export Extensions¶
Custom Export Formats:
// Interface for data export plugins
abstract class DataExporter {
String get formatName;
String get fileExtension;
List<String> get supportedDataTypes;
Future<String> exportObservations(List<Observation> observations);
Future<String> exportClassificationResults(List<ClassificationResult> results);
Future<String> exportSpeciesData(List<MosquitoSpecies> species);
}
// Example: Research-specific export format
class ResearchDataExporter implements DataExporter {
@override
String get formatName => 'Research Analysis Format';
@override
String get fileExtension => '.raf';
@override
List<String> get supportedDataTypes => [
'observations',
'classifications',
'species_data',
'environmental_data'
];
@override
Future<String> exportObservations(List<Observation> observations) async {
final exportData = {
'metadata': {
'export_timestamp': DateTime.now().toIso8601String(),
'format_version': '1.0',
'total_observations': observations.length,
'data_quality_summary': _generateQualitySummary(observations),
},
'observations': observations.map((obs) => {
'id': obs.id,
'species': obs.speciesScientificName,
'location': {
'lat': obs.location.lat,
'lng': obs.location.lng,
'accuracy_m': obs.locationAccuracyM,
},
'temporal': {
'observed_at': obs.observedAt.toIso8601String(),
'day_of_year': obs.observedAt.dayOfYear,
'season': _getSeason(obs.observedAt),
},
'classification': {
'confidence': obs.confidence,
'model_id': obs.modelId,
'data_source': obs.dataSource,
},
'context': {
'notes': obs.notes,
'metadata': obs.metadata,
},
}).toList(),
'analysis': {
'species_distribution': _analyzeSpeciesDistribution(observations),
'temporal_patterns': _analyzeTemporalPatterns(observations),
'spatial_clusters': _analyzeSpatialClusters(observations),
},
};
return jsonEncode(exportData);
}
Map<String, dynamic> _generateQualitySummary(List<Observation> observations) {
final highQuality = observations.where((obs) => obs.isHighQuality).length;
final aiIdentified = observations.where((obs) => obs.isAiIdentified).length;
return {
'high_quality_percentage': (highQuality / observations.length * 100).round(),
'ai_identified_percentage': (aiIdentified / observations.length * 100).round(),
'average_location_accuracy': observations
.where((obs) => obs.locationAccuracyM != null)
.map((obs) => obs.locationAccuracyM!)
.fold(0, (a, b) => a + b) / observations.length,
};
}
String _getSeason(DateTime date) {
final month = date.month;
if (month >= 3 && month <= 5) return 'spring';
if (month >= 6 && month <= 8) return 'summer';
if (month >= 9 && month <= 11) return 'autumn';
return 'winter';
}
@override
Future<String> exportClassificationResults(List<ClassificationResult> results) async {
// Implementation for classification results export
return jsonEncode(results.map((r) => r.toJson()).toList());
}
@override
Future<String> exportSpeciesData(List<MosquitoSpecies> species) async {
// Implementation for species data export
return jsonEncode(species.map((s) => s.toJson()).toList());
}
}
Research Integration Workflows¶
1. Citizen Science Integration¶
Data Collection Workflow:
class CitizenScienceWorkflow {
static Future<void> submitCitizenScienceObservation({
required ClassificationResult classificationResult,
required Location location,
required String participantId,
String? notes,
Map<String, dynamic>? environmentalData,
}) async {
// Create comprehensive observation record
final observation = Observation(
id: 'cs_${DateTime.now().millisecondsSinceEpoch}',
speciesScientificName: classificationResult.species.name,
count: 1,
location: location,
observedAt: DateTime.now(),
notes: notes,
userId: participantId,
dataSource: 'citizen_science',
modelId: 'mobile_hybrid_v1',
confidence: classificationResult.confidence,
metadata: {
'participant_type': 'citizen_scientist',
'app_version': await _getAppVersion(),
'device_info': await _getDeviceInfo(),
'environmental_data': environmentalData,
'classification_metadata': {
'local_confidence': classificationResult.confidence,
'inference_time_ms': classificationResult.inferenceTime,
'related_diseases': classificationResult.relatedDiseases.map((d) => d.id).toList(),
},
},
);
// Submit to research database
await _submitToResearchDatabase(observation);
// Update participant statistics
await _updateParticipantStats(participantId, observation);
// Send confirmation to participant
await _sendParticipantConfirmation(participantId, observation);
}
static Future<void> _submitToResearchDatabase(Observation observation) async {
// Implementation for research database submission
final repository = locator<ClassificationRepository>();
await repository.submitObservation(finalPayload: observation.toJson());
}
static Future<void> _updateParticipantStats(String participantId, Observation observation) async {
// Update participant contribution statistics
final stats = await _getParticipantStats(participantId);
stats['total_observations'] = (stats['total_observations'] ?? 0) + 1;
stats['species_identified'] = _updateSpeciesList(stats['species_identified'], observation.speciesScientificName);
stats['last_contribution'] = observation.observedAt.toIso8601String();
await _saveParticipantStats(participantId, stats);
}
}
2. Professional Research Integration¶
Field Research Workflow:
class FieldResearchWorkflow {
static Future<ResearchSession> startResearchSession({
required String researcherId,
required String projectId,
required Location studyArea,
required String methodology,
}) async {
final session = ResearchSession(
id: 'rs_${DateTime.now().millisecondsSinceEpoch}',
researcherId: researcherId,
projectId: projectId,
studyArea: studyArea,
methodology: methodology,
startTime: DateTime.now(),
observations: [],
environmentalConditions: await _collectEnvironmentalData(studyArea),
);
await _saveResearchSession(session);
return session;
}
static Future<void> addObservationToSession({
required String sessionId,
required ClassificationResult classificationResult,
required Location exactLocation,
String? fieldNotes,
Map<String, dynamic>? additionalData,
}) async {
final session = await _getResearchSession(sessionId);
final observation = Observation(
id: 'obs_${sessionId}_${session.observations.length + 1}',
speciesScientificName: classificationResult.species.name,
count: 1,
location: exactLocation,
observedAt: DateTime.now(),
notes: fieldNotes,
userId: session.researcherId,
dataSource: 'field_research',
modelId: 'mobile_research_v1',
confidence: classificationResult.confidence,
metadata: {
'research_session_id': sessionId,
'project_id': session.projectId,
'methodology': session.methodology,
'environmental_conditions': session.environmentalConditions,
'additional_data': additionalData,
'field_validation': {
'expert_verified': false,
'verification_notes': null,
'verification_timestamp': null,
},
},
);
session.observations.add(observation);
await _updateResearchSession(session);
// Real-time sync to research database
await _syncObservationToResearchDB(observation);
}
static Future<ResearchReport> generateSessionReport(String sessionId) async {
final session = await _getResearchSession(sessionId);
return ResearchReport(
sessionId: sessionId,
summary: _generateSessionSummary(session),
speciesAnalysis: _analyzeSpeciesDistribution(session.observations),
spatialAnalysis: _analyzeSpatialPatterns(session.observations),
temporalAnalysis: _analyzeTemporalPatterns(session.observations),
qualityMetrics: _calculateDataQuality(session.observations),
recommendations: _generateRecommendations(session),
);
}
}
class ResearchSession {
final String id;
final String researcherId;
final String projectId;
final Location studyArea;
final String methodology;
final DateTime startTime;
final List<Observation> observations;
final Map<String, dynamic> environmentalConditions;
DateTime? endTime;
ResearchSession({
required this.id,
required this.researcherId,
required this.projectId,
required this.studyArea,
required this.methodology,
required this.startTime,
required this.observations,
required this.environmentalConditions,
this.endTime,
});
}
3. Public Health Integration¶
Surveillance System Integration:
class PublicHealthIntegration {
static Future<void> submitSurveillanceData({
required List<Observation> observations,
required String healthDistrictId,
required String surveillanceProgram,
}) async {
// Filter for epidemiologically significant species
final significantObservations = observations.where((obs) =>
_isEpidemiologicallySignificant(obs.speciesScientificName)
).toList();
if (significantObservations.isEmpty) return;
// Create surveillance report
final report = SurveillanceReport(
id: 'sr_${DateTime.now().millisecondsSinceEpoch}',
healthDistrictId: healthDistrictId,
program: surveillanceProgram,
reportingPeriod: _getCurrentReportingPeriod(),
observations: significantObservations,
riskAssessment: await _assessVectorRisk(significantObservations),
recommendations: await _generateHealthRecommendations(significantObservations),
);
// Submit to public health system
await _submitToPublicHealthSystem(report);
// Generate alerts if necessary
await _checkForHealthAlerts(report);
}
static bool _isEpidemiologicallySignificant(String speciesName) {
const significantSpecies = [
'Aedes aegypti',
'Aedes albopictus',
'Anopheles gambiae',
'Anopheles funestus',
'Culex quinquefasciatus',
];
return significantSpecies.contains(speciesName);
}
static Future<RiskAssessment> _assessVectorRisk(List<Observation> observations) async {
// Implement risk assessment algorithm
final speciesRisk = <String, double>{};
final locationRisk = <Location, double>{};
for (final obs in observations) {
// Calculate species-specific risk
speciesRisk[obs.speciesScientificName] =
(speciesRisk[obs.speciesScientificName] ?? 0.0) +
(obs.confidence ?? 0.5);
// Calculate location-specific risk
locationRisk[obs.location] =
(locationRisk[obs.location] ?? 0.0) + 1.0;
}
return RiskAssessment(
overallRisk: _calculateOverallRisk(speciesRisk, locationRisk),
speciesRisk: speciesRisk,
locationRisk: locationRisk,
recommendations: _generateRiskRecommendations(speciesRisk, locationRisk),
);
}
}
class SurveillanceReport {
final String id;
final String healthDistrictId;
final String program;
final DateRange reportingPeriod;
final List<Observation> observations;
final RiskAssessment riskAssessment;
final List<String> recommendations;
SurveillanceReport({
required this.id,
required this.healthDistrictId,
required this.program,
required this.reportingPeriod,
required this.observations,
required this.riskAssessment,
required this.recommendations,
});
}
External System Integration¶
1. GIS System Integration¶
Spatial Data Exchange:
class GISIntegration {
static Future<void> exportToGIS({
required List<Observation> observations,
required String gisFormat, // 'shapefile', 'geojson', 'kml'
String? projectionSystem,
}) async {
switch (gisFormat.toLowerCase()) {
case 'geojson':
await _exportToGeoJSON(observations);
break;
case 'shapefile':
await _exportToShapefile(observations, projectionSystem);
break;
case 'kml':
await _exportToKML(observations);
break;
default:
throw ArgumentError('Unsupported GIS format: $gisFormat');
}
}
static Future<void> _exportToGeoJSON(List<Observation> observations) async {
final geoJson = {
'type': 'FeatureCollection',
'crs': {
'type': 'name',
'properties': {'name': 'EPSG:4326'}
},
'features': observations.map((obs) => {
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [obs.location.lng, obs.location.lat]
},
'properties': {
'observation_id': obs.id,
'species_scientific_name': obs.speciesScientificName,
'confidence': obs.confidence,
'observed_at': obs.observedAt.toIso8601String(),
'data_source': obs.dataSource,
'location_accuracy_m': obs.locationAccuracyM,
'notes': obs.notes,
}
}).toList()
};
final file = File('observations_export.geojson');
await file.writeAsString(jsonEncode(geoJson));
}
static Future<List<Observation>> importFromGIS({
required String filePath,
required String gisFormat,
}) async {
switch (gisFormat.toLowerCase()) {
case 'geojson':
return await _importFromGeoJSON(filePath);
case 'shapefile':
return await _importFromShapefile(filePath);
default:
throw ArgumentError('Unsupported import format: $gisFormat');
}
}
}
2. Database Integration¶
Research Database Connectivity:
class DatabaseIntegration {
static Future<void> syncWithResearchDatabase({
required String databaseType, // 'postgresql', 'mysql', 'mongodb'
required Map<String, String> connectionConfig,
required List<Observation> observations,
}) async {
switch (databaseType.toLowerCase()) {
case 'postgresql':
await _syncWithPostgreSQL(connectionConfig, observations);
break;
case 'mysql':
await _syncWithMySQL(connectionConfig, observations);
break;
case 'mongodb':
await _syncWithMongoDB(connectionConfig, observations);
break;
default:
throw ArgumentError('Unsupported database type: $databaseType');
}
}
static Future<void> _syncWithPostgreSQL(
Map<String, String> config,
List<Observation> observations
) async {
// PostgreSQL integration implementation
final connection = PostgreSQLConnection(
config['host']!,
int.parse(config['port']!),
config['database']!,
username: config['username'],
password: config['password'],
);
await connection.open();
try {
for (final obs in observations) {
await connection.execute('''
INSERT INTO mosquito_observations
(id, species_scientific_name, count, latitude, longitude,
observed_at, confidence, data_source, notes, metadata)
VALUES (@id, @species, @count, @lat, @lng, @observed_at,
@confidence, @data_source, @notes, @metadata)
ON CONFLICT (id) DO UPDATE SET
confidence = EXCLUDED.confidence,
metadata = EXCLUDED.metadata
''', substitutionValues: {
'id': obs.id,
'species': obs.speciesScientificName,
'count': obs.count,
'lat': obs.location.lat,
'lng': obs.location.lng,
'observed_at': obs.observedAt,
'confidence': obs.confidence,
'data_source': obs.dataSource,
'notes': obs.notes,
'metadata': jsonEncode(obs.metadata),
});
}
} finally {
await connection.close();
}
}
}
Development Guidelines¶
1. Plugin Development Standards¶
Plugin Structure:
plugins/
├── my_plugin/
│ ├── lib/
│ │ ├── my_plugin.dart
│ │ └── src/
│ │ ├── models/
│ │ ├── services/
│ │ └── widgets/
│ ├── pubspec.yaml
│ ├── README.md
│ └── example/
Plugin Registration:
// In main.dart or plugin manager
class PluginManager {
static final List<CulicidaeLabPlugin> _plugins = [];
static Future<void> registerPlugin(CulicidaeLabPlugin plugin) async {
await plugin.initialize();
_plugins.add(plugin);
}
static Future<void> initializeAllPlugins() async {
for (final plugin in _plugins) {
try {
await plugin.initialize();
} catch (e) {
print('Failed to initialize plugin ${plugin.name}: $e');
}
}
}
static List<CulicidaeLabPlugin> getPluginsByFeature(String feature) {
return _plugins.where((p) => p.supportedFeatures.contains(feature)).toList();
}
}
2. API Extension Guidelines¶
Custom Endpoint Integration:
// Extend the API client for custom endpoints
class ExtendedAPIClient extends CulicidaeLabAPIClient {
Future<Map<String, dynamic>> callCustomEndpoint({
required String endpoint,
required Map<String, dynamic> data,
String method = 'POST',
}) async {
final url = Uri.parse('${CulicidaeLabAPI.baseUrl}$endpoint');
http.Response response;
switch (method.toUpperCase()) {
case 'GET':
response = await httpClient.get(url);
break;
case 'POST':
response = await httpClient.post(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode(data),
);
break;
case 'PUT':
response = await httpClient.put(
url,
headers: {'Content-Type': 'application/json'},
body: jsonEncode(data),
);
break;
default:
throw ArgumentError('Unsupported HTTP method: $method');
}
if (response.statusCode >= 200 && response.statusCode < 300) {
return jsonDecode(response.body);
} else {
throw APIException(
statusCode: response.statusCode,
message: 'Custom endpoint call failed',
body: response.body,
);
}
}
}
3. Data Model Extensions¶
Custom Data Models:
// Extend existing models for custom use cases
class ExtendedObservation extends Observation {
final WeatherData? weatherData;
final SoilData? soilData;
final VegetationData? vegetationData;
final List<String>? associatedSpecies;
ExtendedObservation({
required super.id,
required super.speciesScientificName,
required super.count,
required super.location,
required super.observedAt,
super.notes,
super.userId,
super.locationAccuracyM,
super.dataSource,
super.imageFilename,
super.modelId,
super.confidence,
super.metadata,
this.weatherData,
this.soilData,
this.vegetationData,
this.associatedSpecies,
});
@override
Map<String, dynamic> toJson() {
final json = super.toJson();
json.addAll({
if (weatherData != null) 'weather_data': weatherData!.toJson(),
if (soilData != null) 'soil_data': soilData!.toJson(),
if (vegetationData != null) 'vegetation_data': vegetationData!.toJson(),
if (associatedSpecies != null) 'associated_species': associatedSpecies,
});
return json;
}
factory ExtendedObservation.fromJson(Map<String, dynamic> json) {
final baseObservation = Observation.fromJson(json);
return ExtendedObservation(
id: baseObservation.id,
speciesScientificName: baseObservation.speciesScientificName,
count: baseObservation.count,
location: baseObservation.location,
observedAt: baseObservation.observedAt,
notes: baseObservation.notes,
userId: baseObservation.userId,
locationAccuracyM: baseObservation.locationAccuracyM,
dataSource: baseObservation.dataSource,
imageFilename: baseObservation.imageFilename,
modelId: baseObservation.modelId,
confidence: baseObservation.confidence,
metadata: baseObservation.metadata,
weatherData: json['weather_data'] != null
? WeatherData.fromJson(json['weather_data'])
: null,
soilData: json['soil_data'] != null
? SoilData.fromJson(json['soil_data'])
: null,
vegetationData: json['vegetation_data'] != null
? VegetationData.fromJson(json['vegetation_data'])
: null,
associatedSpecies: json['associated_species'] != null
? List<String>.from(json['associated_species'])
: null,
);
}
}
class WeatherData {
final double temperature;
final double humidity;
final double pressure;
final String conditions;
final double windSpeed;
final String windDirection;
WeatherData({
required this.temperature,
required this.humidity,
required this.pressure,
required this.conditions,
required this.windSpeed,
required this.windDirection,
});
Map<String, dynamic> toJson() => {
'temperature': temperature,
'humidity': humidity,
'pressure': pressure,
'conditions': conditions,
'wind_speed': windSpeed,
'wind_direction': windDirection,
};
factory WeatherData.fromJson(Map<String, dynamic> json) => WeatherData(
temperature: (json['temperature'] as num).toDouble(),
humidity: (json['humidity'] as num).toDouble(),
pressure: (json['pressure'] as num).toDouble(),
conditions: json['conditions'] as String,
windSpeed: (json['wind_speed'] as num).toDouble(),
windDirection: json['wind_direction'] as String,
);
}
Best Practices¶
1. Integration Security¶
- API Authentication: Implement proper authentication for custom endpoints
- Data Validation: Validate all external data before processing
- Error Handling: Implement robust error handling for network operations
- Rate Limiting: Respect API rate limits and implement backoff strategies
2. Performance Optimization¶
- Batch Operations: Use batch processing for large data sets
- Caching: Implement appropriate caching strategies
- Background Processing: Use background tasks for heavy operations
- Memory Management: Monitor memory usage with large datasets
3. Data Quality¶
- Validation Rules: Implement comprehensive data validation
- Quality Metrics: Track and report data quality metrics
- User Feedback: Provide mechanisms for data quality feedback
- Expert Review: Support expert validation workflows
4. Documentation¶
- API Documentation: Maintain comprehensive API documentation
- Plugin Documentation: Document plugin interfaces and examples
- Integration Guides: Provide step-by-step integration guides
- Version Compatibility: Document version compatibility requirements
Future Ecosystem Enhancements¶
Planned Integrations¶
- Real-time Collaboration: Multi-user research sessions
- Advanced Analytics: Machine learning pipeline integration
- IoT Integration: Environmental sensor data integration
- Blockchain: Data provenance and verification
- AR/VR: Augmented reality field guides
Research Opportunities¶
- Federated Learning: Distributed model training
- Edge Computing: Advanced on-device processing
- Satellite Integration: Remote sensing data fusion
- Climate Modeling: Environmental prediction integration
- Genomic Data: Molecular identification integration
This comprehensive ecosystem integration guide provides the foundation for extending the CulicidaeLab mobile application and integrating it with broader research and public health systems. The modular architecture and well-defined interfaces enable flexible customization while maintaining compatibility with the core ecosystem.