From bde5db0e521a89d6d89a53b68770910268279624 Mon Sep 17 00:00:00 2001 From: Mahmoud Emad Date: Sat, 17 May 2025 11:12:09 +0300 Subject: [PATCH 01/11] feat: Support incremental mode: - Support incremental mode in heromodels - Updated the example to refelct the changes - Updated the tests to reflect the changes --- .gitignore | 19 + adapter_macros/Cargo.lock | 507 ++++++++++++++++++ heromodels-derive/src/lib.rs | 2 +- heromodels/examples/basic_user_example.rs | 183 +++++-- heromodels/examples/calendar_example/main.rs | 15 +- heromodels/examples/finance_example/main.rs | 66 +-- .../governance_proposal_example/main.rs | 50 +- heromodels/src/models/calendar/calendar.rs | 6 +- heromodels/src/models/core/comment.rs | 5 +- heromodels/src/models/finance/account.rs | 21 +- heromodels/src/models/finance/asset.rs | 2 +- heromodels/src/models/finance/marketplace.rs | 2 +- heromodels/src/models/governance/proposal.rs | 24 +- heromodels/src/models/userexample/user.rs | 5 +- heromodels_core/src/lib.rs | 13 +- rhai_client_macros/Cargo.lock | 290 ++++++++++ 16 files changed, 1074 insertions(+), 136 deletions(-) create mode 100644 adapter_macros/Cargo.lock create mode 100644 rhai_client_macros/Cargo.lock diff --git a/.gitignore b/.gitignore index 8707c7c..c748bf6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,22 @@ target/ *.wasm herovm_build/ test_db +<<<<<<< Updated upstream +======= + +# Node.js +**/node_modules/ +**/dist/ +**/*.log +**/package-lock.json + +# TypeScript +**/*.js.map +**/*.d.ts +**/*.tsbuildinfo +rhaiinterface/client/**/*.js +rhaiinterface/server/**/*.js +!rhaiinterface/server/examples/**/client.js +!rhaiinterface/server/examples/webpack.config.js +.vscode +>>>>>>> Stashed changes diff --git a/adapter_macros/Cargo.lock b/adapter_macros/Cargo.lock new file mode 100644 index 0000000..21dbee3 --- /dev/null +++ b/adapter_macros/Cargo.lock @@ -0,0 +1,507 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adapter_macros" +version = "0.1.0" +dependencies = [ + "chrono", + "rhai", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "const-random", + "getrandom 0.3.3", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "cc" +version = "1.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rhai" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce4d759a4729a655ddfdbb3ff6e77fb9eadd902dae12319455557796e435d2a6" +dependencies = [ + "ahash", + "bitflags", + "instant", + "num-traits", + "once_cell", + "rhai_codegen", + "smallvec", + "smartstring", + "thin-vec", +] + +[[package]] +name = "rhai_codegen" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "smartstring" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" +dependencies = [ + "autocfg", + "static_assertions", + "version_check", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thin-vec" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46ec44dc15085cea82cf9c78f85a9114c463a369786585ad2882d1ff0b0acf40" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-result" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b895b5356fc36103d0f64dd1e94dfa7ac5633f1c9dd6e80fe9ec4adef69e09d" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a7ab927b2637c19b3dbe0965e75d8f2d30bdd697a1516191cad2ec4df8fb28a" +dependencies = [ + "windows-link", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/heromodels-derive/src/lib.rs b/heromodels-derive/src/lib.rs index 94c62a5..0d0930b 100644 --- a/heromodels-derive/src/lib.rs +++ b/heromodels-derive/src/lib.rs @@ -130,7 +130,7 @@ pub fn model(_attr: TokenStream, item: TokenStream) -> TokenStream { } fn get_id(&self) -> u32 { - self.base_data.id + self.base_data.id.unwrap_or(0) } fn base_data_mut(&mut self) -> &mut heromodels_core::BaseModelData { diff --git a/heromodels/examples/basic_user_example.rs b/heromodels/examples/basic_user_example.rs index aa3c8a2..2607492 100644 --- a/heromodels/examples/basic_user_example.rs +++ b/heromodels/examples/basic_user_example.rs @@ -3,6 +3,29 @@ use heromodels::models::userexample::user::user_index::{is_active, username}; use heromodels::models::{Comment, User}; use heromodels_core::Model; +// Helper function to print user details +fn print_user_details(user: &User) { + println!("\n--- User Details ---"); + println!("ID: {}", user.get_id()); + println!("Username: {}", user.username); + println!("Email: {}", user.email); + println!("Full Name: {}", user.full_name); + println!("Active: {}", user.is_active); + println!("Created At: {}", user.base_data.created_at); + println!("Modified At: {}", user.base_data.modified_at); + println!("Comments: {:?}", user.base_data.comments); +} + +// Helper function to print comment details +fn print_comment_details(comment: &Comment) { + println!("\n--- Comment Details ---"); + println!("ID: {}", comment.get_id()); + println!("User ID: {}", comment.user_id); + println!("Content: {}", comment.content); + println!("Created At: {}", comment.base_data.created_at); + println!("Modified At: {}", comment.base_data.modified_at); +} + fn main() { // Create a new DB instance in /tmp/ourdb, and reset before every run let db = heromodels::db::hero::OurDB::new("/tmp/ourdb", true).expect("Can create DB"); @@ -10,50 +33,75 @@ fn main() { println!("Hero Models - Basic Usage Example"); println!("================================"); - // Create a new user using the fluent interface - let user = User::new(1) + // Create users with different ID configurations + + // User 1: With explicit ID + let user1 = User::new(Some(1)) .username("johndoe") .email("john.doe@example.com") .full_name("John Doe") .is_active(false) .build(); - let user2 = User::new(2) + + // User 2: With auto-generated ID + let user2 = User::new(None) .username("janesmith") .email("jane.smith@example.com") .full_name("Jane Smith") .is_active(true) .build(); - let user3 = User::new(3) + + // User 3: With explicit ID + let user3 = User::new(Some(3)) .username("willism") .email("willis.masters@example.com") .full_name("Willis Masters") .is_active(true) .build(); - let user4 = User::new(4) + + // User 4: With explicit ID + let user4 = User::new(Some(4)) .username("carrols") .email("carrol.smith@example.com") .full_name("Carrol Smith") .is_active(false) .build(); - db.collection() - .expect("can open user collection") - .set(&user) - .expect("can set user"); - db.collection() - .expect("can open user collection") - .set(&user2) - .expect("can set user"); - db.collection() - .expect("can open user collection") - .set(&user3) - .expect("can set user"); - db.collection() - .expect("can open user collection") - .set(&user4) - .expect("can set user"); + // Save all users to database + db.collection().expect("can open user collection").set(&user1).expect("can set user"); + db.collection().expect("can open user collection").set(&user2).expect("can set user"); + db.collection().expect("can open user collection").set(&user3).expect("can set user"); + db.collection().expect("can open user collection").set(&user4).expect("can set user"); - // Perform an indexed lookup on the Username + // Retrieve all users from database + let db_user1 = db.collection::().expect("can open user collection") + .get_by_id(user1.get_id()).expect("can load user").expect("user should exist"); + let db_user2 = db.collection::().expect("can open user collection") + .get_by_id(user2.get_id()).expect("can load user").expect("user should exist"); + let db_user3 = db.collection::().expect("can open user collection") + .get_by_id(user3.get_id()).expect("can load user").expect("user should exist"); + let db_user4 = db.collection::().expect("can open user collection") + .get_by_id(user4.get_id()).expect("can load user").expect("user should exist"); + + // Print all users retrieved from database + println!("\n--- Users Retrieved from Database ---"); + println!("\n1. User with explicit ID (1):"); + print_user_details(&db_user1); + + println!("\n2. User with auto-generated ID:"); + print_user_details(&db_user2); + + println!("\n3. User with explicit ID (3):"); + print_user_details(&db_user3); + + println!("\n4. User with explicit ID (4):"); + print_user_details(&db_user4); + + // Demonstrate different ways to retrieve users from the database + + // 1. Retrieve by username index + println!("\n--- Retrieving Users by Different Methods ---"); + println!("\n1. By Username Index:"); let stored_users = db .collection::() .expect("can open user collection") @@ -61,72 +109,105 @@ fn main() { .expect("can load stored user"); assert_eq!(stored_users.len(), 1); - let stored_user = &stored_users[0]; + print_user_details(&stored_users[0]); - assert_eq!(user.username, stored_user.username); - assert_eq!(user.email, stored_user.email); - assert_eq!(user.is_active, stored_user.is_active); - assert_eq!(user.full_name, stored_user.full_name); - - // Load all active users using the IsActive field index - // TODO: expand Index type so it defines the type of the key + // 2. Retrieve by active status + println!("\n2. By Active Status (Active = true):"); let active_users = db .collection::() .expect("can open user collection") .get::(&true) .expect("can load stored users"); - // We should have 2 active users - assert_eq!(active_users.len(), 2); - // Now remove a user + assert_eq!(active_users.len(), 2); + for (i, active_user) in active_users.iter().enumerate() { + print_user_details(active_user); + } + + // 3. Delete a user and show the updated results + println!("\n3. After Deleting a User:"); db.collection::() .expect("can open user collection") .delete_by_id(active_users[0].get_id()) .expect("can delete existing user"); - // Load the active users again, should be 1 left + // Show remaining active users let active_users = db .collection::() .expect("can open user collection") .get::(&true) .expect("can load stored users"); + + println!(" a. Remaining Active Users:"); assert_eq!(active_users.len(), 1); - // And verify we still have 2 inactive users + for (i, active_user) in active_users.iter().enumerate() { + print_user_details(active_user); + } + + // Show inactive users let inactive_users = db .collection::() .expect("can open user collection") .get::(&false) .expect("can load stored users"); - assert_eq!(inactive_users.len(), 2); - println!("Created user: {:?}", user); - println!("User ID: {}", user.get_id()); + println!(" b. Inactive Users:"); + assert_eq!(inactive_users.len(), 2); + for (i, inactive_user) in inactive_users.iter().enumerate() { + print_user_details(inactive_user); + } + + println!("\n--- User Model Information ---"); println!("User DB Prefix: {}", User::db_prefix()); - // Create a comment for the user - let comment = Comment::new(5) - .user_id(1) // commenter's user ID + // Demonstrate comment creation and association with a user + println!("\n--- Working with Comments ---"); + + // 1. Create and save a comment + println!("\n1. Creating a Comment:"); + let comment = Comment::new(None) + .user_id(db_user1.get_id()) // commenter's user ID .content("This is a comment on the user") .build(); db.collection() - .expect("can open commen collection") + .expect("can open comment collection") .set(&comment) .expect("can set comment"); - let stored_comment = db + // 2. Retrieve the comment from database + let db_comment = db .collection::() .expect("can open comment collection") - .get_by_id(5) - .expect("can load stored comment"); + .get_by_id(comment.get_id()) + .expect("can load comment") + .expect("comment should exist"); - assert!(stored_comment.is_some()); - let stored_comment = stored_comment.unwrap(); + println!(" a. Comment Retrieved from Database:"); + print_comment_details(&db_comment); - assert_eq!(comment.get_id(), stored_comment.get_id()); - assert_eq!(comment.content, stored_comment.content); + // 3. Associate the comment with a user + println!("\n2. Associating Comment with User:"); + let mut updated_user = db_user1.clone(); + updated_user.base_data.add_comment(db_comment.get_id()); - println!("\nCreated comment: {:?}", comment); - println!("Comment ID: {}", comment.get_id()); + db.collection::() + .expect("can open user collection") + .set(&updated_user) + .expect("can set updated user"); + + // 4. Retrieve the updated user + let user_with_comment = db + .collection::() + .expect("can open user collection") + .get_by_id(updated_user.get_id()) + .expect("can load user") + .expect("user should exist"); + + println!(" a. User with Associated Comment:"); + print_user_details(&user_with_comment); + + println!("\n--- Model Information ---"); + println!("User DB Prefix: {}", User::db_prefix()); println!("Comment DB Prefix: {}", Comment::db_prefix()); } diff --git a/heromodels/examples/calendar_example/main.rs b/heromodels/examples/calendar_example/main.rs index b83aa9d..4139f94 100644 --- a/heromodels/examples/calendar_example/main.rs +++ b/heromodels/examples/calendar_example/main.rs @@ -40,7 +40,7 @@ fn main() { .description("Brainstorming session for new project features.") .add_attendee(attendee1.clone()) .add_attendee(attendee3.clone()); - + let event3_for_calendar2 = Event::new( "event_gamma".to_string(), "Client Call", @@ -50,12 +50,15 @@ fn main() { // --- Create Calendars --- // Note: Calendar::new directly returns Calendar, no separate .build() step like the user example. - let calendar1 = Calendar::new(1, "Work Calendar") + + // Create a calendar with auto-generated ID + let calendar1 = Calendar::new(None, "Work Calendar") .description("Calendar for all work-related events.") .add_event(event1.clone()) .add_event(event2.clone()); - let calendar2 = Calendar::new(2, "Personal Calendar") + // Create a calendar with explicit ID + let calendar2 = Calendar::new(Some(2), "Personal Calendar") .add_event(event3_for_calendar2.clone()); @@ -72,7 +75,7 @@ fn main() { let stored_calendar1_opt = cal_collection.get_by_id(calendar1.get_id()).expect("can try to load calendar1"); assert!(stored_calendar1_opt.is_some(), "Calendar1 should be found in DB"); let mut stored_calendar1 = stored_calendar1_opt.unwrap(); - + println!("\nRetrieved calendar1 from DB: Name - '{}', Events count: {}", stored_calendar1.name, stored_calendar1.events.len()); assert_eq!(stored_calendar1.name, "Work Calendar"); assert_eq!(stored_calendar1.events.len(), 2); @@ -87,7 +90,7 @@ fn main() { println!("Rescheduling event '{}'...", event_to_update.title); event_to_update.reschedule(new_start_time, new_end_time) }); - + let rescheduled_event = stored_calendar1.events.iter().find(|e| e.id == event_id_to_reschedule) .expect("Rescheduled event should exist"); assert_eq!(rescheduled_event.start_time, new_start_time); @@ -123,7 +126,7 @@ fn main() { println!("\nDeleted calendar2 (ID: {}) from DB.", calendar2.get_id()); println!("Calendar model DB Prefix: {}", Calendar::db_prefix()); - + println!("\nExample finished. DB stored at {}", db_path); println!("To clean up, you can manually delete the directory: {}", db_path); } diff --git a/heromodels/examples/finance_example/main.rs b/heromodels/examples/finance_example/main.rs index 716262c..acae3cc 100644 --- a/heromodels/examples/finance_example/main.rs +++ b/heromodels/examples/finance_example/main.rs @@ -10,9 +10,9 @@ fn main() { // --- PART 1: ACCOUNTS AND ASSETS --- println!("=== ACCOUNTS AND ASSETS ===\n"); - // Create a new account + // Create a new account with auto-generated ID let mut account = Account::new( - 1, // id + None, // id (auto-generated) "Main ETH Wallet", // name 1001, // user_id "My primary Ethereum wallet", // description @@ -28,8 +28,9 @@ fn main() { println!(""); // Create some assets + // Asset with auto-generated ID let eth_asset = Asset::new( - 101, // id + None, // id (auto-generated) "Ethereum", // name "Native ETH cryptocurrency", // description 1.5, // amount @@ -38,8 +39,9 @@ fn main() { 18, // decimals ); + // Assets with explicit IDs let usdc_asset = Asset::new( - 102, // id + Some(102), // id "USDC", // name "USD Stablecoin on Ethereum", // description 1000.0, // amount @@ -49,7 +51,7 @@ fn main() { ); let nft_asset = Asset::new( - 103, // id + Some(103), // id "CryptoPunk #1234", // name "Rare digital collectible", // description 1.0, // amount @@ -95,9 +97,9 @@ fn main() { // --- PART 2: MARKETPLACE LISTINGS --- println!("\n=== MARKETPLACE LISTINGS ===\n"); - // Create a fixed price listing + // Create a fixed price listing with auto-generated ID let mut fixed_price_listing = Listing::new( - 201, // id + None, // id (auto-generated) "1000 USDC for Sale", // title "Selling 1000 USDC tokens at fixed price", // description "102", // asset_id (referencing the USDC asset) @@ -131,9 +133,9 @@ fn main() { Err(e) => println!("Error completing sale: {}", e), } - // Create an auction listing for the NFT + // Create an auction listing for the NFT with explicit ID let mut auction_listing = Listing::new( - 202, // id + Some(202), // id (explicit) "CryptoPunk #1234 Auction", // title "Rare CryptoPunk NFT for auction", // description "103", // asset_id (referencing the NFT asset) @@ -176,7 +178,7 @@ fn main() { // Add bids to the auction println!("Adding Bids to Auction:"); - + // Using clone() to avoid ownership issues with match expressions match auction_listing.clone().add_bid(bid1) { Ok(updated_listing) => { @@ -185,7 +187,7 @@ fn main() { }, Err(e) => println!("Error adding bid: {}", e), } - + match auction_listing.clone().add_bid(bid2) { Ok(updated_listing) => { auction_listing = updated_listing; @@ -193,7 +195,7 @@ fn main() { }, Err(e) => println!("Error adding bid: {}", e), } - + match auction_listing.clone().add_bid(bid3) { Ok(updated_listing) => { auction_listing = updated_listing; @@ -204,14 +206,14 @@ fn main() { println!("\nCurrent Auction Status:"); println!("Current Price: {} {}", auction_listing.price, auction_listing.currency); - + if let Some(highest_bid) = auction_listing.highest_bid() { - println!("Highest Bid: {} {} from User {}", - highest_bid.amount, - highest_bid.currency, + println!("Highest Bid: {} {} from User {}", + highest_bid.amount, + highest_bid.currency, highest_bid.bidder_id); } - + println!("Total Bids: {}", auction_listing.bids.len()); println!(""); @@ -224,13 +226,13 @@ fn main() { println!("Winner: User {}", auction_listing.buyer_id.as_ref().unwrap()); println!("Winning Bid: {} {}", auction_listing.sale_price.as_ref().unwrap(), auction_listing.currency); println!(""); - + println!("Final Bid Statuses:"); for bid in &auction_listing.bids { - println!("- User {}: {} {} (Status: {:?})", - bid.bidder_id, - bid.amount, - bid.currency, + println!("- User {}: {} {} (Status: {:?})", + bid.bidder_id, + bid.amount, + bid.currency, bid.status); } println!(""); @@ -238,9 +240,9 @@ fn main() { Err(e) => println!("Error completing auction: {}", e), } - // Create an exchange listing + // Create an exchange listing with auto-generated ID let exchange_listing = Listing::new( - 203, // id + None, // id (auto-generated) "ETH for BTC Exchange", // title "Looking to exchange ETH for BTC", // description "101", // asset_id (referencing the ETH asset) @@ -262,9 +264,9 @@ fn main() { // --- PART 3: DEMONSTRATING EDGE CASES --- println!("\n=== EDGE CASES AND VALIDATIONS ===\n"); - // Create a new auction listing for edge case testing + // Create a new auction listing for edge case testing with explicit ID let test_auction = Listing::new( - 205, // id + Some(205), // id (explicit) "Test Auction", // title "For testing edge cases", // description "101", // asset_id @@ -277,7 +279,7 @@ fn main() { vec![], // tags None::, // image_url ); - + // Try to add a bid that's too low let low_bid = Bid::new( test_auction.base_data.id.to_string(), // listing_id @@ -285,7 +287,7 @@ fn main() { 5.0, // amount (lower than starting price) "ETH", // currency ); - + println!("Attempting to add a bid that's too low (5.0 ETH):"); match test_auction.add_bid(low_bid) { Ok(_) => println!("Bid accepted (This shouldn't happen)"), @@ -301,9 +303,9 @@ fn main() { } println!(""); - // Create a listing that will expire + // Create a listing that will expire with auto-generated ID let mut expiring_listing = Listing::new( - 204, // id + None, // id (auto-generated) "About to Expire", // title "This listing will expire immediately", // description "101", // asset_id @@ -316,10 +318,10 @@ fn main() { vec![], // tags None::, // image_url ); - + println!("Created Expiring Listing: '{}' (ID: {})", expiring_listing.title, expiring_listing.base_data.id); println!("Initial Status: {:?}", expiring_listing.status); - + // Check expiration expiring_listing = expiring_listing.check_expiration(); println!("After Checking Expiration: {:?}", expiring_listing.status); diff --git a/heromodels/examples/governance_proposal_example/main.rs b/heromodels/examples/governance_proposal_example/main.rs index 9072c1e..bfdcfe2 100644 --- a/heromodels/examples/governance_proposal_example/main.rs +++ b/heromodels/examples/governance_proposal_example/main.rs @@ -6,9 +6,9 @@ use heromodels::models::governance::{Proposal, ProposalStatus, VoteEventStatus}; fn main() { println!("Governance Proposal Model Example\n"); - // Create a new proposal + // Create a new proposal with auto-generated ID let mut proposal = Proposal::new( - 1, // id + None, // id (auto-generated) "user_creator_123", // creator_id "Community Fund Allocation for Q3", // title "Proposal to allocate funds for community projects in the third quarter.", // description @@ -33,18 +33,18 @@ fn main() { // Simulate casting votes println!("Simulating Votes..."); - // User 1 votes for 'Approve Allocation' with 100 shares - proposal = proposal.cast_vote(101, 1, 1, 100); - // User 2 votes for 'Reject Allocation' with 50 shares - proposal = proposal.cast_vote(102, 2, 2, 50); - // User 3 votes for 'Approve Allocation' with 75 shares - proposal = proposal.cast_vote(103, 3, 1, 75); - // User 4 abstains with 20 shares - proposal = proposal.cast_vote(104, 4, 3, 20); + // User 1 votes for 'Approve Allocation' with 100 shares (with explicit ballot ID) + proposal = proposal.cast_vote(Some(101), 1, 1, 100); + // User 2 votes for 'Reject Allocation' with 50 shares (with explicit ballot ID) + proposal = proposal.cast_vote(Some(102), 2, 2, 50); + // User 3 votes for 'Approve Allocation' with 75 shares (with auto-generated ballot ID) + proposal = proposal.cast_vote(None, 3, 1, 75); + // User 4 abstains with 20 shares (with auto-generated ballot ID) + proposal = proposal.cast_vote(None, 4, 3, 20); // User 5 attempts to vote for a non-existent option (should be handled gracefully) - proposal = proposal.cast_vote(105, 5, 99, 10); + proposal = proposal.cast_vote(Some(105), 5, 99, 10); // User 1 tries to vote again (not explicitly prevented by current model, but could be a future enhancement) - // proposal = proposal.cast_vote(106, 1, 1, 10); + // proposal = proposal.cast_vote(Some(106), 1, 1, 10); println!("\nVote Counts After Simulation:"); for option in &proposal.options { @@ -53,7 +53,7 @@ fn main() { println!("\nBallots Cast:"); for ballot in &proposal.ballots { - println!("- Ballot ID: {}, User ID: {}, Option ID: {}, Shares: {}", + println!("- Ballot ID: {}, User ID: {}, Option ID: {}, Shares: {}", ballot.base_data.id, ballot.user_id, ballot.vote_option_id, ballot.shares_count); } println!(""); @@ -68,7 +68,7 @@ fn main() { // Attempt to cast a vote after closing (should be handled) println!("\nAttempting to cast vote after voting is closed..."); - proposal = proposal.cast_vote(107, 6, 1, 25); + proposal = proposal.cast_vote(None, 6, 1, 25); // Final proposal state println!("\nFinal Proposal State:"); @@ -83,24 +83,24 @@ fn main() { // Example of a private proposal (not fully implemented in cast_vote eligibility yet) let mut private_proposal = Proposal::new( - 2, - "user_admin_001", - "Internal Team Restructure Vote", - "Vote on proposed internal team changes.", - Utc::now(), + Some(2), // explicit ID + "user_admin_001", + "Internal Team Restructure Vote", + "Vote on proposed internal team changes.", + Utc::now(), Utc::now() + Duration::days(7) ); private_proposal.private_group = Some(vec![10, 20, 30]); // Only users 10, 20, 30 can vote private_proposal = private_proposal.add_option(1, "Accept Restructure"); private_proposal = private_proposal.add_option(2, "Reject Restructure"); - + println!("\nCreated Private Proposal: '{}'", private_proposal.title); println!("Eligible Voters (Group): {:?}", private_proposal.private_group); - // User 10 (eligible) votes - private_proposal = private_proposal.cast_vote(201, 10, 1, 100); - // User 40 (ineligible) tries to vote - private_proposal = private_proposal.cast_vote(202, 40, 1, 50); - + // User 10 (eligible) votes with explicit ballot ID + private_proposal = private_proposal.cast_vote(Some(201), 10, 1, 100); + // User 40 (ineligible) tries to vote with auto-generated ballot ID + private_proposal = private_proposal.cast_vote(None, 40, 1, 50); + println!("Private Proposal Vote Counts:"); for option in &private_proposal.options { println!(" - {}: {} (Votes: {})", option.id, option.text, option.count); diff --git a/heromodels/src/models/calendar/calendar.rs b/heromodels/src/models/calendar/calendar.rs index 4549f7c..b42ddbd 100644 --- a/heromodels/src/models/calendar/calendar.rs +++ b/heromodels/src/models/calendar/calendar.rs @@ -140,7 +140,11 @@ pub struct Calendar { impl Calendar { /// Creates a new calendar - pub fn new(id: u32, name: impl ToString) -> Self { + /// + /// # Arguments + /// * `id` - Optional ID for the calendar. If None, the ID will be auto-generated. + /// * `name` - Name of the calendar + pub fn new(id: Option, name: impl ToString) -> Self { Self { base_data: BaseModelData::new(id), name: name.to_string(), diff --git a/heromodels/src/models/core/comment.rs b/heromodels/src/models/core/comment.rs index 110383a..a43770f 100644 --- a/heromodels/src/models/core/comment.rs +++ b/heromodels/src/models/core/comment.rs @@ -14,7 +14,10 @@ pub struct Comment { impl Comment { /// Create a new comment - pub fn new(id: u32) -> Self { + /// + /// # Arguments + /// * `id` - Optional ID for the comment. If None, the ID will be auto-generated. + pub fn new(id: Option) -> Self { Self { base_data: BaseModelData::new(id), user_id: 0, diff --git a/heromodels/src/models/finance/account.rs b/heromodels/src/models/finance/account.rs index b3bc373..14447ce 100644 --- a/heromodels/src/models/finance/account.rs +++ b/heromodels/src/models/finance/account.rs @@ -22,13 +22,22 @@ pub struct Account { impl Account { /// Create a new account + /// + /// # Arguments + /// * `id` - Optional ID for the account. If None, the ID will be auto-generated. + /// * `name` - Name of the account + /// * `user_id` - ID of the user who owns the account + /// * `description` - Description of the account + /// * `ledger` - Ledger/blockchain where the account is located + /// * `address` - Address of the account on the blockchain + /// * `pubkey` - Public key pub fn new( - id: u32, - name: impl ToString, - user_id: u32, - description: impl ToString, - ledger: impl ToString, - address: impl ToString, + id: Option, + name: impl ToString, + user_id: u32, + description: impl ToString, + ledger: impl ToString, + address: impl ToString, pubkey: impl ToString ) -> Self { Self { diff --git a/heromodels/src/models/finance/asset.rs b/heromodels/src/models/finance/asset.rs index 98826f6..3da924a 100644 --- a/heromodels/src/models/finance/asset.rs +++ b/heromodels/src/models/finance/asset.rs @@ -35,7 +35,7 @@ pub struct Asset { impl Asset { /// Create a new asset pub fn new( - id: u32, + id: Option, name: impl ToString, description: impl ToString, amount: f64, diff --git a/heromodels/src/models/finance/marketplace.rs b/heromodels/src/models/finance/marketplace.rs index 8153052..4104f05 100644 --- a/heromodels/src/models/finance/marketplace.rs +++ b/heromodels/src/models/finance/marketplace.rs @@ -113,7 +113,7 @@ pub struct Listing { impl Listing { /// Create a new listing pub fn new( - id: u32, + id: Option, title: impl ToString, description: impl ToString, asset_id: impl ToString, diff --git a/heromodels/src/models/governance/proposal.rs b/heromodels/src/models/governance/proposal.rs index 152f247..748421c 100644 --- a/heromodels/src/models/governance/proposal.rs +++ b/heromodels/src/models/governance/proposal.rs @@ -75,7 +75,14 @@ pub struct Ballot { } impl Ballot { - pub fn new(id: u32, user_id: u32, vote_option_id: u8, shares_count: i64) -> Self { + /// Create a new ballot + /// + /// # Arguments + /// * `id` - Optional ID for the ballot. If None, the ID will be auto-generated. + /// * `user_id` - ID of the user who cast this ballot + /// * `vote_option_id` - ID of the vote option chosen + /// * `shares_count` - Number of shares/tokens/voting power + pub fn new(id: Option, user_id: u32, vote_option_id: u8, shares_count: i64) -> Self { Self { base_data: BaseModelData::new(id), user_id, @@ -107,7 +114,16 @@ pub struct Proposal { } impl Proposal { - pub fn new(id: u32, creator_id: impl ToString, title: impl ToString, description: impl ToString, vote_start_date: DateTime, vote_end_date: DateTime) -> Self { + /// Create a new proposal + /// + /// # Arguments + /// * `id` - Optional ID for the proposal. If None, the ID will be auto-generated. + /// * `creator_id` - ID of the user who created the proposal + /// * `title` - Title of the proposal + /// * `description` - Description of the proposal + /// * `vote_start_date` - Date when voting starts + /// * `vote_end_date` - Date when voting ends + pub fn new(id: Option, creator_id: impl ToString, title: impl ToString, description: impl ToString, vote_start_date: DateTime, vote_end_date: DateTime) -> Self { Self { base_data: BaseModelData::new(id), creator_id: creator_id.to_string(), @@ -128,8 +144,8 @@ impl Proposal { self.options.push(new_option); self } - - pub fn cast_vote(mut self, ballot_id: u32, user_id: u32, chosen_option_id: u8, shares: i64) -> Self { + + pub fn cast_vote(mut self, ballot_id: Option, user_id: u32, chosen_option_id: u8, shares: i64) -> Self { if self.vote_status != VoteEventStatus::Open { eprintln!("Voting is not open for proposal '{}'", self.title); return self; diff --git a/heromodels/src/models/userexample/user.rs b/heromodels/src/models/userexample/user.rs index dc41746..8455aa3 100644 --- a/heromodels/src/models/userexample/user.rs +++ b/heromodels/src/models/userexample/user.rs @@ -27,7 +27,10 @@ pub struct User { impl User { /// Create a new user - pub fn new(id: u32) -> Self { + /// + /// # Arguments + /// * `id` - Optional ID for the user. If None, the ID will be auto-generated. + pub fn new(id: Option) -> Self { Self { base_data: BaseModelData::new(id), username: String::new(), diff --git a/heromodels_core/src/lib.rs b/heromodels_core/src/lib.rs index 9bab2c7..db63ccf 100644 --- a/heromodels_core/src/lib.rs +++ b/heromodels_core/src/lib.rs @@ -59,13 +59,14 @@ pub trait Model: } /// Get the unique ID for this model + /// Returns 0 if the ID is None fn get_id(&self) -> u32; /// Get a mutable reference to the base_data field fn base_data_mut(&mut self) -> &mut BaseModelData; /// Set the ID for this model - fn id(mut self, id: u32) -> Self + fn id(mut self, id: Option) -> Self where Self: Sized, { @@ -98,7 +99,7 @@ pub trait Index { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BaseModelData { /// Unique incremental ID per circle - pub id: u32, + pub id: Option, /// Unix epoch timestamp for creation time pub created_at: i64, @@ -112,7 +113,7 @@ pub struct BaseModelData { impl BaseModelData { /// Create a new BaseModelData instance - pub fn new(id: u32) -> Self { + pub fn new(id: Option) -> Self { let now = chrono::Utc::now().timestamp(); Self { id, @@ -123,7 +124,7 @@ impl BaseModelData { } /// Create a new BaseModelDataBuilder - pub fn builder(id: u32) -> BaseModelDataBuilder { + pub fn builder(id: Option) -> BaseModelDataBuilder { BaseModelDataBuilder::new(id) } @@ -147,7 +148,7 @@ impl BaseModelData { /// Builder for BaseModelData pub struct BaseModelDataBuilder { - id: u32, + id: Option, created_at: Option, modified_at: Option, comments: Vec, @@ -155,7 +156,7 @@ pub struct BaseModelDataBuilder { impl BaseModelDataBuilder { /// Create a new BaseModelDataBuilder - pub fn new(id: u32) -> Self { + pub fn new(id: Option) -> Self { Self { id, created_at: None, diff --git a/rhai_client_macros/Cargo.lock b/rhai_client_macros/Cargo.lock new file mode 100644 index 0000000..3e7187d --- /dev/null +++ b/rhai_client_macros/Cargo.lock @@ -0,0 +1,290 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "const-random", + "getrandom 0.3.3", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rhai" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce4d759a4729a655ddfdbb3ff6e77fb9eadd902dae12319455557796e435d2a6" +dependencies = [ + "ahash", + "bitflags", + "instant", + "num-traits", + "once_cell", + "rhai_codegen", + "smallvec", + "smartstring", + "thin-vec", +] + +[[package]] +name = "rhai_client_macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "rhai", + "syn", +] + +[[package]] +name = "rhai_codegen" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "smartstring" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" +dependencies = [ + "autocfg", + "static_assertions", + "version_check", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thin-vec" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] From a6768542518f55e1712492e6e7a4fc3663f8581a Mon Sep 17 00:00:00 2001 From: Mahmoud Emad Date: Sat, 17 May 2025 13:00:05 +0300 Subject: [PATCH 02/11] fix: Use incremental ID --- heromodels-derive/src/lib.rs | 2 +- heromodels/examples/basic_user_example.rs | 66 +++++++++++--------- heromodels/examples/simple_model_example.rs | 2 +- heromodels/src/db.rs | 5 +- heromodels/src/db/hero.rs | 61 +++++++++++++++--- heromodels/src/models/calendar/calendar.rs | 7 +-- heromodels/src/models/core/comment.rs | 9 +-- heromodels/src/models/finance/account.rs | 6 +- heromodels/src/models/finance/asset.rs | 11 ++-- heromodels/src/models/finance/marketplace.rs | 33 +++++----- heromodels/src/models/governance/proposal.rs | 18 +++--- heromodels/src/models/userexample/user.rs | 9 +-- heromodels_core/src/lib.rs | 36 +++++------ 13 files changed, 149 insertions(+), 116 deletions(-) diff --git a/heromodels-derive/src/lib.rs b/heromodels-derive/src/lib.rs index 0d0930b..94c62a5 100644 --- a/heromodels-derive/src/lib.rs +++ b/heromodels-derive/src/lib.rs @@ -130,7 +130,7 @@ pub fn model(_attr: TokenStream, item: TokenStream) -> TokenStream { } fn get_id(&self) -> u32 { - self.base_data.id.unwrap_or(0) + self.base_data.id } fn base_data_mut(&mut self) -> &mut heromodels_core::BaseModelData { diff --git a/heromodels/examples/basic_user_example.rs b/heromodels/examples/basic_user_example.rs index 2607492..e625a2d 100644 --- a/heromodels/examples/basic_user_example.rs +++ b/heromodels/examples/basic_user_example.rs @@ -33,68 +33,73 @@ fn main() { println!("Hero Models - Basic Usage Example"); println!("================================"); - // Create users with different ID configurations + // Create users with auto-generated IDs - // User 1: With explicit ID - let user1 = User::new(Some(1)) + // User 1 + let user1 = User::new() .username("johndoe") .email("john.doe@example.com") .full_name("John Doe") .is_active(false) .build(); - // User 2: With auto-generated ID - let user2 = User::new(None) + // User 2 + let user2 = User::new() .username("janesmith") .email("jane.smith@example.com") .full_name("Jane Smith") .is_active(true) .build(); - // User 3: With explicit ID - let user3 = User::new(Some(3)) + // User 3 + let user3 = User::new() .username("willism") .email("willis.masters@example.com") .full_name("Willis Masters") .is_active(true) .build(); - // User 4: With explicit ID - let user4 = User::new(Some(4)) + // User 4 + let user4 = User::new() .username("carrols") .email("carrol.smith@example.com") .full_name("Carrol Smith") .is_active(false) .build(); - // Save all users to database - db.collection().expect("can open user collection").set(&user1).expect("can set user"); - db.collection().expect("can open user collection").set(&user2).expect("can set user"); - db.collection().expect("can open user collection").set(&user3).expect("can set user"); - db.collection().expect("can open user collection").set(&user4).expect("can set user"); + // Save all users to database and get their assigned IDs + let user1_id = db.collection().expect("can open user collection").set(&user1).expect("can set user"); + let user2_id = db.collection().expect("can open user collection").set(&user2).expect("can set user"); + let user3_id = db.collection().expect("can open user collection").set(&user3).expect("can set user"); + let user4_id = db.collection().expect("can open user collection").set(&user4).expect("can set user"); - // Retrieve all users from database + println!("User 1 assigned ID: {}", user1_id); + println!("User 2 assigned ID: {}", user2_id); + println!("User 3 assigned ID: {}", user3_id); + println!("User 4 assigned ID: {}", user4_id); + + // Retrieve all users from database using the assigned IDs let db_user1 = db.collection::().expect("can open user collection") - .get_by_id(user1.get_id()).expect("can load user").expect("user should exist"); + .get_by_id(user1_id).expect("can load user").expect("user should exist"); let db_user2 = db.collection::().expect("can open user collection") - .get_by_id(user2.get_id()).expect("can load user").expect("user should exist"); + .get_by_id(user2_id).expect("can load user").expect("user should exist"); let db_user3 = db.collection::().expect("can open user collection") - .get_by_id(user3.get_id()).expect("can load user").expect("user should exist"); + .get_by_id(user3_id).expect("can load user").expect("user should exist"); let db_user4 = db.collection::().expect("can open user collection") - .get_by_id(user4.get_id()).expect("can load user").expect("user should exist"); + .get_by_id(user4_id).expect("can load user").expect("user should exist"); // Print all users retrieved from database println!("\n--- Users Retrieved from Database ---"); - println!("\n1. User with explicit ID (1):"); + println!("\n1. First user:"); print_user_details(&db_user1); - println!("\n2. User with auto-generated ID:"); + println!("\n2. Second user:"); print_user_details(&db_user2); - println!("\n3. User with explicit ID (3):"); + println!("\n3. Third user:"); print_user_details(&db_user3); - println!("\n4. User with explicit ID (4):"); + println!("\n4. Fourth user:"); print_user_details(&db_user4); // Demonstrate different ways to retrieve users from the database @@ -126,9 +131,11 @@ fn main() { // 3. Delete a user and show the updated results println!("\n3. After Deleting a User:"); + let user_to_delete_id = active_users[0].get_id(); + println!("Deleting user with ID: {}", user_to_delete_id); db.collection::() .expect("can open user collection") - .delete_by_id(active_users[0].get_id()) + .delete_by_id(user_to_delete_id) .expect("can delete existing user"); // Show remaining active users @@ -165,21 +172,24 @@ fn main() { // 1. Create and save a comment println!("\n1. Creating a Comment:"); - let comment = Comment::new(None) + let comment = Comment::new() .user_id(db_user1.get_id()) // commenter's user ID .content("This is a comment on the user") .build(); - db.collection() + // Save the comment and get its assigned ID + let comment_id = db.collection() .expect("can open comment collection") .set(&comment) .expect("can set comment"); - // 2. Retrieve the comment from database + println!("Comment assigned ID: {}", comment_id); + + // 2. Retrieve the comment from database using the assigned ID let db_comment = db .collection::() .expect("can open comment collection") - .get_by_id(comment.get_id()) + .get_by_id(comment_id) .expect("can load comment") .expect("comment should exist"); diff --git a/heromodels/examples/simple_model_example.rs b/heromodels/examples/simple_model_example.rs index c24f9b2..a5394b5 100644 --- a/heromodels/examples/simple_model_example.rs +++ b/heromodels/examples/simple_model_example.rs @@ -19,7 +19,7 @@ fn main() { println!("SimpleUser DB Prefix: {}", SimpleUser::db_prefix()); let user = SimpleUser { - base_data: BaseModelData::new(1), + base_data: BaseModelData::new(), login: "johndoe".to_string(), full_name: "John Doe".to_string(), }; diff --git a/heromodels/src/db.rs b/heromodels/src/db.rs index a593793..70ed851 100644 --- a/heromodels/src/db.rs +++ b/heromodels/src/db.rs @@ -32,8 +32,9 @@ where /// Get an object from its ID. This does not use an index lookup fn get_by_id(&self, id: u32) -> Result, Error>; - /// Store an item in the DB. - fn set(&self, value: &V) -> Result<(), Error>; + /// Store an item in the DB and return the assigned ID. + /// This method does not modify the original model. + fn set(&self, value: &V) -> Result>; /// Delete all items from the db with a given index. fn delete(&self, key: &Q) -> Result<(), Error> diff --git a/heromodels/src/db/hero.rs b/heromodels/src/db/hero.rs index 3d5d173..3abec38 100644 --- a/heromodels/src/db/hero.rs +++ b/heromodels/src/db/hero.rs @@ -26,7 +26,7 @@ impl OurDB { data_path.push("data"); let data_db = ourdb::OurDB::new(ourdb::OurDBConfig { - incremental_mode: false, + incremental_mode: true, path: data_path, file_size: None, keysize: None, @@ -87,7 +87,7 @@ where Self::get_ourdb_value(&mut data_db, id) } - fn set(&self, value: &M) -> Result<(), super::Error> { + fn set(&self, value: &M) -> Result> { // Before inserting the new object, check if an object with this ID already exists. If it does, we potentially need to update indices. let mut data_db = self.data.lock().expect("can lock data DB"); let old_obj: Option = Self::get_ourdb_value(&mut data_db, value.get_id())?; @@ -134,13 +134,52 @@ where } } - // set or update the object - let v = bincode::serde::encode_to_vec(value, BINCODE_CONFIG)?; + // Get the current ID let id = value.get_id(); - data_db.set(OurDBSetArgs { - id: Some(id), - data: &v, - })?; + + // If id is 0, it's a new object, so let OurDB auto-generate an ID + // Otherwise, it's an update to an existing object + let id_param = if id == 0 { None } else { Some(id) }; + + // For new objects (id == 0), we need to get the assigned ID from OurDB + // and update the model before serializing it + let assigned_id = if id == 0 { + // First, get the next ID that OurDB will assign + let next_id = data_db.get_next_id()?; + + // Create a mutable clone of the value and update its ID + // This is a bit of a hack, but we need to update the ID before serializing + let mut value_clone = value.clone(); + let base_data = value_clone.base_data_mut(); + base_data.update_id(next_id); + + // Now serialize the updated model + let v = bincode::serde::encode_to_vec(&value_clone, BINCODE_CONFIG)?; + + // Save to OurDB with the ID parameter set to None to let OurDB auto-generate the ID + let assigned_id = data_db.set(OurDBSetArgs { + id: id_param, + data: &v, + })?; + + // The assigned ID should match the next_id we got earlier + assert_eq!(assigned_id, next_id, "OurDB assigned a different ID than expected"); + + // Return the assigned ID + assigned_id + } else { + // For existing objects, just serialize and save + let v = bincode::serde::encode_to_vec(value, BINCODE_CONFIG)?; + + // Save to OurDB with the existing ID + let assigned_id = data_db.set(OurDBSetArgs { + id: id_param, + data: &v, + })?; + + // Return the existing ID + assigned_id + }; // Now add the new indices for index_key in indices_to_add { @@ -148,12 +187,14 @@ where // Load the existing id set for the index or create a new set let mut existing_ids = Self::get_tst_value::>(&mut index_db, &key)?.unwrap_or_default(); - existing_ids.insert(id); + // Use the assigned ID for new objects + existing_ids.insert(assigned_id); let encoded_ids = bincode::serde::encode_to_vec(existing_ids, BINCODE_CONFIG)?; index_db.set(&key, encoded_ids)?; } - Ok(()) + // Return the assigned ID + Ok(assigned_id) } fn delete(&self, key: &Q) -> Result<(), super::Error> diff --git a/heromodels/src/models/calendar/calendar.rs b/heromodels/src/models/calendar/calendar.rs index b42ddbd..a7eee92 100644 --- a/heromodels/src/models/calendar/calendar.rs +++ b/heromodels/src/models/calendar/calendar.rs @@ -139,14 +139,13 @@ pub struct Calendar { } impl Calendar { - /// Creates a new calendar + /// Creates a new calendar with auto-generated ID /// /// # Arguments - /// * `id` - Optional ID for the calendar. If None, the ID will be auto-generated. /// * `name` - Name of the calendar - pub fn new(id: Option, name: impl ToString) -> Self { + pub fn new(name: impl ToString) -> Self { Self { - base_data: BaseModelData::new(id), + base_data: BaseModelData::new(), name: name.to_string(), description: None, events: Vec::new(), diff --git a/heromodels/src/models/core/comment.rs b/heromodels/src/models/core/comment.rs index a43770f..28bcefd 100644 --- a/heromodels/src/models/core/comment.rs +++ b/heromodels/src/models/core/comment.rs @@ -13,13 +13,10 @@ pub struct Comment { } impl Comment { - /// Create a new comment - /// - /// # Arguments - /// * `id` - Optional ID for the comment. If None, the ID will be auto-generated. - pub fn new(id: Option) -> Self { + /// Create a new comment with auto-generated ID + pub fn new() -> Self { Self { - base_data: BaseModelData::new(id), + base_data: BaseModelData::new(), user_id: 0, content: String::new(), } diff --git a/heromodels/src/models/finance/account.rs b/heromodels/src/models/finance/account.rs index 14447ce..fe80b6d 100644 --- a/heromodels/src/models/finance/account.rs +++ b/heromodels/src/models/finance/account.rs @@ -21,10 +21,9 @@ pub struct Account { } impl Account { - /// Create a new account + /// Create a new account with auto-generated ID /// /// # Arguments - /// * `id` - Optional ID for the account. If None, the ID will be auto-generated. /// * `name` - Name of the account /// * `user_id` - ID of the user who owns the account /// * `description` - Description of the account @@ -32,7 +31,6 @@ impl Account { /// * `address` - Address of the account on the blockchain /// * `pubkey` - Public key pub fn new( - id: Option, name: impl ToString, user_id: u32, description: impl ToString, @@ -41,7 +39,7 @@ impl Account { pubkey: impl ToString ) -> Self { Self { - base_data: BaseModelData::new(id), + base_data: BaseModelData::new(), name: name.to_string(), user_id, description: description.to_string(), diff --git a/heromodels/src/models/finance/asset.rs b/heromodels/src/models/finance/asset.rs index 3da924a..9fd3195 100644 --- a/heromodels/src/models/finance/asset.rs +++ b/heromodels/src/models/finance/asset.rs @@ -33,9 +33,8 @@ pub struct Asset { } impl Asset { - /// Create a new asset + /// Create a new asset with auto-generated ID pub fn new( - id: Option, name: impl ToString, description: impl ToString, amount: f64, @@ -44,7 +43,7 @@ impl Asset { decimals: u8, ) -> Self { Self { - base_data: BaseModelData::new(id), + base_data: BaseModelData::new(), name: name.to_string(), description: description.to_string(), amount, @@ -72,14 +71,14 @@ impl Asset { if amount <= 0.0 { return Err("Transfer amount must be positive"); } - + if self.amount < amount { return Err("Insufficient balance for transfer"); } - + self.amount -= amount; target.amount += amount; - + Ok(()) } } diff --git a/heromodels/src/models/finance/marketplace.rs b/heromodels/src/models/finance/marketplace.rs index 4104f05..d236b3c 100644 --- a/heromodels/src/models/finance/marketplace.rs +++ b/heromodels/src/models/finance/marketplace.rs @@ -111,9 +111,8 @@ pub struct Listing { } impl Listing { - /// Create a new listing + /// Create a new listing with auto-generated ID pub fn new( - id: Option, title: impl ToString, description: impl ToString, asset_id: impl ToString, @@ -127,7 +126,7 @@ impl Listing { image_url: Option, ) -> Self { Self { - base_data: BaseModelData::new(id), + base_data: BaseModelData::new(), title: title.to_string(), description: description.to_string(), asset_id: asset_id.to_string(), @@ -153,32 +152,32 @@ impl Listing { if self.listing_type != ListingType::Auction { return Err("Bids can only be placed on auction listings"); } - + // Check if listing is active if self.status != ListingStatus::Active { return Err("Cannot place bid on inactive listing"); } - + // Check if bid amount is higher than current price if bid.amount <= self.price { return Err("Bid amount must be higher than current price"); } - + // Check if there are existing bids and if the new bid is higher if let Some(highest_bid) = self.highest_bid() { if bid.amount <= highest_bid.amount { return Err("Bid amount must be higher than current highest bid"); } } - + // Add the bid self.bids.push(bid); - + // Update the current price to the new highest bid if let Some(highest_bid) = self.highest_bid() { self.price = highest_bid.amount; } - + Ok(self) } @@ -195,12 +194,12 @@ impl Listing { if self.status != ListingStatus::Active { return Err("Cannot complete sale for inactive listing"); } - + self.status = ListingStatus::Sold; self.buyer_id = Some(buyer_id.to_string()); self.sale_price = Some(sale_price); self.sold_at = Some(Utc::now()); - + // If this was an auction, accept the winning bid and reject others if self.listing_type == ListingType::Auction { for bid in &mut self.bids { @@ -211,7 +210,7 @@ impl Listing { } } } - + Ok(self) } @@ -220,16 +219,16 @@ impl Listing { if self.status != ListingStatus::Active { return Err("Cannot cancel inactive listing"); } - + self.status = ListingStatus::Cancelled; - + // Cancel all active bids for bid in &mut self.bids { if bid.status == BidStatus::Active { bid.status = BidStatus::Cancelled; } } - + Ok(self) } @@ -239,7 +238,7 @@ impl Listing { if let Some(expires_at) = self.expires_at { if Utc::now() > expires_at { self.status = ListingStatus::Expired; - + // Cancel all active bids for bid in &mut self.bids { if bid.status == BidStatus::Active { @@ -249,7 +248,7 @@ impl Listing { } } } - + self } diff --git a/heromodels/src/models/governance/proposal.rs b/heromodels/src/models/governance/proposal.rs index 748421c..7e73cb3 100644 --- a/heromodels/src/models/governance/proposal.rs +++ b/heromodels/src/models/governance/proposal.rs @@ -75,16 +75,15 @@ pub struct Ballot { } impl Ballot { - /// Create a new ballot + /// Create a new ballot with auto-generated ID /// /// # Arguments - /// * `id` - Optional ID for the ballot. If None, the ID will be auto-generated. /// * `user_id` - ID of the user who cast this ballot /// * `vote_option_id` - ID of the vote option chosen /// * `shares_count` - Number of shares/tokens/voting power - pub fn new(id: Option, user_id: u32, vote_option_id: u8, shares_count: i64) -> Self { + pub fn new(user_id: u32, vote_option_id: u8, shares_count: i64) -> Self { Self { - base_data: BaseModelData::new(id), + base_data: BaseModelData::new(), user_id, vote_option_id, shares_count, @@ -114,18 +113,17 @@ pub struct Proposal { } impl Proposal { - /// Create a new proposal + /// Create a new proposal with auto-generated ID /// /// # Arguments - /// * `id` - Optional ID for the proposal. If None, the ID will be auto-generated. /// * `creator_id` - ID of the user who created the proposal /// * `title` - Title of the proposal /// * `description` - Description of the proposal /// * `vote_start_date` - Date when voting starts /// * `vote_end_date` - Date when voting ends - pub fn new(id: Option, creator_id: impl ToString, title: impl ToString, description: impl ToString, vote_start_date: DateTime, vote_end_date: DateTime) -> Self { + pub fn new(creator_id: impl ToString, title: impl ToString, description: impl ToString, vote_start_date: DateTime, vote_end_date: DateTime) -> Self { Self { - base_data: BaseModelData::new(id), + base_data: BaseModelData::new(), creator_id: creator_id.to_string(), title: title.to_string(), description: description.to_string(), @@ -145,7 +143,7 @@ impl Proposal { self } - pub fn cast_vote(mut self, ballot_id: Option, user_id: u32, chosen_option_id: u8, shares: i64) -> Self { + pub fn cast_vote(mut self, user_id: u32, chosen_option_id: u8, shares: i64) -> Self { if self.vote_status != VoteEventStatus::Open { eprintln!("Voting is not open for proposal '{}'", self.title); return self; @@ -161,7 +159,7 @@ impl Proposal { } } - let new_ballot = Ballot::new(ballot_id, user_id, chosen_option_id, shares); + let new_ballot = Ballot::new(user_id, chosen_option_id, shares); self.ballots.push(new_ballot); if let Some(option) = self.options.iter_mut().find(|opt| opt.id == chosen_option_id) { diff --git a/heromodels/src/models/userexample/user.rs b/heromodels/src/models/userexample/user.rs index 8455aa3..c9f68f0 100644 --- a/heromodels/src/models/userexample/user.rs +++ b/heromodels/src/models/userexample/user.rs @@ -26,13 +26,10 @@ pub struct User { } impl User { - /// Create a new user - /// - /// # Arguments - /// * `id` - Optional ID for the user. If None, the ID will be auto-generated. - pub fn new(id: Option) -> Self { + /// Create a new user with auto-generated ID + pub fn new() -> Self { Self { - base_data: BaseModelData::new(id), + base_data: BaseModelData::new(), username: String::new(), email: String::new(), full_name: String::new(), diff --git a/heromodels_core/src/lib.rs b/heromodels_core/src/lib.rs index db63ccf..8386423 100644 --- a/heromodels_core/src/lib.rs +++ b/heromodels_core/src/lib.rs @@ -59,21 +59,11 @@ pub trait Model: } /// Get the unique ID for this model - /// Returns 0 if the ID is None fn get_id(&self) -> u32; /// Get a mutable reference to the base_data field fn base_data_mut(&mut self) -> &mut BaseModelData; - /// Set the ID for this model - fn id(mut self, id: Option) -> Self - where - Self: Sized, - { - self.base_data_mut().id = id; - self - } - /// Build the model, updating the modified timestamp fn build(mut self) -> Self where @@ -98,8 +88,8 @@ pub trait Index { /// Base struct that all models should include #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BaseModelData { - /// Unique incremental ID per circle - pub id: Option, + /// Unique incremental ID - will be auto-generated by OurDB + pub id: u32, /// Unix epoch timestamp for creation time pub created_at: i64, @@ -112,11 +102,12 @@ pub struct BaseModelData { } impl BaseModelData { - /// Create a new BaseModelData instance - pub fn new(id: Option) -> Self { + /// Create a new BaseModelData instance with ID set to 0 + /// The ID will be auto-generated by OurDB when the model is saved + pub fn new() -> Self { let now = chrono::Utc::now().timestamp(); Self { - id, + id: 0, // This will be replaced by OurDB with an auto-generated ID created_at: now, modified_at: now, comments: Vec::new(), @@ -124,8 +115,8 @@ impl BaseModelData { } /// Create a new BaseModelDataBuilder - pub fn builder(id: Option) -> BaseModelDataBuilder { - BaseModelDataBuilder::new(id) + pub fn builder() -> BaseModelDataBuilder { + BaseModelDataBuilder::new() } /// Add a comment to this model @@ -144,11 +135,15 @@ impl BaseModelData { pub fn update_modified(&mut self) { self.modified_at = chrono::Utc::now().timestamp(); } + + /// Update the ID of this model + pub fn update_id(&mut self, id: u32) { + self.id = id; + } } /// Builder for BaseModelData pub struct BaseModelDataBuilder { - id: Option, created_at: Option, modified_at: Option, comments: Vec, @@ -156,9 +151,8 @@ pub struct BaseModelDataBuilder { impl BaseModelDataBuilder { /// Create a new BaseModelDataBuilder - pub fn new(id: Option) -> Self { + pub fn new() -> Self { Self { - id, created_at: None, modified_at: None, comments: Vec::new(), @@ -193,7 +187,7 @@ impl BaseModelDataBuilder { pub fn build(self) -> BaseModelData { let now = chrono::Utc::now().timestamp(); BaseModelData { - id: self.id, + id: 0, // This will be replaced by OurDB with an auto-generated ID created_at: self.created_at.unwrap_or(now), modified_at: self.modified_at.unwrap_or(now), comments: self.comments, From 57f59da43ed2f7277efcdb61153524c447e1d3ed Mon Sep 17 00:00:00 2001 From: Mahmoud Emad Date: Sat, 17 May 2025 13:46:16 +0300 Subject: [PATCH 03/11] fix: Use incremental ID --- .gitignore | 3 - heromodels/examples/basic_user_example.rs | 49 +-- heromodels/examples/calendar_example/main.rs | 12 +- heromodels/examples/custom_model_example.rs | 26 +- heromodels/examples/model_macro_example.rs | 41 +- heromodels/examples/simple_model_example.rs | 25 +- heromodels/src/db.rs | 44 +- heromodels/src/db/hero.rs | 427 +++++++++++++++---- heromodels/src/models/calendar/calendar.rs | 10 +- heromodels_core/src/lib.rs | 38 ++ 10 files changed, 519 insertions(+), 156 deletions(-) diff --git a/.gitignore b/.gitignore index c748bf6..8e789fd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,6 @@ target/ *.wasm herovm_build/ test_db -<<<<<<< Updated upstream -======= # Node.js **/node_modules/ @@ -22,4 +20,3 @@ rhaiinterface/server/**/*.js !rhaiinterface/server/examples/**/client.js !rhaiinterface/server/examples/webpack.config.js .vscode ->>>>>>> Stashed changes diff --git a/heromodels/examples/basic_user_example.rs b/heromodels/examples/basic_user_example.rs index e625a2d..cedb29a 100644 --- a/heromodels/examples/basic_user_example.rs +++ b/heromodels/examples/basic_user_example.rs @@ -67,26 +67,18 @@ fn main() { .is_active(false) .build(); - // Save all users to database and get their assigned IDs - let user1_id = db.collection().expect("can open user collection").set(&user1).expect("can set user"); - let user2_id = db.collection().expect("can open user collection").set(&user2).expect("can set user"); - let user3_id = db.collection().expect("can open user collection").set(&user3).expect("can set user"); - let user4_id = db.collection().expect("can open user collection").set(&user4).expect("can set user"); + // Save all users to database and get their assigned IDs and updated models + let (user1_id, db_user1) = db.collection().expect("can open user collection").set(&user1).expect("can set user"); + let (user2_id, db_user2) = db.collection().expect("can open user collection").set(&user2).expect("can set user"); + let (user3_id, db_user3) = db.collection().expect("can open user collection").set(&user3).expect("can set user"); + let (user4_id, db_user4) = db.collection().expect("can open user collection").set(&user4).expect("can set user"); println!("User 1 assigned ID: {}", user1_id); println!("User 2 assigned ID: {}", user2_id); println!("User 3 assigned ID: {}", user3_id); println!("User 4 assigned ID: {}", user4_id); - // Retrieve all users from database using the assigned IDs - let db_user1 = db.collection::().expect("can open user collection") - .get_by_id(user1_id).expect("can load user").expect("user should exist"); - let db_user2 = db.collection::().expect("can open user collection") - .get_by_id(user2_id).expect("can load user").expect("user should exist"); - let db_user3 = db.collection::().expect("can open user collection") - .get_by_id(user3_id).expect("can load user").expect("user should exist"); - let db_user4 = db.collection::().expect("can open user collection") - .get_by_id(user4_id).expect("can load user").expect("user should exist"); + // We already have the updated models from the set method, so we don't need to retrieve them again // Print all users retrieved from database println!("\n--- Users Retrieved from Database ---"); @@ -125,7 +117,7 @@ fn main() { .expect("can load stored users"); assert_eq!(active_users.len(), 2); - for (i, active_user) in active_users.iter().enumerate() { + for (_i, active_user) in active_users.iter().enumerate() { print_user_details(active_user); } @@ -147,7 +139,7 @@ fn main() { println!(" a. Remaining Active Users:"); assert_eq!(active_users.len(), 1); - for (i, active_user) in active_users.iter().enumerate() { + for (_i, active_user) in active_users.iter().enumerate() { print_user_details(active_user); } @@ -160,7 +152,7 @@ fn main() { println!(" b. Inactive Users:"); assert_eq!(inactive_users.len(), 2); - for (i, inactive_user) in inactive_users.iter().enumerate() { + for (_i, inactive_user) in inactive_users.iter().enumerate() { print_user_details(inactive_user); } @@ -177,22 +169,14 @@ fn main() { .content("This is a comment on the user") .build(); - // Save the comment and get its assigned ID - let comment_id = db.collection() + // Save the comment and get its assigned ID and updated model + let (comment_id, db_comment) = db.collection() .expect("can open comment collection") .set(&comment) .expect("can set comment"); println!("Comment assigned ID: {}", comment_id); - // 2. Retrieve the comment from database using the assigned ID - let db_comment = db - .collection::() - .expect("can open comment collection") - .get_by_id(comment_id) - .expect("can load comment") - .expect("comment should exist"); - println!(" a. Comment Retrieved from Database:"); print_comment_details(&db_comment); @@ -201,19 +185,12 @@ fn main() { let mut updated_user = db_user1.clone(); updated_user.base_data.add_comment(db_comment.get_id()); - db.collection::() + // Save the updated user and get the new version + let (_, user_with_comment) = db.collection::() .expect("can open user collection") .set(&updated_user) .expect("can set updated user"); - // 4. Retrieve the updated user - let user_with_comment = db - .collection::() - .expect("can open user collection") - .get_by_id(updated_user.get_id()) - .expect("can load user") - .expect("user should exist"); - println!(" a. User with Associated Comment:"); print_user_details(&user_with_comment); diff --git a/heromodels/examples/calendar_example/main.rs b/heromodels/examples/calendar_example/main.rs index 4139f94..6afec70 100644 --- a/heromodels/examples/calendar_example/main.rs +++ b/heromodels/examples/calendar_example/main.rs @@ -57,16 +57,16 @@ fn main() { .add_event(event1.clone()) .add_event(event2.clone()); - // Create a calendar with explicit ID - let calendar2 = Calendar::new(Some(2), "Personal Calendar") + // Create a calendar with auto-generated ID (explicit IDs are no longer supported) + let calendar2 = Calendar::new(None, "Personal Calendar") .add_event(event3_for_calendar2.clone()); // --- Store Calendars in DB --- let cal_collection = db.collection::().expect("can open calendar collection"); - cal_collection.set(&calendar1).expect("can set calendar1"); - cal_collection.set(&calendar2).expect("can set calendar2"); + let (_, calendar1) = cal_collection.set(&calendar1).expect("can set calendar1"); + let (_, calendar2) = cal_collection.set(&calendar2).expect("can set calendar2"); println!("Created calendar1 (ID: {}): Name - '{}'", calendar1.get_id(), calendar1.name); println!("Created calendar2 (ID: {}): Name - '{}'", calendar2.get_id(), calendar2.name); @@ -98,7 +98,7 @@ fn main() { println!("Event '{}' rescheduled in stored_calendar1.", rescheduled_event.title); // --- Store the modified calendar --- - cal_collection.set(&stored_calendar1).expect("can set modified calendar1"); + let (_, mut stored_calendar1) = cal_collection.set(&stored_calendar1).expect("can set modified calendar1"); let re_retrieved_calendar1_opt = cal_collection.get_by_id(calendar1.get_id()).expect("can try to load modified calendar1"); let re_retrieved_calendar1 = re_retrieved_calendar1_opt.unwrap(); let re_retrieved_event = re_retrieved_calendar1.events.iter().find(|e| e.id == event_id_to_reschedule) @@ -116,7 +116,7 @@ fn main() { ); stored_calendar1 = stored_calendar1.add_event(event4_new); assert_eq!(stored_calendar1.events.len(), 3); - cal_collection.set(&stored_calendar1).expect("can set calendar1 after adding new event"); + let (_, stored_calendar1) = cal_collection.set(&stored_calendar1).expect("can set calendar1 after adding new event"); println!("Added new event '1-on-1' to stored_calendar1. Total events: {}", stored_calendar1.events.len()); // --- Delete a Calendar --- diff --git a/heromodels/examples/custom_model_example.rs b/heromodels/examples/custom_model_example.rs index 8558036..7a3cc71 100644 --- a/heromodels/examples/custom_model_example.rs +++ b/heromodels/examples/custom_model_example.rs @@ -1,3 +1,4 @@ +use heromodels::db::{Collection, Db}; use heromodels_core::{BaseModelData, Model}; use heromodels_derive::model; use serde::{Deserialize, Serialize}; @@ -22,16 +23,35 @@ fn main() { println!("Hero Models - Custom Model Example"); println!("=================================="); + // Create a new DB instance, reset before every run + let db_path = "/tmp/ourdb_custom_model_example"; + let db = heromodels::db::hero::OurDB::new(db_path, true).expect("Can create DB"); + // Example usage of the generated implementation println!("CustomUser DB Prefix: {}", CustomUser::db_prefix()); let user = CustomUser { - base_data: BaseModelData::new(1), + base_data: BaseModelData::new(), // ID will be auto-generated by OurDB login: "johndoe".to_string(), is_active: true, full_name: "John Doe".to_string(), }; - println!("\nCustomUser ID: {}", user.get_id()); - println!("CustomUser DB Keys: {:?}", user.db_keys()); + println!("\nBefore saving - CustomUser ID: {}", user.get_id()); + println!("Before saving - CustomUser DB Keys: {:?}", user.db_keys()); + + // Save the model to the database + let collection = db.collection::().expect("can open user collection"); + let (user_id, saved_user) = collection.set(&user).expect("can save user"); + + println!("\nAfter saving - CustomUser ID: {}", saved_user.get_id()); + println!("After saving - CustomUser DB Keys: {:?}", saved_user.db_keys()); + println!("Returned ID: {}", user_id); + + // Verify that the ID was auto-generated + assert_eq!(saved_user.get_id(), user_id); + assert_ne!(saved_user.get_id(), 0); + + println!("\nExample finished. DB stored at {}", db_path); + println!("To clean up, you can manually delete the directory: {}", db_path); } diff --git a/heromodels/examples/model_macro_example.rs b/heromodels/examples/model_macro_example.rs index 04602fd..3e954ee 100644 --- a/heromodels/examples/model_macro_example.rs +++ b/heromodels/examples/model_macro_example.rs @@ -1,3 +1,4 @@ +use heromodels::db::{Collection, Db}; use heromodels_core::{BaseModelData, Model}; use heromodels_derive::model; use serde::{Deserialize, Serialize}; @@ -33,26 +34,54 @@ fn main() { println!("Hero Models - Model Macro Example"); println!("================================="); + // Create a new DB instance, reset before every run + let db_path = "/tmp/ourdb_model_macro_example"; + let db = heromodels::db::hero::OurDB::new(db_path, true).expect("Can create DB"); + // Example usage of the generated implementations println!("SimpleUser DB Prefix: {}", SimpleUser::db_prefix()); println!("CustomUser DB Prefix: {}", CustomUser::db_prefix()); let user = SimpleUser { - base_data: BaseModelData::new(1), + base_data: BaseModelData::new(), // ID will be auto-generated by OurDB login: "johndoe".to_string(), full_name: "John Doe".to_string(), }; let custom_user = CustomUser { - base_data: BaseModelData::new(2), + base_data: BaseModelData::new(), // ID will be auto-generated by OurDB login_name: "janesmith".to_string(), is_active: true, full_name: "Jane Smith".to_string(), }; - println!("\nSimpleUser ID: {}", user.get_id()); - println!("SimpleUser DB Keys: {:?}", user.db_keys()); + println!("\nBefore saving - SimpleUser ID: {}", user.get_id()); + println!("Before saving - SimpleUser DB Keys: {:?}", user.db_keys()); - println!("\nCustomUser ID: {}", custom_user.get_id()); - println!("CustomUser DB Keys: {:?}", custom_user.db_keys()); + println!("\nBefore saving - CustomUser ID: {}", custom_user.get_id()); + println!("Before saving - CustomUser DB Keys: {:?}", custom_user.db_keys()); + + // Save the models to the database + let simple_collection = db.collection::().expect("can open simple user collection"); + let custom_collection = db.collection::().expect("can open custom user collection"); + + let (user_id, saved_user) = simple_collection.set(&user).expect("can save simple user"); + let (custom_user_id, saved_custom_user) = custom_collection.set(&custom_user).expect("can save custom user"); + + println!("\nAfter saving - SimpleUser ID: {}", saved_user.get_id()); + println!("After saving - SimpleUser DB Keys: {:?}", saved_user.db_keys()); + println!("Returned SimpleUser ID: {}", user_id); + + println!("\nAfter saving - CustomUser ID: {}", saved_custom_user.get_id()); + println!("After saving - CustomUser DB Keys: {:?}", saved_custom_user.db_keys()); + println!("Returned CustomUser ID: {}", custom_user_id); + + // Verify that the IDs were auto-generated + assert_eq!(saved_user.get_id(), user_id); + assert_ne!(saved_user.get_id(), 0); + assert_eq!(saved_custom_user.get_id(), custom_user_id); + assert_ne!(saved_custom_user.get_id(), 0); + + println!("\nExample finished. DB stored at {}", db_path); + println!("To clean up, you can manually delete the directory: {}", db_path); } diff --git a/heromodels/examples/simple_model_example.rs b/heromodels/examples/simple_model_example.rs index a5394b5..fa53b4b 100644 --- a/heromodels/examples/simple_model_example.rs +++ b/heromodels/examples/simple_model_example.rs @@ -1,3 +1,4 @@ +use heromodels::db::{Collection, Db}; use heromodels_core::{BaseModelData, Model}; use heromodels_derive::model; use serde::{Deserialize, Serialize}; @@ -15,16 +16,36 @@ fn main() { println!("Hero Models - Simple Model Example"); println!("=================================="); + // Create a new DB instance, reset before every run + let db_path = "/tmp/ourdb_simple_model_example"; + let db = heromodels::db::hero::OurDB::new(db_path, true).expect("Can create DB"); + // Example usage of the generated implementation println!("SimpleUser DB Prefix: {}", SimpleUser::db_prefix()); + // Create a new user with ID 0 (will be auto-generated when saved) let user = SimpleUser { base_data: BaseModelData::new(), login: "johndoe".to_string(), full_name: "John Doe".to_string(), }; - println!("\nSimpleUser ID: {}", user.get_id()); - println!("SimpleUser DB Keys: {:?}", user.db_keys()); + println!("\nBefore saving - SimpleUser ID: {}", user.get_id()); + println!("Before saving - SimpleUser DB Keys: {:?}", user.db_keys()); + + // Save the user to the database + let collection = db.collection::().expect("can open user collection"); + let (user_id, saved_user) = collection.set(&user).expect("can save user"); + + println!("\nAfter saving - SimpleUser ID: {}", saved_user.get_id()); + println!("After saving - SimpleUser DB Keys: {:?}", saved_user.db_keys()); + println!("Returned ID: {}", user_id); + + // Verify that the ID was auto-generated + assert_eq!(saved_user.get_id(), user_id); + assert_ne!(saved_user.get_id(), 0); + + println!("\nExample finished. DB stored at {}", db_path); + println!("To clean up, you can manually delete the directory: {}", db_path); } diff --git a/heromodels/src/db.rs b/heromodels/src/db.rs index 70ed851..e0d4707 100644 --- a/heromodels/src/db.rs +++ b/heromodels/src/db.rs @@ -32,9 +32,16 @@ where /// Get an object from its ID. This does not use an index lookup fn get_by_id(&self, id: u32) -> Result, Error>; - /// Store an item in the DB and return the assigned ID. - /// This method does not modify the original model. - fn set(&self, value: &V) -> Result>; + /// Store an item in the DB and return the assigned ID and the updated model. + /// + /// # Important Notes + /// - This method does not modify the original model passed as an argument. + /// - For new models (with ID 0), an ID will be auto-generated by OurDB. + /// - The returned model will have the correct ID and should be used instead of the original model. + /// - The original model should not be used after calling this method, as it may have + /// an inconsistent state compared to what's in the database. + /// - ID 0 is reserved for new models and should not be used for existing models. + fn set(&self, value: &V) -> Result<(u32, V), Error>; /// Delete all items from the db with a given index. fn delete(&self, key: &Q) -> Result<(), Error> @@ -46,8 +53,11 @@ where /// Delete an object with a given ID fn delete_by_id(&self, id: u32) -> Result<(), Error>; - /// Get all objects from the colelction + /// Get all objects from the collection fn get_all(&self) -> Result, Error>; + + /// Begin a transaction for this collection + fn begin_transaction(&self) -> Result>, Error>; } /// Errors returned by the DB implementation @@ -59,6 +69,14 @@ pub enum Error { Decode(bincode::error::DecodeError), /// Error encoding a model for storage Encode(bincode::error::EncodeError), + /// Invalid ID used (e.g., using ID 0 for an existing model) + InvalidId(String), + /// ID mismatch (e.g., expected ID 5 but got ID 6) + IdMismatch(String), + /// ID collision (e.g., trying to create a model with an ID that already exists) + IdCollision(String), + /// Type error (e.g., trying to get a model of the wrong type) + TypeError, } impl From for Error { @@ -72,3 +90,21 @@ impl From for Error { Error::Encode(value) } } + +/// A transaction that can be committed or rolled back +pub trait Transaction { + /// Error type for transaction operations + type Error: std::fmt::Debug; + + /// Begin a transaction + fn begin(&self) -> Result<(), Error>; + + /// Commit a transaction + fn commit(&self) -> Result<(), Error>; + + /// Roll back a transaction + fn rollback(&self) -> Result<(), Error>; + + /// Check if a transaction is active + fn is_active(&self) -> bool; +} diff --git a/heromodels/src/db/hero.rs b/heromodels/src/db/hero.rs index 3abec38..70b7f4d 100644 --- a/heromodels/src/db/hero.rs +++ b/heromodels/src/db/hero.rs @@ -1,31 +1,97 @@ use heromodels_core::{Index, Model}; use ourdb::OurDBSetArgs; use serde::Deserialize; +use crate::db::Transaction; use std::{ borrow::Borrow, collections::HashSet, path::PathBuf, - sync::{Arc, Mutex}, + sync::{Arc, Mutex, atomic::{AtomicU32, Ordering}}, }; +/// Configuration for custom ID sequences +pub struct IdSequence { + /// The starting ID for the sequence + start: u32, + /// The increment for the sequence + increment: u32, + /// The current ID in the sequence + current: AtomicU32, +} + +// Implement Clone manually since AtomicU32 doesn't implement Clone +impl Clone for IdSequence { + fn clone(&self) -> Self { + Self { + start: self.start, + increment: self.increment, + current: AtomicU32::new(self.current.load(Ordering::SeqCst)), + } + } +} + +impl IdSequence { + /// Create a new ID sequence with default values (start=1, increment=1) + pub fn new() -> Self { + Self { + start: 1, + increment: 1, + current: AtomicU32::new(1), + } + } + + /// Create a new ID sequence with custom values + pub fn with_config(start: u32, increment: u32) -> Self { + Self { + start, + increment, + current: AtomicU32::new(start), + } + } + + /// Get the next ID in the sequence + pub fn next_id(&self) -> u32 { + self.current.fetch_add(self.increment, Ordering::SeqCst) + } + + /// Reset the sequence to its starting value + pub fn reset(&self) { + self.current.store(self.start, Ordering::SeqCst); + } + + /// Set the current ID in the sequence + pub fn set_current(&self, id: u32) { + self.current.store(id, Ordering::SeqCst); + } +} + const BINCODE_CONFIG: bincode::config::Configuration = bincode::config::standard(); #[derive(Clone)] pub struct OurDB { index: Arc>, data: Arc>, + // Mutex for ID generation to prevent race conditions + id_lock: Arc>, + // Custom ID sequence configuration + id_sequence: Arc, } impl OurDB { - /// Create a new instance of ourdb + /// Create a new instance of ourdb with default ID sequence (start=1, increment=1) pub fn new(path: impl Into, reset: bool) -> Result { + Self::with_id_sequence(path, reset, IdSequence::new()) + } + + /// Create a new instance of ourdb with a custom ID sequence + pub fn with_id_sequence(path: impl Into, reset: bool, id_sequence: IdSequence) -> Result { let mut base_path = path.into(); let mut data_path = base_path.clone(); base_path.push("index"); data_path.push("data"); - let data_db = ourdb::OurDB::new(ourdb::OurDBConfig { + let mut data_db = ourdb::OurDB::new(ourdb::OurDBConfig { incremental_mode: true, path: data_path, file_size: None, @@ -33,9 +99,24 @@ impl OurDB { reset: Some(reset), })?; let index_db = tst::TST::new(base_path.to_str().expect("Path is valid UTF-8"), reset)?; + // If we're resetting the database, also reset the ID sequence + if reset { + id_sequence.reset(); + } else { + // Otherwise, try to find the highest ID in the database and update the sequence + // Since OurDB doesn't have a get_highest_id method, we'll use get_next_id instead + // This is not ideal, but it's the best we can do with the current API + let highest_id = data_db.get_next_id().unwrap_or(id_sequence.start); + if highest_id >= id_sequence.start { + id_sequence.set_current(highest_id + id_sequence.increment); + } + } + Ok(OurDB { index: Arc::new(Mutex::new(index_db)), data: Arc::new(Mutex::new(data_db)), + id_lock: Arc::new(Mutex::new(())), + id_sequence: Arc::new(id_sequence), }) } } @@ -56,6 +137,18 @@ where { type Error = tst::Error; + /// Begin a transaction for this collection + fn begin_transaction(&self) -> Result>, super::Error> { + // Create a new transaction + let transaction = OurDBTransaction::new(); + + // Begin the transaction + transaction.begin()?; + + // Return the transaction + Ok(Box::new(transaction)) + } + fn get(&self, key: &Q) -> Result, super::Error> where I: Index, @@ -87,114 +180,165 @@ where Self::get_ourdb_value(&mut data_db, id) } - fn set(&self, value: &M) -> Result> { - // Before inserting the new object, check if an object with this ID already exists. If it does, we potentially need to update indices. - let mut data_db = self.data.lock().expect("can lock data DB"); - let old_obj: Option = Self::get_ourdb_value(&mut data_db, value.get_id())?; - let (indices_to_delete, indices_to_add) = if let Some(old_obj) = old_obj { - let mut indices_to_delete = vec![]; - let mut indices_to_add = vec![]; - let old_indices = old_obj.db_keys(); - let new_indices = value.db_keys(); - for old_index in old_indices { - for new_index in &new_indices { - if old_index.name == new_index.name { - if old_index.value != new_index.value { - // different value now, remove index - indices_to_delete.push(old_index); - // and later add the new one - indices_to_add.push(new_index.clone()); - break; + fn set(&self, value: &M) -> Result<(u32, M), super::Error> { + // For now, we'll skip using transactions to avoid type inference issues + // In a real implementation, you would use a proper transaction mechanism + + // Use a result variable to track success/failure + let result = (|| { + // Before inserting the new object, check if an object with this ID already exists. If it does, we potentially need to update indices. + let mut data_db = self.data.lock().expect("can lock data DB"); + let old_obj: Option = Self::get_ourdb_value(&mut data_db, value.get_id())?; + let (indices_to_delete, indices_to_add) = if let Some(ref old_obj) = old_obj { + let mut indices_to_delete = vec![]; + let mut indices_to_add = vec![]; + let old_indices = old_obj.db_keys(); + let new_indices = value.db_keys(); + for old_index in old_indices { + for new_index in &new_indices { + if old_index.name == new_index.name { + if old_index.value != new_index.value { + // different value now, remove index + indices_to_delete.push(old_index); + // and later add the new one + indices_to_add.push(new_index.clone()); + break; + } } } } - } - // NOTE: we assume here that the index keys are stable, i.e. new index fields don't appear - // and existing ones don't dissapear - (indices_to_delete, indices_to_add) - } else { - (vec![], value.db_keys()) - }; - - let mut index_db = self.index.lock().expect("can lock index db"); - // First delete old indices which need to change - for old_index in indices_to_delete { - let key = Self::index_key(M::db_prefix(), old_index.name, &old_index.value); - let raw_ids = index_db.get(&key)?; - let (mut ids, _): (HashSet, _) = - bincode::serde::decode_from_slice(&raw_ids, BINCODE_CONFIG)?; - ids.remove(&value.get_id()); - if ids.is_empty() { - // This was the last ID with this index value, remove index entirely - index_db.delete(&key)?; + // NOTE: we assume here that the index keys are stable, i.e. new index fields don't appear + // and existing ones don't dissapear + (indices_to_delete, indices_to_add) } else { - // There are still objects left with this index value, write back updated set - let raw_ids = bincode::serde::encode_to_vec(ids, BINCODE_CONFIG)?; - index_db.set(&key, raw_ids)?; + (vec![], value.db_keys()) + }; + + let mut index_db = self.index.lock().expect("can lock index db"); + // First delete old indices which need to change + for old_index in indices_to_delete { + let key = Self::index_key(M::db_prefix(), old_index.name, &old_index.value); + let raw_ids = index_db.get(&key)?; + let (mut ids, _): (HashSet, _) = + bincode::serde::decode_from_slice(&raw_ids, BINCODE_CONFIG)?; + ids.remove(&value.get_id()); + if ids.is_empty() { + // This was the last ID with this index value, remove index entirely + index_db.delete(&key)?; + } else { + // There are still objects left with this index value, write back updated set + let raw_ids = bincode::serde::encode_to_vec(ids, BINCODE_CONFIG)?; + index_db.set(&key, raw_ids)?; + } } - } - // Get the current ID - let id = value.get_id(); + // Get the current ID + let id = value.get_id(); - // If id is 0, it's a new object, so let OurDB auto-generate an ID - // Otherwise, it's an update to an existing object - let id_param = if id == 0 { None } else { Some(id) }; + // Validate that ID 0 is only used for new models + if id == 0 { + // Check if this model already exists in the database + // If it does, it's an error to use ID 0 for an existing model + if let Some(existing) = Self::get_ourdb_value::(&mut data_db, id)? { + return Err(super::Error::InvalidId(format!( + "ID 0 is reserved for new models. Found existing model with ID 0: {:?}", + existing + ))); + } + } else { + // Validate that IDs > 0 are only used for existing models + // If the model doesn't exist, it's an error to use a specific ID + if id > 0 && Self::get_ourdb_value::(&mut data_db, id)?.is_none() { + return Err(super::Error::InvalidId(format!( + "ID {} does not exist in the database. Use ID 0 for new models.", + id + ))); + } - // For new objects (id == 0), we need to get the assigned ID from OurDB - // and update the model before serializing it - let assigned_id = if id == 0 { - // First, get the next ID that OurDB will assign - let next_id = data_db.get_next_id()?; + // Check for ID collisions when manually setting an ID + if id > 0 && Self::get_ourdb_value::(&mut data_db, id)?.is_some() { + // This is only an error if we're trying to create a new model with this ID + // If we're updating an existing model, this is fine + if old_obj.is_none() { + return Err(super::Error::IdCollision(format!( + "ID {} already exists in the database", + id + ))); + } + } + } - // Create a mutable clone of the value and update its ID - // This is a bit of a hack, but we need to update the ID before serializing - let mut value_clone = value.clone(); - let base_data = value_clone.base_data_mut(); - base_data.update_id(next_id); + // If id is 0, it's a new object, so let OurDB auto-generate an ID + // Otherwise, it's an update to an existing object + let id_param = if id == 0 { None } else { Some(id) }; - // Now serialize the updated model - let v = bincode::serde::encode_to_vec(&value_clone, BINCODE_CONFIG)?; + // Thread-safe approach for handling ID assignment + let assigned_id = if id == 0 { + // For new objects, serialize with ID 0 + let v = bincode::serde::encode_to_vec(value, BINCODE_CONFIG)?; - // Save to OurDB with the ID parameter set to None to let OurDB auto-generate the ID - let assigned_id = data_db.set(OurDBSetArgs { - id: id_param, - data: &v, - })?; + // Save to OurDB with id_param = None to let OurDB auto-generate the ID + let assigned_id = data_db.set(OurDBSetArgs { + id: id_param, + data: &v, + })?; - // The assigned ID should match the next_id we got earlier - assert_eq!(assigned_id, next_id, "OurDB assigned a different ID than expected"); + // Now that we have the actual assigned ID, create a new model with the correct ID + // and save it again to ensure the serialized data contains the correct ID + let mut value_clone = value.clone(); + let base_data = value_clone.base_data_mut(); + base_data.update_id(assigned_id); - // Return the assigned ID - assigned_id - } else { - // For existing objects, just serialize and save - let v = bincode::serde::encode_to_vec(value, BINCODE_CONFIG)?; + // Serialize the updated model + let v = bincode::serde::encode_to_vec(&value_clone, BINCODE_CONFIG)?; - // Save to OurDB with the existing ID - let assigned_id = data_db.set(OurDBSetArgs { - id: id_param, - data: &v, - })?; + // Save again with the explicit ID + data_db.set(OurDBSetArgs { + id: Some(assigned_id), + data: &v, + })?; - // Return the existing ID - assigned_id - }; + // Return the assigned ID + assigned_id + } else { + // For existing objects, just serialize and save + let v = bincode::serde::encode_to_vec(value, BINCODE_CONFIG)?; - // Now add the new indices - for index_key in indices_to_add { - let key = Self::index_key(M::db_prefix(), index_key.name, &index_key.value); - // Load the existing id set for the index or create a new set - let mut existing_ids = - Self::get_tst_value::>(&mut index_db, &key)?.unwrap_or_default(); - // Use the assigned ID for new objects - existing_ids.insert(assigned_id); - let encoded_ids = bincode::serde::encode_to_vec(existing_ids, BINCODE_CONFIG)?; - index_db.set(&key, encoded_ids)?; - } + // Save to OurDB with the existing ID + let assigned_id = data_db.set(OurDBSetArgs { + id: id_param, + data: &v, + })?; - // Return the assigned ID - Ok(assigned_id) + // Return the existing ID + assigned_id + }; + + // Now add the new indices + for index_key in indices_to_add { + let key = Self::index_key(M::db_prefix(), index_key.name, &index_key.value); + // Load the existing id set for the index or create a new set + let mut existing_ids = + Self::get_tst_value::>(&mut index_db, &key)?.unwrap_or_default(); + // Use the assigned ID for new objects + existing_ids.insert(assigned_id); + let encoded_ids = bincode::serde::encode_to_vec(existing_ids, BINCODE_CONFIG)?; + index_db.set(&key, encoded_ids)?; + } + + // Get the updated model from the database + let updated_model = Self::get_ourdb_value::(&mut data_db, assigned_id)? + .ok_or_else(|| super::Error::InvalidId(format!( + "Failed to retrieve model with ID {} after saving", assigned_id + )))?; + + // Return the assigned ID and the updated model + Ok((assigned_id, updated_model)) + })(); + + // Return the result directly + // In a real implementation, you would commit or rollback the transaction here + result } fn delete(&self, key: &Q) -> Result<(), super::Error> @@ -279,6 +423,29 @@ impl OurDB { format!("{collection}::{index}::{value}") } + /// Reserve an ID for future use + pub fn reserve_id(&self) -> u32 { + // Acquire the ID lock to prevent race conditions + let _id_lock = self.id_lock.lock().expect("Failed to acquire ID lock"); + + // Get the next ID from our custom sequence + self.id_sequence.next_id() + } + + /// Reserve multiple IDs for future use + pub fn reserve_ids(&self, count: u32) -> Vec { + // Acquire the ID lock to prevent race conditions + let _id_lock = self.id_lock.lock().expect("Failed to acquire ID lock"); + + // Get the next IDs from our custom sequence + let mut ids = Vec::with_capacity(count as usize); + for _ in 0..count { + ids.push(self.id_sequence.next_id()); + } + + ids + } + /// Wrapper to load values from ourdb and transform a not found error in to Ok(None) fn get_ourdb_value( data: &mut ourdb::OurDB, @@ -326,3 +493,75 @@ impl From for super::Error { super::Error::DB(tst::Error::OurDB(value)) } } + +/// A transaction for OurDB +/// +/// Note: This is a simplified implementation that doesn't actually provide +/// ACID guarantees. In a real implementation, you would need to use a proper +/// transaction mechanism provided by the underlying database. +/// +/// This struct implements Drop to ensure that transactions are properly closed. +/// If a transaction is not explicitly committed or rolled back, it will be +/// rolled back when the transaction is dropped. +struct OurDBTransaction { + active: std::sync::atomic::AtomicBool, +} + +impl OurDBTransaction { + /// Create a new transaction + fn new() -> Self { + Self { active: std::sync::atomic::AtomicBool::new(false) } + } +} + +impl Drop for OurDBTransaction { + fn drop(&mut self) { + // If the transaction is still active when dropped, roll it back + if self.active.load(std::sync::atomic::Ordering::SeqCst) { + // We can't return an error from drop, so we just log it + eprintln!("Warning: Transaction was dropped without being committed or rolled back. Rolling back automatically."); + self.active.store(false, std::sync::atomic::Ordering::SeqCst); + } + } +} + +impl super::Transaction for OurDBTransaction { + type Error = tst::Error; + + /// Begin the transaction + fn begin(&self) -> Result<(), super::Error> { + // In a real implementation, you would start a transaction in the underlying database + // For now, we just set the active flag + self.active.store(true, std::sync::atomic::Ordering::SeqCst); + Ok(()) + } + + /// Commit the transaction + fn commit(&self) -> Result<(), super::Error> { + // In a real implementation, you would commit the transaction in the underlying database + // For now, we just check if the transaction is active + if !self.active.load(std::sync::atomic::Ordering::SeqCst) { + return Err(super::Error::InvalidId("Cannot commit an inactive transaction".to_string())); + } + + self.active.store(false, std::sync::atomic::Ordering::SeqCst); + Ok(()) + } + + /// Roll back the transaction + fn rollback(&self) -> Result<(), super::Error> { + // In a real implementation, you would roll back the transaction in the underlying database + // For now, we just check if the transaction is active + if !self.active.load(std::sync::atomic::Ordering::SeqCst) { + return Err(super::Error::InvalidId("Cannot roll back an inactive transaction".to_string())); + } + + self.active.store(false, std::sync::atomic::Ordering::SeqCst); + Ok(()) + } + + /// Check if the transaction is active + fn is_active(&self) -> bool { + self.active.load(std::sync::atomic::Ordering::SeqCst) + } +} diff --git a/heromodels/src/models/calendar/calendar.rs b/heromodels/src/models/calendar/calendar.rs index a7eee92..f03e4b6 100644 --- a/heromodels/src/models/calendar/calendar.rs +++ b/heromodels/src/models/calendar/calendar.rs @@ -142,10 +142,16 @@ impl Calendar { /// Creates a new calendar with auto-generated ID /// /// # Arguments + /// * `id` - Optional ID for the calendar (use None for auto-generated ID) /// * `name` - Name of the calendar - pub fn new(name: impl ToString) -> Self { + pub fn new(id: Option, name: impl ToString) -> Self { + let mut base_data = BaseModelData::new(); + if let Some(id) = id { + base_data.update_id(id); + } + Self { - base_data: BaseModelData::new(), + base_data, name: name.to_string(), description: None, events: Vec::new(), diff --git a/heromodels_core/src/lib.rs b/heromodels_core/src/lib.rs index 8386423..c79f416 100644 --- a/heromodels_core/src/lib.rs +++ b/heromodels_core/src/lib.rs @@ -86,9 +86,47 @@ pub trait Index { } /// Base struct that all models should include +/// +/// # ID Management +/// +/// The `id` field is managed automatically by OurDB when using incremental mode: +/// +/// 1. When creating a new model, the `id` field is initialized to 0. +/// 2. When saving the model to the database with `set()`, OurDB will: +/// - If the ID is 0, auto-generate a new ID and update the model. +/// - If the ID is not 0, use the provided ID for updates. +/// +/// # Important Notes +/// +/// - Never manually set the `id` field to a specific value. Always use 0 for new models. +/// - The ID 0 is reserved for new models and should not be used for existing models. +/// - After saving a model, use the ID returned by `set()` to retrieve the model from the database. +/// - The original model passed to `set()` is not modified; you need to retrieve the updated model. +/// +/// # Example +/// +/// ```rust +/// // Create a new model with ID 0 +/// let user = User::new() +/// .username("johndoe") +/// .email("john.doe@example.com") +/// .build(); +/// +/// // Save the model and get the assigned ID +/// let user_id = db.collection().set(&user).expect("Failed to save user"); +/// +/// // Retrieve the model with the assigned ID +/// let db_user = db.collection().get_by_id(user_id).expect("Failed to get user"); +/// ``` #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BaseModelData { /// Unique incremental ID - will be auto-generated by OurDB + /// + /// This field is automatically managed by OurDB: + /// - For new models, set this to 0 and OurDB will auto-generate an ID. + /// - For existing models, this will be the ID assigned by OurDB. + /// + /// Do not manually set this field to a specific value. pub id: u32, /// Unix epoch timestamp for creation time From e4c50ca9d73b2211a338ddd381f940256d727fe7 Mon Sep 17 00:00:00 2001 From: Mahmoud Emad Date: Sat, 17 May 2025 16:03:00 +0300 Subject: [PATCH 04/11] fix: Fix all examples --- .../examples/calendar_rhai/calendar.rhai | 8 +- heromodels/examples/calendar_rhai/example.rs | 29 +++---- .../governance_proposal_example/main.rs | 38 +++++++-- .../examples/governance_rhai/example.rs | 77 +++++++++--------- .../examples/governance_rhai/governance.rhai | 18 ++-- heromodels/src/models/finance/account.rs | 9 +- heromodels/src/models/finance/asset.rs | 17 +++- heromodels/src/models/finance/marketplace.rs | 22 ++++- heromodels/src/models/governance/proposal.rs | 24 ++++-- heromodels/temp_calendar_db/data/lookup/.inc | 1 + heromodels/temp_calendar_db/data/lookup/data | Bin 0 -> 4000000 bytes heromodels/temp_calendar_db/index/0.db | Bin 0 -> 35 bytes heromodels/temp_calendar_db/index/lookup/.inc | 1 + heromodels/temp_calendar_db/index/lookup/data | Bin 0 -> 4000000 bytes .../temp_governance_db/data/lookup/.inc | 1 + .../temp_governance_db/data/lookup/data | Bin 0 -> 4000000 bytes heromodels/temp_governance_db/index/0.db | Bin 0 -> 35 bytes .../temp_governance_db/index/lookup/.inc | 1 + .../temp_governance_db/index/lookup/data | Bin 0 -> 4000000 bytes 19 files changed, 166 insertions(+), 80 deletions(-) create mode 100644 heromodels/temp_calendar_db/data/lookup/.inc create mode 100644 heromodels/temp_calendar_db/data/lookup/data create mode 100644 heromodels/temp_calendar_db/index/0.db create mode 100644 heromodels/temp_calendar_db/index/lookup/.inc create mode 100644 heromodels/temp_calendar_db/index/lookup/data create mode 100644 heromodels/temp_governance_db/data/lookup/.inc create mode 100644 heromodels/temp_governance_db/data/lookup/data create mode 100644 heromodels/temp_governance_db/index/0.db create mode 100644 heromodels/temp_governance_db/index/lookup/.inc create mode 100644 heromodels/temp_governance_db/index/lookup/data diff --git a/heromodels/examples/calendar_rhai/calendar.rhai b/heromodels/examples/calendar_rhai/calendar.rhai index 56310c9..88e05a9 100644 --- a/heromodels/examples/calendar_rhai/calendar.rhai +++ b/heromodels/examples/calendar_rhai/calendar.rhai @@ -1,8 +1,8 @@ // Get the database instance let db = get_db(); -// Create a new calendar -let calendar = calendar__builder(1); +// Create a new calendar with auto-generated ID (pass 0 for auto-generated ID) +let calendar = calendar__builder(0); calendar.name = "My First Calendar"; set_description(calendar, "A calendar for testing Rhai integration"); @@ -26,8 +26,8 @@ if calendar_exists(db, 1) { print("Failed to retrieve calendar with ID 1"); } -// Create another calendar -let calendar2 = calendar__builder(2); +// Create another calendar with auto-generated ID +let calendar2 = calendar__builder(0); calendar2.name = "My Second Calendar"; set_description(calendar2, "Another calendar for testing"); diff --git a/heromodels/examples/calendar_rhai/example.rs b/heromodels/examples/calendar_rhai/example.rs index 4963bbc..db6a401 100644 --- a/heromodels/examples/calendar_rhai/example.rs +++ b/heromodels/examples/calendar_rhai/example.rs @@ -18,53 +18,54 @@ fn main() -> Result<(), Box> { // Register a function to get the database instance engine.register_fn("get_db", move || db.clone()); - + // Register a calendar builder function engine.register_fn("calendar__builder", |id: i64| { - Calendar::new(id as u32, "New Calendar") + let id_option = if id <= 0 { None } else { Some(id as u32) }; + Calendar::new(id_option, "New Calendar") }); - + // Register setter methods for Calendar properties engine.register_fn("set_description", |calendar: &mut Calendar, desc: String| { calendar.description = Some(desc); }); - + // Register getter methods for Calendar properties engine.register_fn("get_description", |calendar: Calendar| -> String { calendar.description.clone().unwrap_or_default() }); - + // Register getter for base_data.id engine.register_fn("get_id", |calendar: Calendar| -> i64 { calendar.base_data.id as i64 }); - + // Register additional functions needed by the script engine.register_fn("set_calendar", |_db: Arc, _calendar: Calendar| { // In a real implementation, this would save the calendar to the database println!("Calendar saved: {}", _calendar.name); }); - + engine.register_fn("get_calendar_by_id", |_db: Arc, id: i64| -> Calendar { // In a real implementation, this would retrieve the calendar from the database - Calendar::new(id as u32, "Retrieved Calendar") + Calendar::new(Some(id as u32), "Retrieved Calendar") }); - + // Register a function to check if a calendar exists engine.register_fn("calendar_exists", |_db: Arc, id: i64| -> bool { // In a real implementation, this would check if the calendar exists in the database id == 1 || id == 2 }); - + // Define the function separately to use with the wrap_vec_return macro fn get_all_calendars(_db: Arc) -> Vec { // In a real implementation, this would retrieve all calendars from the database - vec![Calendar::new(1, "Calendar 1"), Calendar::new(2, "Calendar 2")] + vec![Calendar::new(Some(1), "Calendar 1"), Calendar::new(Some(2), "Calendar 2")] } - + // Register the function with the wrap_vec_return macro engine.register_fn("get_all_calendars", wrap_vec_return!(get_all_calendars, Arc => Calendar)); - + engine.register_fn("delete_calendar_by_id", |_db: Arc, _id: i64| { // In a real implementation, this would delete the calendar from the database println!("Calendar deleted with ID: {}", _id); @@ -73,7 +74,7 @@ fn main() -> Result<(), Box> { // Load and evaluate the Rhai script let script_path = Path::new("examples/calendar_rhai/calendar.rhai"); let script = fs::read_to_string(script_path)?; - + match engine.eval::<()>(&script) { Ok(_) => println!("Script executed successfully!"), Err(e) => eprintln!("Script execution failed: {}", e), diff --git a/heromodels/examples/governance_proposal_example/main.rs b/heromodels/examples/governance_proposal_example/main.rs index bfdcfe2..642a9bb 100644 --- a/heromodels/examples/governance_proposal_example/main.rs +++ b/heromodels/examples/governance_proposal_example/main.rs @@ -1,11 +1,16 @@ // heromodels/examples/governance_proposal_example/main.rs use chrono::{Utc, Duration}; +use heromodels::db::{Collection, Db}; use heromodels::models::governance::{Proposal, ProposalStatus, VoteEventStatus}; fn main() { println!("Governance Proposal Model Example\n"); + // Create a new DB instance, reset before every run + let db_path = "/tmp/ourdb_governance_proposal_example"; + let db = heromodels::db::hero::OurDB::new(db_path, true).expect("Can create DB"); + // Create a new proposal with auto-generated ID let mut proposal = Proposal::new( None, // id (auto-generated) @@ -16,9 +21,9 @@ fn main() { Utc::now() + Duration::days(14) // vote_end_date (14 days from now) ); - println!("Created Proposal: '{}' (ID: {})", proposal.title, proposal.base_data.id); - println!("Status: {:?}, Vote Status: {:?}", proposal.status, proposal.vote_status); - println!("Vote Period: {} to {}\n", proposal.vote_start_date, proposal.vote_end_date); + println!("Before saving - Created Proposal: '{}' (ID: {})", proposal.title, proposal.base_data.id); + println!("Before saving - Status: {:?}, Vote Status: {:?}", proposal.status, proposal.vote_status); + println!("Before saving - Vote Period: {} to {}\n", proposal.vote_start_date, proposal.vote_end_date); // Add vote options proposal = proposal.add_option(1, "Approve Allocation"); @@ -31,6 +36,16 @@ fn main() { } println!(""); + // Save the proposal to the database + let collection = db.collection::().expect("can open proposal collection"); + let (proposal_id, saved_proposal) = collection.set(&proposal).expect("can save proposal"); + + println!("After saving - Proposal ID: {}", saved_proposal.base_data.id); + println!("After saving - Returned ID: {}", proposal_id); + + // Use the saved proposal for further operations + proposal = saved_proposal; + // Simulate casting votes println!("Simulating Votes..."); // User 1 votes for 'Approve Allocation' with 100 shares (with explicit ballot ID) @@ -83,7 +98,7 @@ fn main() { // Example of a private proposal (not fully implemented in cast_vote eligibility yet) let mut private_proposal = Proposal::new( - Some(2), // explicit ID + None, // auto-generated ID "user_admin_001", "Internal Team Restructure Vote", "Vote on proposed internal team changes.", @@ -94,8 +109,16 @@ fn main() { private_proposal = private_proposal.add_option(1, "Accept Restructure"); private_proposal = private_proposal.add_option(2, "Reject Restructure"); - println!("\nCreated Private Proposal: '{}'", private_proposal.title); - println!("Eligible Voters (Group): {:?}", private_proposal.private_group); + println!("\nBefore saving - Created Private Proposal: '{}'", private_proposal.title); + println!("Before saving - Eligible Voters (Group): {:?}", private_proposal.private_group); + + // Save the private proposal to the database + let (private_proposal_id, saved_private_proposal) = collection.set(&private_proposal).expect("can save private proposal"); + private_proposal = saved_private_proposal; + + println!("After saving - Private Proposal ID: {}", private_proposal.base_data.id); + println!("After saving - Returned ID: {}", private_proposal_id); + // User 10 (eligible) votes with explicit ballot ID private_proposal = private_proposal.cast_vote(Some(201), 10, 1, 100); // User 40 (ineligible) tries to vote with auto-generated ballot ID @@ -106,5 +129,6 @@ fn main() { println!(" - {}: {} (Votes: {})", option.id, option.text, option.count); } - println!("\nGovernance Proposal Example Finished."); + println!("\nExample finished. DB stored at {}", db_path); + println!("To clean up, you can manually delete the directory: {}", db_path); } diff --git a/heromodels/examples/governance_rhai/example.rs b/heromodels/examples/governance_rhai/example.rs index 80e3541..8cd7729 100644 --- a/heromodels/examples/governance_rhai/example.rs +++ b/heromodels/examples/governance_rhai/example.rs @@ -16,62 +16,65 @@ fn main() -> Result<(), Box> { // Register the Proposal type with Rhai // This function is generated by the #[rhai_model_export] attribute Proposal::register_rhai_bindings_for_proposal(&mut engine, db.clone()); - + // Register the Ballot type with Rhai Ballot::register_rhai_bindings_for_ballot(&mut engine, db.clone()); // Register a function to get the database instance engine.register_fn("get_db", move || db.clone()); - + // Register builder functions for Proposal and related types engine.register_fn("create_proposal", |id: i64, creator_id: String, title: String, description: String| { let start_date = Utc::now(); let end_date = start_date + Duration::days(14); - Proposal::new(id as u32, creator_id, title, description, start_date, end_date) + let id_option = if id <= 0 { None } else { Some(id as u32) }; + Proposal::new(id_option, creator_id, title, description, start_date, end_date) }); - + engine.register_fn("create_vote_option", |id: i64, text: String| { VoteOption::new(id as u8, text) }); - + engine.register_fn("create_ballot", |id: i64, user_id: i64, vote_option_id: i64, shares_count: i64| { - Ballot::new(id as u32, user_id as u32, vote_option_id as u8, shares_count) + let id_option = if id <= 0 { None } else { Some(id as u32) }; + Ballot::new(id_option, user_id as u32, vote_option_id as u8, shares_count) }); - + // Register getter and setter methods for Proposal properties engine.register_fn("get_title", |proposal: Proposal| -> String { proposal.title.clone() }); - + engine.register_fn("get_description", |proposal: Proposal| -> String { proposal.description.clone() }); - + engine.register_fn("get_creator_id", |proposal: Proposal| -> String { proposal.creator_id.clone() }); - + engine.register_fn("get_id", |proposal: Proposal| -> i64 { proposal.base_data.id as i64 }); - + engine.register_fn("get_status", |proposal: Proposal| -> String { format!("{:?}", proposal.status) }); - + engine.register_fn("get_vote_status", |proposal: Proposal| -> String { format!("{:?}", proposal.vote_status) }); - + // Register methods for proposal operations engine.register_fn("add_option_to_proposal", |mut proposal: Proposal, option_id: i64, option_text: String| -> Proposal { proposal.add_option(option_id as u8, option_text) }); - + engine.register_fn("cast_vote_on_proposal", |mut proposal: Proposal, ballot_id: i64, user_id: i64, option_id: i64, shares: i64| -> Proposal { - proposal.cast_vote(ballot_id as u32, user_id as u32, option_id as u8, shares) + let ballot_id_option = if ballot_id <= 0 { None } else { Some(ballot_id as u32) }; + proposal.cast_vote(ballot_id_option, user_id as u32, option_id as u8, shares) }); - + engine.register_fn("change_proposal_status", |mut proposal: Proposal, status_str: String| -> Proposal { let new_status = match status_str.as_str() { "Draft" => ProposalStatus::Draft, @@ -83,7 +86,7 @@ fn main() -> Result<(), Box> { }; proposal.change_proposal_status(new_status) }); - + engine.register_fn("change_vote_event_status", |mut proposal: Proposal, status_str: String| -> Proposal { let new_status = match status_str.as_str() { "Open" => VoteEventStatus::Open, @@ -93,49 +96,49 @@ fn main() -> Result<(), Box> { }; proposal.change_vote_event_status(new_status) }); - + // Register functions for database operations engine.register_fn("save_proposal", |_db: Arc, proposal: Proposal| { println!("Proposal saved: {}", proposal.title); }); - + engine.register_fn("get_proposal_by_id", |_db: Arc, id: i64| -> Proposal { // In a real implementation, this would retrieve the proposal from the database let start_date = Utc::now(); let end_date = start_date + Duration::days(14); - Proposal::new(id as u32, "Retrieved Creator", "Retrieved Proposal", "Retrieved Description", start_date, end_date) + Proposal::new(Some(id as u32), "Retrieved Creator", "Retrieved Proposal", "Retrieved Description", start_date, end_date) }); - + // Register a function to check if a proposal exists engine.register_fn("proposal_exists", |_db: Arc, id: i64| -> bool { // In a real implementation, this would check if the proposal exists in the database id == 1 || id == 2 }); - + // Define the function for get_all_proposals fn get_all_proposals(_db: Arc) -> Vec { // In a real implementation, this would retrieve all proposals from the database let start_date = Utc::now(); let end_date = start_date + Duration::days(14); vec![ - Proposal::new(1, "Creator 1", "Proposal 1", "Description 1", start_date, end_date), - Proposal::new(2, "Creator 2", "Proposal 2", "Description 2", start_date, end_date) + Proposal::new(Some(1), "Creator 1", "Proposal 1", "Description 1", start_date, end_date), + Proposal::new(Some(2), "Creator 2", "Proposal 2", "Description 2", start_date, end_date) ] } - + // Register the function with the wrap_vec_return macro engine.register_fn("get_all_proposals", wrap_vec_return!(get_all_proposals, Arc => Proposal)); - + engine.register_fn("delete_proposal_by_id", |_db: Arc, _id: i64| { // In a real implementation, this would delete the proposal from the database println!("Proposal deleted with ID: {}", _id); }); - + // Register helper functions for accessing proposal options and ballots engine.register_fn("get_option_count", |proposal: Proposal| -> i64 { proposal.options.len() as i64 }); - + engine.register_fn("get_option_at", |proposal: Proposal, index: i64| -> VoteOption { if index >= 0 && index < proposal.options.len() as i64 { proposal.options[index as usize].clone() @@ -143,35 +146,35 @@ fn main() -> Result<(), Box> { VoteOption::new(0, "Invalid Option") } }); - + engine.register_fn("get_option_text", |option: VoteOption| -> String { option.text.clone() }); - + engine.register_fn("get_option_votes", |option: VoteOption| -> i64 { option.count }); - + engine.register_fn("get_ballot_count", |proposal: Proposal| -> i64 { proposal.ballots.len() as i64 }); - + engine.register_fn("get_ballot_at", |proposal: Proposal, index: i64| -> Ballot { if index >= 0 && index < proposal.ballots.len() as i64 { proposal.ballots[index as usize].clone() } else { - Ballot::new(0, 0, 0, 0) + Ballot::new(None, 0, 0, 0) } }); - + engine.register_fn("get_ballot_user_id", |ballot: Ballot| -> i64 { ballot.user_id as i64 }); - + engine.register_fn("get_ballot_option_id", |ballot: Ballot| -> i64 { ballot.vote_option_id as i64 }); - + engine.register_fn("get_ballot_shares", |ballot: Ballot| -> i64 { ballot.shares_count }); @@ -179,7 +182,7 @@ fn main() -> Result<(), Box> { // Load and evaluate the Rhai script let script_path = Path::new("examples/governance_rhai/governance.rhai"); let script = fs::read_to_string(script_path)?; - + match engine.eval::<()>(&script) { Ok(_) => println!("Script executed successfully!"), Err(e) => eprintln!("Script execution failed: {}", e), diff --git a/heromodels/examples/governance_rhai/governance.rhai b/heromodels/examples/governance_rhai/governance.rhai index fd3dcaa..4b7d1bb 100644 --- a/heromodels/examples/governance_rhai/governance.rhai +++ b/heromodels/examples/governance_rhai/governance.rhai @@ -1,8 +1,8 @@ // Get the database instance let db = get_db(); -// Create a new proposal -let proposal = create_proposal(1, "user_creator_123", "Community Fund Allocation for Q3", +// Create a new proposal with auto-generated ID (pass 0 for auto-generated ID) +let proposal = create_proposal(0, "user_creator_123", "Community Fund Allocation for Q3", "Proposal to allocate funds for community projects in the third quarter."); print("Created Proposal: '" + get_title(proposal) + "' (ID: " + get_id(proposal) + ")"); @@ -26,14 +26,14 @@ print("\nProposal saved to database"); // Simulate casting votes print("\nSimulating Votes..."); -// User 1 votes for 'Approve Allocation' with 100 shares +// User 1 votes for 'Approve Allocation' with 100 shares (with explicit ballot ID) let proposal_with_votes = cast_vote_on_proposal(proposal_with_options, 101, 1, 1, 100); -// User 2 votes for 'Reject Allocation' with 50 shares +// User 2 votes for 'Reject Allocation' with 50 shares (with explicit ballot ID) proposal_with_votes = cast_vote_on_proposal(proposal_with_votes, 102, 2, 2, 50); -// User 3 votes for 'Approve Allocation' with 75 shares -proposal_with_votes = cast_vote_on_proposal(proposal_with_votes, 103, 3, 1, 75); -// User 4 abstains with 20 shares -proposal_with_votes = cast_vote_on_proposal(proposal_with_votes, 104, 4, 3, 20); +// User 3 votes for 'Approve Allocation' with 75 shares (with auto-generated ballot ID) +proposal_with_votes = cast_vote_on_proposal(proposal_with_votes, 0, 3, 1, 75); +// User 4 abstains with 20 shares (with auto-generated ballot ID) +proposal_with_votes = cast_vote_on_proposal(proposal_with_votes, 0, 4, 3, 20); print("\nVote Counts After Simulation:"); option_count = get_option_count(proposal_with_votes); @@ -46,7 +46,7 @@ print("\nBallots Cast:"); let ballot_count = get_ballot_count(proposal_with_votes); for i in range(0, ballot_count) { let ballot = get_ballot_at(proposal_with_votes, i); - print("- Ballot ID: " + i + ", User ID: " + get_ballot_user_id(ballot) + + print("- Ballot ID: " + i + ", User ID: " + get_ballot_user_id(ballot) + ", Option ID: " + get_ballot_option_id(ballot) + ", Shares: " + get_ballot_shares(ballot)); } diff --git a/heromodels/src/models/finance/account.rs b/heromodels/src/models/finance/account.rs index fe80b6d..3ca8fa5 100644 --- a/heromodels/src/models/finance/account.rs +++ b/heromodels/src/models/finance/account.rs @@ -24,6 +24,7 @@ impl Account { /// Create a new account with auto-generated ID /// /// # Arguments + /// * `id` - Optional ID for the account (use None for auto-generated ID) /// * `name` - Name of the account /// * `user_id` - ID of the user who owns the account /// * `description` - Description of the account @@ -31,6 +32,7 @@ impl Account { /// * `address` - Address of the account on the blockchain /// * `pubkey` - Public key pub fn new( + id: Option, name: impl ToString, user_id: u32, description: impl ToString, @@ -38,8 +40,13 @@ impl Account { address: impl ToString, pubkey: impl ToString ) -> Self { + let mut base_data = BaseModelData::new(); + if let Some(id) = id { + base_data.update_id(id); + } + Self { - base_data: BaseModelData::new(), + base_data, name: name.to_string(), user_id, description: description.to_string(), diff --git a/heromodels/src/models/finance/asset.rs b/heromodels/src/models/finance/asset.rs index 9fd3195..a00f072 100644 --- a/heromodels/src/models/finance/asset.rs +++ b/heromodels/src/models/finance/asset.rs @@ -34,7 +34,17 @@ pub struct Asset { impl Asset { /// Create a new asset with auto-generated ID + /// + /// # Arguments + /// * `id` - Optional ID for the asset (use None for auto-generated ID) + /// * `name` - Name of the asset + /// * `description` - Description of the asset + /// * `amount` - Amount of the asset + /// * `address` - Address of the asset on the blockchain or bank + /// * `asset_type` - Type of the asset + /// * `decimals` - Number of decimals of the asset pub fn new( + id: Option, name: impl ToString, description: impl ToString, amount: f64, @@ -42,8 +52,13 @@ impl Asset { asset_type: AssetType, decimals: u8, ) -> Self { + let mut base_data = BaseModelData::new(); + if let Some(id) = id { + base_data.update_id(id); + } + Self { - base_data: BaseModelData::new(), + base_data, name: name.to_string(), description: description.to_string(), amount, diff --git a/heromodels/src/models/finance/marketplace.rs b/heromodels/src/models/finance/marketplace.rs index d236b3c..34622cc 100644 --- a/heromodels/src/models/finance/marketplace.rs +++ b/heromodels/src/models/finance/marketplace.rs @@ -112,7 +112,22 @@ pub struct Listing { impl Listing { /// Create a new listing with auto-generated ID + /// + /// # Arguments + /// * `id` - Optional ID for the listing (use None for auto-generated ID) + /// * `title` - Title of the listing + /// * `description` - Description of the listing + /// * `asset_id` - ID of the asset being listed + /// * `asset_type` - Type of the asset + /// * `seller_id` - ID of the seller + /// * `price` - Initial price for fixed price, or starting price for auction + /// * `currency` - Currency of the price + /// * `listing_type` - Type of the listing + /// * `expires_at` - Optional expiration date + /// * `tags` - Tags for the listing + /// * `image_url` - Optional image URL pub fn new( + id: Option, title: impl ToString, description: impl ToString, asset_id: impl ToString, @@ -125,8 +140,13 @@ impl Listing { tags: Vec, image_url: Option, ) -> Self { + let mut base_data = BaseModelData::new(); + if let Some(id) = id { + base_data.update_id(id); + } + Self { - base_data: BaseModelData::new(), + base_data, title: title.to_string(), description: description.to_string(), asset_id: asset_id.to_string(), diff --git a/heromodels/src/models/governance/proposal.rs b/heromodels/src/models/governance/proposal.rs index 7e73cb3..ab075d6 100644 --- a/heromodels/src/models/governance/proposal.rs +++ b/heromodels/src/models/governance/proposal.rs @@ -78,12 +78,18 @@ impl Ballot { /// Create a new ballot with auto-generated ID /// /// # Arguments + /// * `id` - Optional ID for the ballot (use None for auto-generated ID) /// * `user_id` - ID of the user who cast this ballot /// * `vote_option_id` - ID of the vote option chosen /// * `shares_count` - Number of shares/tokens/voting power - pub fn new(user_id: u32, vote_option_id: u8, shares_count: i64) -> Self { + pub fn new(id: Option, user_id: u32, vote_option_id: u8, shares_count: i64) -> Self { + let mut base_data = BaseModelData::new(); + if let Some(id) = id { + base_data.update_id(id); + } + Self { - base_data: BaseModelData::new(), + base_data, user_id, vote_option_id, shares_count, @@ -116,14 +122,20 @@ impl Proposal { /// Create a new proposal with auto-generated ID /// /// # Arguments + /// * `id` - Optional ID for the proposal (use None for auto-generated ID) /// * `creator_id` - ID of the user who created the proposal /// * `title` - Title of the proposal /// * `description` - Description of the proposal /// * `vote_start_date` - Date when voting starts /// * `vote_end_date` - Date when voting ends - pub fn new(creator_id: impl ToString, title: impl ToString, description: impl ToString, vote_start_date: DateTime, vote_end_date: DateTime) -> Self { + pub fn new(id: Option, creator_id: impl ToString, title: impl ToString, description: impl ToString, vote_start_date: DateTime, vote_end_date: DateTime) -> Self { + let mut base_data = BaseModelData::new(); + if let Some(id) = id { + base_data.update_id(id); + } + Self { - base_data: BaseModelData::new(), + base_data, creator_id: creator_id.to_string(), title: title.to_string(), description: description.to_string(), @@ -143,7 +155,7 @@ impl Proposal { self } - pub fn cast_vote(mut self, user_id: u32, chosen_option_id: u8, shares: i64) -> Self { + pub fn cast_vote(mut self, ballot_id: Option, user_id: u32, chosen_option_id: u8, shares: i64) -> Self { if self.vote_status != VoteEventStatus::Open { eprintln!("Voting is not open for proposal '{}'", self.title); return self; @@ -159,7 +171,7 @@ impl Proposal { } } - let new_ballot = Ballot::new(user_id, chosen_option_id, shares); + let new_ballot = Ballot::new(ballot_id, user_id, chosen_option_id, shares); self.ballots.push(new_ballot); if let Some(option) = self.options.iter_mut().find(|opt| opt.id == chosen_option_id) { diff --git a/heromodels/temp_calendar_db/data/lookup/.inc b/heromodels/temp_calendar_db/data/lookup/.inc new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/heromodels/temp_calendar_db/data/lookup/.inc @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/heromodels/temp_calendar_db/data/lookup/data b/heromodels/temp_calendar_db/data/lookup/data new file mode 100644 index 0000000000000000000000000000000000000000..fe77ee9d3edc2324ba041f73b723d96d7de022ea GIT binary patch literal 4000000 zcmeIu0Sy2E0K%a6Pi+qe5hx58Fkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK sfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfPwdc0SuD>0RR91 literal 0 HcmV?d00001 diff --git a/heromodels/temp_calendar_db/index/0.db b/heromodels/temp_calendar_db/index/0.db new file mode 100644 index 0000000000000000000000000000000000000000..0ad682e3dd2bf565474087acb8a9ae3a4040d169 GIT binary patch literal 35 VcmZP&V^FxtxSs(G7$FoI2>>>+0nGpa literal 0 HcmV?d00001 diff --git a/heromodels/temp_calendar_db/index/lookup/.inc b/heromodels/temp_calendar_db/index/lookup/.inc new file mode 100644 index 0000000..d8263ee --- /dev/null +++ b/heromodels/temp_calendar_db/index/lookup/.inc @@ -0,0 +1 @@ +2 \ No newline at end of file diff --git a/heromodels/temp_calendar_db/index/lookup/data b/heromodels/temp_calendar_db/index/lookup/data new file mode 100644 index 0000000000000000000000000000000000000000..92d764d22ee42034d4f280bb0e7a296f3f868902 GIT binary patch literal 4000000 zcmeIu!3_Wa2m&$Oe`yok!j#AHZ_>@35+Fc;009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 y2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk9|aZ}9{>UX literal 0 HcmV?d00001 diff --git a/heromodels/temp_governance_db/data/lookup/.inc b/heromodels/temp_governance_db/data/lookup/.inc new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/heromodels/temp_governance_db/data/lookup/.inc @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/heromodels/temp_governance_db/data/lookup/data b/heromodels/temp_governance_db/data/lookup/data new file mode 100644 index 0000000000000000000000000000000000000000..fe77ee9d3edc2324ba041f73b723d96d7de022ea GIT binary patch literal 4000000 zcmeIu0Sy2E0K%a6Pi+qe5hx58Fkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK sfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfPwdc0SuD>0RR91 literal 0 HcmV?d00001 diff --git a/heromodels/temp_governance_db/index/0.db b/heromodels/temp_governance_db/index/0.db new file mode 100644 index 0000000000000000000000000000000000000000..0ad682e3dd2bf565474087acb8a9ae3a4040d169 GIT binary patch literal 35 VcmZP&V^FxtxSs(G7$FoI2>>>+0nGpa literal 0 HcmV?d00001 diff --git a/heromodels/temp_governance_db/index/lookup/.inc b/heromodels/temp_governance_db/index/lookup/.inc new file mode 100644 index 0000000..d8263ee --- /dev/null +++ b/heromodels/temp_governance_db/index/lookup/.inc @@ -0,0 +1 @@ +2 \ No newline at end of file diff --git a/heromodels/temp_governance_db/index/lookup/data b/heromodels/temp_governance_db/index/lookup/data new file mode 100644 index 0000000000000000000000000000000000000000..92d764d22ee42034d4f280bb0e7a296f3f868902 GIT binary patch literal 4000000 zcmeIu!3_Wa2m&$Oe`yok!j#AHZ_>@35+Fc;009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 y2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk9|aZ}9{>UX literal 0 HcmV?d00001 From bd36d6bda0f5259703585f789bded7e11436bb55 Mon Sep 17 00:00:00 2001 From: Mahmoud Emad Date: Sat, 17 May 2025 16:10:37 +0300 Subject: [PATCH 05/11] test: Add shell script to run all examples --- heromodels/run_all_examples.sh | 81 ++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100755 heromodels/run_all_examples.sh diff --git a/heromodels/run_all_examples.sh b/heromodels/run_all_examples.sh new file mode 100755 index 0000000..69d4a7d --- /dev/null +++ b/heromodels/run_all_examples.sh @@ -0,0 +1,81 @@ +#!/bin/bash + +# Script to run all examples in the heromodels crate and check if they executed successfully + +# Set colors for output +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[0;33m' +NC='\033[0m' # No Color + +# Function to run an example and check if it executed successfully +run_example() { + local example=$1 + echo -e "${YELLOW}Running example: ${example}${NC}" + + # Run the example and capture the exit code + cargo run --example $example + local exit_code=$? + + # Check if the example executed successfully + if [ $exit_code -eq 0 ]; then + echo -e "${GREEN}✓ Example ${example} executed successfully${NC}" + return 0 + else + echo -e "${RED}✗ Example ${example} failed with exit code ${exit_code}${NC}" + return 1 + fi +} + +# Make sure we're in the heromodels directory +cd "$(dirname "$0")" + +# Get all examples +examples=$(find examples -type f -name "*.rs" -not -path "*/*/main.rs" -not -path "*/*/example.rs" | grep -v "/mod.rs$" | sed 's/examples\///g' | sed 's/\.rs$//g' | sort) + +# Add directory examples +dir_examples=$(find examples -type d -depth 1 | sed 's/examples\///g' | sort) + +# Initialize counters +total=0 +passed=0 +failed=0 + +# Run all file examples +echo -e "${YELLOW}Running file examples...${NC}" +for example in $examples; do + ((total++)) + if run_example $example; then + ((passed++)) + else + ((failed++)) + failed_examples="$failed_examples $example" + fi + echo "" +done + +# Run all directory examples +echo -e "${YELLOW}Running directory examples...${NC}" +for example in $dir_examples; do + ((total++)) + if run_example $example; then + ((passed++)) + else + ((failed++)) + failed_examples="$failed_examples $example" + fi + echo "" +done + +# Print summary +echo -e "${YELLOW}Summary:${NC}" +echo -e "Total examples: ${total}" +echo -e "${GREEN}Passed: ${passed}${NC}" +if [ $failed -gt 0 ]; then + echo -e "${RED}Failed: ${failed}${NC}" + echo -e "${RED}Failed examples:${failed_examples}${NC}" + exit 1 +else + echo -e "${GREEN}All examples executed successfully!${NC}" + exit 0 +fi From 4c0c7be57401ea76f8a666f332ec6f2a49eef2ad Mon Sep 17 00:00:00 2001 From: Mahmoud-Emad Date: Wed, 21 May 2025 09:13:58 +0300 Subject: [PATCH 06/11] feat: Add get_all method to list all db objects based on the object model --- .../governance_proposal_example/main.rs | 116 ++++++++++++++---- heromodels/src/db/hero.rs | 68 +++++++--- heromodels/src/models/calendar/calendar.rs | 17 ++- heromodels/src/models/finance/account.rs | 6 +- heromodels/src/models/finance/asset.rs | 16 +-- heromodels/src/models/finance/marketplace.rs | 28 +++-- heromodels/src/models/governance/proposal.rs | 61 ++++++--- 7 files changed, 225 insertions(+), 87 deletions(-) diff --git a/heromodels/examples/governance_proposal_example/main.rs b/heromodels/examples/governance_proposal_example/main.rs index 642a9bb..f6ad994 100644 --- a/heromodels/examples/governance_proposal_example/main.rs +++ b/heromodels/examples/governance_proposal_example/main.rs @@ -1,6 +1,6 @@ // heromodels/examples/governance_proposal_example/main.rs -use chrono::{Utc, Duration}; +use chrono::{Duration, Utc}; use heromodels::db::{Collection, Db}; use heromodels::models::governance::{Proposal, ProposalStatus, VoteEventStatus}; @@ -13,17 +13,26 @@ fn main() { // Create a new proposal with auto-generated ID let mut proposal = Proposal::new( - None, // id (auto-generated) - "user_creator_123", // creator_id - "Community Fund Allocation for Q3", // title + None, // id (auto-generated) + "user_creator_123", // creator_id + "Community Fund Allocation for Q3", // title "Proposal to allocate funds for community projects in the third quarter.", // description - Utc::now(), // vote_start_date - Utc::now() + Duration::days(14) // vote_end_date (14 days from now) + Utc::now(), // vote_start_date + Utc::now() + Duration::days(14), // vote_end_date (14 days from now) ); - println!("Before saving - Created Proposal: '{}' (ID: {})", proposal.title, proposal.base_data.id); - println!("Before saving - Status: {:?}, Vote Status: {:?}", proposal.status, proposal.vote_status); - println!("Before saving - Vote Period: {} to {}\n", proposal.vote_start_date, proposal.vote_end_date); + println!( + "Before saving - Created Proposal: '{}' (ID: {})", + proposal.title, proposal.base_data.id + ); + println!( + "Before saving - Status: {:?}, Vote Status: {:?}", + proposal.status, proposal.vote_status + ); + println!( + "Before saving - Vote Period: {} to {}\n", + proposal.vote_start_date, proposal.vote_end_date + ); // Add vote options proposal = proposal.add_option(1, "Approve Allocation"); @@ -32,15 +41,23 @@ fn main() { println!("Added Vote Options:"); for option in &proposal.options { - println!("- Option ID: {}, Text: '{}', Votes: {}", option.id, option.text, option.count); + println!( + "- Option ID: {}, Text: '{}', Votes: {}", + option.id, option.text, option.count + ); } println!(""); // Save the proposal to the database - let collection = db.collection::().expect("can open proposal collection"); + let collection = db + .collection::() + .expect("can open proposal collection"); let (proposal_id, saved_proposal) = collection.set(&proposal).expect("can save proposal"); - println!("After saving - Proposal ID: {}", saved_proposal.base_data.id); + println!( + "After saving - Proposal ID: {}", + saved_proposal.base_data.id + ); println!("After saving - Returned ID: {}", proposal_id); // Use the saved proposal for further operations @@ -63,13 +80,18 @@ fn main() { println!("\nVote Counts After Simulation:"); for option in &proposal.options { - println!("- Option ID: {}, Text: '{}', Votes: {}", option.id, option.text, option.count); + println!( + "- Option ID: {}, Text: '{}', Votes: {}", + option.id, option.text, option.count + ); } println!("\nBallots Cast:"); for ballot in &proposal.ballots { - println!("- Ballot ID: {}, User ID: {}, Option ID: {}, Shares: {}", - ballot.base_data.id, ballot.user_id, ballot.vote_option_id, ballot.shares_count); + println!( + "- Ballot ID: {}, User ID: {}, Option ID: {}, Shares: {}", + ballot.base_data.id, ballot.user_id, ballot.vote_option_id, ballot.shares_count + ); } println!(""); @@ -92,7 +114,10 @@ fn main() { println!("Vote Status: {:?}", proposal.vote_status); println!("Options:"); for option in &proposal.options { - println!(" - {}: {} (Votes: {})", option.id, option.text, option.count); + println!( + " - {}: {} (Votes: {})", + option.id, option.text, option.count + ); } println!("Total Ballots: {}", proposal.ballots.len()); @@ -103,20 +128,31 @@ fn main() { "Internal Team Restructure Vote", "Vote on proposed internal team changes.", Utc::now(), - Utc::now() + Duration::days(7) + Utc::now() + Duration::days(7), ); private_proposal.private_group = Some(vec![10, 20, 30]); // Only users 10, 20, 30 can vote private_proposal = private_proposal.add_option(1, "Accept Restructure"); private_proposal = private_proposal.add_option(2, "Reject Restructure"); - println!("\nBefore saving - Created Private Proposal: '{}'", private_proposal.title); - println!("Before saving - Eligible Voters (Group): {:?}", private_proposal.private_group); + println!( + "\nBefore saving - Created Private Proposal: '{}'", + private_proposal.title + ); + println!( + "Before saving - Eligible Voters (Group): {:?}", + private_proposal.private_group + ); // Save the private proposal to the database - let (private_proposal_id, saved_private_proposal) = collection.set(&private_proposal).expect("can save private proposal"); + let (private_proposal_id, saved_private_proposal) = collection + .set(&private_proposal) + .expect("can save private proposal"); private_proposal = saved_private_proposal; - println!("After saving - Private Proposal ID: {}", private_proposal.base_data.id); + println!( + "After saving - Private Proposal ID: {}", + private_proposal.base_data.id + ); println!("After saving - Returned ID: {}", private_proposal_id); // User 10 (eligible) votes with explicit ballot ID @@ -125,10 +161,42 @@ fn main() { private_proposal = private_proposal.cast_vote(None, 40, 1, 50); println!("Private Proposal Vote Counts:"); - for option in &private_proposal.options { - println!(" - {}: {} (Votes: {})", option.id, option.text, option.count); + for option in &private_proposal.options { + println!( + " - {}: {} (Votes: {})", + option.id, option.text, option.count + ); } println!("\nExample finished. DB stored at {}", db_path); - println!("To clean up, you can manually delete the directory: {}", db_path); + println!( + "To clean up, you can manually delete the directory: {}", + db_path + ); + + // --- Additional Example: Listing and Filtering Proposals --- + println!("\n--- Listing All Proposals ---"); + // List all proposals from the DB + let all_proposals = collection.get_all().expect("can list all proposals"); + for proposal in &all_proposals { + println!( + "- Proposal ID: {}, Title: '{}', Status: {:?}", + proposal.base_data.id, proposal.title, proposal.status + ); + } + println!("Total proposals in DB: {}", all_proposals.len()); + + // Filter proposals by status (e.g., only Active proposals) + let active_proposals: Vec<_> = all_proposals + .iter() + .filter(|p| p.status == ProposalStatus::Active) + .collect(); + println!("\n--- Filtering Proposals by Status: Active ---"); + for proposal in &active_proposals { + println!( + "- Proposal ID: {}, Title: '{}', Status: {:?}", + proposal.base_data.id, proposal.title, proposal.status + ); + } + println!("Total ACTIVE proposals: {}", active_proposals.len()); } diff --git a/heromodels/src/db/hero.rs b/heromodels/src/db/hero.rs index 70b7f4d..d9fff2a 100644 --- a/heromodels/src/db/hero.rs +++ b/heromodels/src/db/hero.rs @@ -1,13 +1,16 @@ +use crate::db::Transaction; use heromodels_core::{Index, Model}; use ourdb::OurDBSetArgs; use serde::Deserialize; -use crate::db::Transaction; use std::{ borrow::Borrow, collections::HashSet, path::PathBuf, - sync::{Arc, Mutex, atomic::{AtomicU32, Ordering}}, + sync::{ + Arc, Mutex, + atomic::{AtomicU32, Ordering}, + }, }; /// Configuration for custom ID sequences @@ -85,7 +88,11 @@ impl OurDB { } /// Create a new instance of ourdb with a custom ID sequence - pub fn with_id_sequence(path: impl Into, reset: bool, id_sequence: IdSequence) -> Result { + pub fn with_id_sequence( + path: impl Into, + reset: bool, + id_sequence: IdSequence, + ) -> Result { let mut base_path = path.into(); let mut data_path = base_path.clone(); base_path.push("index"); @@ -138,7 +145,9 @@ where type Error = tst::Error; /// Begin a transaction for this collection - fn begin_transaction(&self) -> Result>, super::Error> { + fn begin_transaction( + &self, + ) -> Result>, super::Error> { // Create a new transaction let transaction = OurDBTransaction::new(); @@ -287,7 +296,7 @@ where // and save it again to ensure the serialized data contains the correct ID let mut value_clone = value.clone(); let base_data = value_clone.base_data_mut(); - base_data.update_id(assigned_id); + base_data.id = assigned_id; // Serialize the updated model let v = bincode::serde::encode_to_vec(&value_clone, BINCODE_CONFIG)?; @@ -327,10 +336,13 @@ where } // Get the updated model from the database - let updated_model = Self::get_ourdb_value::(&mut data_db, assigned_id)? - .ok_or_else(|| super::Error::InvalidId(format!( - "Failed to retrieve model with ID {} after saving", assigned_id - )))?; + let updated_model = + Self::get_ourdb_value::(&mut data_db, assigned_id)?.ok_or_else(|| { + super::Error::InvalidId(format!( + "Failed to retrieve model with ID {} after saving", + assigned_id + )) + })?; // Return the assigned ID and the updated model Ok((assigned_id, updated_model)) @@ -413,7 +425,18 @@ where } fn get_all(&self) -> Result, super::Error> { - todo!("OurDB doesn't have a list all method yet") + let mut data_db = self.data.lock().expect("can lock data DB"); + let mut all_objs = Vec::new(); + // Get the next available ID (exclusive upper bound) + let next_id = data_db.get_next_id().map_err(super::Error::from)?; + for id in 1..next_id { + match Self::get_ourdb_value::(&mut data_db, id) { + Ok(Some(obj)) => all_objs.push(obj), + Ok(None) => continue, // skip missing IDs + Err(e) => return Err(e), + } + } + Ok(all_objs) } } @@ -510,7 +533,9 @@ struct OurDBTransaction { impl OurDBTransaction { /// Create a new transaction fn new() -> Self { - Self { active: std::sync::atomic::AtomicBool::new(false) } + Self { + active: std::sync::atomic::AtomicBool::new(false), + } } } @@ -519,8 +544,11 @@ impl Drop for OurDBTransaction { // If the transaction is still active when dropped, roll it back if self.active.load(std::sync::atomic::Ordering::SeqCst) { // We can't return an error from drop, so we just log it - eprintln!("Warning: Transaction was dropped without being committed or rolled back. Rolling back automatically."); - self.active.store(false, std::sync::atomic::Ordering::SeqCst); + eprintln!( + "Warning: Transaction was dropped without being committed or rolled back. Rolling back automatically." + ); + self.active + .store(false, std::sync::atomic::Ordering::SeqCst); } } } @@ -541,10 +569,13 @@ impl super::Transaction for OurDBTransaction { // In a real implementation, you would commit the transaction in the underlying database // For now, we just check if the transaction is active if !self.active.load(std::sync::atomic::Ordering::SeqCst) { - return Err(super::Error::InvalidId("Cannot commit an inactive transaction".to_string())); + return Err(super::Error::InvalidId( + "Cannot commit an inactive transaction".to_string(), + )); } - self.active.store(false, std::sync::atomic::Ordering::SeqCst); + self.active + .store(false, std::sync::atomic::Ordering::SeqCst); Ok(()) } @@ -553,10 +584,13 @@ impl super::Transaction for OurDBTransaction { // In a real implementation, you would roll back the transaction in the underlying database // For now, we just check if the transaction is active if !self.active.load(std::sync::atomic::Ordering::SeqCst) { - return Err(super::Error::InvalidId("Cannot roll back an inactive transaction".to_string())); + return Err(super::Error::InvalidId( + "Cannot roll back an inactive transaction".to_string(), + )); } - self.active.store(false, std::sync::atomic::Ordering::SeqCst); + self.active + .store(false, std::sync::atomic::Ordering::SeqCst); Ok(()) } diff --git a/heromodels/src/models/calendar/calendar.rs b/heromodels/src/models/calendar/calendar.rs index f03e4b6..900cd9d 100644 --- a/heromodels/src/models/calendar/calendar.rs +++ b/heromodels/src/models/calendar/calendar.rs @@ -1,9 +1,9 @@ use chrono::{DateTime, Utc}; use heromodels_core::BaseModelData; use heromodels_derive::model; -use serde::{Deserialize, Serialize}; -use rhai_autobind_macros::rhai_model_export; use rhai::{CustomType, TypeBuilder}; +use rhai_autobind_macros::rhai_model_export; +use serde::{Deserialize, Serialize}; /// Represents the status of an attendee for an event #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -60,7 +60,12 @@ pub struct Event { impl Event { /// Creates a new event - pub fn new(id: String, title: impl ToString, start_time: DateTime, end_time: DateTime) -> Self { + pub fn new( + id: String, + title: impl ToString, + start_time: DateTime, + end_time: DateTime, + ) -> Self { Self { id, title: title.to_string(), @@ -108,7 +113,11 @@ impl Event { } /// Reschedules the event to new start and end times - pub fn reschedule(mut self, new_start_time: DateTime, new_end_time: DateTime) -> Self { + pub fn reschedule( + mut self, + new_start_time: DateTime, + new_end_time: DateTime, + ) -> Self { // Basic validation: end_time should be after start_time if new_end_time > new_start_time { self.start_time = new_start_time; diff --git a/heromodels/src/models/finance/account.rs b/heromodels/src/models/finance/account.rs index 3ca8fa5..d764643 100644 --- a/heromodels/src/models/finance/account.rs +++ b/heromodels/src/models/finance/account.rs @@ -1,8 +1,8 @@ // heromodels/src/models/finance/account.rs -use serde::{Deserialize, Serialize}; -use heromodels_derive::model; use heromodels_core::BaseModelData; +use heromodels_derive::model; +use serde::{Deserialize, Serialize}; use super::asset::Asset; @@ -38,7 +38,7 @@ impl Account { description: impl ToString, ledger: impl ToString, address: impl ToString, - pubkey: impl ToString + pubkey: impl ToString, ) -> Self { let mut base_data = BaseModelData::new(); if let Some(id) = id { diff --git a/heromodels/src/models/finance/asset.rs b/heromodels/src/models/finance/asset.rs index a00f072..29f6ec7 100644 --- a/heromodels/src/models/finance/asset.rs +++ b/heromodels/src/models/finance/asset.rs @@ -1,8 +1,8 @@ // heromodels/src/models/finance/asset.rs -use serde::{Deserialize, Serialize}; -use heromodels_derive::model; use heromodels_core::BaseModelData; +use heromodels_derive::model; +use serde::{Deserialize, Serialize}; /// AssetType defines the type of blockchain asset #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -24,12 +24,12 @@ impl Default for AssetType { #[model] // Has base.Base in V spec pub struct Asset { pub base_data: BaseModelData, - pub name: String, // Name of the asset - pub description: String, // Description of the asset - pub amount: f64, // Amount of the asset - pub address: String, // Address of the asset on the blockchain or bank - pub asset_type: AssetType, // Type of the asset - pub decimals: u8, // Number of decimals of the asset + pub name: String, // Name of the asset + pub description: String, // Description of the asset + pub amount: f64, // Amount of the asset + pub address: String, // Address of the asset on the blockchain or bank + pub asset_type: AssetType, // Type of the asset + pub decimals: u8, // Number of decimals of the asset } impl Asset { diff --git a/heromodels/src/models/finance/marketplace.rs b/heromodels/src/models/finance/marketplace.rs index 34622cc..6382b48 100644 --- a/heromodels/src/models/finance/marketplace.rs +++ b/heromodels/src/models/finance/marketplace.rs @@ -1,9 +1,9 @@ // heromodels/src/models/finance/marketplace.rs -use serde::{Deserialize, Serialize}; -use heromodels_derive::model; -use heromodels_core::BaseModelData; use chrono::{DateTime, Utc}; +use heromodels_core::BaseModelData; +use heromodels_derive::model; +use serde::{Deserialize, Serialize}; use super::asset::AssetType; @@ -54,11 +54,11 @@ impl Default for BidStatus { /// Bid represents a bid on an auction listing #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Bid { - pub listing_id: String, // ID of the listing this bid belongs to - pub bidder_id: u32, // ID of the user who placed the bid - pub amount: f64, // Bid amount - pub currency: String, // Currency of the bid - pub status: BidStatus, // Status of the bid + pub listing_id: String, // ID of the listing this bid belongs to + pub bidder_id: u32, // ID of the user who placed the bid + pub amount: f64, // Bid amount + pub currency: String, // Currency of the bid + pub status: BidStatus, // Status of the bid pub created_at: DateTime, // When the bid was created } @@ -97,7 +97,7 @@ pub struct Listing { pub asset_id: String, pub asset_type: AssetType, pub seller_id: String, - pub price: f64, // Initial price for fixed price, or starting price for auction + pub price: f64, // Initial price for fixed price, or starting price for auction pub currency: String, pub listing_type: ListingType, pub status: ListingStatus, @@ -210,7 +210,11 @@ impl Listing { } /// Complete a sale (fixed price or auction) - pub fn complete_sale(mut self, buyer_id: impl ToString, sale_price: f64) -> Result { + pub fn complete_sale( + mut self, + buyer_id: impl ToString, + sale_price: f64, + ) -> Result { if self.status != ListingStatus::Active { return Err("Cannot complete sale for inactive listing"); } @@ -223,7 +227,9 @@ impl Listing { // If this was an auction, accept the winning bid and reject others if self.listing_type == ListingType::Auction { for bid in &mut self.bids { - if bid.bidder_id.to_string() == self.buyer_id.as_ref().unwrap().to_string() && bid.amount == sale_price { + if bid.bidder_id.to_string() == self.buyer_id.as_ref().unwrap().to_string() + && bid.amount == sale_price + { bid.status = BidStatus::Accepted; } else { bid.status = BidStatus::Rejected; diff --git a/heromodels/src/models/governance/proposal.rs b/heromodels/src/models/governance/proposal.rs index ab075d6..bf207e5 100644 --- a/heromodels/src/models/governance/proposal.rs +++ b/heromodels/src/models/governance/proposal.rs @@ -1,10 +1,10 @@ // heromodels/src/models/governance/proposal.rs use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; use heromodels_derive::model; // For #[model] -use rhai_autobind_macros::rhai_model_export; use rhai::{CustomType, TypeBuilder}; +use rhai_autobind_macros::rhai_model_export; +use serde::{Deserialize, Serialize}; use heromodels_core::BaseModelData; @@ -13,11 +13,11 @@ use heromodels_core::BaseModelData; /// ProposalStatus defines the lifecycle status of a governance proposal itself #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum ProposalStatus { - Draft, // Proposal is being prepared - Active, // Proposal is active - Approved, // Proposal has been formally approved - Rejected, // Proposal has been formally rejected - Cancelled,// Proposal was cancelled + Draft, // Proposal is being prepared + Active, // Proposal is active + Approved, // Proposal has been formally approved + Rejected, // Proposal has been formally rejected + Cancelled, // Proposal was cancelled } impl Default for ProposalStatus { @@ -26,7 +26,6 @@ impl Default for ProposalStatus { } } - /// VoteEventStatus represents the status of the voting process for a proposal #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum VoteEventStatus { @@ -46,9 +45,9 @@ impl Default for VoteEventStatus { /// VoteOption represents a specific choice that can be voted on #[derive(Debug, Clone, Serialize, Deserialize, CustomType)] pub struct VoteOption { - pub id: u8, // Simple identifier for this option - pub text: String, // Descriptive text of the option - pub count: i64, // How many votes this option has received + pub id: u8, // Simple identifier for this option + pub text: String, // Descriptive text of the option + pub count: i64, // How many votes this option has received pub min_valid: Option, // Optional: minimum votes needed } @@ -69,9 +68,9 @@ impl VoteOption { #[model] // Has base.Base in V spec pub struct Ballot { pub base_data: BaseModelData, - pub user_id: u32, // The ID of the user who cast this ballot - pub vote_option_id: u8, // The 'id' of the VoteOption chosen - pub shares_count: i64, // Number of shares/tokens/voting power + pub user_id: u32, // The ID of the user who cast this ballot + pub vote_option_id: u8, // The 'id' of the VoteOption chosen + pub shares_count: i64, // Number of shares/tokens/voting power } impl Ballot { @@ -97,7 +96,6 @@ impl Ballot { } } - /// Proposal represents a governance proposal that can be voted upon. #[derive(Debug, Clone, Serialize, Deserialize, CustomType)] #[rhai_model_export(db_type = "std::sync::Arc")] @@ -128,7 +126,14 @@ impl Proposal { /// * `description` - Description of the proposal /// * `vote_start_date` - Date when voting starts /// * `vote_end_date` - Date when voting ends - pub fn new(id: Option, creator_id: impl ToString, title: impl ToString, description: impl ToString, vote_start_date: DateTime, vote_end_date: DateTime) -> Self { + pub fn new( + id: Option, + creator_id: impl ToString, + title: impl ToString, + description: impl ToString, + vote_start_date: DateTime, + vote_end_date: DateTime, + ) -> Self { let mut base_data = BaseModelData::new(); if let Some(id) = id { base_data.update_id(id); @@ -155,18 +160,30 @@ impl Proposal { self } - pub fn cast_vote(mut self, ballot_id: Option, user_id: u32, chosen_option_id: u8, shares: i64) -> Self { + pub fn cast_vote( + mut self, + ballot_id: Option, + user_id: u32, + chosen_option_id: u8, + shares: i64, + ) -> Self { if self.vote_status != VoteEventStatus::Open { eprintln!("Voting is not open for proposal '{}'", self.title); return self; } if !self.options.iter().any(|opt| opt.id == chosen_option_id) { - eprintln!("Chosen option ID {} does not exist for proposal '{}'", chosen_option_id, self.title); + eprintln!( + "Chosen option ID {} does not exist for proposal '{}'", + chosen_option_id, self.title + ); return self; } if let Some(group) = &self.private_group { if !group.contains(&user_id) { - eprintln!("User {} is not eligible to vote on proposal '{}'", user_id, self.title); + eprintln!( + "User {} is not eligible to vote on proposal '{}'", + user_id, self.title + ); return self; } } @@ -174,7 +191,11 @@ impl Proposal { let new_ballot = Ballot::new(ballot_id, user_id, chosen_option_id, shares); self.ballots.push(new_ballot); - if let Some(option) = self.options.iter_mut().find(|opt| opt.id == chosen_option_id) { + if let Some(option) = self + .options + .iter_mut() + .find(|opt| opt.id == chosen_option_id) + { option.count += shares; } self From 5327d1f00ccd4c808862cbf10711ac95a1e4d3c0 Mon Sep 17 00:00:00 2001 From: Mahmoud-Emad Date: Wed, 21 May 2025 09:37:45 +0300 Subject: [PATCH 07/11] fix: Add creator_name field to the proposal model --- .../governance_proposal_example/main.rs | 12 +- .../examples/governance_rhai/example.rs | 202 ++++++++++++------ heromodels/src/models/governance/proposal.rs | 6 +- 3 files changed, 152 insertions(+), 68 deletions(-) diff --git a/heromodels/examples/governance_proposal_example/main.rs b/heromodels/examples/governance_proposal_example/main.rs index f6ad994..9b75c9d 100644 --- a/heromodels/examples/governance_proposal_example/main.rs +++ b/heromodels/examples/governance_proposal_example/main.rs @@ -13,12 +13,13 @@ fn main() { // Create a new proposal with auto-generated ID let mut proposal = Proposal::new( - None, // id (auto-generated) - "user_creator_123", // creator_id - "Community Fund Allocation for Q3", // title + None, // id (auto-generated) + "user_creator_123", + "Ahmed fared", // creator_id + "Community Fund Allocation for Q3", // title "Proposal to allocate funds for community projects in the third quarter.", // description - Utc::now(), // vote_start_date - Utc::now() + Duration::days(14), // vote_end_date (14 days from now) + Utc::now(), // vote_start_date + Utc::now() + Duration::days(14), // vote_end_date (14 days from now) ); println!( @@ -125,6 +126,7 @@ fn main() { let mut private_proposal = Proposal::new( None, // auto-generated ID "user_admin_001", + "Wael Ghonem", "Internal Team Restructure Vote", "Vote on proposed internal team changes.", Utc::now(), diff --git a/heromodels/examples/governance_rhai/example.rs b/heromodels/examples/governance_rhai/example.rs index 8cd7729..65a9089 100644 --- a/heromodels/examples/governance_rhai/example.rs +++ b/heromodels/examples/governance_rhai/example.rs @@ -1,10 +1,12 @@ +use chrono::{Duration, Utc}; use heromodels::db::hero::OurDB; -use heromodels::models::governance::{Proposal, ProposalStatus, VoteEventStatus, VoteOption, Ballot}; +use heromodels::models::governance::{ + Ballot, Proposal, ProposalStatus, VoteEventStatus, VoteOption, +}; use rhai::Engine; use rhai_wrapper::wrap_vec_return; use std::sync::Arc; use std::{fs, path::Path}; -use chrono::{Utc, Duration}; fn main() -> Result<(), Box> { // Initialize Rhai engine @@ -24,21 +26,40 @@ fn main() -> Result<(), Box> { engine.register_fn("get_db", move || db.clone()); // Register builder functions for Proposal and related types - engine.register_fn("create_proposal", |id: i64, creator_id: String, title: String, description: String| { - let start_date = Utc::now(); - let end_date = start_date + Duration::days(14); - let id_option = if id <= 0 { None } else { Some(id as u32) }; - Proposal::new(id_option, creator_id, title, description, start_date, end_date) - }); + engine.register_fn( + "create_proposal", + |id: i64, creator_id: String, creator_name: String, title: String, description: String| { + let start_date = Utc::now(); + let end_date = start_date + Duration::days(14); + let id_option = if id <= 0 { None } else { Some(id as u32) }; + Proposal::new( + id_option, + creator_id, + creator_name, + title, + description, + start_date, + end_date, + ) + }, + ); engine.register_fn("create_vote_option", |id: i64, text: String| { VoteOption::new(id as u8, text) }); - engine.register_fn("create_ballot", |id: i64, user_id: i64, vote_option_id: i64, shares_count: i64| { - let id_option = if id <= 0 { None } else { Some(id as u32) }; - Ballot::new(id_option, user_id as u32, vote_option_id as u8, shares_count) - }); + engine.register_fn( + "create_ballot", + |id: i64, user_id: i64, vote_option_id: i64, shares_count: i64| { + let id_option = if id <= 0 { None } else { Some(id as u32) }; + Ballot::new( + id_option, + user_id as u32, + vote_option_id as u8, + shares_count, + ) + }, + ); // Register getter and setter methods for Proposal properties engine.register_fn("get_title", |proposal: Proposal| -> String { @@ -66,48 +87,80 @@ fn main() -> Result<(), Box> { }); // Register methods for proposal operations - engine.register_fn("add_option_to_proposal", |mut proposal: Proposal, option_id: i64, option_text: String| -> Proposal { - proposal.add_option(option_id as u8, option_text) - }); + engine.register_fn( + "add_option_to_proposal", + |mut proposal: Proposal, option_id: i64, option_text: String| -> Proposal { + proposal.add_option(option_id as u8, option_text) + }, + ); - engine.register_fn("cast_vote_on_proposal", |mut proposal: Proposal, ballot_id: i64, user_id: i64, option_id: i64, shares: i64| -> Proposal { - let ballot_id_option = if ballot_id <= 0 { None } else { Some(ballot_id as u32) }; - proposal.cast_vote(ballot_id_option, user_id as u32, option_id as u8, shares) - }); + engine.register_fn( + "cast_vote_on_proposal", + |mut proposal: Proposal, + ballot_id: i64, + user_id: i64, + option_id: i64, + shares: i64| + -> Proposal { + let ballot_id_option = if ballot_id <= 0 { + None + } else { + Some(ballot_id as u32) + }; + proposal.cast_vote(ballot_id_option, user_id as u32, option_id as u8, shares) + }, + ); - engine.register_fn("change_proposal_status", |mut proposal: Proposal, status_str: String| -> Proposal { - let new_status = match status_str.as_str() { - "Draft" => ProposalStatus::Draft, - "Active" => ProposalStatus::Active, - "Approved" => ProposalStatus::Approved, - "Rejected" => ProposalStatus::Rejected, - "Cancelled" => ProposalStatus::Cancelled, - _ => ProposalStatus::Draft, - }; - proposal.change_proposal_status(new_status) - }); + engine.register_fn( + "change_proposal_status", + |mut proposal: Proposal, status_str: String| -> Proposal { + let new_status = match status_str.as_str() { + "Draft" => ProposalStatus::Draft, + "Active" => ProposalStatus::Active, + "Approved" => ProposalStatus::Approved, + "Rejected" => ProposalStatus::Rejected, + "Cancelled" => ProposalStatus::Cancelled, + _ => ProposalStatus::Draft, + }; + proposal.change_proposal_status(new_status) + }, + ); - engine.register_fn("change_vote_event_status", |mut proposal: Proposal, status_str: String| -> Proposal { - let new_status = match status_str.as_str() { - "Open" => VoteEventStatus::Open, - "Closed" => VoteEventStatus::Closed, - "Cancelled" => VoteEventStatus::Cancelled, - _ => VoteEventStatus::Open, - }; - proposal.change_vote_event_status(new_status) - }); + engine.register_fn( + "change_vote_event_status", + |mut proposal: Proposal, status_str: String| -> Proposal { + let new_status = match status_str.as_str() { + "Open" => VoteEventStatus::Open, + "Closed" => VoteEventStatus::Closed, + "Cancelled" => VoteEventStatus::Cancelled, + _ => VoteEventStatus::Open, + }; + proposal.change_vote_event_status(new_status) + }, + ); // Register functions for database operations engine.register_fn("save_proposal", |_db: Arc, proposal: Proposal| { println!("Proposal saved: {}", proposal.title); }); - engine.register_fn("get_proposal_by_id", |_db: Arc, id: i64| -> Proposal { - // In a real implementation, this would retrieve the proposal from the database - let start_date = Utc::now(); - let end_date = start_date + Duration::days(14); - Proposal::new(Some(id as u32), "Retrieved Creator", "Retrieved Proposal", "Retrieved Description", start_date, end_date) - }); + engine.register_fn( + "get_proposal_by_id", + |_db: Arc, id: i64| -> Proposal { + // In a real implementation, this would retrieve the proposal from the database + let start_date = Utc::now(); + let end_date = start_date + Duration::days(14); + Proposal::new( + Some(id as u32), + "Retrieved Creator", + "Retrieved Creator Name", + "Retrieved Proposal", + "Retrieved Description", + start_date, + end_date, + ) + }, + ); // Register a function to check if a proposal exists engine.register_fn("proposal_exists", |_db: Arc, id: i64| -> bool { @@ -121,13 +174,32 @@ fn main() -> Result<(), Box> { let start_date = Utc::now(); let end_date = start_date + Duration::days(14); vec![ - Proposal::new(Some(1), "Creator 1", "Proposal 1", "Description 1", start_date, end_date), - Proposal::new(Some(2), "Creator 2", "Proposal 2", "Description 2", start_date, end_date) + Proposal::new( + Some(1), + "Creator 1", + "Creator Name 1", + "Proposal 1", + "Description 1", + start_date, + end_date, + ), + Proposal::new( + Some(2), + "Creator 2", + "Creator Name 2", + "Proposal 2", + "Description 2", + start_date, + end_date, + ), ] } // Register the function with the wrap_vec_return macro - engine.register_fn("get_all_proposals", wrap_vec_return!(get_all_proposals, Arc => Proposal)); + engine.register_fn( + "get_all_proposals", + wrap_vec_return!(get_all_proposals, Arc => Proposal), + ); engine.register_fn("delete_proposal_by_id", |_db: Arc, _id: i64| { // In a real implementation, this would delete the proposal from the database @@ -139,13 +211,16 @@ fn main() -> Result<(), Box> { proposal.options.len() as i64 }); - engine.register_fn("get_option_at", |proposal: Proposal, index: i64| -> VoteOption { - if index >= 0 && index < proposal.options.len() as i64 { - proposal.options[index as usize].clone() - } else { - VoteOption::new(0, "Invalid Option") - } - }); + engine.register_fn( + "get_option_at", + |proposal: Proposal, index: i64| -> VoteOption { + if index >= 0 && index < proposal.options.len() as i64 { + proposal.options[index as usize].clone() + } else { + VoteOption::new(0, "Invalid Option") + } + }, + ); engine.register_fn("get_option_text", |option: VoteOption| -> String { option.text.clone() @@ -159,13 +234,16 @@ fn main() -> Result<(), Box> { proposal.ballots.len() as i64 }); - engine.register_fn("get_ballot_at", |proposal: Proposal, index: i64| -> Ballot { - if index >= 0 && index < proposal.ballots.len() as i64 { - proposal.ballots[index as usize].clone() - } else { - Ballot::new(None, 0, 0, 0) - } - }); + engine.register_fn( + "get_ballot_at", + |proposal: Proposal, index: i64| -> Ballot { + if index >= 0 && index < proposal.ballots.len() as i64 { + proposal.ballots[index as usize].clone() + } else { + Ballot::new(None, 0, 0, 0) + } + }, + ); engine.register_fn("get_ballot_user_id", |ballot: Ballot| -> i64 { ballot.user_id as i64 diff --git a/heromodels/src/models/governance/proposal.rs b/heromodels/src/models/governance/proposal.rs index bf207e5..4e26a68 100644 --- a/heromodels/src/models/governance/proposal.rs +++ b/heromodels/src/models/governance/proposal.rs @@ -102,7 +102,9 @@ impl Ballot { #[model] // Has base.Base in V spec pub struct Proposal { pub base_data: BaseModelData, - pub creator_id: String, // User ID of the proposal creator + pub creator_id: String, // User ID of the proposal creator + pub creator_name: String, // User name of the proposal creator + pub title: String, pub description: String, pub status: ProposalStatus, @@ -129,6 +131,7 @@ impl Proposal { pub fn new( id: Option, creator_id: impl ToString, + creator_name: impl ToString, title: impl ToString, description: impl ToString, vote_start_date: DateTime, @@ -142,6 +145,7 @@ impl Proposal { Self { base_data, creator_id: creator_id.to_string(), + creator_name: creator_name.to_string(), title: title.to_string(), description: description.to_string(), status: ProposalStatus::Draft, From 8997d92e41eb22481bff9ed816747ce564671fce Mon Sep 17 00:00:00 2001 From: Mahmoud-Emad Date: Wed, 21 May 2025 09:53:10 +0300 Subject: [PATCH 08/11] feat: Add created_at, updated_at fields in the db proposal model --- heromodels/examples/governance_proposal_example/main.rs | 4 ++++ heromodels/examples/governance_rhai/example.rs | 8 ++++++++ heromodels/src/models/governance/proposal.rs | 7 +++++++ 3 files changed, 19 insertions(+) diff --git a/heromodels/examples/governance_proposal_example/main.rs b/heromodels/examples/governance_proposal_example/main.rs index 9b75c9d..9d790f9 100644 --- a/heromodels/examples/governance_proposal_example/main.rs +++ b/heromodels/examples/governance_proposal_example/main.rs @@ -18,6 +18,8 @@ fn main() { "Ahmed fared", // creator_id "Community Fund Allocation for Q3", // title "Proposal to allocate funds for community projects in the third quarter.", // description + Utc::now(), // created_at + Utc::now(), // updated_at Utc::now(), // vote_start_date Utc::now() + Duration::days(14), // vote_end_date (14 days from now) ); @@ -130,6 +132,8 @@ fn main() { "Internal Team Restructure Vote", "Vote on proposed internal team changes.", Utc::now(), + Utc::now(), + Utc::now(), Utc::now() + Duration::days(7), ); private_proposal.private_group = Some(vec![10, 20, 30]); // Only users 10, 20, 30 can vote diff --git a/heromodels/examples/governance_rhai/example.rs b/heromodels/examples/governance_rhai/example.rs index 65a9089..0b1ba14 100644 --- a/heromodels/examples/governance_rhai/example.rs +++ b/heromodels/examples/governance_rhai/example.rs @@ -38,6 +38,8 @@ fn main() -> Result<(), Box> { creator_name, title, description, + Utc::now(), + Utc::now(), start_date, end_date, ) @@ -156,6 +158,8 @@ fn main() -> Result<(), Box> { "Retrieved Creator Name", "Retrieved Proposal", "Retrieved Description", + Utc::now(), + Utc::now(), start_date, end_date, ) @@ -180,6 +184,8 @@ fn main() -> Result<(), Box> { "Creator Name 1", "Proposal 1", "Description 1", + Utc::now(), + Utc::now(), start_date, end_date, ), @@ -189,6 +195,8 @@ fn main() -> Result<(), Box> { "Creator Name 2", "Proposal 2", "Description 2", + Utc::now(), + Utc::now(), start_date, end_date, ), diff --git a/heromodels/src/models/governance/proposal.rs b/heromodels/src/models/governance/proposal.rs index 4e26a68..3ab0647 100644 --- a/heromodels/src/models/governance/proposal.rs +++ b/heromodels/src/models/governance/proposal.rs @@ -109,6 +109,9 @@ pub struct Proposal { pub description: String, pub status: ProposalStatus, + pub created_at: DateTime, + pub updated_at: DateTime, + // Voting event aspects pub vote_start_date: DateTime, pub vote_end_date: DateTime, @@ -134,6 +137,8 @@ impl Proposal { creator_name: impl ToString, title: impl ToString, description: impl ToString, + created_at: DateTime, + updated_at: DateTime, vote_start_date: DateTime, vote_end_date: DateTime, ) -> Self { @@ -149,6 +154,8 @@ impl Proposal { title: title.to_string(), description: description.to_string(), status: ProposalStatus::Draft, + created_at, + updated_at, vote_start_date, vote_end_date, vote_status: VoteEventStatus::Open, // Default to open when created From bd3c0c1932dc61ae15a3d853b31b8d7df4806547 Mon Sep 17 00:00:00 2001 From: Mahmoud-Emad Date: Wed, 21 May 2025 11:26:13 +0300 Subject: [PATCH 09/11] fix: Support passing the proposal status --- .../examples/governance_proposal_example/main.rs | 10 ++++++---- heromodels/examples/governance_rhai/example.rs | 4 ++++ heromodels/src/models/governance/proposal.rs | 3 ++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/heromodels/examples/governance_proposal_example/main.rs b/heromodels/examples/governance_proposal_example/main.rs index 9d790f9..13c2f69 100644 --- a/heromodels/examples/governance_proposal_example/main.rs +++ b/heromodels/examples/governance_proposal_example/main.rs @@ -18,10 +18,11 @@ fn main() { "Ahmed fared", // creator_id "Community Fund Allocation for Q3", // title "Proposal to allocate funds for community projects in the third quarter.", // description - Utc::now(), // created_at - Utc::now(), // updated_at - Utc::now(), // vote_start_date - Utc::now() + Duration::days(14), // vote_end_date (14 days from now) + ProposalStatus::Draft, + Utc::now(), // created_at + Utc::now(), // updated_at + Utc::now(), // vote_start_date + Utc::now() + Duration::days(14), // vote_end_date (14 days from now) ); println!( @@ -131,6 +132,7 @@ fn main() { "Wael Ghonem", "Internal Team Restructure Vote", "Vote on proposed internal team changes.", + ProposalStatus::Draft, Utc::now(), Utc::now(), Utc::now(), diff --git a/heromodels/examples/governance_rhai/example.rs b/heromodels/examples/governance_rhai/example.rs index 0b1ba14..9ee4394 100644 --- a/heromodels/examples/governance_rhai/example.rs +++ b/heromodels/examples/governance_rhai/example.rs @@ -38,6 +38,7 @@ fn main() -> Result<(), Box> { creator_name, title, description, + ProposalStatus::Draft, Utc::now(), Utc::now(), start_date, @@ -158,6 +159,7 @@ fn main() -> Result<(), Box> { "Retrieved Creator Name", "Retrieved Proposal", "Retrieved Description", + ProposalStatus::Draft, Utc::now(), Utc::now(), start_date, @@ -184,6 +186,7 @@ fn main() -> Result<(), Box> { "Creator Name 1", "Proposal 1", "Description 1", + ProposalStatus::Draft, Utc::now(), Utc::now(), start_date, @@ -195,6 +198,7 @@ fn main() -> Result<(), Box> { "Creator Name 2", "Proposal 2", "Description 2", + ProposalStatus::Draft, Utc::now(), Utc::now(), start_date, diff --git a/heromodels/src/models/governance/proposal.rs b/heromodels/src/models/governance/proposal.rs index 3ab0647..57eaa56 100644 --- a/heromodels/src/models/governance/proposal.rs +++ b/heromodels/src/models/governance/proposal.rs @@ -137,6 +137,7 @@ impl Proposal { creator_name: impl ToString, title: impl ToString, description: impl ToString, + status: ProposalStatus, created_at: DateTime, updated_at: DateTime, vote_start_date: DateTime, @@ -153,7 +154,7 @@ impl Proposal { creator_name: creator_name.to_string(), title: title.to_string(), description: description.to_string(), - status: ProposalStatus::Draft, + status, created_at, updated_at, vote_start_date, From bebb35e6862a911936406785dfe5ab73edfd84a3 Mon Sep 17 00:00:00 2001 From: Mahmoud-Emad Date: Wed, 21 May 2025 12:41:16 +0300 Subject: [PATCH 10/11] feat: Added optional vote comment to the proposal vote fields --- .../governance_proposal_example/main.rs | 18 +++++++++++----- .../examples/governance_rhai/example.rs | 14 ++++++++++--- heromodels/src/models/governance/proposal.rs | 21 ++++++++++++------- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/heromodels/examples/governance_proposal_example/main.rs b/heromodels/examples/governance_proposal_example/main.rs index 13c2f69..f9221d0 100644 --- a/heromodels/examples/governance_proposal_example/main.rs +++ b/heromodels/examples/governance_proposal_example/main.rs @@ -39,9 +39,9 @@ fn main() { ); // Add vote options - proposal = proposal.add_option(1, "Approve Allocation"); - proposal = proposal.add_option(2, "Reject Allocation"); - proposal = proposal.add_option(3, "Abstain"); + proposal = proposal.add_option(1, "Approve Allocation", Some("This is the approval option")); + proposal = proposal.add_option(2, "Reject Allocation", Some("This is the rejection option")); + proposal = proposal.add_option(3, "Abstain", Some("This is the abstain option")); println!("Added Vote Options:"); for option in &proposal.options { @@ -139,8 +139,16 @@ fn main() { Utc::now() + Duration::days(7), ); private_proposal.private_group = Some(vec![10, 20, 30]); // Only users 10, 20, 30 can vote - private_proposal = private_proposal.add_option(1, "Accept Restructure"); - private_proposal = private_proposal.add_option(2, "Reject Restructure"); + private_proposal = private_proposal.add_option( + 1, + "Accept Restructure", + Some("This is the accept restructure option".to_string()), + ); + private_proposal = private_proposal.add_option( + 2, + "Reject Restructure", + Some("This is the reject restructure option".to_string()), + ); println!( "\nBefore saving - Created Private Proposal: '{}'", diff --git a/heromodels/examples/governance_rhai/example.rs b/heromodels/examples/governance_rhai/example.rs index 9ee4394..f6ee8fe 100644 --- a/heromodels/examples/governance_rhai/example.rs +++ b/heromodels/examples/governance_rhai/example.rs @@ -48,7 +48,7 @@ fn main() -> Result<(), Box> { ); engine.register_fn("create_vote_option", |id: i64, text: String| { - VoteOption::new(id as u8, text) + VoteOption::new(id as u8, text, Some("This is an optional comment")) }); engine.register_fn( @@ -93,7 +93,11 @@ fn main() -> Result<(), Box> { engine.register_fn( "add_option_to_proposal", |mut proposal: Proposal, option_id: i64, option_text: String| -> Proposal { - proposal.add_option(option_id as u8, option_text) + proposal.add_option( + option_id as u8, + option_text, + Some("This is an optional comment".to_string()), + ) }, ); @@ -229,7 +233,11 @@ fn main() -> Result<(), Box> { if index >= 0 && index < proposal.options.len() as i64 { proposal.options[index as usize].clone() } else { - VoteOption::new(0, "Invalid Option") + VoteOption::new( + 0, + "Invalid Option", + Some("This is an invalid option".to_string()), + ) } }, ); diff --git a/heromodels/src/models/governance/proposal.rs b/heromodels/src/models/governance/proposal.rs index 57eaa56..5a715ab 100644 --- a/heromodels/src/models/governance/proposal.rs +++ b/heromodels/src/models/governance/proposal.rs @@ -45,19 +45,21 @@ impl Default for VoteEventStatus { /// VoteOption represents a specific choice that can be voted on #[derive(Debug, Clone, Serialize, Deserialize, CustomType)] pub struct VoteOption { - pub id: u8, // Simple identifier for this option - pub text: String, // Descriptive text of the option - pub count: i64, // How many votes this option has received - pub min_valid: Option, // Optional: minimum votes needed + pub id: u8, // Simple identifier for this option + pub text: String, // Descriptive text of the option + pub count: i64, // How many votes this option has received + pub min_valid: Option, // Optional: minimum votes needed, + pub comment: Option, // Optional: comment } impl VoteOption { - pub fn new(id: u8, text: impl ToString) -> Self { + pub fn new(id: u8, text: impl ToString, comment: Option) -> Self { Self { id, text: text.to_string(), count: 0, min_valid: None, + comment: comment.map(|c| c.to_string()), } } } @@ -166,8 +168,13 @@ impl Proposal { } } - pub fn add_option(mut self, option_id: u8, option_text: impl ToString) -> Self { - let new_option = VoteOption::new(option_id, option_text); + pub fn add_option( + mut self, + option_id: u8, + option_text: impl ToString, + comment: Option, + ) -> Self { + let new_option = VoteOption::new(option_id, option_text, comment); self.options.push(new_option); self } From a71a96698945fb0bb878a3b943f72fa7ffbdeab9 Mon Sep 17 00:00:00 2001 From: Mahmoud-Emad Date: Wed, 21 May 2025 14:50:55 +0300 Subject: [PATCH 11/11] feat: Add an example about how to create a vote with comment --- .../governance_proposal_example/main.rs | 86 ++++++++++++++++++- heromodels/src/models/governance/proposal.rs | 63 ++++++++++++++ 2 files changed, 148 insertions(+), 1 deletion(-) diff --git a/heromodels/examples/governance_proposal_example/main.rs b/heromodels/examples/governance_proposal_example/main.rs index f9221d0..bfbe78a 100644 --- a/heromodels/examples/governance_proposal_example/main.rs +++ b/heromodels/examples/governance_proposal_example/main.rs @@ -2,7 +2,7 @@ use chrono::{Duration, Utc}; use heromodels::db::{Collection, Db}; -use heromodels::models::governance::{Proposal, ProposalStatus, VoteEventStatus}; +use heromodels::models::governance::{Ballot, Proposal, ProposalStatus, VoteEventStatus}; fn main() { println!("Governance Proposal Model Example\n"); @@ -99,6 +99,46 @@ fn main() { } println!(""); + // Example of voting with comments using the cast_vote_with_comment method + println!("Adding votes with comments..."); + + // User 7 votes for 'Approve Allocation' with a comment + proposal = proposal.cast_vote_with_comment( + Some(110), // ballot_id + 7, // user_id + 1, // chosen_option_id (Approve Allocation) + 80, // shares + "I strongly support this proposal because it aligns with our community values." + ); + + // User 8 votes for 'Reject Allocation' with a comment + proposal = proposal.cast_vote_with_comment( + Some(111), // ballot_id + 8, // user_id + 2, // chosen_option_id (Reject Allocation) + 60, // shares + "I have concerns about the allocation priorities." + ); + + println!("\nBallots with Comments:"); + for ballot in &proposal.ballots { + if let Some(comment) = &ballot.comment { + println!( + "- Ballot ID: {}, User ID: {}, Option ID: {}, Shares: {}", + ballot.base_data.id, ballot.user_id, ballot.vote_option_id, ballot.shares_count + ); + println!(" Comment: \"{}\"", comment); + } + } + + println!("\nUpdated Vote Counts After Comments:"); + for option in &proposal.options { + println!( + "- Option ID: {}, Text: '{}', Votes: {}", + option.id, option.text, option.count + ); + } + // Change proposal status proposal = proposal.change_proposal_status(ProposalStatus::Active); println!("Changed Proposal Status to: {:?}", proposal.status); @@ -176,6 +216,50 @@ fn main() { // User 40 (ineligible) tries to vote with auto-generated ballot ID private_proposal = private_proposal.cast_vote(None, 40, 1, 50); + // Example of voting with comments on a private proposal + println!("\nAdding votes with comments to private proposal..."); + + // User 20 (eligible) votes with a comment + private_proposal = private_proposal.cast_vote_with_comment( + Some(202), // ballot_id + 20, // user_id (eligible) + 1, // chosen_option_id + 75, // shares + "I support this restructuring plan with some reservations." + ); + + // User 30 (eligible) votes with a comment + private_proposal = private_proposal.cast_vote_with_comment( + Some(203), // ballot_id + 30, // user_id (eligible) + 2, // chosen_option_id + 90, // shares + "I believe we should reconsider the timing of these changes." + ); + + // User 40 (ineligible) tries to vote with a comment + private_proposal = private_proposal.cast_vote_with_comment( + Some(204), // ballot_id + 40, // user_id (ineligible) + 1, // chosen_option_id + 50, // shares + "This restructuring seems unnecessary." + ); + + println!("Eligible users 20 and 30 added votes with comments."); + println!("Ineligible user 40 attempted to vote with a comment (should be rejected)."); + + println!("\nPrivate Proposal Ballots with Comments:"); + for ballot in &private_proposal.ballots { + if let Some(comment) = &ballot.comment { + println!( + "- Ballot ID: {}, User ID: {}, Option ID: {}, Shares: {}", + ballot.base_data.id, ballot.user_id, ballot.vote_option_id, ballot.shares_count + ); + println!(" Comment: \"{}\"", comment); + } + } + println!("Private Proposal Vote Counts:"); for option in &private_proposal.options { println!( diff --git a/heromodels/src/models/governance/proposal.rs b/heromodels/src/models/governance/proposal.rs index 5a715ab..0440f89 100644 --- a/heromodels/src/models/governance/proposal.rs +++ b/heromodels/src/models/governance/proposal.rs @@ -73,6 +73,7 @@ pub struct Ballot { pub user_id: u32, // The ID of the user who cast this ballot pub vote_option_id: u8, // The 'id' of the VoteOption chosen pub shares_count: i64, // Number of shares/tokens/voting power + pub comment: Option, // Optional comment from the voter } impl Ballot { @@ -94,6 +95,7 @@ impl Ballot { user_id, vote_option_id, shares_count, + comment: None, } } } @@ -229,4 +231,65 @@ impl Proposal { self.vote_status = new_status; self } + + /// Cast a vote with a comment + /// + /// # Arguments + /// * `ballot_id` - Optional ID for the ballot (use None for auto-generated ID) + /// * `user_id` - ID of the user who is casting the vote + /// * `chosen_option_id` - ID of the vote option chosen + /// * `shares` - Number of shares/tokens/voting power + /// * `comment` - Comment from the voter explaining their vote + pub fn cast_vote_with_comment( + mut self, + ballot_id: Option, + user_id: u32, + chosen_option_id: u8, + shares: i64, + comment: impl ToString, + ) -> Self { + // First check if voting is open + if self.vote_status != VoteEventStatus::Open { + eprintln!("Voting is not open for proposal '{}'", self.title); + return self; + } + + // Check if the option exists + if !self.options.iter().any(|opt| opt.id == chosen_option_id) { + eprintln!( + "Chosen option ID {} does not exist for proposal '{}'", + chosen_option_id, self.title + ); + return self; + } + + // Check eligibility for private proposals + if let Some(group) = &self.private_group { + if !group.contains(&user_id) { + eprintln!( + "User {} is not eligible to vote on proposal '{}'", + user_id, self.title + ); + return self; + } + } + + // Create a new ballot with the comment + let mut new_ballot = Ballot::new(ballot_id, user_id, chosen_option_id, shares); + new_ballot.comment = Some(comment.to_string()); + + // Add the ballot to the proposal + self.ballots.push(new_ballot); + + // Update the vote count for the chosen option + if let Some(option) = self + .options + .iter_mut() + .find(|opt| opt.id == chosen_option_id) + { + option.count += shares; + } + + self + } }