petter2025 commited on
Commit
4e92bf6
Β·
verified Β·
1 Parent(s): 7cfde2b

Update hf_demo.py

Browse files
Files changed (1) hide show
  1. hf_demo.py +59 -74
hf_demo.py CHANGED
@@ -18,6 +18,8 @@ import logging
18
  import asyncio
19
  import sqlite3
20
  import requests
 
 
21
  from datetime import datetime, timedelta
22
  from typing import Dict, List, Optional, Any, Tuple
23
  from contextlib import contextmanager
@@ -31,44 +33,64 @@ gr.close_all()
31
  from fastapi import FastAPI, HTTPException, Depends, status
32
  from fastapi.middleware.cors import CORSMiddleware
33
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
34
- from pydantic import BaseModel, Field, field_validator
35
- from pydantic_settings import BaseSettings # <-- NEW: Pydantic settings
36
  from gradio import mount_gradio_app
37
 
38
- # ============== CONFIGURATION (Pydantic) ==============
 
 
 
 
 
 
 
 
 
 
39
  class Settings(BaseSettings):
40
- """Centralized configuration using Pydantic Settings"""
41
 
42
- # Hugging Face settings
43
- HF_SPACE_ID: str = Field(default='local', env='SPACE_ID')
44
- HF_TOKEN: str = Field(default='', env='HF_TOKEN')
45
 
46
  # Persistence - HF persistent storage
47
- DATA_DIR: str = Field(default='/data' if os.path.exists('/data') else './data')
 
 
 
48
 
49
  # Lead generation
50
- LEAD_EMAIL: str = "petter2025us@outlook.com"
51
- CALENDLY_URL: str = "https://calendly.com/petter2025us/arf-demo"
52
 
53
  # Webhook for lead alerts (set in HF secrets)
54
- SLACK_WEBHOOK: str = Field(default='', env='SLACK_WEBHOOK')
55
- SENDGRID_API_KEY: str = Field(default='', env='SENDGRID_API_KEY')
56
 
57
  # Security
58
- API_KEY: str = Field(default_factory=lambda: str(uuid.uuid4()), env='ARF_API_KEY')
 
 
 
59
 
60
  # ARF defaults
61
- DEFAULT_CONFIDENCE_THRESHOLD: float = 0.9
62
- DEFAULT_MAX_RISK: str = "MEDIUM"
63
 
64
- class Config:
65
- env_file = '.env' # optionally load from .env file
66
- extra = 'ignore' # ignore extra env vars
 
 
 
 
67
 
68
  def __init__(self, **kwargs):
69
  super().__init__(**kwargs)
70
  # Ensure data directory exists
71
- os.makedirs(self.DATA_DIR, exist_ok=True)
72
 
73
  settings = Settings()
74
 
@@ -77,7 +99,7 @@ logging.basicConfig(
77
  level=logging.INFO,
78
  format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
79
  handlers=[
80
- logging.FileHandler(f'{settings.DATA_DIR}/arf.log'),
81
  logging.StreamHandler()
82
  ]
83
  )
@@ -123,7 +145,7 @@ class BayesianRiskEngine:
123
  'default': {'alpha': 2.0, 'beta': 5.0}
124
  }
125
 
126
- self.evidence_db = f"{settings.DATA_DIR}/evidence.db"
127
  self._init_db()
128
 
129
  def _init_db(self):
@@ -190,41 +212,31 @@ class BayesianRiskEngine:
190
  return (row[0] or 0, row[1] or 0) if row else (0, 0)
191
  except sqlite3.Error as e:
192
  logger.error(f"Failed to retrieve evidence: {e}")
193
- return (0, 0) # fallback to no evidence
194
 
195
  def calculate_posterior(self,
196
  action_text: str,
197
  context: Dict[str, Any]) -> Dict[str, Any]:
198
- # ... (same as before, no changes needed) ...
199
- # 1. Classify action for appropriate prior
200
  action_type = self.classify_action(action_text)
201
  alpha0, beta0 = self.get_prior(action_type)
202
 
203
- # 2. Get historical evidence
204
  action_hash = hashlib.sha256(action_text.encode()).hexdigest()
205
  successes, trials = self.get_evidence(action_hash)
