Initial commit
This commit is contained in:
@@ -0,0 +1,634 @@
|
||||
//! Sample data and configurations for the SeismicAI demo.
|
||||
//!
|
||||
//! Provides realistic earthquake scenarios, station networks, and velocity models
|
||||
//! for demonstration and testing purposes.
|
||||
|
||||
use seismic_shared::{
|
||||
EarthquakeSource, FocalMechanism, GeoLocation, InstrumentType, SimulationConfig, SiteClass,
|
||||
StationConfig, VelocityLayer, VelocityModel, WarningConfig,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Sample Earthquake Scenarios
|
||||
// ============================================================================
|
||||
|
||||
/// Create a local shallow earthquake (M4.5, 8km depth).
|
||||
/// Representative of typical California small/moderate events.
|
||||
pub fn local_earthquake() -> EarthquakeSource {
|
||||
let mut source = EarthquakeSource::new(
|
||||
37.8044, // Near Berkeley, CA (Hayward Fault)
|
||||
-122.2712,
|
||||
8.0, // Shallow depth
|
||||
4.5, // Moderate magnitude
|
||||
FocalMechanism::strike_slip(),
|
||||
);
|
||||
source.event_id = "NC72345678".to_string();
|
||||
source.stress_drop_mpa = Some(5.0);
|
||||
source
|
||||
}
|
||||
|
||||
/// Create a regional moderate earthquake (M6.0, 15km depth).
|
||||
/// Representative of damaging regional events.
|
||||
pub fn regional_earthquake() -> EarthquakeSource {
|
||||
let mut source = EarthquakeSource::new(
|
||||
36.5, // Central California (San Andreas Fault)
|
||||
-121.0,
|
||||
15.0, // Mid-crustal depth
|
||||
6.0, // Significant magnitude
|
||||
FocalMechanism::strike_slip(),
|
||||
);
|
||||
source.event_id = "NC72456789".to_string();
|
||||
source.stress_drop_mpa = Some(10.0);
|
||||
source
|
||||
}
|
||||
|
||||
/// Create a major earthquake scenario (M7.0, 10km depth).
|
||||
/// Representative of potentially catastrophic events.
|
||||
pub fn major_earthquake() -> EarthquakeSource {
|
||||
let mut source = EarthquakeSource::new(
|
||||
37.4, // Near San Jose (Hayward Fault)
|
||||
-122.1,
|
||||
10.0,
|
||||
7.0, // Major magnitude
|
||||
FocalMechanism {
|
||||
strike: 150.0,
|
||||
dip: 80.0,
|
||||
rake: 175.0, // Right-lateral strike-slip
|
||||
},
|
||||
);
|
||||
source.event_id = "NC72567890".to_string();
|
||||
source.stress_drop_mpa = Some(8.0);
|
||||
source.rupture_duration_s = Some(15.0);
|
||||
source
|
||||
}
|
||||
|
||||
/// Create a subduction zone earthquake scenario (M8.0, 20km depth).
|
||||
/// Representative of Cascadia-style megathrust events.
|
||||
pub fn subduction_earthquake() -> EarthquakeSource {
|
||||
let mut source = EarthquakeSource::new(
|
||||
44.0, // Offshore Oregon (Cascadia Subduction Zone)
|
||||
-125.0,
|
||||
20.0,
|
||||
8.0, // Great earthquake
|
||||
FocalMechanism::reverse(),
|
||||
);
|
||||
source.event_id = "PW72678901".to_string();
|
||||
source.stress_drop_mpa = Some(3.0); // Lower stress drop for subduction
|
||||
source.rupture_area_km2 = Some(10000.0);
|
||||
source.rupture_duration_s = Some(60.0);
|
||||
source
|
||||
}
|
||||
|
||||
/// Create a deep earthquake scenario (M5.5, 100km depth).
|
||||
/// Representative of deep intraslab events.
|
||||
pub fn deep_earthquake() -> EarthquakeSource {
|
||||
let mut source = EarthquakeSource::new(
|
||||
47.5, // Puget Sound region
|
||||
-122.5,
|
||||
100.0, // Deep intraslab
|
||||
5.5,
|
||||
FocalMechanism::normal(),
|
||||
);
|
||||
source.event_id = "UW72789012".to_string();
|
||||
source.stress_drop_mpa = Some(50.0); // Higher stress drop for deep events
|
||||
source
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Sample Station Networks
|
||||
// ============================================================================
|
||||
|
||||
/// Create a dense urban seismic network (100 stations in 10x10 grid).
|
||||
/// Representative of ShakeAlert-style networks in urban areas.
|
||||
pub fn dense_network() -> Vec<StationConfig> {
|
||||
let center = GeoLocation::new(37.8, -122.4);
|
||||
let grid_spacing = 0.05; // ~5km at this latitude
|
||||
|
||||
let mut stations = Vec::with_capacity(100);
|
||||
|
||||
for i in 0..10 {
|
||||
for j in 0..10 {
|
||||
let lat = center.latitude - 0.25 + i as f64 * grid_spacing;
|
||||
let lon = center.longitude - 0.25 + j as f64 * grid_spacing;
|
||||
|
||||
let mut station = StationConfig::new(&format!("BK{:02}{:02}", i, j), lat, lon);
|
||||
station.name = format!("Bay Area Station {:02}-{:02}", i, j);
|
||||
station.network = "BK".to_string();
|
||||
station.instrument = InstrumentType::Accelerometer;
|
||||
|
||||
// Vary site classes
|
||||
station.site_class = match (i + j) % 5 {
|
||||
0 => SiteClass::A,
|
||||
1 => SiteClass::B,
|
||||
2 => SiteClass::C,
|
||||
3 => SiteClass::D,
|
||||
_ => SiteClass::E,
|
||||
};
|
||||
|
||||
stations.push(station);
|
||||
}
|
||||
}
|
||||
|
||||
stations
|
||||
}
|
||||
|
||||
/// Create a sparse regional seismic network (5 stations).
|
||||
/// Representative of USGS/university backbone networks.
|
||||
pub fn sparse_network() -> Vec<StationConfig> {
|
||||
vec![
|
||||
{
|
||||
let mut s = StationConfig::new("BK.BKS", 37.8764, -122.2356);
|
||||
s.name = "Berkeley Seismic Station".to_string();
|
||||
s.network = "BK".to_string();
|
||||
s.instrument = InstrumentType::Broadband;
|
||||
s.site_class = SiteClass::B;
|
||||
s.vs30 = Some(950.0);
|
||||
s
|
||||
},
|
||||
{
|
||||
let mut s = StationConfig::new("BK.CMB", 38.0346, -120.3865);
|
||||
s.name = "Columbia College".to_string();
|
||||
s.network = "BK".to_string();
|
||||
s.instrument = InstrumentType::Broadband;
|
||||
s.site_class = SiteClass::B;
|
||||
s.vs30 = Some(800.0);
|
||||
s
|
||||
},
|
||||
{
|
||||
let mut s = StationConfig::new("BK.SAO", 36.7640, -121.4472);
|
||||
s.name = "San Andreas Observatory".to_string();
|
||||
s.network = "BK".to_string();
|
||||
s.instrument = InstrumentType::Broadband;
|
||||
s.site_class = SiteClass::C;
|
||||
s.vs30 = Some(550.0);
|
||||
s
|
||||
},
|
||||
{
|
||||
let mut s = StationConfig::new("NC.FARB", 37.6977, -123.0016);
|
||||
s.name = "Farallon Islands".to_string();
|
||||
s.network = "NC".to_string();
|
||||
s.instrument = InstrumentType::Accelerometer;
|
||||
s.site_class = SiteClass::A;
|
||||
s.vs30 = Some(1500.0);
|
||||
s
|
||||
},
|
||||
{
|
||||
let mut s = StationConfig::new("NC.SF", 37.7749, -122.4194);
|
||||
s.name = "San Francisco".to_string();
|
||||
s.network = "NC".to_string();
|
||||
s.instrument = InstrumentType::Accelerometer;
|
||||
s.site_class = SiteClass::D;
|
||||
s.vs30 = Some(280.0);
|
||||
s.z1_0 = Some(0.5);
|
||||
s
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
/// Create a linear array for source characterization.
|
||||
pub fn linear_array() -> Vec<StationConfig> {
|
||||
let start = GeoLocation::new(37.0, -122.0);
|
||||
let end = GeoLocation::new(38.0, -122.0);
|
||||
let num_stations = 20;
|
||||
|
||||
(0..num_stations)
|
||||
.map(|i| {
|
||||
let t = i as f64 / (num_stations - 1) as f64;
|
||||
let lat = start.latitude + t * (end.latitude - start.latitude);
|
||||
let lon = start.longitude + t * (end.longitude - start.longitude);
|
||||
|
||||
let mut station = StationConfig::new(&format!("LA{:02}", i), lat, lon);
|
||||
station.name = format!("Linear Array Station {:02}", i);
|
||||
station.network = "XX".to_string();
|
||||
station.instrument = InstrumentType::ShortPeriod;
|
||||
station.site_class = SiteClass::C;
|
||||
station
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Create a MEMS network for community early warning.
|
||||
pub fn mems_network() -> Vec<StationConfig> {
|
||||
let center = GeoLocation::new(34.0522, -118.2437); // Los Angeles
|
||||
let num_stations = 50;
|
||||
|
||||
(0..num_stations)
|
||||
.map(|i| {
|
||||
// Random-ish distribution
|
||||
let angle = i as f64 * 2.4; // Golden angle
|
||||
let radius = 0.1 + 0.05 * (i as f64).sqrt();
|
||||
let lat = center.latitude + radius * angle.cos();
|
||||
let lon = center.longitude + radius * angle.sin();
|
||||
|
||||
let mut station = StationConfig::new(&format!("ME{:02}", i), lat, lon);
|
||||
station.name = format!("MEMS Station {:02}", i);
|
||||
station.network = "CE".to_string();
|
||||
station.instrument = InstrumentType::Mems;
|
||||
station.site_class = if i % 3 == 0 {
|
||||
SiteClass::D
|
||||
} else {
|
||||
SiteClass::C
|
||||
};
|
||||
station
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Sample Velocity Models
|
||||
// ============================================================================
|
||||
|
||||
/// Create California 1D velocity model (SCEC CVM-H based).
|
||||
pub fn california_velocity_model() -> VelocityModel {
|
||||
let mut model = VelocityModel::new("California CVM-H Simplified");
|
||||
model.reference_lat = 35.0;
|
||||
model.reference_lon = -118.0;
|
||||
|
||||
// Layered model from surface to Moho
|
||||
model.add_layer(VelocityLayer {
|
||||
depth_km: 0.0,
|
||||
thickness_km: 1.0,
|
||||
vp: 2.5,
|
||||
vs: 1.2,
|
||||
density: 2.1,
|
||||
qp: 100.0,
|
||||
qs: 50.0,
|
||||
});
|
||||
|
||||
model.add_layer(VelocityLayer {
|
||||
depth_km: 1.0,
|
||||
thickness_km: 2.0,
|
||||
vp: 4.0,
|
||||
vs: 2.3,
|
||||
density: 2.4,
|
||||
qp: 200.0,
|
||||
qs: 100.0,
|
||||
});
|
||||
|
||||
model.add_layer(VelocityLayer {
|
||||
depth_km: 3.0,
|
||||
thickness_km: 5.0,
|
||||
vp: 5.5,
|
||||
vs: 3.2,
|
||||
density: 2.6,
|
||||
qp: 400.0,
|
||||
qs: 200.0,
|
||||
});
|
||||
|
||||
model.add_layer(VelocityLayer {
|
||||
depth_km: 8.0,
|
||||
thickness_km: 7.0,
|
||||
vp: 6.3,
|
||||
vs: 3.6,
|
||||
density: 2.8,
|
||||
qp: 600.0,
|
||||
qs: 300.0,
|
||||
});
|
||||
|
||||
model.add_layer(VelocityLayer {
|
||||
depth_km: 15.0,
|
||||
thickness_km: 10.0,
|
||||
vp: 6.7,
|
||||
vs: 3.9,
|
||||
density: 2.9,
|
||||
qp: 800.0,
|
||||
qs: 400.0,
|
||||
});
|
||||
|
||||
model.add_layer(VelocityLayer {
|
||||
depth_km: 25.0,
|
||||
thickness_km: 10.0,
|
||||
vp: 7.2,
|
||||
vs: 4.1,
|
||||
density: 3.0,
|
||||
qp: 1000.0,
|
||||
qs: 500.0,
|
||||
});
|
||||
|
||||
// Moho
|
||||
model.add_layer(VelocityLayer {
|
||||
depth_km: 35.0,
|
||||
thickness_km: 15.0,
|
||||
vp: 7.8,
|
||||
vs: 4.4,
|
||||
density: 3.2,
|
||||
qp: 1500.0,
|
||||
qs: 750.0,
|
||||
});
|
||||
|
||||
model
|
||||
}
|
||||
|
||||
/// Create Pacific Northwest velocity model (Cascadia).
|
||||
pub fn cascadia_velocity_model() -> VelocityModel {
|
||||
let mut model = VelocityModel::new("Cascadia Subduction Zone");
|
||||
model.reference_lat = 45.0;
|
||||
model.reference_lon = -123.0;
|
||||
|
||||
// Sedimentary basin
|
||||
model.add_layer(VelocityLayer::new(0.0, 3.0, 3.5, 2.0, 2.2));
|
||||
|
||||
// Upper crust
|
||||
model.add_layer(VelocityLayer::new(3.0, 12.0, 6.0, 3.5, 2.7));
|
||||
|
||||
// Lower crust
|
||||
model.add_layer(VelocityLayer::new(15.0, 15.0, 6.8, 3.9, 2.9));
|
||||
|
||||
// Subducting oceanic crust
|
||||
model.add_layer(VelocityLayer::new(30.0, 10.0, 7.2, 4.1, 3.1));
|
||||
|
||||
// Mantle
|
||||
model.add_layer(VelocityLayer::new(40.0, 60.0, 8.1, 4.6, 3.3));
|
||||
|
||||
model
|
||||
}
|
||||
|
||||
/// Create a simple generic velocity model.
|
||||
pub fn generic_velocity_model() -> VelocityModel {
|
||||
let mut model = VelocityModel::new("Generic");
|
||||
model.add_layer(VelocityLayer::new(0.0, 5.0, 5.0, 2.9, 2.5));
|
||||
model.add_layer(VelocityLayer::new(5.0, 15.0, 6.5, 3.7, 2.8));
|
||||
model.add_layer(VelocityLayer::new(20.0, 15.0, 7.5, 4.3, 3.1));
|
||||
model.add_layer(VelocityLayer::new(35.0, 65.0, 8.0, 4.6, 3.3));
|
||||
model
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Sample Configurations
|
||||
// ============================================================================
|
||||
|
||||
/// High-resolution simulation configuration.
|
||||
pub fn high_resolution_config() -> SimulationConfig {
|
||||
SimulationConfig {
|
||||
dt: 0.005,
|
||||
duration: 120.0,
|
||||
dx: 0.25,
|
||||
domain_size: (200.0, 200.0, 60.0),
|
||||
max_frequency: 20.0,
|
||||
include_attenuation: true,
|
||||
include_site_effects: true,
|
||||
use_neural_operator: true,
|
||||
fno_modes: 32,
|
||||
hidden_dim: 128,
|
||||
}
|
||||
}
|
||||
|
||||
/// Standard simulation configuration.
|
||||
pub fn standard_config() -> SimulationConfig {
|
||||
SimulationConfig::default()
|
||||
}
|
||||
|
||||
/// Fast/coarse simulation configuration.
|
||||
pub fn fast_config() -> SimulationConfig {
|
||||
SimulationConfig {
|
||||
dt: 0.02,
|
||||
duration: 60.0,
|
||||
dx: 1.0,
|
||||
domain_size: (100.0, 100.0, 40.0),
|
||||
max_frequency: 5.0,
|
||||
include_attenuation: true,
|
||||
include_site_effects: true,
|
||||
use_neural_operator: true,
|
||||
fno_modes: 8,
|
||||
hidden_dim: 32,
|
||||
}
|
||||
}
|
||||
|
||||
/// Early warning configuration for urban areas.
|
||||
pub fn urban_warning_config() -> WarningConfig {
|
||||
WarningConfig {
|
||||
min_magnitude: 3.5,
|
||||
min_stations: 4,
|
||||
alert_threshold_seconds: 5.0,
|
||||
enable_sound: true,
|
||||
enable_notifications: true,
|
||||
alert_radius_km: 150.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Early warning configuration for regional networks.
|
||||
pub fn regional_warning_config() -> WarningConfig {
|
||||
WarningConfig {
|
||||
min_magnitude: 4.5,
|
||||
min_stations: 3,
|
||||
alert_threshold_seconds: 10.0,
|
||||
enable_sound: true,
|
||||
enable_notifications: true,
|
||||
alert_radius_km: 300.0,
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Complete Scenarios
|
||||
// ============================================================================
|
||||
|
||||
/// Complete scenario: Hayward Fault rupture affecting Bay Area.
|
||||
pub fn hayward_scenario() -> (
|
||||
EarthquakeSource,
|
||||
Vec<StationConfig>,
|
||||
VelocityModel,
|
||||
SimulationConfig,
|
||||
) {
|
||||
(
|
||||
major_earthquake(),
|
||||
dense_network(),
|
||||
california_velocity_model(),
|
||||
standard_config(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Complete scenario: Small local event for testing.
|
||||
pub fn local_test_scenario() -> (
|
||||
EarthquakeSource,
|
||||
Vec<StationConfig>,
|
||||
VelocityModel,
|
||||
SimulationConfig,
|
||||
) {
|
||||
(
|
||||
local_earthquake(),
|
||||
sparse_network(),
|
||||
generic_velocity_model(),
|
||||
fast_config(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Complete scenario: Cascadia megathrust.
|
||||
pub fn cascadia_scenario() -> (
|
||||
EarthquakeSource,
|
||||
Vec<StationConfig>,
|
||||
VelocityModel,
|
||||
SimulationConfig,
|
||||
) {
|
||||
(
|
||||
subduction_earthquake(),
|
||||
sparse_network(),
|
||||
cascadia_velocity_model(),
|
||||
high_resolution_config(),
|
||||
)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Tests
|
||||
// ============================================================================
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_local_earthquake() {
|
||||
let eq = local_earthquake();
|
||||
assert!(eq.magnitude >= 4.0 && eq.magnitude <= 5.0);
|
||||
assert!(eq.hypocenter.depth_km < 15.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_regional_earthquake() {
|
||||
let eq = regional_earthquake();
|
||||
assert!(eq.magnitude >= 5.5 && eq.magnitude <= 6.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_major_earthquake() {
|
||||
let eq = major_earthquake();
|
||||
assert!(eq.magnitude >= 6.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_subduction_earthquake() {
|
||||
let eq = subduction_earthquake();
|
||||
assert!(eq.magnitude >= 7.5);
|
||||
assert!(eq.hypocenter.depth_km >= 15.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deep_earthquake() {
|
||||
let eq = deep_earthquake();
|
||||
assert!(eq.hypocenter.depth_km >= 50.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dense_network() {
|
||||
let network = dense_network();
|
||||
assert_eq!(network.len(), 100);
|
||||
|
||||
// Check diversity of site classes
|
||||
let site_classes: Vec<_> = network.iter().map(|s| s.site_class).collect();
|
||||
assert!(site_classes.contains(&SiteClass::A));
|
||||
assert!(site_classes.contains(&SiteClass::E));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sparse_network() {
|
||||
let network = sparse_network();
|
||||
assert!(network.len() >= 4 && network.len() <= 10);
|
||||
|
||||
// All should have valid Vs30
|
||||
for station in &network {
|
||||
assert!(station.get_vs30() > 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_linear_array() {
|
||||
let array = linear_array();
|
||||
assert_eq!(array.len(), 20);
|
||||
|
||||
// Check stations are in a line
|
||||
let first_lon = array[0].location.longitude;
|
||||
for station in &array {
|
||||
assert!((station.location.longitude - first_lon).abs() < 0.01);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mems_network() {
|
||||
let network = mems_network();
|
||||
assert_eq!(network.len(), 50);
|
||||
|
||||
for station in &network {
|
||||
assert_eq!(station.instrument, InstrumentType::Mems);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_california_velocity_model() {
|
||||
let model = california_velocity_model();
|
||||
assert!(!model.layers.is_empty());
|
||||
|
||||
// Velocity should increase with depth
|
||||
for i in 1..model.layers.len() {
|
||||
assert!(model.layers[i].vp >= model.layers[i - 1].vp * 0.9);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cascadia_velocity_model() {
|
||||
let model = cascadia_velocity_model();
|
||||
assert!(!model.layers.is_empty());
|
||||
|
||||
// Should have deep layers for subduction
|
||||
let max_depth: f64 = model
|
||||
.layers
|
||||
.iter()
|
||||
.map(|l| l.depth_km + l.thickness_km)
|
||||
.fold(0.0, f64::max);
|
||||
assert!(max_depth >= 50.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simulation_configs() {
|
||||
let fast = fast_config();
|
||||
let standard = standard_config();
|
||||
let high = high_resolution_config();
|
||||
|
||||
// Higher resolution means smaller dx
|
||||
assert!(high.dx < standard.dx);
|
||||
assert!(standard.dx < fast.dx);
|
||||
|
||||
// Higher resolution means smaller dt
|
||||
assert!(high.dt < standard.dt);
|
||||
assert!(standard.dt < fast.dt);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_warning_configs() {
|
||||
let urban = urban_warning_config();
|
||||
let regional = regional_warning_config();
|
||||
|
||||
// Urban should have lower magnitude threshold
|
||||
assert!(urban.min_magnitude <= regional.min_magnitude);
|
||||
|
||||
// Both should require multiple stations
|
||||
assert!(urban.min_stations >= 2);
|
||||
assert!(regional.min_stations >= 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hayward_scenario() {
|
||||
let (source, stations, model, config) = hayward_scenario();
|
||||
|
||||
assert!(source.magnitude >= 6.5);
|
||||
assert!(stations.len() >= 50);
|
||||
assert!(!model.layers.is_empty());
|
||||
assert!(config.duration > 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_local_test_scenario() {
|
||||
let (source, stations, model, config) = local_test_scenario();
|
||||
|
||||
assert!(source.magnitude < 6.0);
|
||||
assert!(stations.len() < 20);
|
||||
assert!(config.dt >= 0.01); // Not too fine for testing
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cascadia_scenario() {
|
||||
let (source, _stations, model, config) = cascadia_scenario();
|
||||
|
||||
assert!(source.magnitude >= 7.5);
|
||||
assert_eq!(model.name, "Cascadia Subduction Zone");
|
||||
assert!(config.max_frequency >= 10.0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user