using System.ComponentModel.DataAnnotations; namespace JobsMedical.Web.Models; /// /// Single-row (Id=1) platform settings the admin controls at runtime — chiefly the ingestion /// automation policy and the optional AI audit layer. Kept in the DB (not appsettings) so it's /// editable from the admin panel without a redeploy. /// public class AppSetting { public int Id { get; set; } = 1; // --- Ingestion automation --- public IngestionMode Mode { get; set; } = IngestionMode.Manual; /// In Automatic mode WITHOUT AI, listings at/above this confidence auto-publish. public int AutoPublishMinConfidence { get; set; } = 85; // --- AI audit layer (optional) --- public bool AiEnabled { get; set; } = false; /// OpenAI-compatible chat-completions endpoint (self-hosted or Iranian provider). [MaxLength(500)] public string? AiEndpoint { get; set; } [MaxLength(200)] public string? AiApiKey { get; set; } [MaxLength(120)] public string? AiModel { get; set; } = "gpt-4o-mini"; /// The prompt + "framework" the AI follows to approve / reject / structure a listing. [MaxLength(4000)] public string AiSystemPrompt { get; set; } = DefaultPrompt; /// If AI approves AND Mode is Automatic, publish without human review. public bool AiAutoApprove { get; set; } = false; // --- Channel scraping sources (configured here, NOT in env) --- /// Run the ingestion worker on a timer. public bool AutoIngestEnabled { get; set; } = false; public int IngestIntervalMinutes { get; set; } = 30; public bool TelegramEnabled { get; set; } = false; /// Public Telegram channel usernames, one per line or comma-separated. [MaxLength(2000)] public string? TelegramChannels { get; set; } public bool BaleEnabled { get; set; } = false; [MaxLength(200)] public string? BaleBotToken { get; set; } /// Demo mode — keep the sample Tehran board seeded/visible (for showcasing). public bool DemoMode { get; set; } = false; public bool WebsitesEnabled { get; set; } = false; /// Generic web pages to scrape, one URL per line. [MaxLength(4000)] public string? WebsiteUrls { get; set; } public bool DivarEnabled { get; set; } = false; [MaxLength(60)] public string? DivarCity { get; set; } = "tehran"; /// Divar search terms, one per line or comma-separated. [MaxLength(2000)] public string? DivarQueries { get; set; } /// Scrape medjobs.ir job ads (WordPress classifieds — crawled via its sitemaps). public bool MedjobsEnabled { get; set; } = false; /// Max ads to fetch per ingestion run (be polite; dedupe skips already-seen). public int MedjobsMaxAds { get; set; } = 40; // --- SMS OTP (Kavenegar). When off, the code is shown on screen (dev only). --- public bool SmsEnabled { get; set; } = false; [MaxLength(200)] public string? SmsApiKey { get; set; } /// Kavenegar verify/lookup template name (preferred OTP method in Iran). [MaxLength(100)] public string? SmsTemplate { get; set; } /// Sender line for plain SMS fallback when no template is set. [MaxLength(30)] public string? SmsSender { get; set; } /// Neshan web map.js API key — enables the click-to-pick map on the facility form /// (Google Maps is blocked in Iran). Empty → only the "my location" button is shown. [MaxLength(200)] public string? NeshanMapKey { get; set; } // --- Web Push (PWA notifications). VAPID keypair; generate once with the web-push tooling. --- public bool PushEnabled { get; set; } = false; [MaxLength(200)] public string? VapidPublicKey { get; set; } [MaxLength(200)] public string? VapidPrivateKey { get; set; } [MaxLength(120)] public string? VapidSubject { get; set; } = "mailto:admin@hamkadr.ir"; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; /// Split a textarea (newline/comma separated) into trimmed non-empty items. public static List SplitList(string? s) => string.IsNullOrWhiteSpace(s) ? new() : s.Split(new[] { '\n', '\r', ',', '،' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) .ToList(); public const string DefaultPrompt = """ تو دستیار بررسی آگهی‌های کاری حوزه درمان برای پلتفرم «همکادر» هستی. هر آگهی خام را بخوان و تصمیم بگیر: - approve: آگهی واقعی و مرتبط با شیفت/استخدام کادر درمان است و اطلاعات کافی دارد. - reject: تبلیغ، اسپم، نامرتبط، یا فاقد اطلاعات حداقلی است. - review: مرتبط است اما ناقص/مبهم و نیاز به بررسی انسانی دارد. نقش، شهر/محله، نوع شیفت، نوع همکاری، مبلغ یا درصد سهم، و عنوان را در صورت وجود استخراج کن. فقط با یک شیء JSON پاسخ بده با کلیدهای: decision (approve|reject|review)، confidence (0-100)، reason (فارسی کوتاه)، kind (shift|job)، role، city، district، shiftType (day|evening|night|oncall)، employmentType (fulltime|parttime|contract|plan)، payAmount (عدد تومان یا null)، sharePercent (0-100 یا null)، title، facilityName. """; }