206
 
207
- # 3. Update prior with evidence β†’ posterior
208
  alpha_n = alpha0 + successes
209
  beta_n = beta0 + (trials - successes)
210
 
211
- # 4. Posterior mean (expected risk)
212
  posterior_mean = alpha_n / (alpha_n + beta_n)
213
-
214
- # 5. Incorporate context as likelihood adjustment
215
  context_multiplier = self._context_likelihood(context)
216
 
217
- # 6. Final risk score (posterior predictive)
218
  risk_score = posterior_mean * context_multiplier
219
  risk_score = min(0.99, max(0.01, risk_score))
220
 
221
- # 7. 95% credible interval (approximation)
222
  variance = (alpha_n * beta_n) / ((alpha_n + beta_n)**2 * (alpha_n + beta_n + 1))
223
  std_dev = variance ** 0.5
224
  ci_lower = max(0.01, posterior_mean - 1.96 * std_dev)
225
  ci_upper = min(0.99, posterior_mean + 1.96 * std_dev)
226
 
227
- # 8. Risk level
228
  if risk_score > 0.8:
229
  risk_level = RiskLevel.CRITICAL
230
  elif risk_score > 0.6:
@@ -289,11 +301,10 @@ class BayesianRiskEngine:
289
 
290
  # ============== POLICY ENGINE ==============
291
  class PolicyEngine:
292
- # ... (unchanged) ...
293
  def __init__(self):
294
  self.config = {
295
- "confidence_threshold": settings.DEFAULT_CONFIDENCE_THRESHOLD,
296
- "max_autonomous_risk": settings.DEFAULT_MAX_RISK,
297
  "risk_thresholds": {
298
  RiskLevel.LOW: 0.7,
299
  RiskLevel.MEDIUM: 0.5,
@@ -318,7 +329,6 @@ class PolicyEngine:
318
  action: str,
319
  risk: Dict[str, Any],
320
  confidence: float) -> Dict[str, Any]:
321
- # ... unchanged ...
322
  gates = []
323
 
324
  # Gate 1: Confidence threshold
@@ -387,7 +397,6 @@ class PolicyEngine:
387
  "type": "license"
388
  })
389
 
390
- # Overall decision
391
  all_passed = all(g["passed"] for g in gates)
392
 
393
  if not all_passed:
@@ -416,9 +425,8 @@ class PolicyEngine:
416
 
417
  # ============== RAG MEMORY WITH PERSISTENCE ==============
418
  class RAGMemory:
419
- # ... (unchanged except error handling) ...
420
  def __init__(self):
421
- self.db_path = f"{settings.DATA_DIR}/memory.db"
422
  self._init_db()
423
  self.embedding_cache = {}
424
 
@@ -472,7 +480,6 @@ class RAGMemory:
472
  conn.close()
473
 
474
  def _simple_embedding(self, text: str) -> List[float]:
475
- # ... unchanged ...
476
  if text in self.embedding_cache:
477
  return self.embedding_cache[text]
478
 
@@ -592,19 +599,18 @@ class RAGMemory:
592
  return signal
593
 
594
  def _notify_sales_team(self, signal: Dict):
595
- if settings.SLACK_WEBHOOK:
596
  try:
597
- requests.post(settings.SLACK_WEBHOOK, json={
598
  "text": f"🚨 *Enterprise Lead Signal*\n"
599
  f"Type: {signal['signal_type']}\n"
600
  f"Action: {signal['action']}\n"
601
  f"Risk Score: {signal['risk_score']:.2f}\n"
602
  f"Time: {signal['timestamp']}\n"
603
- f"Contact: {settings.LEAD_EMAIL}"
604
  }, timeout=5)
605
  except requests.RequestException as e:
606
  logger.error(f"Slack notification failed: {e}")
607
- # Email via SendGrid (if configured) could be added similarly
608
 
609
  def get_uncontacted_signals(self) -> List[Dict]:
610
  try:
@@ -641,8 +647,7 @@ class RAGMemory:
641
  security = HTTPBearer()
642
 
643
  async def verify_api_key(credentials: HTTPAuthorizationCredentials = Depends(security)):
