diff --git a/ZeroLevel.Discovery/DiscoveryService.cs b/Apps/ZeroLevel.Discovery/DiscoveryService.cs
similarity index 100%
rename from ZeroLevel.Discovery/DiscoveryService.cs
rename to Apps/ZeroLevel.Discovery/DiscoveryService.cs
diff --git a/ZeroLevel.Discovery/Program.cs b/Apps/ZeroLevel.Discovery/Program.cs
similarity index 100%
rename from ZeroLevel.Discovery/Program.cs
rename to Apps/ZeroLevel.Discovery/Program.cs
diff --git a/ZeroLevel.Discovery/Properties/PublishProfiles/FolderProfile.pubxml b/Apps/ZeroLevel.Discovery/Properties/PublishProfiles/FolderProfile.pubxml
similarity index 100%
rename from ZeroLevel.Discovery/Properties/PublishProfiles/FolderProfile.pubxml
rename to Apps/ZeroLevel.Discovery/Properties/PublishProfiles/FolderProfile.pubxml
diff --git a/ZeroLevel.Discovery/ServiceEndpointsTable.cs b/Apps/ZeroLevel.Discovery/ServiceEndpointsTable.cs
similarity index 100%
rename from ZeroLevel.Discovery/ServiceEndpointsTable.cs
rename to Apps/ZeroLevel.Discovery/ServiceEndpointsTable.cs
diff --git a/ZeroLevel.Discovery/ZeroLevel.Discovery.csproj b/Apps/ZeroLevel.Discovery/ZeroLevel.Discovery.csproj
similarity index 85%
rename from ZeroLevel.Discovery/ZeroLevel.Discovery.csproj
rename to Apps/ZeroLevel.Discovery/ZeroLevel.Discovery.csproj
index e6651e9..2b9352f 100644
--- a/ZeroLevel.Discovery/ZeroLevel.Discovery.csproj
+++ b/Apps/ZeroLevel.Discovery/ZeroLevel.Discovery.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/ZeroLevel.Discovery/app.config b/Apps/ZeroLevel.Discovery/app.config
similarity index 100%
rename from ZeroLevel.Discovery/app.config
rename to Apps/ZeroLevel.Discovery/app.config
diff --git a/ZeroLevel.Logger/App.config b/Apps/ZeroLevel.Logger/App.config
similarity index 100%
rename from ZeroLevel.Logger/App.config
rename to Apps/ZeroLevel.Logger/App.config
diff --git a/ZeroLevel.Logger/LogService.cs b/Apps/ZeroLevel.Logger/LogService.cs
similarity index 100%
rename from ZeroLevel.Logger/LogService.cs
rename to Apps/ZeroLevel.Logger/LogService.cs
diff --git a/ZeroLevel.Logger/Program.cs b/Apps/ZeroLevel.Logger/Program.cs
similarity index 100%
rename from ZeroLevel.Logger/Program.cs
rename to Apps/ZeroLevel.Logger/Program.cs
diff --git a/ZeroLevel.Logger/Properties/PublishProfiles/FolderProfile.pubxml b/Apps/ZeroLevel.Logger/Properties/PublishProfiles/FolderProfile.pubxml
similarity index 100%
rename from ZeroLevel.Logger/Properties/PublishProfiles/FolderProfile.pubxml
rename to Apps/ZeroLevel.Logger/Properties/PublishProfiles/FolderProfile.pubxml
diff --git a/ZeroLevel.Logger/ProxySample/LogMessage.cs b/Apps/ZeroLevel.Logger/ProxySample/LogMessage.cs
similarity index 100%
rename from ZeroLevel.Logger/ProxySample/LogMessage.cs
rename to Apps/ZeroLevel.Logger/ProxySample/LogMessage.cs
diff --git a/ZeroLevel.Logger/ProxySample/LogProxy.cs b/Apps/ZeroLevel.Logger/ProxySample/LogProxy.cs
similarity index 100%
rename from ZeroLevel.Logger/ProxySample/LogProxy.cs
rename to Apps/ZeroLevel.Logger/ProxySample/LogProxy.cs
diff --git a/ZeroLevel.Logger/ZeroLevel.Logger.csproj b/Apps/ZeroLevel.Logger/ZeroLevel.Logger.csproj
similarity index 77%
rename from ZeroLevel.Logger/ZeroLevel.Logger.csproj
rename to Apps/ZeroLevel.Logger/ZeroLevel.Logger.csproj
index f9b6f57..c24edfc 100644
--- a/ZeroLevel.Logger/ZeroLevel.Logger.csproj
+++ b/Apps/ZeroLevel.Logger/ZeroLevel.Logger.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/ZeroNetworkMonitor/App.xaml b/Apps/ZeroNetworkMonitor/App.xaml
similarity index 100%
rename from ZeroNetworkMonitor/App.xaml
rename to Apps/ZeroNetworkMonitor/App.xaml
diff --git a/ZeroNetworkMonitor/App.xaml.cs b/Apps/ZeroNetworkMonitor/App.xaml.cs
similarity index 100%
rename from ZeroNetworkMonitor/App.xaml.cs
rename to Apps/ZeroNetworkMonitor/App.xaml.cs
diff --git a/ZeroNetworkMonitor/AssemblyInfo.cs b/Apps/ZeroNetworkMonitor/AssemblyInfo.cs
similarity index 100%
rename from ZeroNetworkMonitor/AssemblyInfo.cs
rename to Apps/ZeroNetworkMonitor/AssemblyInfo.cs
diff --git a/ZeroNetworkMonitor/MainWindow.xaml b/Apps/ZeroNetworkMonitor/MainWindow.xaml
similarity index 100%
rename from ZeroNetworkMonitor/MainWindow.xaml
rename to Apps/ZeroNetworkMonitor/MainWindow.xaml
diff --git a/ZeroNetworkMonitor/MainWindow.xaml.cs b/Apps/ZeroNetworkMonitor/MainWindow.xaml.cs
similarity index 100%
rename from ZeroNetworkMonitor/MainWindow.xaml.cs
rename to Apps/ZeroNetworkMonitor/MainWindow.xaml.cs
diff --git a/ZeroNetworkMonitor/ServiceControlPanel.xaml b/Apps/ZeroNetworkMonitor/ServiceControlPanel.xaml
similarity index 100%
rename from ZeroNetworkMonitor/ServiceControlPanel.xaml
rename to Apps/ZeroNetworkMonitor/ServiceControlPanel.xaml
diff --git a/ZeroNetworkMonitor/ServiceControlPanel.xaml.cs b/Apps/ZeroNetworkMonitor/ServiceControlPanel.xaml.cs
similarity index 100%
rename from ZeroNetworkMonitor/ServiceControlPanel.xaml.cs
rename to Apps/ZeroNetworkMonitor/ServiceControlPanel.xaml.cs
diff --git a/ZeroNetworkMonitor/ZeroNetworkMonitor.csproj b/Apps/ZeroNetworkMonitor/ZeroNetworkMonitor.csproj
similarity index 88%
rename from ZeroNetworkMonitor/ZeroNetworkMonitor.csproj
rename to Apps/ZeroNetworkMonitor/ZeroNetworkMonitor.csproj
index 0331aec..0fb5a3d 100644
--- a/ZeroNetworkMonitor/ZeroNetworkMonitor.csproj
+++ b/Apps/ZeroNetworkMonitor/ZeroNetworkMonitor.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/Tests/Sleopok.Tests/Program.cs b/Tests/Sleopok.Tests/Program.cs
new file mode 100644
index 0000000..40e6f90
--- /dev/null
+++ b/Tests/Sleopok.Tests/Program.cs
@@ -0,0 +1,141 @@
+using ZeroLevel.Services.Semantic;
+using ZeroLevel.Services.Serialization;
+using ZeroLevel.Sleopok.Engine;
+using ZeroLevel.Sleopok.Engine.Models;
+using ZeroLevel.Sleopok.Engine.Services;
+using ZeroLevel.Sleopok.Engine.Services.Storage;
+
+namespace Sleopok.Tests
+{
+ internal class Program
+ {
+ public sealed class BookDocument
+ {
+ public string Id { get; set; }
+
+ [SleoIndex("title", 200.0f)]
+ public string Title { get; set; }
+
+ [SleoIndex("titlelm", 100.0f)]
+ public string TitleLemmas { get; set; }
+
+ [SleoIndex("author", 10.0f)]
+ public string Author { get; set; }
+
+ [SleoIndex("genre", 1.0f)]
+ public string Genre { get; set; }
+ }
+
+ private static Dictionary _titles = new Dictionary
+ {
+ { "66056bc0481e83af64c55022", "Документ без названия" },
+ { "6605698d481e83af64c45ad7", "На развилке дорог. Часть 2"},
+ { "660581bc481e83af64cb8b4d", "Паниклав"},
+ { "66057aa2481e83af64c9bb11", "Князь. Война магов (сборник)"},
+ { "66057f75481e83af64cb04f7", "Антология севетского детектива-8. Компиляция. Книги 1-17"},
+ { "66057bd4481e83af64ca0779", "Вор черной масти"},
+ { "66057247481e83af64c76860", "Выбор"},
+ { "66056807481e83af64c3a64f", "Последняя лекция"},
+ { "66057f13481e83af64caed5d", "Оружие Круппа. История династии пушечных королей"},
+ { "66057a37481e83af64c9a14b", "Месть Черного Дракона"},
+ { "660588e8481e83af64cd2d3e", "Мгла над старыми могилами"},
+ { "66056e88481e83af64c64e81", "Кровь и железо"},
+ { "66057a8e481e83af64c9b673", "Маленькая страна"},
+ { "6605687d481e83af64c3e360", "Санкт-Петербург – история в преданиях и легендах"},
+ { "66057987481e83af64c9770c", "Контракт на рабство"},
+ { "66059052481e83af64cf5e31", "Агент космического сыска"},
+ { "660580f9481e83af64cb61c9", "Две жизни Алессы Коэн"},
+ { "66056807481e84af64c3a64f", "Последняя история"},
+ { "66057f13481e85af64caed5d", "История Китая"},
+ { "66057a37481e86af64c9a14b", "Время Черного Дракона"},
+ { "660588e8481e87af64cd2d3e", "Страна которой нет"},
+ };
+
+ static async Task Main(string[] args)
+ {
+ // TestCompression();
+ // await FillOneFieldIndex();
+ // await TestSearch();
+ await TestEngine();
+ }
+
+ static async Task TestEngine()
+ {
+ var engine = new SleoEngine(@"H:\Test", b => b.Id);
+ using (var builder = engine.CreateBuilder())
+ {
+ builder.Write(new[]
+ {
+ new BookDocument{ Id = "01", Title = "Страж птица", },
+ new BookDocument{ Id = "02" },
+ new BookDocument{ Id = "03" },
+ new BookDocument{ Id = "04" },
+ });
+ }
+ }
+
+ static void TestCompression()
+ {
+ var strings = new string[]
+ {
+ string.Empty,
+ "doc1",
+ "doc2",
+ "",
+ " ",
+ "\r\n",
+ "last",
+ "doc3",
+ "doc4",
+ "doc5",
+ "doc6",
+ "doc7",
+ "doc8",
+ "doc9",
+ "doc10",
+ };
+
+ var clearbytes = MessageSerializer.SerializeCompatible(strings).Length;
+ var compressed = Compressor.Compress(strings);
+ Console.WriteLine($"{compressed.Length} / {clearbytes} bytes");
+
+ var decomressed = Compressor.DecompressToDocuments(compressed);
+ int index = 0;
+ foreach (var s in decomressed)
+ {
+ if (!(string.IsNullOrEmpty(s) && string.IsNullOrEmpty(strings[index])) && 0 != string.CompareOrdinal(strings[index], s))
+ {
+ Console.WriteLine($"Got {s}. Expected {strings[index]}");
+ }
+ index++;
+ }
+ }
+
+ static async Task FillOneFieldIndex()
+ {
+ var store = new DataStorage(@"H:\TEST");
+ using (var writer = store.GetWriter("title"))
+ {
+ foreach (var kv in _titles)
+ {
+ var tokens = WordTokenizer.Tokenize(kv.Value);
+ foreach (var t in tokens)
+ {
+ await writer.Write(t, kv.Key);
+ }
+ }
+ await writer.Complete();
+ }
+ }
+
+ static async Task TestSearch()
+ {
+ var store = new DataStorage(@"H:\TEST");
+ var docs = await store.GetDocuments("title", new string[] { "кровь", "страна", "железо", "история", "оружие" }, 1.0f, false);
+ foreach (var kv in docs.OrderByDescending(kv => kv.Value))
+ {
+ Console.WriteLine($"[{kv.Key}: {kv.Value}] {_titles[kv.Key]}");
+ }
+ }
+ }
+}
diff --git a/Tests/Sleopok.Tests/Sleopok.Tests.csproj b/Tests/Sleopok.Tests/Sleopok.Tests.csproj
new file mode 100644
index 0000000..510152e
--- /dev/null
+++ b/Tests/Sleopok.Tests/Sleopok.Tests.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/ZeroLevel.Sleopok.Engine/Models/IndexInfo.cs b/ZeroLevel.Sleopok.Engine/Models/IndexInfo.cs
new file mode 100644
index 0000000..fc45d4a
--- /dev/null
+++ b/ZeroLevel.Sleopok.Engine/Models/IndexInfo.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using ZeroLevel;
+using ZeroLevel.Services.FileSystem;
+using ZeroLevel.Services.Reflection;
+using ZeroLevel.Services.Extensions;
+
+namespace ZeroLevel.Sleopok.Engine.Models
+{
+ internal class IndexInfo
+ {
+ private readonly Func _identityExtractor;
+ private readonly List _fields;
+
+ public string GetId(T item) => _identityExtractor.Invoke(item);
+
+ public IReadOnlyCollection Fields => _fields;
+
+ public IndexInfo(Func identityExtractor)
+ {
+ _identityExtractor = identityExtractor;
+ _fields = new List();
+ typeof(T).GetMembers(
+ BindingFlags.Public |
+ BindingFlags.FlattenHierarchy |
+ BindingFlags.GetField |
+ BindingFlags.GetProperty |
+ BindingFlags.Instance).
+ Do(members =>
+ {
+ foreach (var member in members)
+ {
+ var sleoAttribute = member.GetCustomAttribute();
+ if (sleoAttribute == null) continue;
+
+ Func