644
- """Verify API key for protected endpoints"""
645
- if credentials.credentials != settings.API_KEY:
646
  raise HTTPException(
647
  status_code=status.HTTP_403_FORBIDDEN,
648
  detail="Invalid API key"
@@ -704,7 +709,7 @@ app = FastAPI(
704
  description="Real ARF OSS components for enterprise lead generation",
705
  contact={
706
  "name": "ARF Sales",
707
- "email": settings.LEAD_EMAIL,
708
  }
709
  )
710
 
@@ -760,7 +765,6 @@ async def evaluate_action(request: ActionRequest):
760
  Real ARF OSS evaluation pipeline (protected)
761
  """
762
  try:
763
- # Build context
764
  context = {
765
  "environment": "production",
766
  "user_role": request.user_role,
@@ -768,23 +772,19 @@ async def evaluate_action(request: ActionRequest):
768
  "requires_human": request.requiresHuman
769
  }
770
 
771
- # 1. Bayesian risk assessment
772
  risk = risk_engine.calculate_posterior(
773
  action_text=request.proposedAction,
774
  context=context
775
  )
776
 
777
- # 2. Policy evaluation
778
  policy = policy_engine.evaluate(
779
  action=request.proposedAction,
780
  risk=risk,
781
  confidence=request.confidenceScore
782
  )
783
 
784
- # 3. RAG memory recall
785
  similar = memory.find_similar(request.proposedAction, limit=3)
786
 
787
- # 4. Track enterprise signals
788
  if not policy["allowed"] and risk["score"] > 0.7:
789
  memory.track_enterprise_signal(
790
  signal_type=LeadSignal.HIGH_RISK_BLOCKED,
@@ -805,7 +805,6 @@ async def evaluate_action(request: ActionRequest):
805
  metadata={"similar_count": len(similar)}
806
  )
807
 
808
- # 5. Store in memory
809
  memory.store_incident(
810
  action=request.proposedAction,
811
  risk_score=risk["score"],
@@ -815,7 +814,6 @@ async def evaluate_action(request: ActionRequest):
815
  gates=policy["gates"]
816
  )
817
 
818
- # 6. Format gates for response
819
  gates = []
820
  for g in policy["gates"]:
821
  gates.append(GateResult(
@@ -828,7 +826,6 @@ async def evaluate_action(request: ActionRequest):
828
  metadata=g.get("metadata")
829
  ))
830
 
831
- # 7. Build execution ladder
832
  execution_ladder = {
833
  "levels": [
834
  {"name": "AUTONOMOUS_LOW", "required": gates[0].passed and gates[1].passed},
@@ -888,31 +885,24 @@ async def get_enterprise_signals(contacted: bool = False):
888
 
889
  @app.post("/api/v1/enterprise/signals/{signal_id}/contact", dependencies=[Depends(verify_api_key)])
890
  async def mark_signal_contacted(signal_id: str):
891
- """Mark a lead signal as contacted (protected)"""
892
  memory.mark_contacted(signal_id)
893
  return {"status": "success", "message": "Signal marked as contacted"}
894
 
895
  @app.get("/api/v1/memory/similar", dependencies=[Depends(verify_api_key)])
896
  async def get_similar_actions(action: str, limit: int = 5):
897
- """Find similar historical actions (protected)"""
898
  similar = memory.find_similar(action, limit=limit)
899
  return {"similar": similar, "count": len(similar)}
900
 
901
  @app.post("/api/v1/feedback", dependencies=[Depends(verify_api_key)])
902
  async def record_outcome(action: str, success: bool):
903
- """
904
- Record actual outcome for Bayesian updating (protected)
905
- """
906
  risk_engine.record_outcome(action, success)
907
  return {"status": "success", "message": "Outcome recorded"}
908
 
909
  # ============== GRADIO LEAD GENERATION UI ==============
910
  def create_lead_gen_ui():
911
  """Professional lead generation interface (no auth needed for UI)"""
912
- # ... (unchanged) ...
913
  with gr.Blocks(title="ARF OSS - Enterprise Reliability Intelligence") as ui:
914
 
915
- # Header
916
  gr.HTML(f"""
917
  <div style="padding: 2rem; border-radius: 1rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; text-align: center;">
918
  <h1 style="font-size: 3em; margin-bottom: 0.5rem;">πŸ€– ARF OSS v3.3.9</h1>
@@ -926,7 +916,6 @@ def create_lead_gen_ui():
926
  </div>
927
  """)
928
 
929
- # Value Proposition
930
  with gr.Row():
931
  with gr.Column():
932
  gr.HTML("""
@@ -939,7 +928,6 @@ def create_lead_gen_ui():
939
  </div>
940
  """)
941
 
942
- # Features Grid
943
  with gr.Row():
944
  with gr.Column():
945
  gr.HTML("""
@@ -972,7 +960,6 @@ def create_lead_gen_ui():
972
  </div>
973
  """)
974
 
975
- # Live Demo Stats
976
  demo_stats = gr.JSON(
977
  label="πŸ“Š Live Demo Statistics",
978
  value={
@@ -983,7 +970,6 @@ def create_lead_gen_ui():
983
  }
984
  )
985
 
986
- # CTA Section
987
  gr.HTML(f"""
988
  <div style="margin: 3rem 0; padding: 3rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
989
  border-radius: 1rem; text-align: center; color: white;">
@@ -993,11 +979,11 @@ def create_lead_gen_ui():
993
  </p>
994
 
995
  <div style="display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;">
996
- <a href="mailto:{settings.LEAD_EMAIL}?subject=ARF%20Enterprise%20Demo%20Request&body=I%20saw%20the%20real%20ARF%20OSS%20demo%20and%20would%20like%20to%20discuss%20Enterprise%20capabilities."
997
  style="background: white; color: #667eea; padding: 1rem 2rem; border-radius: 2rem; font-weight: bold; text-decoration: none; display: inline-block; margin: 0.5rem;">
998
- πŸ“§ {settings.LEAD_EMAIL}
999
  </a>
1000
- <a href="{settings.CALENDLY_URL}" target="_blank"
1001
  style="background: #FFD700; color: #333; padding: 1rem 2rem; border-radius: 2rem; font-weight: bold; text-decoration: none; display: inline-block; margin: 0.5rem;">
1002
  πŸ“… Schedule Technical Demo
1003
  </a>
@@ -1010,11 +996,10 @@ def create_lead_gen_ui():
1010
  </div>
1011
  """)
1012
 
1013
- # Footer
1014
  gr.HTML(f"""
1015
  <div style="text-align: center; padding: 2rem; color: #666; border-top: 1px solid #eee;">
1016
  <p>
1017
- πŸ“§ <a href="mailto:{settings.LEAD_EMAIL}" style="color: #667eea;">{settings.LEAD_EMAIL}</a> β€’
1018
  πŸ™ <a href="https://github.com/petterjuan/agentic-reliability-framework" style="color: #667eea;">GitHub</a>
1019
  </p>
1020
  <p style="font-size: 0.9rem;">
@@ -1046,9 +1031,9 @@ if __name__ == "__main__":
1046
 
1047
  logger.info("="*60)
1048
  logger.info("πŸš€ ARF OSS v3.3.9 Starting")
1049
- logger.info(f"πŸ“Š Data directory: {settings.DATA_DIR}")
1050
- logger.info(f"πŸ“§ Lead email: {settings.LEAD_EMAIL}")
1051
- logger.info(f"πŸ”‘ API Key: {settings.API_KEY[:8]}... (set in HF secrets)")
1052
  logger.info(f"🌐 Serving at: http://0.0.0.0:{port}")
1053
  logger.info("="*60)
1054
 
 
18
  import asyncio
19
  import sqlite3
20
  import requests
21
+ import fcntl # <-- NEW: for file locking
22
+ import sys
23
  from datetime import datetime, timedelta
24
  from typing import Dict, List, Optional, Any, Tuple
25
  from contextlib import contextmanager
 
33
  from fastapi import FastAPI, HTTPException, Depends, status
34
  from fastapi.middleware.cors import CORSMiddleware
35
  from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
36
+ from pydantic import BaseModel, Field, field_validator, ConfigDict # <-- NEW: ConfigDict
37
+ from pydantic_settings import BaseSettings, SettingsConfigDict # <-- NEW: SettingsConfigDict
38
  from gradio import mount_gradio_app
39
 
40
+ # ============== SINGLE INSTANCE LOCK ==============
41
+ LOCK_FILE = '/tmp/arf_app.lock'
42
+ try:
43
+ lock_fd = open(LOCK_FILE, 'w')
44
+ fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
45
+ except (IOError, OSError):
46
+ print("Another instance is already running. Exiting.")
47
+ sys.exit(1)
48
+ # ==================================================
49
+
50
+ # ============== CONFIGURATION (Pydantic V2) ==============
51
  class Settings(BaseSettings):
52
+ """Centralized configuration using Pydantic Settings V2"""
53
 
54
+ # Hugging Face settings (aliased to match expected env vars)
55
+ hf_space_id: str = Field(default='local', alias='SPACE_ID')
56
+ hf_token: str = Field(default='', alias='HF_TOKEN')
57
 
58
  # Persistence - HF persistent storage
59
+ data_dir: str = Field(
60
+ default='/data' if os.path.exists('/data') else './data',
61
+ alias='DATA_DIR'
62
+ )
63
 
64
  # Lead generation
65
+ lead_email: str = "petter2025us@outlook.com"
66
+ calendly_url: str = "https://calendly.com/petter2025us/arf-demo"
67
 
68
  # Webhook for lead alerts (set in HF secrets)
69
+ slack_webhook: str = Field(default='', alias='SLACK_WEBHOOK')
70
+ sendgrid_api_key: str = Field(default='', alias='SENDGRID_API_KEY')
71
 
72
  # Security
73
+ api_key: str = Field(
74
+ default_factory=lambda: str(uuid.uuid4()),
75
+ alias='ARF_API_KEY'
76
+ )
77
 
78
  # ARF defaults
79
+ default_confidence_threshold: float = 0.9
80
+ default_max_risk: str = "MEDIUM"
81
 
82
+ # Pydantic V2 configuration
83
+ model_config = SettingsConfigDict(
84
+ populate_by_name=True, # allows use of field names or aliases
85
+ extra='ignore', # ignore extra env vars
86
+ env_prefix='', # no prefix
87
+ case_sensitive=False # case-insensitive matching
88
+ )
89
 
90
  def __init__(self, **kwargs):
91
  super().__init__(**kwargs)
92
  # Ensure data directory exists
93
+ os.makedirs(self.data_dir, exist_ok=True)
94
 
95
  settings = Settings()
96
 
 
99
  level=logging.INFO,
100
  format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
101
  handlers=[
102
+ logging.FileHandler(f'{settings.data_dir}/arf.log'),
103
  logging.StreamHandler()
104
  ]
105
  )
 
145
  'default': {'alpha': 2.0, 'beta': 5.0}
146
  }
147
 
148
+ self.evidence_db = f"{settings.data_dir}/evidence.db"
149
  self._init_db()
150
 
151
  def _init_db(self):
 
212
  return (row[0] or 0, row[1] or 0) if row else (0, 0)
213
  except sqlite3.Error as e:
214
  logger.error(f"Failed to retrieve evidence: {e}")
215
+ return (0, 0)
216
 
217
  def calculate_posterior(self,
218
  action_text: str,
219
  context: Dict[str, Any]) -> Dict[str, Any]:
 
 
220
  action_type = self.classify_action(action_text)
221
  alpha0, beta0 = self.get_prior(action_type)
222
 
 
223
  action_hash = hashlib.sha256(action_text.encode()).hexdigest()
224
  successes, trials = self.get_evidence(action_hash)
225
 
 
226
  alpha_n = alpha0 + successes
227
  beta_n = beta0 + (trials - successes)
228
 
 
229
  posterior_mean = alpha_n / (alpha_n + beta_n)
 
 
230
  context_multiplier = self._context_likelihood(context)
231
 
 
232
  risk_score = posterior_mean * context_multiplier
233
  risk_score = min(0.99, max(0.01, risk_score))
234
 
 
235
  variance = (alpha_n * beta_n) / ((alpha_n + beta_n)**2 * (alpha_n + beta_n + 1))
236
  std_dev = variance ** 0.5
237
  ci_lower = max(0.01, posterior_mean - 1.96 * std_dev)
238
  ci_upper = min(0.99, posterior_mean + 1.96 * std_dev)
239
 
 
240
  if risk_score > 0.8:
241
  risk_level = RiskLevel.CRITICAL
242
  elif risk_score > 0.6:
 
301
 
302
  # ============== POLICY ENGINE ==============
303
  class PolicyEngine:
 
304
  def __init__(self):
305
  self.config = {
306
+ "confidence_threshold": settings.default_confidence_threshold,
307
+ "max_autonomous_risk": settings.default_max_risk,
308
  "risk_thresholds": {
309
  RiskLevel.LOW: 0.7,
310
  RiskLevel.MEDIUM: 0.5,
 
329
  action: str,
330
  risk: Dict[str, Any],
331
  confidence: float) -> Dict[str, Any]:
 
332
  gates = []
333
 
334
  # Gate 1: Confidence threshold
 
397
  "type": "license"
398
  })
399
 
 
400
  all_passed = all(g["passed"] for g in gates)
401
 
402
  if not all_passed:
 
425
 
426
  # ============== RAG MEMORY WITH PERSISTENCE ==============
427
  class RAGMemory:
 
428
  def __init__(self):
429
+ self.db_path = f"{settings.data_dir}/memory.db"
430
  self._init_db()
431
  self.embedding_cache = {}
432
 
 
480
  conn.close()
481
 
482
  def _simple_embedding(self, text: str) -> List[float]:
 
483
  if text in self.embedding_cache:
484
  return self.embedding_cache[text]
485
 
 
599
  return signal
600
 
601
  def _notify_sales_team(self, signal: Dict):
602
+ if settings.slack_webhook:
603
  try:
604
+ requests.post(settings.slack_webhook, json={
605
  "text": f"🚨 *Enterprise Lead Signal*\n"
606
  f"Type: {signal['signal_type']}\n"
607
  f"Action: {signal['action']}\n"
608
  f"Risk Score: {signal['risk_score']:.2f}\n"
609
  f"Time: {signal['timestamp']}\n"
610
+ f"Contact: {settings.lead_email}"
611
  }, timeout=5)
612
  except requests.RequestException as e:
613
  logger.error(f"Slack notification failed: {e}")
 
614
 
615
  def get_uncontacted_signals(self) -> List[Dict]:
616
  try:
 
647
  security = HTTPBearer()
648
 
649
  async def verify_api_key(credentials: HTTPAuthorizationCredentials = Depends(security)):
650
+ if credentials.credentials != settings.api_key:
 
651
  raise HTTPException(
652
  status_code=status.HTTP_403_FORBIDDEN,
653
  detail="Invalid API key"
 
709
  description="Real ARF OSS components for enterprise lead generation",
710
  contact={
711
  "name": "ARF Sales",
712
+ "email": settings.lead_email,
713
  }
714
  )
715
 
 
765
  Real ARF OSS evaluation pipeline (protected)
766
  """
767
  try:
 
768
  context = {
769
  "environment": "production",
770
  "user_role": request.user_role,
 
772
  "requires_human": request.requiresHuman
773
  }
774
 
 
775
  risk = risk_engine.calculate_posterior(
776
  action_text=request.proposedAction,
777
  context=context
778
  )
779
 
 
780
  policy = policy_engine.evaluate(
781
  action=request.proposedAction,
782
  risk=risk,
783
  confidence=request.confidenceScore
784
  )
785
 
 
786
  similar = memory.find_similar(request.proposedAction, limit=3)
787
 
 
788
  if not policy["allowed"] and risk["score"] > 0.7:
789
  memory.track_enterprise_signal(
790
  signal_type=LeadSignal.HIGH_RISK_BLOCKED,
 
805
  metadata={"similar_count": len(similar)}
806
  )
807
 
 
808
  memory.store_incident(
809
  action=request.proposedAction,
810
  risk_score=risk["score"],
 
814
  gates=policy["gates"]
815
  )
816
 
 
817
  gates = []
818
  for g in policy["gates"]:
819
  gates.append(GateResult(
 
826
  metadata=g.get("metadata")
827
  ))
828
 
 
829
  execution_ladder = {
830
  "levels": [
831
  {"name": "AUTONOMOUS_LOW", "required": gates[0].passed and gates[1].passed},
 
885
 
886
  @app.post("/api/v1/enterprise/signals/{signal_id}/contact", dependencies=[Depends(verify_api_key)])
887
  async def mark_signal_contacted(signal_id: str):
 
888
  memory.mark_contacted(signal_id)
889
  return {"status": "success", "message": "Signal marked as contacted"}
890
 
891
  @app.get("/api/v1/memory/similar", dependencies=[Depends(verify_api_key)])
892
  async def get_similar_actions(action: str, limit: int = 5):
 
893
  similar = memory.find_similar(action, limit=limit)
894
  return {"similar": similar, "count": len(similar)}
895
 
896
  @app.post("/api/v1/feedback", dependencies=[Depends(verify_api_key)])
897
  async def record_outcome(action: str, success: bool):
 
 
 
898
  risk_engine.record_outcome(action, success)
899
  return {"status": "success", "message": "Outcome recorded"}
900
 
901
  # ============== GRADIO LEAD GENERATION UI ==============
902
  def create_lead_gen_ui():
903
  """Professional lead generation interface (no auth needed for UI)"""
 
904
  with gr.Blocks(title="ARF OSS - Enterprise Reliability Intelligence") as ui:
905
 
 
906
  gr.HTML(f"""
907
  <div style="padding: 2rem; border-radius: 1rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; text-align: center;">
908
  <h1 style="font-size: 3em; margin-bottom: 0.5rem;">πŸ€– ARF OSS v3.3.9</h1>
 
916
  </div>
917
  """)
918
 
 
919
  with gr.Row():
920
  with gr.Column():
921
  gr.HTML("""
 
928
  </div>
929
  """)
930
 
 
931
  with gr.Row():
932
  with gr.Column():
933
  gr.HTML("""
 
960
  </div>
961
  """)
962
 
 
963
  demo_stats = gr.JSON(
964
  label="πŸ“Š Live Demo Statistics",
965
  value={
 
970
  }
971
  )
972
 
 
973
  gr.HTML(f"""
974
  <div style="margin: 3rem 0; padding: 3rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
975
  border-radius: 1rem; text-align: center; color: white;">
 
979
  </p>
980
 
981
  <div style="display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap;">
982
+ <a href="mailto:{settings.lead_email}?subject=ARF%20Enterprise%20Demo%20Request&body=I%20saw%20the%20real%20ARF%20OSS%20demo%20and%20would%20like%20to%20discuss%20Enterprise%20capabilities."
983
  style="background: white; color: #667eea; padding: 1rem 2rem; border-radius: 2rem; font-weight: bold; text-decoration: none; display: inline-block; margin: 0.5rem;">
984
+ πŸ“§ {settings.lead_email}
985
  </a>
986
+ <a href="{settings.calendly_url}" target="_blank"
987
  style="background: #FFD700; color: #333; padding: 1rem 2rem; border-radius: 2rem; font-weight: bold; text-decoration: none; display: inline-block; margin: 0.5rem;">
988
  πŸ“… Schedule Technical Demo
989
  </a>
 
996
  </div>
997
  """)
998
 
 
999
  gr.HTML(f"""
1000
  <div style="text-align: center; padding: 2rem; color: #666; border-top: 1px solid #eee;">
1001
  <p>
1002
+ πŸ“§ <a href="mailto:{settings.lead_email}" style="color: #667eea;">{settings.lead_email}</a> β€’
1003
  πŸ™ <a href="https://github.com/petterjuan/agentic-reliability-framework" style="color: #667eea;">GitHub</a>
1004
  </p>
1005
  <p style="font-size: 0.9rem;">
 
1031
 
1032
  logger.info("="*60)
1033
  logger.info("πŸš€ ARF OSS v3.3.9 Starting")
1034
+ logger.info(f"πŸ“Š Data directory: {settings.data_dir}")
1035
+ logger.info(f"πŸ“§ Lead email: {settings.lead_email}")
1036
+ logger.info(f"πŸ”‘ API Key: {settings.api_key[:8]}... (set in HF secrets)")
1037
  logger.info(f"🌐 Serving at: http://0.0.0.0:{port}")
1038
  logger.info("="*60)
1039