arjitmat commited on
Commit
13e7acd
·
verified ·
1 Parent(s): 59b84e5

Upload 72 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +1 -0
  2. README.md +315 -8
  3. app.py +17 -0
  4. app/__init__.py +0 -0
  5. app/__pycache__/__init__.cpython-313.pyc +0 -0
  6. app/__pycache__/streamlit_app.cpython-313.pyc +0 -0
  7. app/components/__init__.py +15 -0
  8. app/components/__pycache__/__init__.cpython-313.pyc +0 -0
  9. app/components/__pycache__/input_form.cpython-313.pyc +0 -0
  10. app/components/__pycache__/monitoring_setup.cpython-313.pyc +0 -0
  11. app/components/__pycache__/results_display.cpython-313.pyc +0 -0
  12. app/components/input_form.py +223 -0
  13. app/components/monitoring_setup.py +247 -0
  14. app/components/results_display.py +374 -0
  15. app/streamlit_app.py +208 -0
  16. chroma_db/c30b23e7-6559-433a-b87d-7290f0978053/data_level0.bin +3 -0
  17. chroma_db/c30b23e7-6559-433a-b87d-7290f0978053/header.bin +3 -0
  18. chroma_db/c30b23e7-6559-433a-b87d-7290f0978053/length.bin +3 -0
  19. chroma_db/c30b23e7-6559-433a-b87d-7290f0978053/link_lists.bin +3 -0
  20. chroma_db/chroma.sqlite3 +3 -0
  21. config.toml +15 -0
  22. requirements.txt +32 -0
  23. src/__init__.py +0 -0
  24. src/__pycache__/__init__.cpython-313.pyc +0 -0
  25. src/__pycache__/config.cpython-313.pyc +0 -0
  26. src/agents/__init__.py +0 -0
  27. src/agents/__pycache__/__init__.cpython-313.pyc +0 -0
  28. src/agents/__pycache__/orchestrator.cpython-313.pyc +0 -0
  29. src/agents/__pycache__/tools.cpython-313.pyc +0 -0
  30. src/agents/orchestrator.py +621 -0
  31. src/agents/tools.py +419 -0
  32. src/analysis/__init__.py +0 -0
  33. src/analysis/__pycache__/__init__.cpython-313.pyc +0 -0
  34. src/analysis/__pycache__/compliance_engine.cpython-313.pyc +0 -0
  35. src/analysis/__pycache__/cost_calculator.cpython-313.pyc +0 -0
  36. src/analysis/__pycache__/risk_scorer.cpython-313.pyc +0 -0
  37. src/analysis/__pycache__/token_classifier.cpython-313.pyc +0 -0
  38. src/analysis/compliance_engine.py +486 -0
  39. src/analysis/cost_calculator.py +544 -0
  40. src/analysis/risk_scorer.py +454 -0
  41. src/analysis/token_classifier.py +487 -0
  42. src/config.py +70 -0
  43. src/data/__init__.py +0 -0
  44. src/data/__pycache__/__init__.cpython-313.pyc +0 -0
  45. src/data/__pycache__/vectordb.cpython-313.pyc +0 -0
  46. src/data/regulations/eu/mica-asset-referenced-tokens-2024.json +266 -0
  47. src/data/regulations/schema.json +178 -0
  48. src/data/regulations/singapore/mas-cmp-real-estate-tokens-2024.json +275 -0
  49. src/data/regulations/uae/vara-sto-real-estate-2024.json +229 -0
  50. src/data/regulations/uk/fca-cis-property-tokens-2024.json +282 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ chroma_db/chroma.sqlite3 filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,12 +1,319 @@
1
  ---
2
- title: Crypto Compliance Agent
3
- emoji: 📉
4
- colorFrom: yellow
5
- colorTo: green
6
- sdk: gradio
7
- sdk_version: 5.49.1
8
- app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Crypto Compliance Intelligence Agent
3
+ emoji: 🔒
4
+ colorFrom: blue
5
+ colorTo: indigo
6
+ sdk: streamlit
7
+ sdk_version: 1.29.0
8
+ app_file: app/streamlit_app.py
9
  pinned: false
10
+ license: mit
11
  ---
12
 
13
+ # Crypto Compliance Intelligence Agent
14
+
15
+ > Multi-jurisdiction crypto regulatory compliance analysis powered by AI agents and RAG
16
+
17
+ ---
18
+
19
+ ## Overview
20
+
21
+ An AI-powered system that helps crypto businesses navigate complex regulatory requirements across multiple jurisdictions. Get instant compliance analysis, risk scoring, and cost estimates in under 60 seconds.
22
+
23
+ **Supported Jurisdictions**: US (SEC + State MTLs) | EU (MiCA) | Singapore (MAS) | UK (FCA) | UAE (VARA)
24
+
25
+ ---
26
+
27
+ ## Features
28
+
29
+ - **Multi-Jurisdiction Analysis**: Comprehensive compliance mapping across US, EU, Singapore, and UK
30
+ - **AI Agent Architecture**: LangGraph-powered agent with tool use and reasoning
31
+ - **RAG-Based Retrieval**: Semantic search over regulatory documents using ChromaDB
32
+ - **Token Classification**: Automated Howey Test analysis for security determination
33
+ - **Risk Scoring**: 0-100 weighted risk assessment based on gaps and severity
34
+ - **Cost Estimation**: First-year and ongoing compliance cost breakdowns
35
+ - **Regulatory Monitoring**: Automated scraping of SEC, MiCA, MAS, and FCA sources
36
+ - **Explainable AI**: Full agent reasoning chain visible in results
37
+
38
+ ---
39
+
40
+ ## Tech Stack
41
+
42
+ | Component | Technology |
43
+ |-----------|------------|
44
+ | **LLM** | Google Gemini Flash 2.5 (2M context window) |
45
+ | **Agent Framework** | LangGraph (state machines + tool use) |
46
+ | **Vector Database** | ChromaDB (local persistence) |
47
+ | **Embeddings** | sentence-transformers/all-MiniLM-L6-v2 |
48
+ | **NLP Models** | FinBERT, Legal-BERT |
49
+ | **Web Framework** | Streamlit |
50
+ | **Data Processing** | pandas, pdfplumber, BeautifulSoup4 |
51
+ | **Deployment** | HuggingFace Spaces |
52
+
53
+ ---
54
+
55
+ ## Quick Start
56
+
57
+ ### Prerequisites
58
+
59
+ - Python 3.11+ (tested on 3.13.5)
60
+ - Google Gemini API key ([Get it here](https://makersuite.google.com/app/apikey))
61
+ - HuggingFace token (optional, for model downloads)
62
+
63
+ ### Installation
64
+
65
+ ```bash
66
+ # Clone repository
67
+ git clone https://github.com/yourusername/crypto-compliance-agent.git
68
+ cd crypto-compliance-agent
69
+
70
+ # Create virtual environment
71
+ python -m venv venv
72
+ source venv/bin/activate # Windows: venv\Scripts\activate
73
+
74
+ # Install dependencies
75
+ pip install -r requirements.txt
76
+
77
+ # Configure environment
78
+ cp .env.example .env
79
+ # Edit .env and add your GEMINI_API_KEY
80
+ ```
81
+
82
+ ### Initialize Database
83
+
84
+ ```bash
85
+ # Populate ChromaDB with regulatory data
86
+ python scripts/setup_vectordb.py
87
+ ```
88
+
89
+ ### Run Locally
90
+
91
+ ```bash
92
+ streamlit run app/streamlit_app.py
93
+ ```
94
+
95
+ Navigate to `http://localhost:8501`
96
+
97
+ ---
98
+
99
+ ## Project Structure
100
+
101
+ ```
102
+ crypto-compliance-agent/
103
+ ├── src/
104
+ │ ├── config.py # Configuration management
105
+ │ ├── models/ # LLM and embeddings
106
+ │ │ ├── llm.py # Gemini integration
107
+ │ │ └── embeddings.py # Sentence transformers
108
+ │ ├── agents/ # LangGraph agents
109
+ │ │ ├── orchestrator.py # Main agent coordinator
110
+ │ │ ├── tools.py # Agent tools (search, calculate, etc.)
111
+ │ │ ├── classifier.py # Activity/token classification
112
+ │ │ └── analyzer.py # Compliance analysis
113
+ │ ├── data/ # Data layer
114
+ │ │ ├── vectordb.py # ChromaDB interface
115
+ │ │ ├── scrapers/ # Regulatory source scrapers
116
+ │ │ │ ├── sec.py # US SEC
117
+ │ │ │ ├── mica.py # EU MiCA
118
+ │ │ │ ├── mas.py # Singapore MAS
119
+ │ │ │ └── fca.py # UK FCA
120
+ │ │ └── regulations/ # Regulatory knowledge base
121
+ │ │ ├── us/
122
+ │ │ ├── eu/
123
+ │ │ ├── singapore/
124
+ │ │ └── uk/
125
+ │ ├── processors/ # Document processing
126
+ │ │ ├── document_parser.py # PDF/text extraction
127
+ │ │ └── entity_extraction.py # FinBERT NER
128
+ │ ├── analysis/ # Analysis engines
129
+ │ │ ├── compliance_engine.py # Rule matching
130
+ │ │ ├── risk_scorer.py # Risk calculation
131
+ │ │ ├── token_classifier.py # Howey Test
132
+ │ │ └── cost_calculator.py # Cost estimation
133
+ │ └── utils/ # Utilities
134
+ ├── app/
135
+ │ ├── streamlit_app.py # Main UI entry point
136
+ │ └── components/ # UI components
137
+ │ ├── input_form.py
138
+ │ ├── results_display.py
139
+ │ └── monitoring_setup.py
140
+ ├── docs/
141
+ │ ├── system_index.md # Technical documentation
142
+ │ ├── navigation_guide.md # Non-technical guide
143
+ │ └── project_explanation.md # Detailed explanation
144
+ ├── context/
145
+ │ ├── development_log.md # Project history
146
+ │ ├── resume_context.md # Session recovery (not committed)
147
+ │ └── portfolio_doc.md # Career documentation (not committed)
148
+ ├── tests/ # Unit tests
149
+ ├── scripts/ # Utility scripts
150
+ │ ├── setup_vectordb.py
151
+ │ ├── update_regulations.py
152
+ │ └── test_agent.py
153
+ └── requirements.txt
154
+ ```
155
+
156
+ ---
157
+
158
+ ## Usage Example
159
+
160
+ ### Input
161
+
162
+ ```python
163
+ {
164
+ "jurisdictions": ["United States", "European Union"],
165
+ "activities": ["NFT marketplace", "Crypto payment processor"],
166
+ "description": "Users buy and sell NFTs using credit cards and crypto. We take a 2% fee.",
167
+ "token_info": None
168
+ }
169
+ ```
170
+
171
+ ### Output
172
+
173
+ ```
174
+ Risk Score: 65/100 (Medium-High)
175
+ Compliance Gaps: 4
176
+ Estimated First-Year Cost: $250,000 - $550,000
177
+
178
+ US Compliance:
179
+ ❌ FinCEN MSB Registration Required ($5k-$10k)
180
+ ❌ State MTL Licenses (15 states targeted: $750k-$2.25M)
181
+ ✅ No SEC registration (NFTs not securities)
182
+
183
+ EU Compliance:
184
+ ❌ MiCA CASP Authorization (€100k-€300k)
185
+ ❌ AML/KYC Compliance (€50k/year ongoing)
186
+
187
+ Recommendations:
188
+ 1. File FinCEN MSB within 180 days
189
+ 2. Prioritize MTL applications for high-volume states (CA, NY, TX)
190
+ 3. Engage MiCA legal counsel in EU member state of choice
191
+ 4. Implement KYC/AML procedures before EU launch
192
+ ```
193
+
194
+ ---
195
+
196
+ ## Documentation
197
+
198
+ - **[System Index](docs/system_index.md)**: Technical architecture and API reference
199
+ - **[Navigation Guide](docs/navigation_guide.md)**: Non-technical codebase guide
200
+ - **[Project Explanation](docs/project_explanation.md)**: Detailed project overview
201
+
202
+ ---
203
+
204
+ ## Development
205
+
206
+ ### Running Tests
207
+
208
+ ```bash
209
+ pytest tests/
210
+ ```
211
+
212
+ ### Code Quality
213
+
214
+ ```bash
215
+ # Format code
216
+ black src/ app/ tests/
217
+
218
+ # Lint
219
+ flake8 src/ app/ tests/
220
+ ```
221
+
222
+ ### Update Regulations
223
+
224
+ ```bash
225
+ # Run weekly to fetch latest regulations
226
+ python scripts/update_regulations.py
227
+ ```
228
+
229
+ ---
230
+
231
+ ## Deployment
232
+
233
+ ### HuggingFace Spaces
234
+
235
+ 1. Create new Space: [https://huggingface.co/new-space](https://huggingface.co/new-space)
236
+ 2. Select **Streamlit** SDK
237
+ 3. Link GitHub repository
238
+ 4. Add secrets in Space settings:
239
+ - `GEMINI_API_KEY`
240
+ - `HF_TOKEN` (optional)
241
+ 5. Deploy
242
+
243
+ ### Environment Variables
244
+
245
+ Required:
246
+ - `GEMINI_API_KEY`: Google Gemini API key
247
+
248
+ Optional:
249
+ - `HF_TOKEN`: HuggingFace token
250
+ - `GITHUB_TOKEN`: GitHub access (for scrapers)
251
+ - `LOG_LEVEL`: Logging level (default: INFO)
252
+
253
+ ---
254
+
255
+ ## Important Disclaimers
256
+
257
+ ### ⚠️ NOT LEGAL ADVICE
258
+
259
+ This tool provides **general information only** and does NOT constitute legal, financial, or regulatory advice.
260
+
261
+ - **No Warranties**: Accuracy not guaranteed. Regulations change frequently.
262
+ - **No Liability**: Users assume all risk. Creators not liable for compliance failures, fines, or legal issues.
263
+ - **Consult Lawyers**: Always consult qualified legal counsel before making compliance decisions.
264
+
265
+ Use at your own risk.
266
+
267
+ ---
268
+
269
+ ## Roadmap
270
+
271
+ - [x] Phase 1: Foundation setup
272
+ - [x] Phase 2: Regulatory data layer (5 jurisdictions, ChromaDB)
273
+ - [x] Phase 3: NLP & entity extraction (Howey Test, entity recognition)
274
+ - [x] Phase 4: Agent architecture (LangGraph, 6 nodes, 6 tools)
275
+ - [x] Phase 5: Analysis & scoring engine (ComplianceEngine, RiskScorer, CostCalculator)
276
+ - [x] Phase 6: Streamlit UI (3-tab interface, Plotly visualizations)
277
+ - [x] Phase 7: Testing & optimization (Integration tests, performance validation)
278
+ - [ ] Phase 8: Deployment to HuggingFace Spaces (IN PROGRESS)
279
+
280
+ See `context/development_log.md` for detailed progress.
281
+
282
+ ---
283
+
284
+ ## Contributing
285
+
286
+ Contributions welcome! Please:
287
+
288
+ 1. Fork the repository
289
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
290
+ 3. Commit changes (`git commit -m 'Add amazing feature'`)
291
+ 4. Push to branch (`git push origin feature/amazing-feature`)
292
+ 5. Open a Pull Request
293
+
294
+ ---
295
+
296
+ ## License
297
+
298
+ [To be determined]
299
+
300
+ ---
301
+
302
+ ## Contact
303
+
304
+ - **Issues**: [GitHub Issues](https://github.com/yourusername/crypto-compliance-agent/issues)
305
+ - **Documentation**: `docs/` folder
306
+
307
+ ---
308
+
309
+ ## Acknowledgments
310
+
311
+ - **Google Gemini**: LLM provider
312
+ - **LangChain/LangGraph**: Agent framework
313
+ - **ChromaDB**: Vector database
314
+ - **HuggingFace**: Model hosting and deployment
315
+ - **Regulatory Sources**: SEC, EU Official Journal, MAS, FCA
316
+
317
+ ---
318
+
319
+ **Built with AI assistance** (Claude Code) | **Status**: Phases 1-7 Complete - Ready for Deployment | **Last Updated**: 2025-10-22
app.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Entry point for HuggingFace Spaces deployment.
3
+ Redirects to the main Streamlit app.
4
+ """
5
+
6
+ import subprocess
7
+ import sys
8
+
9
+ if __name__ == "__main__":
10
+ # Run the Streamlit app
11
+ subprocess.run([
12
+ sys.executable, "-m", "streamlit", "run",
13
+ "app/streamlit_app.py",
14
+ "--server.headless=true",
15
+ "--server.port=7860",
16
+ "--server.enableCORS=false"
17
+ ])
app/__init__.py ADDED
File without changes
app/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (191 Bytes). View file
 
app/__pycache__/streamlit_app.cpython-313.pyc ADDED
Binary file (8.81 kB). View file
 
app/components/__init__.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ UI Components for Streamlit app
3
+ """
4
+
5
+ from app.components.input_form import render_input_form, get_jurisdiction_display_name, get_activity_display_name
6
+ from app.components.results_display import render_results
7
+ from app.components.monitoring_setup import render_monitoring_setup
8
+
9
+ __all__ = [
10
+ 'render_input_form',
11
+ 'render_results',
12
+ 'render_monitoring_setup',
13
+ 'get_jurisdiction_display_name',
14
+ 'get_activity_display_name'
15
+ ]
app/components/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (608 Bytes). View file
 
app/components/__pycache__/input_form.cpython-313.pyc ADDED
Binary file (9.75 kB). View file
 
app/components/__pycache__/monitoring_setup.cpython-313.pyc ADDED
Binary file (10.4 kB). View file
 
app/components/__pycache__/results_display.cpython-313.pyc ADDED
Binary file (17.3 kB). View file
 
app/components/input_form.py ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Input Form Component
3
+
4
+ Renders the user input form for compliance analysis.
5
+ """
6
+
7
+ import streamlit as st
8
+ from typing import Dict, Optional
9
+
10
+
11
+ def render_input_form() -> Optional[Dict]:
12
+ """
13
+ Render input form for compliance analysis.
14
+
15
+ Returns:
16
+ Dictionary with form data if submitted, None otherwise
17
+ """
18
+
19
+ with st.form("compliance_form"):
20
+ st.markdown("### Business Information")
21
+
22
+ # Jurisdiction selection
23
+ st.markdown("#### 1. Select Target Jurisdictions")
24
+ st.caption("Choose all jurisdictions where you plan to operate")
25
+
26
+ col1, col2 = st.columns(2)
27
+
28
+ with col1:
29
+ us_selected = st.checkbox("🇺🇸 United States (SEC)", value=False)
30
+ eu_selected = st.checkbox("🇪🇺 European Union (MiCA)", value=False)
31
+ singapore_selected = st.checkbox("🇸🇬 Singapore (MAS)", value=False)
32
+
33
+ with col2:
34
+ uk_selected = st.checkbox("🇬🇧 United Kingdom (FCA)", value=False)
35
+ uae_selected = st.checkbox("🇦🇪 UAE (VARA)", value=False)
36
+
37
+ # Build jurisdictions list
38
+ jurisdictions = []
39
+ if us_selected:
40
+ jurisdictions.append('us')
41
+ if eu_selected:
42
+ jurisdictions.append('eu')
43
+ if singapore_selected:
44
+ jurisdictions.append('singapore')
45
+ if uk_selected:
46
+ jurisdictions.append('uk')
47
+ if uae_selected:
48
+ jurisdictions.append('uae')
49
+
50
+ st.markdown("---")
51
+
52
+ # Activity selection
53
+ st.markdown("#### 2. Select Crypto Activities")
54
+ st.caption("Check all activities your business will perform")
55
+
56
+ col1, col2, col3 = st.columns(3)
57
+
58
+ with col1:
59
+ exchange = st.checkbox("💱 Exchange/Trading Platform", value=False)
60
+ custody = st.checkbox("🔐 Custody Services", value=False)
61
+ staking = st.checkbox("📊 Staking Services", value=False)
62
+ lending = st.checkbox("💰 Lending", value=False)
63
+ borrowing = st.checkbox("📈 Borrowing", value=False)
64
+
65
+ with col2:
66
+ payment = st.checkbox("💳 Payment Processing", value=False)
67
+ token_issuance = st.checkbox("🪙 Token Issuance", value=False)
68
+ mining = st.checkbox("⛏️ Mining", value=False)
69
+ nft = st.checkbox("🖼️ NFT Marketplace", value=False)
70
+
71
+ with col3:
72
+ defi = st.checkbox("🔄 DeFi Protocol", value=False)
73
+ derivatives = st.checkbox("📉 Derivatives", value=False)
74
+ otc = st.checkbox("🤝 OTC Trading", value=False)
75
+ wallet = st.checkbox("👛 Wallet Services", value=False)
76
+
77
+ # Build activities list
78
+ activities = []
79
+ if exchange:
80
+ activities.append('exchange')
81
+ if custody:
82
+ activities.append('custody')
83
+ if staking:
84
+ activities.append('staking')
85
+ if lending:
86
+ activities.append('lending')
87
+ if borrowing:
88
+ activities.append('borrowing')
89
+ if payment:
90
+ activities.append('payment_processing')
91
+ if token_issuance:
92
+ activities.append('token_issuance')
93
+ if mining:
94
+ activities.append('mining')
95
+ if nft:
96
+ activities.append('nft_marketplace')
97
+ if defi:
98
+ activities.append('defi_protocol')
99
+ if derivatives:
100
+ activities.append('derivatives')
101
+ if otc:
102
+ activities.append('otc_trading')
103
+ if wallet:
104
+ activities.append('wallet_services')
105
+
106
+ st.markdown("---")
107
+
108
+ # Business description
109
+ st.markdown("#### 3. Describe Your Business")
110
+ st.caption("Provide a detailed description of your business model, target users, and how your platform works")
111
+
112
+ business_description = st.text_area(
113
+ "Business Description",
114
+ height=150,
115
+ placeholder="Example: We're building a DeFi lending protocol where users can deposit USDC and earn 6% APY. "
116
+ "Borrowers can take loans at 8% interest rate. We'll issue a governance token to early users.",
117
+ label_visibility="collapsed"
118
+ )
119
+
120
+ st.markdown("---")
121
+
122
+ # Token information (optional)
123
+ st.markdown("#### 4. Token Information (Optional)")
124
+ st.caption("If you're issuing a token, provide details for classification analysis")
125
+
126
+ issue_token = st.checkbox("I am issuing a token", value=False)
127
+
128
+ token_description = None
129
+ if issue_token:
130
+ col1, col2 = st.columns(2)
131
+
132
+ with col1:
133
+ token_name = st.text_input("Token Name", placeholder="e.g., MyToken")
134
+ token_symbol = st.text_input("Token Symbol", placeholder="e.g., MTK")
135
+
136
+ with col2:
137
+ token_use_case = st.selectbox(
138
+ "Primary Use Case",
139
+ ["Utility", "Governance", "Payment", "Security/Investment", "Hybrid"]
140
+ )
141
+
142
+ token_details = st.text_area(
143
+ "Token Details",
144
+ height=100,
145
+ placeholder="Describe token distribution, vesting, utility, and how holders benefit",
146
+ help="Include: Distribution model, vesting schedules, utility within platform, rights/benefits for holders"
147
+ )
148
+
149
+ # Build token description
150
+ if token_name or token_details:
151
+ token_description = f"Token Name: {token_name or 'N/A'}\n"
152
+ token_description += f"Token Symbol: {token_symbol or 'N/A'}\n"
153
+ token_description += f"Primary Use Case: {token_use_case}\n"
154
+ token_description += f"Details: {token_details or 'N/A'}"
155
+
156
+ st.markdown("---")
157
+
158
+ # Submit button
159
+ submitted = st.form_submit_button("🚀 Analyze Compliance", use_container_width=True)
160
+
161
+ if submitted:
162
+ # Validation
163
+ errors = []
164
+
165
+ if not jurisdictions:
166
+ errors.append("⚠️ Please select at least one jurisdiction")
167
+
168
+ if not activities:
169
+ errors.append("⚠️ Please select at least one crypto activity")
170
+
171
+ if not business_description or len(business_description) < 50:
172
+ errors.append("⚠️ Please provide a detailed business description (at least 50 characters)")
173
+
174
+ if issue_token and not token_details:
175
+ errors.append("⚠️ Please provide token details if you're issuing a token")
176
+
177
+ if errors:
178
+ for error in errors:
179
+ st.error(error)
180
+ return None
181
+
182
+ # Return form data
183
+ return {
184
+ 'jurisdictions': jurisdictions,
185
+ 'activities': activities,
186
+ 'business_description': business_description,
187
+ 'token_description': token_description,
188
+ 'issue_token': issue_token
189
+ }
190
+
191
+ return None
192
+
193
+
194
+ def get_jurisdiction_display_name(jurisdiction: str) -> str:
195
+ """Get display name for jurisdiction."""
196
+ mapping = {
197
+ 'us': '🇺🇸 United States',
198
+ 'eu': '🇪🇺 European Union',
199
+ 'singapore': '🇸🇬 Singapore',
200
+ 'uk': '🇬🇧 United Kingdom',
201
+ 'uae': '🇦🇪 UAE'
202
+ }
203
+ return mapping.get(jurisdiction, jurisdiction.upper())
204
+
205
+
206
+ def get_activity_display_name(activity: str) -> str:
207
+ """Get display name for activity."""
208
+ mapping = {
209
+ 'exchange': '💱 Exchange/Trading',
210
+ 'custody': '🔐 Custody',
211
+ 'staking': '📊 Staking',
212
+ 'lending': '💰 Lending',
213
+ 'borrowing': '📈 Borrowing',
214
+ 'payment_processing': '💳 Payment Processing',
215
+ 'token_issuance': '🪙 Token Issuance',
216
+ 'mining': '⛏️ Mining',
217
+ 'nft_marketplace': '🖼️ NFT Marketplace',
218
+ 'defi_protocol': '🔄 DeFi Protocol',
219
+ 'derivatives': '📉 Derivatives',
220
+ 'otc_trading': '🤝 OTC Trading',
221
+ 'wallet_services': '👛 Wallet Services'
222
+ }
223
+ return mapping.get(activity, activity.replace('_', ' ').title())
app/components/monitoring_setup.py ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Monitoring Setup Component
3
+
4
+ Allows users to set up alerts for regulatory changes.
5
+ """
6
+
7
+ import streamlit as st
8
+ from datetime import datetime
9
+
10
+
11
+ def render_monitoring_setup():
12
+ """
13
+ Render monitoring and alert configuration interface.
14
+ """
15
+
16
+ st.markdown("""
17
+ Stay informed about regulatory changes that may affect your business.
18
+ Configure alerts to receive notifications about new regulations, deadlines, and compliance updates.
19
+ """)
20
+
21
+ st.markdown("---")
22
+
23
+ # Email alerts section
24
+ st.markdown("### 📧 Email Alerts")
25
+
26
+ with st.form("email_alerts_form"):
27
+ st.markdown("Receive email notifications for regulatory updates")
28
+
29
+ email = st.text_input(
30
+ "Email Address",
31
+ placeholder="your.email@company.com"
32
+ )
33
+
34
+ st.markdown("**Alert Frequency:**")
35
+ frequency = st.radio(
36
+ "How often would you like to receive alerts?",
37
+ ["Immediate (as updates occur)", "Daily digest", "Weekly summary"],
38
+ label_visibility="collapsed"
39
+ )
40
+
41
+ st.markdown("**Jurisdictions to Monitor:**")
42
+ col1, col2 = st.columns(2)
43
+
44
+ with col1:
45
+ monitor_us = st.checkbox("🇺🇸 United States", value=False)
46
+ monitor_eu = st.checkbox("🇪🇺 European Union", value=False)
47
+ monitor_singapore = st.checkbox("🇸🇬 Singapore", value=False)
48
+
49
+ with col2:
50
+ monitor_uk = st.checkbox("🇬🇧 United Kingdom", value=False)
51
+ monitor_uae = st.checkbox("🇦🇪 UAE", value=False)
52
+
53
+ st.markdown("**Activity Types:**")
54
+ activities = st.multiselect(
55
+ "Select activities to monitor",
56
+ [
57
+ "Exchange/Trading",
58
+ "Custody",
59
+ "Staking",
60
+ "Lending/Borrowing",
61
+ "Token Issuance",
62
+ "DeFi",
63
+ "NFTs",
64
+ "All Activities"
65
+ ],
66
+ label_visibility="collapsed"
67
+ )
68
+
69
+ submit_email = st.form_submit_button("🔔 Subscribe to Alerts", use_container_width=True)
70
+
71
+ if submit_email:
72
+ if email and '@' in email:
73
+ st.success(f"✅ Successfully subscribed {email} to regulatory alerts!")
74
+ st.info("📬 You'll receive a confirmation email shortly.")
75
+ else:
76
+ st.error("⚠️ Please enter a valid email address")
77
+
78
+ st.markdown("---")
79
+
80
+ # RSS Feed section
81
+ st.markdown("### 📰 RSS Feed")
82
+
83
+ st.markdown("""
84
+ Subscribe to our RSS feed to stay updated using your preferred feed reader.
85
+ The feed includes:
86
+ - New regulations and proposed rules
87
+ - Effective date changes
88
+ - Enforcement actions
89
+ - Compliance deadlines
90
+ """)
91
+
92
+ # Generate RSS feed URL (placeholder)
93
+ rss_url = "https://compliance-agent.example.com/feed/regulations.xml"
94
+
95
+ col1, col2 = st.columns([3, 1])
96
+
97
+ with col1:
98
+ st.code(rss_url, language=None)
99
+
100
+ with col2:
101
+ st.button("📋 Copy URL", use_container_width=True)
102
+
103
+ st.caption("Popular RSS readers: Feedly, Inoreader, NewsBlur, RSS Reader")
104
+
105
+ st.markdown("---")
106
+
107
+ # Webhook section
108
+ st.markdown("### 🔗 Webhook Integration")
109
+
110
+ st.markdown("""
111
+ Set up webhooks to receive real-time regulatory updates in your own systems.
112
+ Ideal for enterprise integrations and custom alerting workflows.
113
+ """)
114
+
115
+ with st.form("webhook_form"):
116
+ webhook_url = st.text_input(
117
+ "Webhook URL",
118
+ placeholder="https://your-domain.com/api/webhooks/compliance"
119
+ )
120
+
121
+ webhook_secret = st.text_input(
122
+ "Secret Key (optional)",
123
+ type="password",
124
+ placeholder="Used to verify webhook authenticity"
125
+ )
126
+
127
+ st.markdown("**Trigger Events:**")
128
+ col1, col2 = st.columns(2)
129
+
130
+ with col1:
131
+ trigger_new = st.checkbox("New regulations published", value=True)
132
+ trigger_updated = st.checkbox("Regulations updated", value=True)
133
+
134
+ with col2:
135
+ trigger_deadline = st.checkbox("Upcoming deadlines (7 days)", value=True)
136
+ trigger_enforcement = st.checkbox("Enforcement actions", value=False)
137
+
138
+ submit_webhook = st.form_submit_button("🔌 Configure Webhook", use_container_width=True)
139
+
140
+ if submit_webhook:
141
+ if webhook_url and webhook_url.startswith('http'):
142
+ st.success("✅ Webhook configured successfully!")
143
+ st.json({
144
+ "url": webhook_url,
145
+ "events": [
146
+ e for e, enabled in [
147
+ ("regulation.new", trigger_new),
148
+ ("regulation.updated", trigger_updated),
149
+ ("deadline.upcoming", trigger_deadline),
150
+ ("enforcement.action", trigger_enforcement)
151
+ ] if enabled
152
+ ],
153
+ "created_at": datetime.now().isoformat()
154
+ })
155
+ else:
156
+ st.error("⚠️ Please enter a valid HTTPS webhook URL")
157
+
158
+ st.markdown("---")
159
+
160
+ # Recent updates section
161
+ st.markdown("### 📊 Recent Regulatory Updates")
162
+
163
+ st.markdown("View the latest regulatory changes across all jurisdictions:")
164
+
165
+ # Mock recent updates (in production, this would query the database)
166
+ recent_updates = [
167
+ {
168
+ "date": "2025-10-20",
169
+ "jurisdiction": "🇪🇺 EU",
170
+ "title": "MiCA: Final technical standards published",
171
+ "type": "New Regulation",
172
+ "impact": "High"
173
+ },
174
+ {
175
+ "date": "2025-10-18",
176
+ "jurisdiction": "🇺🇸 US",
177
+ "title": "SEC updates custody rule guidance",
178
+ "type": "Guidance",
179
+ "impact": "Medium"
180
+ },
181
+ {
182
+ "date": "2025-10-15",
183
+ "jurisdiction": "🇸🇬 Singapore",
184
+ "title": "MAS clarifies staking service requirements",
185
+ "type": "Clarification",
186
+ "impact": "Medium"
187
+ },
188
+ {
189
+ "date": "2025-10-12",
190
+ "jurisdiction": "🇬🇧 UK",
191
+ "title": "FCA financial promotions regime deadline extended",
192
+ "type": "Deadline Change",
193
+ "impact": "Low"
194
+ },
195
+ {
196
+ "date": "2025-10-10",
197
+ "jurisdiction": "🇦🇪 UAE",
198
+ "title": "VARA issues marketing and promotions rulebook",
199
+ "type": "New Regulation",
200
+ "impact": "High"
201
+ }
202
+ ]
203
+
204
+ for update in recent_updates:
205
+ with st.container():
206
+ col1, col2, col3, col4 = st.columns([1, 3, 2, 1])
207
+
208
+ with col1:
209
+ st.markdown(f"**{update['date']}**")
210
+
211
+ with col2:
212
+ st.markdown(f"{update['jurisdiction']} • {update['title']}")
213
+
214
+ with col3:
215
+ st.markdown(f"*{update['type']}*")
216
+
217
+ with col4:
218
+ impact_colors = {
219
+ 'High': 'red',
220
+ 'Medium': 'orange',
221
+ 'Low': 'green'
222
+ }
223
+ color = impact_colors.get(update['impact'], 'blue')
224
+ st.markdown(f":{color}[{update['impact']}]")
225
+
226
+ st.markdown("---")
227
+
228
+ # Statistics
229
+ st.markdown("### 📈 Update Statistics")
230
+
231
+ col1, col2, col3, col4 = st.columns(4)
232
+
233
+ with col1:
234
+ st.metric("Updates This Week", "12")
235
+
236
+ with col2:
237
+ st.metric("Active Subscribers", "247")
238
+
239
+ with col3:
240
+ st.metric("Avg. Updates/Month", "48")
241
+
242
+ with col4:
243
+ st.metric("Coverage", "5 jurisdictions")
244
+
245
+ st.markdown("---")
246
+
247
+ st.caption("💡 Tip: Enable notifications for multiple jurisdictions to stay informed about regulatory arbitrage opportunities")
app/components/results_display.py ADDED
@@ -0,0 +1,374 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Results Display Component
3
+
4
+ Renders analysis results with visualizations and detailed breakdowns.
5
+ """
6
+
7
+ import streamlit as st
8
+ import plotly.graph_objects as go
9
+ from typing import Dict, Any
10
+ from datetime import datetime, timedelta
11
+
12
+
13
+ def render_results(result: Dict[Any, Any], form_data: Dict[str, Any]):
14
+ """
15
+ Render comprehensive analysis results.
16
+
17
+ Args:
18
+ result: Agent analysis result
19
+ form_data: Original form data
20
+ """
21
+
22
+ # Summary section
23
+ st.markdown("### 📊 Executive Summary")
24
+
25
+ col1, col2, col3, col4 = st.columns(4)
26
+
27
+ with col1:
28
+ risk_score = result.get('risk_score', 0)
29
+ if risk_score:
30
+ st.metric("Risk Score", f"{risk_score:.1f}/100")
31
+ else:
32
+ st.metric("Risk Score", "N/A")
33
+
34
+ with col2:
35
+ gaps = result.get('compliance_gaps', [])
36
+ st.metric("Compliance Gaps", len(gaps) if gaps else 0)
37
+
38
+ with col3:
39
+ cost_estimate = result.get('cost_estimate', {})
40
+ # Calculate total from jurisdiction costs
41
+ first_year_cost = 0
42
+ if cost_estimate:
43
+ for jur_cost in cost_estimate.values():
44
+ if isinstance(jur_cost, dict) and 'first_year' in jur_cost:
45
+ first_year_cost += jur_cost['first_year'].get('estimate', 0)
46
+ st.metric("First Year Cost", f"${first_year_cost:,.0f}" if first_year_cost > 0 else "N/A")
47
+
48
+ with col4:
49
+ jurisdictions = form_data.get('jurisdictions', [])
50
+ st.metric("Jurisdictions", len(jurisdictions))
51
+
52
+ st.markdown("---")
53
+
54
+ # Risk gauge
55
+ render_risk_gauge(risk_score)
56
+
57
+ st.markdown("---")
58
+
59
+ # Compliance gaps
60
+ if gaps:
61
+ render_compliance_gaps(gaps, form_data.get('jurisdictions', []))
62
+ else:
63
+ st.success("✅ No major compliance gaps identified!")
64
+
65
+ st.markdown("---")
66
+
67
+ # Token analysis (if applicable)
68
+ if form_data.get('issue_token') and result.get('token_classification'):
69
+ render_token_analysis(result['token_classification'])
70
+ st.markdown("---")
71
+
72
+ # Cost breakdown
73
+ if cost_estimate:
74
+ render_cost_breakdown(cost_estimate, form_data.get('jurisdictions', []))
75
+ st.markdown("---")
76
+
77
+ # Recommendations
78
+ recommendations = result.get('recommendations', [])
79
+ if recommendations:
80
+ render_recommendations(recommendations)
81
+ st.markdown("---")
82
+
83
+ # Agent reasoning - hidden from user view (moved to logs only)
84
+ # Users don't need to see internal agent workflow
85
+ # reasoning = result.get('reasoning', [])
86
+ # if reasoning:
87
+ # render_reasoning_chain(reasoning)
88
+
89
+
90
+ def render_risk_gauge(risk_score: float):
91
+ """Render risk score gauge chart."""
92
+
93
+ st.markdown("### 🎯 Risk Assessment")
94
+
95
+ # Determine risk level and color
96
+ if risk_score >= 86:
97
+ risk_level = "CRITICAL"
98
+ color = "darkred"
99
+ elif risk_score >= 71:
100
+ risk_level = "HIGH"
101
+ color = "red"
102
+ elif risk_score >= 41:
103
+ risk_level = "MEDIUM"
104
+ color = "orange"
105
+ else:
106
+ risk_level = "LOW"
107
+ color = "green"
108
+
109
+ # Create gauge chart
110
+ fig = go.Figure(go.Indicator(
111
+ mode="gauge+number",
112
+ value=risk_score,
113
+ domain={'x': [0, 1], 'y': [0, 1]},
114
+ title={'text': f"Risk Level: {risk_level}", 'font': {'size': 24}},
115
+ gauge={
116
+ 'axis': {'range': [None, 100], 'tickwidth': 1, 'tickcolor': "darkblue"},
117
+ 'bar': {'color': color},
118
+ 'bgcolor': "white",
119
+ 'borderwidth': 2,
120
+ 'bordercolor': "gray",
121
+ 'steps': [
122
+ {'range': [0, 40], 'color': 'lightgreen'},
123
+ {'range': [40, 70], 'color': 'lightyellow'},
124
+ {'range': [70, 85], 'color': 'lightcoral'},
125
+ {'range': [85, 100], 'color': 'darkred'}
126
+ ],
127
+ 'threshold': {
128
+ 'line': {'color': "red", 'width': 4},
129
+ 'thickness': 0.75,
130
+ 'value': 85
131
+ }
132
+ }
133
+ ))
134
+
135
+ fig.update_layout(
136
+ height=300,
137
+ margin=dict(l=20, r=20, t=60, b=20)
138
+ )
139
+
140
+ st.plotly_chart(fig, use_container_width=True)
141
+
142
+ # Risk explanation
143
+ col1, col2 = st.columns([1, 3])
144
+
145
+ with col1:
146
+ if risk_level == "CRITICAL":
147
+ st.error("🚨 CRITICAL RISK")
148
+ elif risk_level == "HIGH":
149
+ st.warning("⚠️ HIGH RISK")
150
+ elif risk_level == "MEDIUM":
151
+ st.info("ℹ️ MEDIUM RISK")
152
+ else:
153
+ st.success("✅ LOW RISK")
154
+
155
+ with col2:
156
+ explanations = {
157
+ "CRITICAL": "Immediate action required. Multiple critical compliance gaps that could result in severe penalties or business shutdown.",
158
+ "HIGH": "Urgent compliance measures needed. Significant gaps that pose substantial legal and financial risk.",
159
+ "MEDIUM": "Compliance improvements recommended. Several gaps identified that should be addressed systematically.",
160
+ "LOW": "Minimal compliance concerns. Continue monitoring and address minor items as identified."
161
+ }
162
+ st.markdown(explanations[risk_level])
163
+
164
+
165
+ def render_compliance_gaps(gaps: list, jurisdictions: list):
166
+ """Render compliance gaps table by jurisdiction."""
167
+
168
+ st.markdown("### ⚠️ Compliance Gaps")
169
+
170
+ # Agent returns gaps as simple list without jurisdiction field
171
+ # Display all gaps in a single section for now
172
+ if gaps:
173
+ st.markdown(f"**{len(gaps)} compliance gaps identified across all jurisdictions**")
174
+
175
+ for gap in gaps:
176
+ render_gap_card(gap)
177
+ else:
178
+ st.success("✅ No major compliance gaps identified!")
179
+
180
+
181
+ def render_gap_card(gap: Dict[str, Any]):
182
+ """Render individual gap card - always expanded for immediate visibility."""
183
+
184
+ # Agent returns gaps as simple dicts with 'description' and 'severity'
185
+ severity = gap.get('severity', 'medium')
186
+ description = gap.get('description', 'Compliance requirement')
187
+
188
+ # Severity badge with icons
189
+ severity_config = {
190
+ 'critical': {'color': 'red', 'icon': '🚨', 'bg': '#ffe6e6'},
191
+ 'high': {'color': 'orange', 'icon': '⚠️', 'bg': '#fff3e0'},
192
+ 'medium': {'color': 'blue', 'icon': 'ℹ️', 'bg': '#e3f2fd'},
193
+ 'low': {'color': 'green', 'icon': '✓', 'bg': '#e8f5e9'}
194
+ }
195
+ config = severity_config.get(severity, severity_config['medium'])
196
+
197
+ # Parse description - Gemini returns multi-line format:
198
+ # Line 1: **[SEVERITY] Requirement Name (Jurisdiction) - Deadline: X, Cost: $Y**
199
+ # Line 2+: Description: [details]
200
+ lines = description.strip().split('\n', 1)
201
+ title_line = lines[0].strip()
202
+ detail_text = lines[1].strip() if len(lines) > 1 else ""
203
+
204
+ # Remove markdown bold from title if present
205
+ title_clean = title_line.replace('**', '').replace('*', '')
206
+
207
+ # Render as card (no expander - always visible)
208
+ st.markdown(f"""
209
+ <div style="background-color: {config['bg']}; padding: 1rem; border-left: 5px solid {config['color']}; border-radius: 5px; margin-bottom: 1rem;">
210
+ <div style="font-weight: bold; margin-bottom: 0.5rem; font-size: 1.1em;">
211
+ {config['icon']} {title_clean}
212
+ </div>
213
+ <div style="color: #666; margin-bottom: 0.5rem;">
214
+ <strong>Severity:</strong> <span style="color: {config['color']};">{severity.upper()}</span>
215
+ </div>
216
+ <div style="margin-top: 0.5rem; line-height: 1.6;">
217
+ {detail_text if detail_text else title_clean}
218
+ </div>
219
+ </div>
220
+ """, unsafe_allow_html=True)
221
+
222
+
223
+ def render_token_analysis(token_classification: Dict[str, Any]):
224
+ """Render token classification analysis."""
225
+
226
+ st.markdown("### 🪙 Token Classification")
227
+
228
+ st.info("Token classification determines applicable regulatory frameworks and compliance requirements.")
229
+
230
+ # Create columns for different jurisdictions
231
+ for jurisdiction, classification in token_classification.items():
232
+ st.markdown(f"#### {get_jurisdiction_name(jurisdiction)}")
233
+
234
+ col1, col2 = st.columns([2, 1])
235
+
236
+ with col1:
237
+ classification_type = classification.get('classification', 'Unknown')
238
+ confidence = classification.get('confidence', 0) * 100
239
+
240
+ if classification_type in ['security', 'capital markets product']:
241
+ st.error(f"**Classification:** {classification_type.title()}")
242
+ st.warning("⚠️ Securities regulations apply. SEC/MAS registration may be required.")
243
+ else:
244
+ st.success(f"**Classification:** {classification_type.title()}")
245
+
246
+ st.progress(confidence / 100, text=f"Confidence: {confidence:.0f}%")
247
+
248
+ with col2:
249
+ # Howey Test results (if available)
250
+ howey = classification.get('howey_test', {})
251
+ if howey:
252
+ st.markdown("**Howey Test:**")
253
+ for prong, result in howey.items():
254
+ icon = "✅" if result else "❌"
255
+ st.markdown(f"{icon} {prong.replace('_', ' ').title()}")
256
+
257
+ # Implications
258
+ implications = classification.get('implications', [])
259
+ if implications:
260
+ st.markdown("**Regulatory Implications:**")
261
+ for implication in implications:
262
+ st.markdown(f"- {implication}")
263
+
264
+ st.markdown("---")
265
+
266
+
267
+ def render_cost_breakdown(cost_estimate: Dict[str, Any], jurisdictions: list):
268
+ """Render cost breakdown and projections."""
269
+
270
+ st.markdown("### 💰 Cost Breakdown")
271
+
272
+ if not cost_estimate:
273
+ st.info("Cost estimation not available")
274
+ return
275
+
276
+ # Calculate grand totals from jurisdiction costs
277
+ total_first_year = 0
278
+ total_annual = 0
279
+
280
+ for jur_cost in cost_estimate.values():
281
+ if isinstance(jur_cost, dict):
282
+ if 'first_year' in jur_cost and isinstance(jur_cost['first_year'], dict):
283
+ total_first_year += jur_cost['first_year'].get('estimate', 0)
284
+ if 'annual' in jur_cost and isinstance(jur_cost['annual'], dict):
285
+ total_annual += jur_cost['annual'].get('estimate', 0)
286
+
287
+ # Grand totals
288
+ if total_first_year > 0 or total_annual > 0:
289
+ st.markdown("#### Total Estimated Costs")
290
+
291
+ col1, col2, col3 = st.columns(3)
292
+
293
+ with col1:
294
+ st.metric("First Year", f"${total_first_year:,.0f}")
295
+
296
+ with col2:
297
+ st.metric("Annual Ongoing", f"${total_annual:,.0f}")
298
+
299
+ with col3:
300
+ three_year = total_first_year + (total_annual * 2)
301
+ st.metric("3-Year Total", f"${three_year:,.0f}")
302
+
303
+ st.markdown("---")
304
+
305
+ # By jurisdiction
306
+ st.markdown("#### Cost by Jurisdiction")
307
+
308
+ for jurisdiction in jurisdictions:
309
+ jur_cost = cost_estimate.get(jurisdiction, {})
310
+
311
+ if jur_cost and isinstance(jur_cost, dict):
312
+ with st.expander(f"{get_jurisdiction_name(jurisdiction)}", expanded=True):
313
+ col1, col2 = st.columns(2)
314
+
315
+ with col1:
316
+ first_year_dict = jur_cost.get('first_year', {})
317
+ if isinstance(first_year_dict, dict):
318
+ first_year = first_year_dict.get('estimate', 0)
319
+ st.markdown(f"**First Year:** ${first_year:,.0f}")
320
+
321
+ with col2:
322
+ annual_dict = jur_cost.get('annual', {})
323
+ if isinstance(annual_dict, dict):
324
+ annual = annual_dict.get('estimate', 0)
325
+ st.markdown(f"**Annual:** ${annual:,.0f}")
326
+
327
+
328
+ def render_recommendations(recommendations: list):
329
+ """Render prioritized recommendations."""
330
+
331
+ st.markdown("### 📋 Recommendations")
332
+
333
+ st.info("Prioritized action items to achieve compliance")
334
+
335
+ for i, rec in enumerate(recommendations, 1):
336
+ if isinstance(rec, dict):
337
+ title = rec.get('title', f'Recommendation {i}')
338
+ description = rec.get('description', '')
339
+ priority = rec.get('priority', 'medium')
340
+ timeline = rec.get('timeline', '')
341
+
342
+ priority_icons = {'high': '🔴', 'medium': '🟡', 'low': '🟢'}
343
+ icon = priority_icons.get(priority, '🔵')
344
+
345
+ with st.expander(f"{icon} **{i}. {title}**", expanded=(i <= 3)):
346
+ if description:
347
+ st.markdown(description)
348
+ if timeline:
349
+ st.markdown(f"**Timeline:** {timeline}")
350
+ else:
351
+ # Simple string recommendation
352
+ st.markdown(f"{i}. {rec}")
353
+
354
+
355
+ def render_reasoning_chain(reasoning: list):
356
+ """Render agent reasoning chain for transparency."""
357
+
358
+ with st.expander("🧠 Agent Reasoning Chain (Explainability)", expanded=False):
359
+ st.markdown("The AI agent's decision-making process:")
360
+
361
+ for i, step in enumerate(reasoning, 1):
362
+ st.markdown(f"**Step {i}:** {step}")
363
+
364
+
365
+ def get_jurisdiction_name(jurisdiction: str) -> str:
366
+ """Get display name for jurisdiction."""
367
+ mapping = {
368
+ 'us': '🇺🇸 United States',
369
+ 'eu': '🇪🇺 European Union',
370
+ 'singapore': '🇸🇬 Singapore',
371
+ 'uk': '🇬🇧 United Kingdom',
372
+ 'uae': '🇦🇪 UAE'
373
+ }
374
+ return mapping.get(jurisdiction, jurisdiction.upper())
app/streamlit_app.py ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Crypto Compliance Intelligence Agent - Streamlit Web Interface
3
+
4
+ Main application entry point for the compliance analysis web app.
5
+ """
6
+
7
+ import streamlit as st
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ # Add src to path
12
+ sys.path.insert(0, str(Path(__file__).parent.parent))
13
+
14
+ from src.config import Config
15
+ from src.agents.orchestrator import ComplianceAgent
16
+ from app.components.input_form import render_input_form
17
+ from app.components.results_display import render_results
18
+ from app.components.monitoring_setup import render_monitoring_setup
19
+
20
+ # Page configuration
21
+ st.set_page_config(
22
+ page_title="Crypto Compliance Intelligence Agent",
23
+ page_icon="🔒",
24
+ layout="wide",
25
+ initial_sidebar_state="expanded"
26
+ )
27
+
28
+ # Custom CSS
29
+ st.markdown("""
30
+ <style>
31
+ .main-header {
32
+ font-size: 2.5rem;
33
+ font-weight: 700;
34
+ color: #1f77b4;
35
+ margin-bottom: 0.5rem;
36
+ }
37
+ .sub-header {
38
+ font-size: 1.2rem;
39
+ color: #666;
40
+ margin-bottom: 2rem;
41
+ }
42
+ .disclaimer-box {
43
+ background-color: #fff3cd;
44
+ border-left: 5px solid #ffc107;
45
+ padding: 1rem;
46
+ margin: 1rem 0;
47
+ border-radius: 0.25rem;
48
+ }
49
+ .stButton>button {
50
+ width: 100%;
51
+ background-color: #1f77b4;
52
+ color: white;
53
+ font-weight: 600;
54
+ padding: 0.75rem;
55
+ border-radius: 0.5rem;
56
+ }
57
+ .stButton>button:hover {
58
+ background-color: #1557a0;
59
+ }
60
+ </style>
61
+ """, unsafe_allow_html=True)
62
+
63
+
64
+ @st.cache_resource(ttl=600) # Cache for 10 minutes, then refresh
65
+ def initialize_agent():
66
+ """Initialize the compliance agent (cached for performance)."""
67
+ try:
68
+ agent = ComplianceAgent()
69
+ return agent
70
+ except Exception as e:
71
+ st.error(f"Failed to initialize agent: {e}")
72
+ return None
73
+
74
+
75
+ def render_header():
76
+ """Render application header."""
77
+ st.markdown('<div class="main-header">🔒 Crypto Compliance Intelligence Agent</div>', unsafe_allow_html=True)
78
+ st.markdown(
79
+ '<div class="sub-header">Multi-jurisdiction compliance analysis powered by AI</div>',
80
+ unsafe_allow_html=True
81
+ )
82
+
83
+
84
+ def render_disclaimer():
85
+ """Render legal disclaimer."""
86
+ st.markdown("""
87
+ <div class="disclaimer-box">
88
+ <h4>⚠️ Important Disclaimer</h4>
89
+ <p>This tool provides <strong>general information only</strong> and does <strong>NOT</strong> constitute legal, financial, or regulatory advice. The analysis is based on publicly available information and AI models, which may contain errors or be outdated.</p>
90
+ <p><strong>Always consult qualified legal counsel</strong> before making compliance decisions. The creators assume no liability for decisions made based on this tool.</p>
91
+ </div>
92
+ """, unsafe_allow_html=True)
93
+
94
+
95
+ def render_sidebar():
96
+ """Render sidebar with information and settings."""
97
+ with st.sidebar:
98
+ st.image("https://via.placeholder.com/300x100/1f77b4/ffffff?text=Compliance+Agent", use_column_width=True)
99
+
100
+ st.markdown("### About")
101
+ st.markdown("""
102
+ This AI-powered system analyzes crypto compliance requirements across:
103
+ - 🇺🇸 United States (SEC)
104
+ - 🇪🇺 European Union (MiCA)
105
+ - 🇸🇬 Singapore (MAS)
106
+ - 🇬🇧 United Kingdom (FCA)
107
+ - 🇦🇪 UAE (VARA)
108
+ """)
109
+
110
+ st.markdown("### Features")
111
+ st.markdown("""
112
+ ✅ Multi-jurisdiction analysis
113
+ ✅ Token classification (Howey Test)
114
+ ✅ Risk scoring (0-100)
115
+ ✅ Cost estimation
116
+ ✅ Compliance gap identification
117
+ ✅ Actionable recommendations
118
+ """)
119
+
120
+ st.markdown("### How It Works")
121
+ st.markdown("""
122
+ 1. Describe your business and activities
123
+ 2. Select target jurisdictions
124
+ 3. AI agent analyzes compliance requirements
125
+ 4. Receive comprehensive report (30-60s)
126
+ """)
127
+
128
+ st.markdown("---")
129
+ st.markdown("**Powered by:**")
130
+ st.markdown("- Google Gemini Flash 2.5")
131
+ st.markdown("- LangGraph Agents")
132
+ st.markdown("- ChromaDB Vector Search")
133
+
134
+ st.markdown("---")
135
+ st.caption("Version 1.0 | Phase 6")
136
+
137
+
138
+ def main():
139
+ """Main application logic."""
140
+
141
+ # Render header and disclaimer
142
+ render_header()
143
+ render_disclaimer()
144
+
145
+ # Render sidebar
146
+ render_sidebar()
147
+
148
+ # Initialize agent
149
+ agent = initialize_agent()
150
+
151
+ if agent is None:
152
+ st.error("Failed to initialize compliance agent. Please check your configuration and API keys.")
153
+ st.stop()
154
+
155
+ # Create tabs
156
+ tab1, tab2, tab3 = st.tabs(["📝 Analysis", "📊 Results", "🔔 Monitoring"])
157
+
158
+ with tab1:
159
+ st.markdown("## Compliance Analysis")
160
+ st.markdown("Provide information about your crypto business to get a comprehensive compliance analysis.")
161
+
162
+ # Render input form
163
+ form_data = render_input_form()
164
+
165
+ if form_data:
166
+ # User submitted the form
167
+ st.markdown("---")
168
+
169
+ with st.spinner("🤖 AI Agent is analyzing your compliance requirements... This may take 30-60 seconds."):
170
+ try:
171
+ # Run agent analysis
172
+ result = agent.run(
173
+ user_input=form_data['business_description'],
174
+ jurisdictions=form_data['jurisdictions'],
175
+ activities=form_data['activities'],
176
+ token_description=form_data.get('token_description')
177
+ )
178
+
179
+ # Store result in session state
180
+ st.session_state['analysis_result'] = result
181
+ st.session_state['form_data'] = form_data
182
+
183
+ st.success("✅ Analysis complete! View results in the **Results** tab.")
184
+
185
+ except Exception as e:
186
+ st.error(f"❌ Analysis failed: {str(e)}")
187
+ st.exception(e)
188
+
189
+ with tab2:
190
+ st.markdown("## Analysis Results")
191
+
192
+ if 'analysis_result' in st.session_state:
193
+ result = st.session_state['analysis_result']
194
+ form_data = st.session_state.get('form_data', {})
195
+
196
+ render_results(result, form_data)
197
+ else:
198
+ st.info("👈 Complete the analysis in the **Analysis** tab to see results here.")
199
+
200
+ with tab3:
201
+ st.markdown("## Compliance Monitoring")
202
+ st.markdown("Set up alerts to stay informed about regulatory changes.")
203
+
204
+ render_monitoring_setup()
205
+
206
+
207
+ if __name__ == "__main__":
208
+ main()
chroma_db/c30b23e7-6559-433a-b87d-7290f0978053/data_level0.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:571c95cfab76b4001dd6f0a0510f8b5741632272159d49f94759e4a66493ed90
3
+ size 1676000
chroma_db/c30b23e7-6559-433a-b87d-7290f0978053/header.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e87a1dc8bcae6f2c4bea6d5dd5005454d4dace8637dae29bff3c037ea771411e
3
+ size 100
chroma_db/c30b23e7-6559-433a-b87d-7290f0978053/length.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fc19b1997119425765295aeab72d76faa6927d4f83985d328c26f20468d6cc76
3
+ size 4000
chroma_db/c30b23e7-6559-433a-b87d-7290f0978053/link_lists.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
3
+ size 0
chroma_db/chroma.sqlite3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dd2c9e8fc0f11da0446bdf60f58c0bd05fbc048dc036cb5717c62d9ed9f10ce5
3
+ size 819200
config.toml ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [theme]
2
+ primaryColor = "#1f77b4"
3
+ backgroundColor = "#FFFFFF"
4
+ secondaryBackgroundColor = "#F0F2F6"
5
+ textColor = "#262730"
6
+ font = "sans serif"
7
+
8
+ [server]
9
+ headless = true
10
+ port = 7860
11
+ enableCORS = false
12
+ enableXsrfProtection = true
13
+
14
+ [browser]
15
+ gatherUsageStats = false
requirements.txt ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Web Framework
2
+ streamlit==1.29.0
3
+
4
+ # LLM and Agent Framework
5
+ langchain>=0.1.0
6
+ langgraph>=0.0.20
7
+ langchain-google-genai>=1.0.0
8
+ google-generativeai>=0.4.0
9
+
10
+ # Vector Database and Embeddings (lightweight)
11
+ chromadb==0.4.22
12
+ sentence-transformers==2.3.1
13
+
14
+ # Data Processing (minimal)
15
+ pandas>=2.2.0
16
+ numpy>=1.26.0
17
+ pdfplumber>=0.10.0
18
+ beautifulsoup4>=4.12.0
19
+ lxml>=5.1.0
20
+
21
+ # HTTP and Web Scraping
22
+ requests==2.31.0
23
+
24
+ # Utilities
25
+ python-dotenv==1.0.0
26
+ python-dateutil==2.8.2
27
+
28
+ # Plotting
29
+ plotly>=5.18.0
30
+
31
+ # Note: FinBERT and Legal-BERT accessed via HuggingFace Inference API
32
+ # No need to download torch/transformers locally - saves storage!
src/__init__.py ADDED
File without changes
src/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (191 Bytes). View file
 
src/__pycache__/config.cpython-313.pyc ADDED
Binary file (3.17 kB). View file
 
src/agents/__init__.py ADDED
File without changes
src/agents/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (198 Bytes). View file
 
src/agents/__pycache__/orchestrator.cpython-313.pyc ADDED
Binary file (27.2 kB). View file
 
src/agents/__pycache__/tools.cpython-313.pyc ADDED
Binary file (15 kB). View file
 
src/agents/orchestrator.py ADDED
@@ -0,0 +1,621 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ LangGraph orchestrator for compliance analysis workflow.
3
+ Implements a state machine with 6 nodes for end-to-end analysis.
4
+ """
5
+
6
+ import logging
7
+ from typing import Dict, List, Optional, TypedDict, Annotated
8
+ from datetime import datetime
9
+ import operator
10
+
11
+ from langgraph.graph import StateGraph, END
12
+ from langchain_google_genai import ChatGoogleGenerativeAI
13
+
14
+ from src.config import Config
15
+ from src.agents.tools import (
16
+ search_regulations,
17
+ calculate_compliance_cost,
18
+ analyze_token_security,
19
+ extract_entities_from_text
20
+ )
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ # Agent State Definition
26
+ class ComplianceState(TypedDict):
27
+ """State that flows through the agent workflow."""
28
+ # Input
29
+ user_input: str
30
+ jurisdictions: List[str]
31
+ activities: List[str]
32
+ token_description: Optional[str]
33
+
34
+ # Intermediate results
35
+ business_type: Optional[str]
36
+ extracted_entities: Optional[Dict]
37
+ token_classification: Optional[Dict]
38
+ relevant_regulations: Optional[List[Dict]]
39
+ compliance_gaps: Optional[List[Dict]]
40
+ risk_score: Optional[float]
41
+ cost_estimate: Optional[Dict]
42
+
43
+ # Output
44
+ recommendations: Optional[List[str]]
45
+ reasoning: Annotated[List[str], operator.add] # Accumulate reasoning steps
46
+
47
+ # Metadata
48
+ timestamp: str
49
+ errors: Annotated[List[str], operator.add]
50
+
51
+
52
+ class ComplianceAgent:
53
+ """
54
+ LangGraph-based compliance agent orchestrator.
55
+
56
+ Workflow:
57
+ 1. classify_input -> Determine business model type
58
+ 2. extract_entities -> Extract key information from description
59
+ 3. retrieve_regulations -> Search vector DB for relevant rules
60
+ 4. analyze_compliance -> Identify gaps and requirements
61
+ 5. calculate_risk -> Generate risk score
62
+ 6. generate_recommendations -> Create actionable steps
63
+ """
64
+
65
+ def __init__(self):
66
+ """Initialize the compliance agent."""
67
+ # Initialize LLM
68
+ self.llm = ChatGoogleGenerativeAI(
69
+ model=Config.GEMINI_MODEL,
70
+ google_api_key=Config.GEMINI_API_KEY,
71
+ temperature=Config.GEMINI_TEMPERATURE,
72
+ max_tokens=Config.GEMINI_MAX_TOKENS
73
+ )
74
+
75
+ # Initialize VectorDB (once, reused for all searches)
76
+ from src.data.vectordb import RegulatoryVectorDB
77
+ self.vectordb = RegulatoryVectorDB()
78
+ logger.info(f"VectorDB initialized with collection: {self.vectordb.collection_name}")
79
+
80
+ # Build workflow graph
81
+ self.workflow = self._build_workflow()
82
+ self.app = self.workflow.compile()
83
+
84
+ logger.info("ComplianceAgent initialized with LangGraph workflow")
85
+
86
+ def _build_workflow(self) -> StateGraph:
87
+ """Build the LangGraph state machine workflow."""
88
+ workflow = StateGraph(ComplianceState)
89
+
90
+ # Add nodes
91
+ workflow.add_node("classify_input", self.classify_input_node)
92
+ workflow.add_node("extract_entities", self.extract_entities_node)
93
+ workflow.add_node("retrieve_regulations", self.retrieve_regulations_node)
94
+ workflow.add_node("analyze_compliance", self.analyze_compliance_node)
95
+ workflow.add_node("calculate_risk", self.calculate_risk_node)
96
+ workflow.add_node("generate_recommendations", self.generate_recommendations_node)
97
+
98
+ # Define edges
99
+ workflow.set_entry_point("classify_input")
100
+ workflow.add_edge("classify_input", "extract_entities")
101
+ workflow.add_edge("extract_entities", "retrieve_regulations")
102
+ workflow.add_edge("retrieve_regulations", "analyze_compliance")
103
+ workflow.add_edge("analyze_compliance", "calculate_risk")
104
+ workflow.add_edge("calculate_risk", "generate_recommendations")
105
+ workflow.add_edge("generate_recommendations", END)
106
+
107
+ return workflow
108
+
109
+ def classify_input_node(self, state: ComplianceState) -> ComplianceState:
110
+ """
111
+ Node 1: Classify the business model type from user input.
112
+
113
+ Args:
114
+ state: Current agent state
115
+
116
+ Returns:
117
+ Updated state with business_type
118
+ """
119
+ logger.info("Node 1: Classifying business model...")
120
+
121
+ try:
122
+ prompt = f"""
123
+ Analyze this crypto business description and classify the business model type.
124
+
125
+ Description: {state['user_input']}
126
+ Activities: {', '.join(state['activities'])}
127
+
128
+ Classify into ONE of these categories:
129
+ - Exchange/Trading Platform
130
+ - Custody/Wallet Service
131
+ - DeFi Protocol (lending, staking, yield)
132
+ - Token/NFT Platform
133
+ - Payment Processor
134
+ - Mining/Validator Service
135
+ - Other (specify)
136
+
137
+ Respond with just the category name.
138
+ """
139
+
140
+ response = self.llm.invoke(prompt)
141
+ business_type = response.content.strip()
142
+
143
+ state['business_type'] = business_type
144
+ state['reasoning'].append(f"Classified business as: {business_type}")
145
+
146
+ logger.info(f"Business classified as: {business_type}")
147
+
148
+ except Exception as e:
149
+ logger.error(f"Error in classify_input_node: {e}")
150
+ state['errors'].append(f"Classification error: {str(e)}")
151
+ state['business_type'] = "Unknown"
152
+
153
+ return state
154
+
155
+ def extract_entities_node(self, state: ComplianceState) -> ComplianceState:
156
+ """
157
+ Node 2: Extract entities and classify token if applicable.
158
+
159
+ Args:
160
+ state: Current agent state
161
+
162
+ Returns:
163
+ Updated state with extracted_entities and token_classification
164
+ """
165
+ logger.info("Node 2: Extracting entities...")
166
+
167
+ try:
168
+ # Extract entities from user input
169
+ entities = extract_entities_from_text.invoke({"text": state['user_input']})
170
+ state['extracted_entities'] = entities
171
+
172
+ reasoning = f"Extracted entities: {entities['summary'].get('total_entities', 0)} total"
173
+ state['reasoning'].append(reasoning)
174
+
175
+ # Classify token if description provided
176
+ if state.get('token_description'):
177
+ # Classify in each requested jurisdiction
178
+ classifications = {}
179
+ for jurisdiction in state['jurisdictions']:
180
+ if jurisdiction in ['us', 'eu', 'singapore']:
181
+ result = analyze_token_security.invoke({
182
+ "token_description": state['token_description'],
183
+ "jurisdiction": jurisdiction
184
+ })
185
+ classifications[jurisdiction] = result
186
+
187
+ state['token_classification'] = classifications
188
+
189
+ # Check if token is security in any jurisdiction
190
+ is_security_anywhere = any(
191
+ c.get('classification') == 'security' or
192
+ c.get('classification') == 'capital markets product'
193
+ for c in classifications.values()
194
+ )
195
+
196
+ reasoning = f"Token classified - Security: {is_security_anywhere}"
197
+ state['reasoning'].append(reasoning)
198
+
199
+ logger.info(f"Token classified - Security anywhere: {is_security_anywhere}")
200
+
201
+ except Exception as e:
202
+ logger.error(f"Error in extract_entities_node: {e}")
203
+ state['errors'].append(f"Entity extraction error: {str(e)}")
204
+
205
+ return state
206
+
207
+ def retrieve_regulations_node(self, state: ComplianceState) -> ComplianceState:
208
+ """
209
+ Node 3: Retrieve relevant regulations from vector database.
210
+
211
+ Args:
212
+ state: Current agent state
213
+
214
+ Returns:
215
+ Updated state with relevant_regulations
216
+ """
217
+ logger.info("Node 3: Retrieving regulations...")
218
+
219
+ try:
220
+ all_regulations = []
221
+
222
+ # Build search query from activities and entities
223
+ activities_str = ', '.join(state['activities'])
224
+ query = f"{activities_str} compliance requirements regulations"
225
+
226
+ # Search each jurisdiction using agent's VectorDB instance
227
+ for jurisdiction in state['jurisdictions']:
228
+ results = self.vectordb.search_relevant_regulations(
229
+ query=query,
230
+ jurisdiction=jurisdiction,
231
+ top_k=5
232
+ )
233
+
234
+ logger.info(f"VectorDB search ({jurisdiction}): {len(results)} results")
235
+ all_regulations.extend(results)
236
+
237
+ state['relevant_regulations'] = all_regulations
238
+
239
+ reasoning = f"Retrieved {len(all_regulations)} relevant regulations across {len(state['jurisdictions'])} jurisdictions"
240
+ state['reasoning'].append(reasoning)
241
+
242
+ logger.info(f"Retrieved {len(all_regulations)} regulations total")
243
+
244
+ except Exception as e:
245
+ logger.error(f"Error in retrieve_regulations_node: {e}")
246
+ state['errors'].append(f"Regulation retrieval error: {str(e)}")
247
+ state['relevant_regulations'] = []
248
+
249
+ return state
250
+
251
+ def analyze_compliance_node(self, state: ComplianceState) -> ComplianceState:
252
+ """
253
+ Node 4: Analyze compliance gaps by matching activities to regulations.
254
+
255
+ Args:
256
+ state: Current agent state
257
+
258
+ Returns:
259
+ Updated state with compliance_gaps
260
+ """
261
+ logger.info("Node 4: Analyzing compliance...")
262
+
263
+ try:
264
+ # Build detailed regulations context with specific requirements
265
+ regulations_detail = []
266
+ for reg in state.get('relevant_regulations', [])[:5]: # Top 5 most relevant
267
+ reg_text = f"\n**{reg.get('title', 'Unknown')} ({reg.get('jurisdiction', '').upper()})**\n"
268
+ reg_text += f"Summary: {reg.get('summary', '')}\n"
269
+
270
+ # Include specific requirements from regulation
271
+ requirements = reg.get('requirements', [])
272
+ if requirements:
273
+ reg_text += f"\nKey Requirements ({len(requirements)} total):\n"
274
+ for i, req in enumerate(requirements[:8], 1): # Top 8 requirements per regulation
275
+ req_name = req.get('requirement', 'Unknown requirement')
276
+ req_desc = req.get('description', '')[:200]
277
+ severity = req.get('severity', 'medium')
278
+ cost = req.get('estimated_cost_usd', {})
279
+ cost_range = f"${cost.get('min', 0):,.0f}-${cost.get('max', 0):,.0f}" if cost else "N/A"
280
+ deadline = req.get('deadline_days', 'N/A')
281
+
282
+ reg_text += f"{i}. {req_name} [{severity.upper()}]\n"
283
+ reg_text += f" Description: {req_desc}...\n"
284
+ reg_text += f" Cost: {cost_range} | Deadline: {deadline} days\n"
285
+
286
+ regulations_detail.append(reg_text)
287
+
288
+ regulations_context = "\n".join(regulations_detail)
289
+
290
+ # Get token classification for relevant jurisdiction
291
+ token_class = "N/A"
292
+ if state.get('token_classification'):
293
+ # Get first jurisdiction's classification
294
+ first_jur = state['jurisdictions'][0] if state['jurisdictions'] else None
295
+ if first_jur and first_jur in state['token_classification']:
296
+ token_class = state['token_classification'][first_jur].get('classification', 'N/A')
297
+
298
+ prompt = f"""
299
+ You are a crypto compliance expert. Analyze compliance gaps for this specific business:
300
+
301
+ **Business Details:**
302
+ - Business Type: {state.get('business_type', 'Unknown')}
303
+ - Activities: {', '.join(state['activities'])}
304
+ - Jurisdictions: {', '.join(state['jurisdictions'])}
305
+ - Description: {state['user_input'][:300]}
306
+ - Token Classification: {token_class}
307
+
308
+ **Relevant Regulatory Requirements:**
309
+ {regulations_context}
310
+
311
+ **Task:**
312
+ Based on the SPECIFIC requirements listed above, identify compliance gaps for this business.
313
+ For each gap, provide:
314
+ 1. The EXACT requirement name from the regulations above
315
+ 2. Specific details (costs, deadlines, severity) from the regulation
316
+ 3. Why this applies to THIS specific business model
317
+
318
+ Be SPECIFIC - reference actual requirement names, costs, and timelines from the regulations provided.
319
+ Do NOT make up generic requirements.
320
+
321
+ Format each gap as:
322
+ [SEVERITY] Requirement Name (Jurisdiction) - Deadline: X days, Cost: $X-Y
323
+ Description: [Why this applies + key details from regulation]
324
+
325
+ Provide 5-10 most critical gaps.
326
+ """
327
+
328
+ response = self.llm.invoke(prompt)
329
+ gaps_text = response.content.strip()
330
+
331
+ # DEBUG: Log the raw response
332
+ logger.debug(f"LLM Response (first 500 chars): {gaps_text[:500]}")
333
+
334
+ # Parse into structured format - more flexible parsing
335
+ gaps = []
336
+ lines = gaps_text.split('\n')
337
+
338
+ for line in lines:
339
+ line = line.strip()
340
+ # Skip empty lines
341
+ if not line:
342
+ continue
343
+
344
+ # Match lines that look like requirements (numbered, bulleted, or bracketed)
345
+ if (line[0].isdigit() and '. ' in line[:5]) or \
346
+ line.startswith('[') or \
347
+ line.startswith('-') or \
348
+ line.startswith('*') or \
349
+ ('CRITICAL' in line.upper() or 'HIGH' in line.upper() or 'MEDIUM' in line.upper()):
350
+ gaps.append({
351
+ 'description': line,
352
+ 'severity': self._extract_severity(line)
353
+ })
354
+
355
+ # If no gaps parsed but we have content, add full response as single gap for debugging
356
+ if not gaps and gaps_text:
357
+ logger.warning(f"Failed to parse gaps from LLM response. Adding full response as single gap.")
358
+ gaps.append({
359
+ 'description': gaps_text[:500],
360
+ 'severity': 'medium'
361
+ })
362
+
363
+ state['compliance_gaps'] = gaps
364
+ state['reasoning'].append(f"Identified {len(gaps)} compliance gaps from {len(regulations_detail)} regulations")
365
+
366
+ logger.info(f"Identified {len(gaps)} compliance gaps")
367
+
368
+ except Exception as e:
369
+ logger.error(f"Error in analyze_compliance_node: {e}")
370
+ state['errors'].append(f"Compliance analysis error: {str(e)}")
371
+ state['compliance_gaps'] = []
372
+
373
+ return state
374
+
375
+ def calculate_risk_node(self, state: ComplianceState) -> ComplianceState:
376
+ """
377
+ Node 5: Calculate overall risk score and cost estimates.
378
+
379
+ Args:
380
+ state: Current agent state
381
+
382
+ Returns:
383
+ Updated state with risk_score and cost_estimate
384
+ """
385
+ logger.info("Node 5: Calculating risk and costs...")
386
+
387
+ try:
388
+ # Calculate risk score based on gaps
389
+ gaps = state.get('compliance_gaps', [])
390
+ gap_count = len(gaps)
391
+
392
+ # Simple risk scoring (0-100)
393
+ # Base score from gap count
394
+ risk_from_gaps = min(gap_count * 15, 60)
395
+
396
+ # Add risk for security token
397
+ risk_from_token = 0
398
+ if state.get('token_classification'):
399
+ classifications = state['token_classification']
400
+ if any(c.get('classification') == 'security' for c in classifications.values()):
401
+ risk_from_token = 25
402
+
403
+ # Add risk for severity
404
+ risk_from_severity = 0
405
+ critical_gaps = sum(1 for g in gaps if 'critical' in g.get('description', '').lower())
406
+ risk_from_severity = min(critical_gaps * 5, 15)
407
+
408
+ total_risk = min(risk_from_gaps + risk_from_token + risk_from_severity, 100)
409
+
410
+ state['risk_score'] = total_risk
411
+ state['reasoning'].append(f"Risk score: {total_risk}/100")
412
+
413
+ # Calculate costs
414
+ token_class = state.get('token_classification')
415
+ is_security = any(
416
+ c.get('classification') == 'security'
417
+ for c in token_class.values()
418
+ ) if token_class else False
419
+
420
+ costs = calculate_compliance_cost.invoke({
421
+ "jurisdictions": state['jurisdictions'],
422
+ "activities": state['activities'],
423
+ "is_security_token": is_security
424
+ })
425
+
426
+ state['cost_estimate'] = costs
427
+
428
+ total_first_year = sum(
429
+ c['first_year']['estimate']
430
+ for c in costs.values()
431
+ )
432
+
433
+ state['reasoning'].append(f"Estimated first-year cost: ${total_first_year:,}")
434
+
435
+ logger.info(f"Risk score: {total_risk}, Est. cost: ${total_first_year:,}")
436
+
437
+ except Exception as e:
438
+ logger.error(f"Error in calculate_risk_node: {e}")
439
+ state['errors'].append(f"Risk calculation error: {str(e)}")
440
+ state['risk_score'] = 50.0 # Default medium risk
441
+
442
+ return state
443
+
444
+ def generate_recommendations_node(self, state: ComplianceState) -> ComplianceState:
445
+ """
446
+ Node 6: Generate actionable recommendations.
447
+
448
+ Args:
449
+ state: Current agent state
450
+
451
+ Returns:
452
+ Updated state with recommendations
453
+ """
454
+ logger.info("Node 6: Generating recommendations...")
455
+
456
+ try:
457
+ # Use LLM to generate recommendations
458
+ gaps_summary = "\n".join([
459
+ f"- {gap['description']}"
460
+ for gap in state.get('compliance_gaps', [])[:10]
461
+ ])
462
+
463
+ prompt = f"""
464
+ Generate prioritized compliance recommendations for this crypto business:
465
+
466
+ Business Type: {state.get('business_type', 'Unknown')}
467
+ Risk Score: {state.get('risk_score', 'Unknown')}/100
468
+ Jurisdictions: {', '.join(state['jurisdictions'])}
469
+
470
+ Compliance Gaps:
471
+ {gaps_summary}
472
+
473
+ Provide 5-8 prioritized, actionable recommendations. Each should:
474
+ 1. Be specific and actionable
475
+ 2. Include estimated timeline
476
+ 3. Indicate priority (P0/P1/P2)
477
+
478
+ Format as numbered list with priority labels.
479
+ """
480
+
481
+ response = self.llm.invoke(prompt)
482
+ recommendations_text = response.content.strip()
483
+
484
+ # Parse into list
485
+ recommendations = [
486
+ line.strip()
487
+ for line in recommendations_text.split('\n')
488
+ if line.strip() and (line[0].isdigit() or line.startswith('-') or line.startswith('P'))
489
+ ]
490
+
491
+ state['recommendations'] = recommendations
492
+ state['reasoning'].append(f"Generated {len(recommendations)} recommendations")
493
+
494
+ logger.info(f"Generated {len(recommendations)} recommendations")
495
+
496
+ except Exception as e:
497
+ logger.error(f"Error in generate_recommendations_node: {e}")
498
+ state['errors'].append(f"Recommendation generation error: {str(e)}")
499
+ state['recommendations'] = ["Consult with legal counsel for compliance guidance"]
500
+
501
+ return state
502
+
503
+ def _extract_severity(self, text: str) -> str:
504
+ """Extract severity level from text."""
505
+ text_lower = text.lower()
506
+ if 'critical' in text_lower:
507
+ return 'critical'
508
+ elif 'high' in text_lower:
509
+ return 'high'
510
+ elif 'medium' in text_lower:
511
+ return 'medium'
512
+ elif 'low' in text_lower:
513
+ return 'low'
514
+ return 'medium'
515
+
516
+ def run(
517
+ self,
518
+ user_input: str,
519
+ jurisdictions: List[str],
520
+ activities: List[str],
521
+ token_description: Optional[str] = None
522
+ ) -> ComplianceState:
523
+ """
524
+ Run the compliance analysis workflow.
525
+
526
+ Args:
527
+ user_input: Business description
528
+ jurisdictions: List of jurisdictions to analyze
529
+ activities: List of crypto activities
530
+ token_description: Optional token description for classification
531
+
532
+ Returns:
533
+ Final state with complete analysis
534
+ """
535
+ logger.info(f"Starting compliance analysis for {len(jurisdictions)} jurisdictions, {len(activities)} activities")
536
+
537
+ # Initialize state
538
+ initial_state: ComplianceState = {
539
+ 'user_input': user_input,
540
+ 'jurisdictions': jurisdictions,
541
+ 'activities': activities,
542
+ 'token_description': token_description,
543
+ 'business_type': None,
544
+ 'extracted_entities': None,
545
+ 'token_classification': None,
546
+ 'relevant_regulations': None,
547
+ 'compliance_gaps': None,
548
+ 'risk_score': None,
549
+ 'cost_estimate': None,
550
+ 'recommendations': None,
551
+ 'reasoning': [],
552
+ 'timestamp': datetime.now().isoformat(),
553
+ 'errors': []
554
+ }
555
+
556
+ # Run workflow
557
+ try:
558
+ final_state = self.app.invoke(initial_state)
559
+ logger.info("Workflow completed successfully")
560
+ return final_state
561
+
562
+ except Exception as e:
563
+ logger.error(f"Workflow execution error: {e}")
564
+ initial_state['errors'].append(f"Workflow error: {str(e)}")
565
+ return initial_state
566
+
567
+
568
+ # Convenience function
569
+ def analyze_compliance(
570
+ user_input: str,
571
+ jurisdictions: List[str],
572
+ activities: List[str],
573
+ token_description: Optional[str] = None
574
+ ) -> Dict:
575
+ """
576
+ Quick compliance analysis.
577
+
578
+ Args:
579
+ user_input: Business description
580
+ jurisdictions: List of jurisdictions
581
+ activities: List of activities
582
+ token_description: Optional token description
583
+
584
+ Returns:
585
+ Analysis results dictionary
586
+ """
587
+ agent = ComplianceAgent()
588
+ result = agent.run(user_input, jurisdictions, activities, token_description)
589
+ return dict(result)
590
+
591
+
592
+ if __name__ == "__main__":
593
+ # Test the agent
594
+ print("\n=== Testing Compliance Agent ===\n")
595
+
596
+ test_input = """
597
+ We are launching a crypto exchange platform that allows users to trade
598
+ Bitcoin, Ethereum, and other cryptocurrencies. We will provide custody
599
+ services and allow users to stake their tokens to earn yields. The platform
600
+ will operate in the US and EU.
601
+ """
602
+
603
+ result = analyze_compliance(
604
+ user_input=test_input,
605
+ jurisdictions=['us', 'eu'],
606
+ activities=['exchange', 'custody', 'staking'],
607
+ token_description=None
608
+ )
609
+
610
+ print(f"Business Type: {result['business_type']}")
611
+ print(f"Risk Score: {result['risk_score']}/100")
612
+ print(f"\nCompliance Gaps: {len(result.get('compliance_gaps', []))}")
613
+ print(f"\nRecommendations:")
614
+ for rec in result.get('recommendations', [])[:5]:
615
+ print(f" - {rec}")
616
+
617
+ print(f"\nReasoning Chain:")
618
+ for step in result.get('reasoning', []):
619
+ print(f" → {step}")
620
+
621
+ print("\n=== Agent test complete! ===\n")
src/agents/tools.py ADDED
@@ -0,0 +1,419 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Agent tools for LangChain integration.
3
+ Tools that the compliance agent can use to perform tasks.
4
+ """
5
+
6
+ import logging
7
+ from typing import Dict, List, Optional
8
+ from datetime import datetime, timedelta
9
+ from langchain.tools import tool
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ @tool
15
+ def search_regulations(
16
+ query: str,
17
+ jurisdiction: Optional[str] = None,
18
+ top_k: int = 5
19
+ ) -> List[Dict]:
20
+ """
21
+ Search for relevant regulations in the vector database.
22
+
23
+ Args:
24
+ query: Search query describing compliance requirements
25
+ jurisdiction: Filter by jurisdiction (us/eu/singapore/uk/uae) or None for all
26
+ top_k: Number of results to return
27
+
28
+ Returns:
29
+ List of relevant regulations with metadata
30
+ """
31
+ try:
32
+ from src.data.vectordb import RegulatoryVectorDB
33
+
34
+ db = RegulatoryVectorDB()
35
+ results = db.search_relevant_regulations(
36
+ query=query,
37
+ jurisdiction=jurisdiction,
38
+ top_k=top_k
39
+ )
40
+
41
+ logger.info(
42
+ f"Found {len(results)} regulations for query: '{query}' "
43
+ f"(jurisdiction: {jurisdiction or 'all'})"
44
+ )
45
+
46
+ return results
47
+
48
+ except Exception as e:
49
+ logger.error(f"Error searching regulations: {e}")
50
+ return []
51
+
52
+
53
+ @tool
54
+ def calculate_compliance_cost(
55
+ jurisdictions: List[str],
56
+ activities: List[str],
57
+ is_security_token: bool = False
58
+ ) -> Dict:
59
+ """
60
+ Calculate estimated compliance costs for given jurisdictions and activities.
61
+
62
+ Args:
63
+ jurisdictions: List of jurisdictions (e.g., ['us', 'eu'])
64
+ activities: List of crypto activities (e.g., ['exchange', 'custody'])
65
+ is_security_token: Whether token is classified as a security
66
+
67
+ Returns:
68
+ Dictionary with cost estimates per jurisdiction
69
+ """
70
+ # Cost database (approximate ranges in USD)
71
+ COST_DATABASE = {
72
+ 'us': {
73
+ 'exchange': {'first_year': (200000, 500000), 'annual': (100000, 250000)},
74
+ 'custody': {'first_year': (100000, 300000), 'annual': (50000, 150000)},
75
+ 'staking': {'first_year': (50000, 150000), 'annual': (25000, 75000)},
76
+ 'lending': {'first_year': (100000, 250000), 'annual': (50000, 125000)},
77
+ 'token_issuance': {'first_year': (50000, 200000), 'annual': (25000, 100000)},
78
+ 'security_token_premium': {'first_year': (200000, 500000), 'annual': (100000, 250000)},
79
+ 'base': {'first_year': (50000, 100000), 'annual': (25000, 50000)}
80
+ },
81
+ 'eu': {
82
+ 'exchange': {'first_year': (150000, 400000), 'annual': (75000, 200000)},
83
+ 'custody': {'first_year': (100000, 250000), 'annual': (50000, 125000)},
84
+ 'staking': {'first_year': (50000, 125000), 'annual': (25000, 60000)},
85
+ 'lending': {'first_year': (75000, 200000), 'annual': (40000, 100000)},
86
+ 'token_issuance': {'first_year': (100000, 300000), 'annual': (50000, 150000)},
87
+ 'base': {'first_year': (50000, 100000), 'annual': (25000, 50000)}
88
+ },
89
+ 'singapore': {
90
+ 'exchange': {'first_year': (150000, 350000), 'annual': (75000, 175000)},
91
+ 'custody': {'first_year': (75000, 200000), 'annual': (40000, 100000)},
92
+ 'staking': {'first_year': (40000, 100000), 'annual': (20000, 50000)},
93
+ 'lending': {'first_year': (60000, 150000), 'annual': (30000, 75000)},
94
+ 'token_issuance': {'first_year': (75000, 250000), 'annual': (40000, 125000)},
95
+ 'base': {'first_year': (40000, 80000), 'annual': (20000, 40000)}
96
+ },
97
+ 'uk': {
98
+ 'exchange': {'first_year': (125000, 300000), 'annual': (60000, 150000)},
99
+ 'custody': {'first_year': (75000, 200000), 'annual': (40000, 100000)},
100
+ 'staking': {'first_year': (40000, 100000), 'annual': (20000, 50000)},
101
+ 'lending': {'first_year': (60000, 150000), 'annual': (30000, 75000)},
102
+ 'token_issuance': {'first_year': (50000, 150000), 'annual': (25000, 75000)},
103
+ 'base': {'first_year': (40000, 75000), 'annual': (20000, 40000)}
104
+ },
105
+ 'uae': {
106
+ 'exchange': {'first_year': (100000, 250000), 'annual': (50000, 125000)},
107
+ 'custody': {'first_year': (60000, 150000), 'annual': (30000, 75000)},
108
+ 'staking': {'first_year': (30000, 80000), 'annual': (15000, 40000)},
109
+ 'lending': {'first_year': (50000, 125000), 'annual': (25000, 60000)},
110
+ 'token_issuance': {'first_year': (50000, 150000), 'annual': (25000, 75000)},
111
+ 'base': {'first_year': (30000, 60000), 'annual': (15000, 30000)}
112
+ }
113
+ }
114
+
115
+ results = {}
116
+
117
+ for jurisdiction in jurisdictions:
118
+ if jurisdiction not in COST_DATABASE:
119
+ logger.warning(f"Unknown jurisdiction: {jurisdiction}")
120
+ continue
121
+
122
+ costs = COST_DATABASE[jurisdiction]
123
+ first_year_min = 0
124
+ first_year_max = 0
125
+ annual_min = 0
126
+ annual_max = 0
127
+
128
+ # Base costs
129
+ first_year_min += costs['base']['first_year'][0]
130
+ first_year_max += costs['base']['first_year'][1]
131
+ annual_min += costs['base']['annual'][0]
132
+ annual_max += costs['base']['annual'][1]
133
+
134
+ # Activity-specific costs
135
+ for activity in activities:
136
+ if activity in costs:
137
+ first_year_min += costs[activity]['first_year'][0]
138
+ first_year_max += costs[activity]['first_year'][1]
139
+ annual_min += costs[activity]['annual'][0]
140
+ annual_max += costs[activity]['annual'][1]
141
+
142
+ # Security token premium (US)
143
+ if is_security_token and jurisdiction == 'us':
144
+ first_year_min += costs['security_token_premium']['first_year'][0]
145
+ first_year_max += costs['security_token_premium']['first_year'][1]
146
+ annual_min += costs['security_token_premium']['annual'][0]
147
+ annual_max += costs['security_token_premium']['annual'][1]
148
+
149
+ results[jurisdiction] = {
150
+ 'first_year': {
151
+ 'min': first_year_min,
152
+ 'max': first_year_max,
153
+ 'estimate': (first_year_min + first_year_max) // 2
154
+ },
155
+ 'annual_ongoing': {
156
+ 'min': annual_min,
157
+ 'max': annual_max,
158
+ 'estimate': (annual_min + annual_max) // 2
159
+ },
160
+ 'breakdown': {
161
+ 'base_compliance': costs['base'],
162
+ 'activities': [
163
+ {'activity': act, 'cost': costs.get(act, {'first_year': (0, 0), 'annual': (0, 0)})}
164
+ for act in activities if act in costs
165
+ ]
166
+ }
167
+ }
168
+
169
+ logger.info(
170
+ f"Calculated costs for {len(results)} jurisdictions, "
171
+ f"{len(activities)} activities, security_token={is_security_token}"
172
+ )
173
+
174
+ return results
175
+
176
+
177
+ @tool
178
+ def check_effective_date(regulation_id: str) -> Dict:
179
+ """
180
+ Check if a regulation is currently effective or pending.
181
+
182
+ Args:
183
+ regulation_id: ID of the regulation to check
184
+
185
+ Returns:
186
+ Dictionary with status information
187
+ """
188
+ try:
189
+ from src.data.vectordb import RegulatoryVectorDB
190
+
191
+ db = RegulatoryVectorDB()
192
+ regulation = db.get_regulation_by_id(regulation_id)
193
+
194
+ if not regulation:
195
+ return {
196
+ 'found': False,
197
+ 'message': f"Regulation {regulation_id} not found"
198
+ }
199
+
200
+ status = regulation.get('status', 'unknown')
201
+ effective_date_str = regulation.get('effective_date', '')
202
+ announced_date_str = regulation.get('announced_date', '')
203
+
204
+ result = {
205
+ 'found': True,
206
+ 'regulation_id': regulation_id,
207
+ 'title': regulation.get('title', ''),
208
+ 'status': status,
209
+ 'announced_date': announced_date_str,
210
+ 'effective_date': effective_date_str,
211
+ 'is_effective': status == 'effective',
212
+ 'is_proposed': status == 'proposed',
213
+ 'is_repealed': status == 'repealed'
214
+ }
215
+
216
+ # Calculate time until effective (if applicable)
217
+ if effective_date_str and status == 'proposed':
218
+ try:
219
+ effective_date = datetime.fromisoformat(effective_date_str.replace('Z', '+00:00'))
220
+ now = datetime.now(effective_date.tzinfo) if effective_date.tzinfo else datetime.now()
221
+ days_until = (effective_date - now).days
222
+
223
+ result['days_until_effective'] = days_until
224
+ result['time_to_comply'] = f"{days_until} days" if days_until > 0 else "Overdue"
225
+
226
+ except Exception as e:
227
+ logger.warning(f"Could not parse effective date: {e}")
228
+
229
+ logger.info(f"Checked regulation {regulation_id}: status={status}")
230
+
231
+ return result
232
+
233
+ except Exception as e:
234
+ logger.error(f"Error checking effective date: {e}")
235
+ return {
236
+ 'found': False,
237
+ 'error': str(e)
238
+ }
239
+
240
+
241
+ @tool
242
+ def analyze_token_security(token_description: str, jurisdiction: str = 'us') -> Dict:
243
+ """
244
+ Analyze whether a token is a security using the Howey Test or other frameworks.
245
+
246
+ Args:
247
+ token_description: Description of the token's functionality and economics
248
+ jurisdiction: Jurisdiction for classification (us/eu/singapore)
249
+
250
+ Returns:
251
+ Token classification result with confidence score
252
+ """
253
+ try:
254
+ from src.analysis.token_classifier import classify_token
255
+
256
+ result = classify_token(token_description, jurisdiction=jurisdiction)
257
+
258
+ # Format for agent consumption
259
+ formatted = {
260
+ 'jurisdiction': jurisdiction,
261
+ 'classification': result.get('classification', 'unknown'),
262
+ 'confidence': result.get('confidence', 0.0),
263
+ 'framework': result.get('framework', ''),
264
+ 'implications': result.get('regulatory_implications', []),
265
+ }
266
+
267
+ # Add Howey Test details for US
268
+ if jurisdiction == 'us' and 'howey_test' in result:
269
+ howey = result['howey_test']
270
+ formatted['howey_test'] = {
271
+ 'prongs_met': howey.get('prongs_met', 0),
272
+ 'is_security': howey.get('is_security', False),
273
+ 'prongs': {
274
+ name: data.get('met', False)
275
+ for name, data in howey.get('prongs', {}).items()
276
+ }
277
+ }
278
+
279
+ logger.info(
280
+ f"Token analysis ({jurisdiction}): {formatted['classification']} "
281
+ f"(confidence: {formatted['confidence']:.2f})"
282
+ )
283
+
284
+ return formatted
285
+
286
+ except Exception as e:
287
+ logger.error(f"Error analyzing token: {e}")
288
+ return {
289
+ 'error': str(e),
290
+ 'classification': 'error'
291
+ }
292
+
293
+
294
+ @tool
295
+ def extract_entities_from_text(text: str) -> Dict:
296
+ """
297
+ Extract financial and crypto entities from text.
298
+
299
+ Args:
300
+ text: Input text to analyze
301
+
302
+ Returns:
303
+ Dictionary of extracted entities
304
+ """
305
+ try:
306
+ from src.processors.entity_extraction import extract_entities
307
+
308
+ entities = extract_entities(text)
309
+
310
+ # Simplify for agent consumption
311
+ simplified = {
312
+ 'summary': entities.get('summary', {}),
313
+ 'amounts': [e['text'] for e in entities.get('financial', {}).get('amounts', [])],
314
+ 'dates': [e['text'] for e in entities.get('financial', {}).get('dates', [])],
315
+ 'institutions': [e['text'] for e in entities.get('financial', {}).get('institutions', [])],
316
+ 'tokens': [e['name'] for e in entities.get('crypto', {}).get('tokens', [])],
317
+ 'protocols': [e['name'] for e in entities.get('crypto', {}).get('protocols', [])],
318
+ 'activities': list(set([e['activity'] for e in entities.get('crypto', {}).get('activities', [])]))
319
+ }
320
+
321
+ logger.info(f"Extracted {entities['summary']['total_entities']} entities from text")
322
+
323
+ return simplified
324
+
325
+ except Exception as e:
326
+ logger.error(f"Error extracting entities: {e}")
327
+ return {'error': str(e)}
328
+
329
+
330
+ @tool
331
+ def parse_document(file_path: str) -> Dict:
332
+ """
333
+ Parse a document and extract text and metadata.
334
+
335
+ Args:
336
+ file_path: Path to document file
337
+
338
+ Returns:
339
+ Parsed document with text, type, and metadata
340
+ """
341
+ try:
342
+ from src.processors.document_parser import parse_document as parse_doc
343
+
344
+ result = parse_doc(file_path)
345
+
346
+ # Simplify for agent
347
+ simplified = {
348
+ 'text': result.get('text', ''),
349
+ 'document_type': result.get('document_type', 'unknown'),
350
+ 'confidence': result.get('type_confidence', 0.0),
351
+ 'word_count': result.get('word_count', 0),
352
+ 'char_count': result.get('char_count', 0),
353
+ 'filename': result.get('metadata', {}).get('filename', '')
354
+ }
355
+
356
+ logger.info(
357
+ f"Parsed document: {simplified['filename']} "
358
+ f"(type: {simplified['document_type']}, {simplified['word_count']} words)"
359
+ )
360
+
361
+ return simplified
362
+
363
+ except Exception as e:
364
+ logger.error(f"Error parsing document: {e}")
365
+ return {'error': str(e)}
366
+
367
+
368
+ # Tool list for LangChain agent
369
+ COMPLIANCE_TOOLS = [
370
+ search_regulations,
371
+ calculate_compliance_cost,
372
+ check_effective_date,
373
+ analyze_token_security,
374
+ extract_entities_from_text,
375
+ parse_document
376
+ ]
377
+
378
+
379
+ if __name__ == "__main__":
380
+ # Test tools
381
+ print("\n=== Testing Agent Tools ===\n")
382
+
383
+ # Test 1: Search regulations
384
+ print("1. Testing search_regulations...")
385
+ results = search_regulations.invoke({
386
+ "query": "crypto custody requirements",
387
+ "jurisdiction": "us",
388
+ "top_k": 3
389
+ })
390
+ print(f" Found {len(results)} regulations")
391
+
392
+ # Test 2: Calculate costs
393
+ print("\n2. Testing calculate_compliance_cost...")
394
+ costs = calculate_compliance_cost.invoke({
395
+ "jurisdictions": ["us", "eu"],
396
+ "activities": ["exchange", "custody"],
397
+ "is_security_token": False
398
+ })
399
+ for jur, cost_data in costs.items():
400
+ print(f" {jur.upper()}: ${cost_data['first_year']['estimate']:,} first year")
401
+
402
+ # Test 3: Token analysis
403
+ print("\n3. Testing analyze_token_security...")
404
+ token_result = analyze_token_security.invoke({
405
+ "token_description": "Investors buy tokens to earn profits from platform growth",
406
+ "jurisdiction": "us"
407
+ })
408
+ print(f" Classification: {token_result['classification']}")
409
+ print(f" Confidence: {token_result['confidence']:.2f}")
410
+
411
+ # Test 4: Entity extraction
412
+ print("\n4. Testing extract_entities_from_text...")
413
+ text = "SEC announced $10 million fine for Coinbase on January 15, 2024"
414
+ entities = extract_entities_from_text.invoke({"text": text})
415
+ print(f" Amounts: {entities['amounts']}")
416
+ print(f" Dates: {entities['dates']}")
417
+ print(f" Institutions: {entities['institutions']}")
418
+
419
+ print("\n=== All tools tested successfully! ===\n")
src/analysis/__init__.py ADDED
File without changes
src/analysis/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (200 Bytes). View file
 
src/analysis/__pycache__/compliance_engine.cpython-313.pyc ADDED
Binary file (15.7 kB). View file
 
src/analysis/__pycache__/cost_calculator.cpython-313.pyc ADDED
Binary file (11.8 kB). View file
 
src/analysis/__pycache__/risk_scorer.cpython-313.pyc ADDED
Binary file (17.2 kB). View file
 
src/analysis/__pycache__/token_classifier.cpython-313.pyc ADDED
Binary file (15.7 kB). View file
 
src/analysis/compliance_engine.py ADDED
@@ -0,0 +1,486 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Compliance Engine for structured rule matching and gap identification.
3
+ Provides deterministic compliance analysis without relying solely on LLM prompts.
4
+ """
5
+
6
+ import logging
7
+ from typing import Dict, List, Optional, Set
8
+ from datetime import datetime, timedelta
9
+ from enum import Enum
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class Severity(Enum):
15
+ """Severity levels for compliance gaps."""
16
+ CRITICAL = "critical" # Immediate action required, high enforcement risk
17
+ HIGH = "high" # Action needed within 3 months
18
+ MEDIUM = "medium" # Action needed within 6 months
19
+ LOW = "low" # Monitor, action within 1 year
20
+
21
+
22
+ class Urgency(Enum):
23
+ """Urgency levels based on deadlines."""
24
+ IMMEDIATE = "immediate" # < 30 days
25
+ URGENT = "urgent" # 30-90 days
26
+ MODERATE = "moderate" # 90-180 days
27
+ PLANNING = "planning" # > 180 days
28
+
29
+
30
+ class ComplianceGap:
31
+ """Represents a single compliance gap."""
32
+
33
+ def __init__(
34
+ self,
35
+ requirement: str,
36
+ jurisdiction: str,
37
+ activity: str,
38
+ severity: Severity,
39
+ urgency: Urgency,
40
+ regulation_id: Optional[str] = None,
41
+ deadline: Optional[str] = None,
42
+ cost_estimate: Optional[Dict] = None,
43
+ remediation_steps: Optional[List[str]] = None
44
+ ):
45
+ self.requirement = requirement
46
+ self.jurisdiction = jurisdiction
47
+ self.activity = activity
48
+ self.severity = severity
49
+ self.urgency = urgency
50
+ self.regulation_id = regulation_id
51
+ self.deadline = deadline
52
+ self.cost_estimate = cost_estimate
53
+ self.remediation_steps = remediation_steps or []
54
+
55
+ def to_dict(self) -> Dict:
56
+ """Convert to dictionary."""
57
+ return {
58
+ 'requirement': self.requirement,
59
+ 'jurisdiction': self.jurisdiction,
60
+ 'activity': self.activity,
61
+ 'severity': self.severity.value,
62
+ 'urgency': self.urgency.value,
63
+ 'regulation_id': self.regulation_id,
64
+ 'deadline': self.deadline,
65
+ 'cost_estimate': self.cost_estimate,
66
+ 'remediation_steps': self.remediation_steps
67
+ }
68
+
69
+
70
+ class ComplianceEngine:
71
+ """
72
+ Structured compliance engine for rule matching and gap analysis.
73
+
74
+ Maps activities to requirements per jurisdiction and identifies gaps.
75
+ """
76
+
77
+ def __init__(self):
78
+ """Initialize compliance engine with requirement mappings."""
79
+ self.requirements = self._build_requirements_database()
80
+ logger.info("ComplianceEngine initialized with requirements database")
81
+
82
+ def _build_requirements_database(self) -> Dict:
83
+ """
84
+ Build requirements database mapping activities to compliance requirements.
85
+
86
+ Structure: {jurisdiction: {activity: [requirements]}}
87
+ """
88
+ return {
89
+ 'us': {
90
+ 'exchange': [
91
+ {
92
+ 'requirement': 'FinCEN MSB Registration',
93
+ 'severity': Severity.CRITICAL,
94
+ 'deadline_days': 180,
95
+ 'description': 'Register as Money Services Business with FinCEN',
96
+ 'cost_range': (10000, 25000),
97
+ 'steps': [
98
+ 'File FinCEN Form 107',
99
+ 'Implement AML/KYC program',
100
+ 'Appoint compliance officer'
101
+ ]
102
+ },
103
+ {
104
+ 'requirement': 'State Money Transmitter Licenses',
105
+ 'severity': Severity.CRITICAL,
106
+ 'deadline_days': 365,
107
+ 'description': 'Obtain MTL in each state of operation',
108
+ 'cost_range': (50000, 150000),
109
+ 'steps': [
110
+ 'File applications per state',
111
+ 'Post surety bonds',
112
+ 'Meet minimum capital requirements'
113
+ ]
114
+ }
115
+ ],
116
+ 'custody': [
117
+ {
118
+ 'requirement': 'SEC Custody Rule Compliance',
119
+ 'severity': Severity.HIGH,
120
+ 'deadline_days': 180,
121
+ 'description': 'Comply with SEC custody and safeguarding rules',
122
+ 'cost_range': (75000, 200000),
123
+ 'steps': [
124
+ 'Implement qualified custody solution',
125
+ 'Annual surprise audits',
126
+ 'Insurance requirements'
127
+ ]
128
+ }
129
+ ],
130
+ 'staking': [
131
+ {
132
+ 'requirement': 'Securities Law Review',
133
+ 'severity': Severity.HIGH,
134
+ 'deadline_days': 90,
135
+ 'description': 'Determine if staking constitutes securities offering',
136
+ 'cost_range': (25000, 75000),
137
+ 'steps': [
138
+ 'Legal counsel review',
139
+ 'Howey Test analysis',
140
+ 'Consider SEC exemptions'
141
+ ]
142
+ }
143
+ ],
144
+ 'token_issuance': [
145
+ {
146
+ 'requirement': 'SEC Registration or Exemption',
147
+ 'severity': Severity.CRITICAL,
148
+ 'deadline_days': 180,
149
+ 'description': 'Register securities or qualify for exemption',
150
+ 'cost_range': (100000, 500000),
151
+ 'steps': [
152
+ 'Determine if token is security',
153
+ 'File Form D (Reg D) or Form 1-A (Reg A+)',
154
+ 'Accredited investor verification'
155
+ ]
156
+ }
157
+ ]
158
+ },
159
+ 'eu': {
160
+ 'exchange': [
161
+ {
162
+ 'requirement': 'MiCA CASP Authorization',
163
+ 'severity': Severity.CRITICAL,
164
+ 'deadline_days': 365,
165
+ 'description': 'Obtain Crypto-Asset Service Provider authorization',
166
+ 'cost_range': (100000, 300000),
167
+ 'steps': [
168
+ 'Submit application to national regulator',
169
+ 'Meet capital requirements',
170
+ 'Implement governance framework'
171
+ ]
172
+ },
173
+ {
174
+ 'requirement': 'AMLD5 Compliance',
175
+ 'severity': Severity.CRITICAL,
176
+ 'deadline_days': 180,
177
+ 'description': 'Anti-Money Laundering Directive compliance',
178
+ 'cost_range': (50000, 150000),
179
+ 'steps': [
180
+ 'Customer due diligence procedures',
181
+ 'Transaction monitoring',
182
+ 'Suspicious activity reporting'
183
+ ]
184
+ }
185
+ ],
186
+ 'custody': [
187
+ {
188
+ 'requirement': 'MiCA Custody Requirements',
189
+ 'severity': Severity.HIGH,
190
+ 'deadline_days': 180,
191
+ 'description': 'Safeguarding of client crypto-assets',
192
+ 'cost_range': (75000, 200000),
193
+ 'steps': [
194
+ 'Segregation of client assets',
195
+ 'Professional indemnity insurance',
196
+ 'Custodian arrangements'
197
+ ]
198
+ }
199
+ ],
200
+ 'token_issuance': [
201
+ {
202
+ 'requirement': 'MiCA White Paper',
203
+ 'severity': Severity.HIGH,
204
+ 'deadline_days': 90,
205
+ 'description': 'Publish and notify white paper to regulator',
206
+ 'cost_range': (25000, 75000),
207
+ 'steps': [
208
+ 'Draft comprehensive white paper',
209
+ 'Notify competent authority',
210
+ 'Ongoing disclosure obligations'
211
+ ]
212
+ }
213
+ ]
214
+ },
215
+ 'singapore': {
216
+ 'exchange': [
217
+ {
218
+ 'requirement': 'MAS DPT License',
219
+ 'severity': Severity.CRITICAL,
220
+ 'deadline_days': 365,
221
+ 'description': 'Digital Payment Token service license',
222
+ 'cost_range': (75000, 250000),
223
+ 'steps': [
224
+ 'Submit MAS license application',
225
+ 'Meet fit and proper criteria',
226
+ 'Implement technology risk management'
227
+ ]
228
+ }
229
+ ],
230
+ 'custody': [
231
+ {
232
+ 'requirement': 'DPT Custody Standards',
233
+ 'severity': Severity.HIGH,
234
+ 'deadline_days': 180,
235
+ 'description': 'MAS guidelines for DPT custody',
236
+ 'cost_range': (50000, 150000),
237
+ 'steps': [
238
+ 'Segregation of customer DPTs',
239
+ 'Cold storage requirements',
240
+ 'Insurance or capital reserves'
241
+ ]
242
+ }
243
+ ]
244
+ },
245
+ 'uk': {
246
+ 'exchange': [
247
+ {
248
+ 'requirement': 'FCA Cryptoasset Registration',
249
+ 'severity': Severity.CRITICAL,
250
+ 'deadline_days': 365,
251
+ 'description': 'Register with FCA for AML/CTF',
252
+ 'cost_range': (50000, 150000),
253
+ 'steps': [
254
+ 'FCA registration application',
255
+ 'AML/CTF procedures',
256
+ 'Senior management approval'
257
+ ]
258
+ },
259
+ {
260
+ 'requirement': 'Financial Promotions Compliance',
261
+ 'severity': Severity.HIGH,
262
+ 'deadline_days': 90,
263
+ 'description': 'Comply with cryptoasset promotions regime',
264
+ 'cost_range': (15000, 50000),
265
+ 'steps': [
266
+ 'Ensure promotions are fair, clear, not misleading',
267
+ 'Risk warnings required',
268
+ 'Approval by authorized firm'
269
+ ]
270
+ }
271
+ ]
272
+ },
273
+ 'uae': {
274
+ 'exchange': [
275
+ {
276
+ 'requirement': 'VARA VASP License',
277
+ 'severity': Severity.CRITICAL,
278
+ 'deadline_days': 365,
279
+ 'description': 'Virtual Asset Service Provider license from VARA',
280
+ 'cost_range': (75000, 200000),
281
+ 'steps': [
282
+ 'Submit VARA application',
283
+ 'Meet minimum capital (AED 50k)',
284
+ 'Comply with VARA rulebook'
285
+ ]
286
+ }
287
+ ]
288
+ }
289
+ }
290
+
291
+ def analyze_compliance(
292
+ self,
293
+ jurisdictions: List[str],
294
+ activities: List[str],
295
+ is_security_token: bool = False
296
+ ) -> Dict:
297
+ """
298
+ Analyze compliance requirements and identify gaps.
299
+
300
+ Args:
301
+ jurisdictions: List of jurisdictions to analyze
302
+ activities: List of crypto activities
303
+ is_security_token: Whether token is classified as security
304
+
305
+ Returns:
306
+ Dictionary with gaps, compliant items, and summary
307
+ """
308
+ gaps = []
309
+ compliant = []
310
+ warnings = []
311
+
312
+ for jurisdiction in jurisdictions:
313
+ if jurisdiction not in self.requirements:
314
+ warnings.append(f"No requirements database for jurisdiction: {jurisdiction}")
315
+ continue
316
+
317
+ jurisdiction_reqs = self.requirements[jurisdiction]
318
+
319
+ for activity in activities:
320
+ if activity not in jurisdiction_reqs:
321
+ # No specific requirements for this activity in this jurisdiction
322
+ warnings.append(
323
+ f"No specific requirements found for {activity} in {jurisdiction}"
324
+ )
325
+ continue
326
+
327
+ requirements = jurisdiction_reqs[activity]
328
+
329
+ for req in requirements:
330
+ # Create compliance gap
331
+ urgency = self._calculate_urgency(req.get('deadline_days', 365))
332
+
333
+ gap = ComplianceGap(
334
+ requirement=req['requirement'],
335
+ jurisdiction=jurisdiction,
336
+ activity=activity,
337
+ severity=req['severity'],
338
+ urgency=urgency,
339
+ deadline=self._calculate_deadline(req.get('deadline_days', 365)),
340
+ cost_estimate={
341
+ 'min': req['cost_range'][0],
342
+ 'max': req['cost_range'][1],
343
+ 'estimate': sum(req['cost_range']) // 2
344
+ },
345
+ remediation_steps=req.get('steps', [])
346
+ )
347
+
348
+ gaps.append(gap)
349
+
350
+ # Additional check for security tokens
351
+ if is_security_token and 'us' in jurisdictions:
352
+ if 'token_issuance' not in activities:
353
+ # Add securities registration requirement
354
+ sec_gap = ComplianceGap(
355
+ requirement='SEC Securities Registration',
356
+ jurisdiction='us',
357
+ activity='token_issuance',
358
+ severity=Severity.CRITICAL,
359
+ urgency=Urgency.IMMEDIATE,
360
+ deadline=self._calculate_deadline(60),
361
+ cost_estimate={'min': 200000, 'max': 500000, 'estimate': 350000},
362
+ remediation_steps=[
363
+ 'Immediate legal counsel consultation',
364
+ 'Howey Test confirms security status',
365
+ 'File registration or seek exemption',
366
+ 'Consider Regulation D or A+'
367
+ ]
368
+ )
369
+ gaps.append(sec_gap)
370
+
371
+ # Sort gaps by severity and urgency
372
+ gaps.sort(key=lambda g: (
373
+ ['critical', 'high', 'medium', 'low'].index(g.severity.value),
374
+ ['immediate', 'urgent', 'moderate', 'planning'].index(g.urgency.value)
375
+ ))
376
+
377
+ summary = {
378
+ 'total_gaps': len(gaps),
379
+ 'critical_gaps': sum(1 for g in gaps if g.severity == Severity.CRITICAL),
380
+ 'high_gaps': sum(1 for g in gaps if g.severity == Severity.HIGH),
381
+ 'immediate_action': sum(1 for g in gaps if g.urgency == Urgency.IMMEDIATE),
382
+ 'estimated_total_cost': sum(
383
+ g.cost_estimate['estimate'] for g in gaps if g.cost_estimate
384
+ ),
385
+ 'jurisdictions_analyzed': len(jurisdictions),
386
+ 'activities_analyzed': len(activities),
387
+ 'warnings': warnings
388
+ }
389
+
390
+ logger.info(
391
+ f"Compliance analysis: {len(gaps)} gaps found across "
392
+ f"{len(jurisdictions)} jurisdictions, {len(activities)} activities"
393
+ )
394
+
395
+ return {
396
+ 'gaps': [g.to_dict() for g in gaps],
397
+ 'compliant': compliant,
398
+ 'summary': summary,
399
+ 'analyzed_at': datetime.now().isoformat()
400
+ }
401
+
402
+ def _calculate_urgency(self, deadline_days: int) -> Urgency:
403
+ """Calculate urgency based on deadline."""
404
+ if deadline_days <= 30:
405
+ return Urgency.IMMEDIATE
406
+ elif deadline_days <= 90:
407
+ return Urgency.URGENT
408
+ elif deadline_days <= 180:
409
+ return Urgency.MODERATE
410
+ else:
411
+ return Urgency.PLANNING
412
+
413
+ def _calculate_deadline(self, days: int) -> str:
414
+ """Calculate deadline date from days."""
415
+ deadline = datetime.now() + timedelta(days=days)
416
+ return deadline.strftime('%Y-%m-%d')
417
+
418
+ def get_requirement_details(
419
+ self,
420
+ jurisdiction: str,
421
+ activity: str
422
+ ) -> Optional[List[Dict]]:
423
+ """
424
+ Get detailed requirements for a specific jurisdiction and activity.
425
+
426
+ Args:
427
+ jurisdiction: Jurisdiction code
428
+ activity: Activity type
429
+
430
+ Returns:
431
+ List of requirement dictionaries or None
432
+ """
433
+ if jurisdiction not in self.requirements:
434
+ return None
435
+
436
+ if activity not in self.requirements[jurisdiction]:
437
+ return None
438
+
439
+ return self.requirements[jurisdiction][activity]
440
+
441
+
442
+ # Convenience function
443
+ def analyze_compliance(
444
+ jurisdictions: List[str],
445
+ activities: List[str],
446
+ is_security_token: bool = False
447
+ ) -> Dict:
448
+ """
449
+ Quick compliance analysis.
450
+
451
+ Args:
452
+ jurisdictions: List of jurisdictions
453
+ activities: List of activities
454
+ is_security_token: Whether token is a security
455
+
456
+ Returns:
457
+ Analysis results
458
+ """
459
+ engine = ComplianceEngine()
460
+ return engine.analyze_compliance(jurisdictions, activities, is_security_token)
461
+
462
+
463
+ if __name__ == "__main__":
464
+ # Test the engine
465
+ print("\n=== Testing Compliance Engine ===\n")
466
+
467
+ result = analyze_compliance(
468
+ jurisdictions=['us', 'eu'],
469
+ activities=['exchange', 'custody'],
470
+ is_security_token=False
471
+ )
472
+
473
+ print(f"Total gaps: {result['summary']['total_gaps']}")
474
+ print(f"Critical gaps: {result['summary']['critical_gaps']}")
475
+ print(f"Estimated cost: ${result['summary']['estimated_total_cost']:,}")
476
+
477
+ print(f"\nTop 3 gaps:")
478
+ for i, gap in enumerate(result['gaps'][:3], 1):
479
+ print(f"\n{i}. {gap['requirement']}")
480
+ print(f" Jurisdiction: {gap['jurisdiction'].upper()}")
481
+ print(f" Severity: {gap['severity']}")
482
+ print(f" Urgency: {gap['urgency']}")
483
+ print(f" Deadline: {gap['deadline']}")
484
+ print(f" Cost: ${gap['cost_estimate']['estimate']:,}")
485
+
486
+ print("\n=== Engine test complete! ===\n")
src/analysis/cost_calculator.py ADDED
@@ -0,0 +1,544 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Cost Calculator for compliance cost estimation.
3
+ Migrated from tools.py to provide dedicated class with advanced features.
4
+ """
5
+
6
+ import logging
7
+ from typing import Dict, List, Optional
8
+ from datetime import datetime
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class CostCalculator:
14
+ """
15
+ Calculate estimated compliance costs for crypto businesses.
16
+
17
+ Provides cost estimates across jurisdictions, activities, and time periods.
18
+ """
19
+
20
+ def __init__(self):
21
+ """Initialize cost calculator with comprehensive cost database."""
22
+ self.cost_database = self._build_cost_database()
23
+ logger.info("CostCalculator initialized with cost database")
24
+
25
+ def _build_cost_database(self) -> Dict:
26
+ """
27
+ Build comprehensive cost database.
28
+
29
+ Structure: {jurisdiction: {activity: {first_year, annual}}}
30
+ All costs in USD.
31
+ """
32
+ return {
33
+ 'us': {
34
+ 'exchange': {
35
+ 'first_year': (200000, 500000),
36
+ 'annual': (100000, 250000),
37
+ 'breakdown': {
38
+ 'FinCEN MSB registration': (10000, 25000),
39
+ 'State MTL licenses': (150000, 400000),
40
+ 'Legal counsel': (25000, 50000),
41
+ 'Compliance staff': (75000, 150000),
42
+ 'AML/KYC systems': (50000, 100000)
43
+ }
44
+ },
45
+ 'custody': {
46
+ 'first_year': (100000, 300000),
47
+ 'annual': (50000, 150000),
48
+ 'breakdown': {
49
+ 'Custody infrastructure': (50000, 150000),
50
+ 'Insurance': (25000, 75000),
51
+ 'Audits': (15000, 50000),
52
+ 'Compliance program': (10000, 25000)
53
+ }
54
+ },
55
+ 'staking': {
56
+ 'first_year': (50000, 150000),
57
+ 'annual': (25000, 75000),
58
+ 'breakdown': {
59
+ 'Legal analysis': (25000, 75000),
60
+ 'Compliance monitoring': (15000, 50000),
61
+ 'Disclosure requirements': (10000, 25000)
62
+ }
63
+ },
64
+ 'lending': {
65
+ 'first_year': (100000, 250000),
66
+ 'annual': (50000, 125000),
67
+ 'breakdown': {
68
+ 'Securities review': (50000, 125000),
69
+ 'State lending licenses': (30000, 75000),
70
+ 'Compliance program': (20000, 50000)
71
+ }
72
+ },
73
+ 'token_issuance': {
74
+ 'first_year': (50000, 200000),
75
+ 'annual': (25000, 100000),
76
+ 'breakdown': {
77
+ 'Legal counsel': (30000, 100000),
78
+ 'SEC filing (if required)': (15000, 75000),
79
+ 'Ongoing reporting': (5000, 25000)
80
+ }
81
+ },
82
+ 'security_token_premium': {
83
+ 'first_year': (200000, 500000),
84
+ 'annual': (100000, 250000),
85
+ 'breakdown': {
86
+ 'SEC registration': (100000, 250000),
87
+ 'Transfer agent': (30000, 75000),
88
+ 'Legal opinions': (50000, 125000),
89
+ 'Compliance officer': (20000, 50000)
90
+ }
91
+ },
92
+ 'payment_processing': {
93
+ 'first_year': (75000, 200000),
94
+ 'annual': (40000, 100000),
95
+ 'breakdown': {
96
+ 'FinCEN registration': (10000, 20000),
97
+ 'State licenses': (50000, 150000),
98
+ 'AML compliance': (15000, 30000)
99
+ }
100
+ },
101
+ 'mining': {
102
+ 'first_year': (25000, 75000),
103
+ 'annual': (15000, 40000),
104
+ 'breakdown': {
105
+ 'Energy regulation compliance': (15000, 50000),
106
+ 'Tax reporting': (10000, 25000)
107
+ }
108
+ },
109
+ 'nft_marketplace': {
110
+ 'first_year': (40000, 100000),
111
+ 'annual': (20000, 50000),
112
+ 'breakdown': {
113
+ 'IP compliance': (15000, 40000),
114
+ 'Consumer protection': (15000, 40000),
115
+ 'Payment processing': (10000, 20000)
116
+ }
117
+ },
118
+ 'defi_protocol': {
119
+ 'first_year': (75000, 250000),
120
+ 'annual': (40000, 125000),
121
+ 'breakdown': {
122
+ 'Securities analysis': (50000, 150000),
123
+ 'Smart contract audits': (15000, 75000),
124
+ 'Legal counsel': (10000, 25000)
125
+ }
126
+ },
127
+ 'base': {
128
+ 'first_year': (50000, 100000),
129
+ 'annual': (25000, 50000),
130
+ 'breakdown': {
131
+ 'General counsel': (25000, 50000),
132
+ 'Compliance monitoring': (15000, 30000),
133
+ 'Record keeping': (10000, 20000)
134
+ }
135
+ }
136
+ },
137
+ 'eu': {
138
+ 'exchange': {
139
+ 'first_year': (150000, 400000),
140
+ 'annual': (75000, 200000),
141
+ 'breakdown': {
142
+ 'MiCA CASP authorization': (100000, 300000),
143
+ 'AMLD5 compliance': (30000, 75000),
144
+ 'Legal counsel': (20000, 25000)
145
+ }
146
+ },
147
+ 'custody': {
148
+ 'first_year': (100000, 250000),
149
+ 'annual': (50000, 125000),
150
+ 'breakdown': {
151
+ 'Safeguarding requirements': (50000, 150000),
152
+ 'Insurance': (30000, 75000),
153
+ 'Compliance program': (20000, 25000)
154
+ }
155
+ },
156
+ 'staking': {
157
+ 'first_year': (50000, 125000),
158
+ 'annual': (25000, 60000),
159
+ 'breakdown': {
160
+ 'MiCA classification': (25000, 75000),
161
+ 'Disclosure requirements': (15000, 35000),
162
+ 'Ongoing monitoring': (10000, 15000)
163
+ }
164
+ },
165
+ 'lending': {
166
+ 'first_year': (75000, 200000),
167
+ 'annual': (40000, 100000),
168
+ 'breakdown': {
169
+ 'MiCA compliance': (50000, 125000),
170
+ 'Consumer credit rules': (15000, 50000),
171
+ 'Legal counsel': (10000, 25000)
172
+ }
173
+ },
174
+ 'token_issuance': {
175
+ 'first_year': (100000, 300000),
176
+ 'annual': (50000, 150000),
177
+ 'breakdown': {
178
+ 'White paper preparation': (50000, 150000),
179
+ 'Regulatory notification': (30000, 100000),
180
+ 'Ongoing disclosures': (20000, 50000)
181
+ }
182
+ },
183
+ 'base': {
184
+ 'first_year': (50000, 100000),
185
+ 'annual': (25000, 50000),
186
+ 'breakdown': {
187
+ 'General compliance': (30000, 60000),
188
+ 'Data protection (GDPR)': (20000, 40000)
189
+ }
190
+ }
191
+ },
192
+ 'singapore': {
193
+ 'exchange': {
194
+ 'first_year': (150000, 350000),
195
+ 'annual': (75000, 175000),
196
+ 'breakdown': {
197
+ 'MAS DPT license': (100000, 250000),
198
+ 'Technology risk management': (30000, 75000),
199
+ 'Compliance program': (20000, 25000)
200
+ }
201
+ },
202
+ 'custody': {
203
+ 'first_year': (75000, 200000),
204
+ 'annual': (40000, 100000),
205
+ 'breakdown': {
206
+ 'Custody standards': (50000, 125000),
207
+ 'Insurance/reserves': (15000, 50000),
208
+ 'Audits': (10000, 25000)
209
+ }
210
+ },
211
+ 'staking': {
212
+ 'first_year': (40000, 100000),
213
+ 'annual': (20000, 50000),
214
+ 'breakdown': {
215
+ 'MAS guidelines': (25000, 60000),
216
+ 'Disclosure requirements': (10000, 30000),
217
+ 'Ongoing compliance': (5000, 10000)
218
+ }
219
+ },
220
+ 'lending': {
221
+ 'first_year': (60000, 150000),
222
+ 'annual': (30000, 75000),
223
+ 'breakdown': {
224
+ 'Regulatory assessment': (30000, 75000),
225
+ 'Compliance program': (20000, 50000),
226
+ 'Legal counsel': (10000, 25000)
227
+ }
228
+ },
229
+ 'token_issuance': {
230
+ 'first_year': (75000, 250000),
231
+ 'annual': (40000, 125000),
232
+ 'breakdown': {
233
+ 'MAS exemption/license': (50000, 175000),
234
+ 'Legal counsel': (15000, 50000),
235
+ 'Prospectus preparation': (10000, 25000)
236
+ }
237
+ },
238
+ 'base': {
239
+ 'first_year': (40000, 80000),
240
+ 'annual': (20000, 40000),
241
+ 'breakdown': {
242
+ 'General compliance': (25000, 50000),
243
+ 'Tax advisory': (15000, 30000)
244
+ }
245
+ }
246
+ },
247
+ 'uk': {
248
+ 'exchange': {
249
+ 'first_year': (125000, 300000),
250
+ 'annual': (60000, 150000),
251
+ 'breakdown': {
252
+ 'FCA registration': (75000, 200000),
253
+ 'AML/CTF compliance': (30000, 75000),
254
+ 'Financial promotions': (20000, 25000)
255
+ }
256
+ },
257
+ 'custody': {
258
+ 'first_year': (75000, 200000),
259
+ 'annual': (40000, 100000),
260
+ 'breakdown': {
261
+ 'Custody requirements': (50000, 125000),
262
+ 'Client money rules': (15000, 50000),
263
+ 'Audits': (10000, 25000)
264
+ }
265
+ },
266
+ 'staking': {
267
+ 'first_year': (40000, 100000),
268
+ 'annual': (20000, 50000),
269
+ 'breakdown': {
270
+ 'FCA guidance': (20000, 50000),
271
+ 'Disclosure requirements': (15000, 35000),
272
+ 'Monitoring': (5000, 15000)
273
+ }
274
+ },
275
+ 'lending': {
276
+ 'first_year': (60000, 150000),
277
+ 'annual': (30000, 75000),
278
+ 'breakdown': {
279
+ 'Consumer credit rules': (35000, 90000),
280
+ 'FCA compliance': (15000, 40000),
281
+ 'Legal counsel': (10000, 20000)
282
+ }
283
+ },
284
+ 'token_issuance': {
285
+ 'first_year': (50000, 150000),
286
+ 'annual': (25000, 75000),
287
+ 'breakdown': {
288
+ 'Regulatory assessment': (30000, 90000),
289
+ 'Promotions compliance': (15000, 45000),
290
+ 'Legal opinions': (5000, 15000)
291
+ }
292
+ },
293
+ 'base': {
294
+ 'first_year': (40000, 75000),
295
+ 'annual': (20000, 40000),
296
+ 'breakdown': {
297
+ 'General compliance': (25000, 50000),
298
+ 'AML monitoring': (15000, 25000)
299
+ }
300
+ }
301
+ },
302
+ 'uae': {
303
+ 'exchange': {
304
+ 'first_year': (100000, 250000),
305
+ 'annual': (50000, 125000),
306
+ 'breakdown': {
307
+ 'VARA VASP license': (60000, 150000),
308
+ 'Rulebook compliance': (25000, 75000),
309
+ 'Compliance program': (15000, 25000)
310
+ }
311
+ },
312
+ 'custody': {
313
+ 'first_year': (60000, 150000),
314
+ 'annual': (30000, 75000),
315
+ 'breakdown': {
316
+ 'VARA custody rules': (40000, 100000),
317
+ 'Insurance': (15000, 35000),
318
+ 'Monitoring': (5000, 15000)
319
+ }
320
+ },
321
+ 'staking': {
322
+ 'first_year': (30000, 80000),
323
+ 'annual': (15000, 40000),
324
+ 'breakdown': {
325
+ 'VARA guidance': (20000, 50000),
326
+ 'Disclosure': (10000, 25000)
327
+ }
328
+ },
329
+ 'lending': {
330
+ 'first_year': (50000, 125000),
331
+ 'annual': (25000, 60000),
332
+ 'breakdown': {
333
+ 'Regulatory compliance': (30000, 75000),
334
+ 'Legal counsel': (15000, 40000),
335
+ 'Monitoring': (5000, 10000)
336
+ }
337
+ },
338
+ 'token_issuance': {
339
+ 'first_year': (50000, 150000),
340
+ 'annual': (25000, 75000),
341
+ 'breakdown': {
342
+ 'VARA token rules': (35000, 100000),
343
+ 'White paper': (10000, 40000),
344
+ 'Ongoing disclosure': (5000, 10000)
345
+ }
346
+ },
347
+ 'base': {
348
+ 'first_year': (30000, 60000),
349
+ 'annual': (15000, 30000),
350
+ 'breakdown': {
351
+ 'General compliance': (20000, 40000),
352
+ 'AML/CTF': (10000, 20000)
353
+ }
354
+ }
355
+ }
356
+ }
357
+
358
+ def calculate_costs(
359
+ self,
360
+ jurisdictions: List[str],
361
+ activities: List[str],
362
+ is_security_token: bool = False,
363
+ years: int = 3
364
+ ) -> Dict:
365
+ """
366
+ Calculate comprehensive cost estimates.
367
+
368
+ Args:
369
+ jurisdictions: List of jurisdictions
370
+ activities: List of activities
371
+ is_security_token: Whether token is a security
372
+ years: Number of years to project (default 3)
373
+
374
+ Returns:
375
+ Dictionary with cost breakdown
376
+ """
377
+ results = {}
378
+
379
+ for jurisdiction in jurisdictions:
380
+ if jurisdiction not in self.cost_database:
381
+ logger.warning(f"No cost data for jurisdiction: {jurisdiction}")
382
+ continue
383
+
384
+ costs = self.cost_database[jurisdiction]
385
+
386
+ # Initialize totals
387
+ first_year_min = 0
388
+ first_year_max = 0
389
+ annual_min = 0
390
+ annual_max = 0
391
+ all_breakdowns = []
392
+
393
+ # Base costs
394
+ if 'base' in costs:
395
+ first_year_min += costs['base']['first_year'][0]
396
+ first_year_max += costs['base']['first_year'][1]
397
+ annual_min += costs['base']['annual'][0]
398
+ annual_max += costs['base']['annual'][1]
399
+ all_breakdowns.append({
400
+ 'category': 'Base Compliance',
401
+ 'first_year': costs['base']['first_year'],
402
+ 'annual': costs['base']['annual'],
403
+ 'breakdown': costs['base'].get('breakdown', {})
404
+ })
405
+
406
+ # Activity-specific costs
407
+ for activity in activities:
408
+ if activity in costs:
409
+ first_year_min += costs[activity]['first_year'][0]
410
+ first_year_max += costs[activity]['first_year'][1]
411
+ annual_min += costs[activity]['annual'][0]
412
+ annual_max += costs[activity]['annual'][1]
413
+ all_breakdowns.append({
414
+ 'category': activity.replace('_', ' ').title(),
415
+ 'first_year': costs[activity]['first_year'],
416
+ 'annual': costs[activity]['annual'],
417
+ 'breakdown': costs[activity].get('breakdown', {})
418
+ })
419
+
420
+ # Security token premium (US only)
421
+ if is_security_token and jurisdiction == 'us' and 'security_token_premium' in costs:
422
+ premium = costs['security_token_premium']
423
+ first_year_min += premium['first_year'][0]
424
+ first_year_max += premium['first_year'][1]
425
+ annual_min += premium['annual'][0]
426
+ annual_max += premium['annual'][1]
427
+ all_breakdowns.append({
428
+ 'category': 'Security Token Premium',
429
+ 'first_year': premium['first_year'],
430
+ 'annual': premium['annual'],
431
+ 'breakdown': premium.get('breakdown', {})
432
+ })
433
+
434
+ # Multi-year projection
435
+ multi_year = []
436
+ for year in range(1, years + 1):
437
+ if year == 1:
438
+ year_min = first_year_min
439
+ year_max = first_year_max
440
+ else:
441
+ year_min = annual_min
442
+ year_max = annual_max
443
+
444
+ multi_year.append({
445
+ 'year': year,
446
+ 'min': year_min,
447
+ 'max': year_max,
448
+ 'estimate': (year_min + year_max) // 2
449
+ })
450
+
451
+ # Calculate totals
452
+ total_min = first_year_min + (annual_min * (years - 1))
453
+ total_max = first_year_max + (annual_max * (years - 1))
454
+
455
+ results[jurisdiction] = {
456
+ 'first_year': {
457
+ 'min': first_year_min,
458
+ 'max': first_year_max,
459
+ 'estimate': (first_year_min + first_year_max) // 2
460
+ },
461
+ 'annual_ongoing': {
462
+ 'min': annual_min,
463
+ 'max': annual_max,
464
+ 'estimate': (annual_min + annual_max) // 2
465
+ },
466
+ f'total_{years}_years': {
467
+ 'min': total_min,
468
+ 'max': total_max,
469
+ 'estimate': (total_min + total_max) // 2
470
+ },
471
+ 'breakdown': all_breakdowns,
472
+ 'multi_year_projection': multi_year
473
+ }
474
+
475
+ # Calculate grand total
476
+ grand_total_first = sum(
477
+ j['first_year']['estimate'] for j in results.values()
478
+ )
479
+ grand_total_annual = sum(
480
+ j['annual_ongoing']['estimate'] for j in results.values()
481
+ )
482
+ grand_total_multi = sum(
483
+ j[f'total_{years}_years']['estimate'] for j in results.values()
484
+ )
485
+
486
+ logger.info(
487
+ f"Cost calculation: {len(results)} jurisdictions, "
488
+ f"first year: ${grand_total_first:,}, "
489
+ f"{years}-year total: ${grand_total_multi:,}"
490
+ )
491
+
492
+ return {
493
+ 'by_jurisdiction': results,
494
+ 'grand_totals': {
495
+ 'first_year': grand_total_first,
496
+ 'annual_ongoing': grand_total_annual,
497
+ f'total_{years}_years': grand_total_multi
498
+ },
499
+ 'parameters': {
500
+ 'jurisdictions': jurisdictions,
501
+ 'activities': activities,
502
+ 'is_security_token': is_security_token,
503
+ 'projection_years': years
504
+ },
505
+ 'calculated_at': datetime.now().isoformat()
506
+ }
507
+
508
+
509
+ # Convenience function
510
+ def calculate_costs(
511
+ jurisdictions: List[str],
512
+ activities: List[str],
513
+ is_security_token: bool = False,
514
+ years: int = 3
515
+ ) -> Dict:
516
+ """Quick cost calculation."""
517
+ calculator = CostCalculator()
518
+ return calculator.calculate_costs(jurisdictions, activities, is_security_token, years)
519
+
520
+
521
+ if __name__ == "__main__":
522
+ # Test the calculator
523
+ print("\n=== Testing Cost Calculator ===\n")
524
+
525
+ result = calculate_costs(
526
+ jurisdictions=['us', 'eu', 'singapore'],
527
+ activities=['exchange', 'custody'],
528
+ is_security_token=False,
529
+ years=3
530
+ )
531
+
532
+ print(f"Grand Totals:")
533
+ print(f" First year: ${result['grand_totals']['first_year']:,}")
534
+ print(f" Annual ongoing: ${result['grand_totals']['annual_ongoing']:,}")
535
+ print(f" 3-year total: ${result['grand_totals']['total_3_years']:,}")
536
+
537
+ print(f"\nBy Jurisdiction:")
538
+ for jur, data in result['by_jurisdiction'].items():
539
+ print(f"\n{jur.upper()}:")
540
+ print(f" First year: ${data['first_year']['estimate']:,}")
541
+ print(f" Annual: ${data['annual_ongoing']['estimate']:,}")
542
+ print(f" 3-year total: ${data['total_3_years']['estimate']:,}")
543
+
544
+ print("\n=== Calculator test complete! ===\n")
src/analysis/risk_scorer.py ADDED
@@ -0,0 +1,454 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Risk Scorer with configurable weights for compliance risk assessment.
3
+ Provides structured, explainable risk scoring from 0-100.
4
+ """
5
+
6
+ import logging
7
+ from typing import Dict, List, Optional
8
+ from datetime import datetime
9
+ from enum import Enum
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class RiskLevel(Enum):
15
+ """Risk level categories."""
16
+ LOW = "low" # 0-40
17
+ MEDIUM = "medium" # 41-70
18
+ HIGH = "high" # 71-85
19
+ CRITICAL = "critical" # 86-100
20
+
21
+
22
+ class RiskFactor:
23
+ """Represents a single risk factor contribution."""
24
+
25
+ def __init__(
26
+ self,
27
+ name: str,
28
+ score: float,
29
+ weight: float,
30
+ weighted_score: float,
31
+ description: str
32
+ ):
33
+ self.name = name
34
+ self.score = score # 0-100 for this factor
35
+ self.weight = weight # Weight in final score
36
+ self.weighted_score = weighted_score # score * weight
37
+ self.description = description
38
+
39
+ def to_dict(self) -> Dict:
40
+ """Convert to dictionary."""
41
+ return {
42
+ 'name': self.name,
43
+ 'score': round(self.score, 2),
44
+ 'weight': round(self.weight, 2),
45
+ 'weighted_score': round(self.weighted_score, 2),
46
+ 'description': self.description
47
+ }
48
+
49
+
50
+ class RiskScorer:
51
+ """
52
+ Configurable risk scorer for compliance analysis.
53
+
54
+ Calculates risk score (0-100) based on multiple weighted factors.
55
+ """
56
+
57
+ def __init__(self, custom_weights: Optional[Dict[str, float]] = None):
58
+ """
59
+ Initialize risk scorer.
60
+
61
+ Args:
62
+ custom_weights: Optional custom weights for factors
63
+ Format: {'gap_severity': 0.35, 'gap_count': 0.25, ...}
64
+ """
65
+ # Default weights (must sum to 1.0)
66
+ self.weights = {
67
+ 'gap_severity': 0.35, # Severity of compliance gaps
68
+ 'gap_count': 0.25, # Number of gaps
69
+ 'token_classification': 0.15, # Security token risk
70
+ 'urgency': 0.15, # Deadline urgency
71
+ 'enforcement_history': 0.10 # Jurisdiction enforcement risk
72
+ }
73
+
74
+ # Override with custom weights if provided
75
+ if custom_weights:
76
+ self.weights.update(custom_weights)
77
+
78
+ # Normalize weights to sum to 1.0
79
+ total_weight = sum(self.weights.values())
80
+ if total_weight != 1.0:
81
+ logger.warning(f"Weights sum to {total_weight}, normalizing to 1.0")
82
+ self.weights = {k: v / total_weight for k, v in self.weights.items()}
83
+
84
+ # Enforcement risk scores by jurisdiction (0-100)
85
+ self.enforcement_risk = {
86
+ 'us': 85, # US: High enforcement, especially SEC
87
+ 'eu': 70, # EU: Moderate-high, MiCA implementation
88
+ 'singapore': 60, # Singapore: Moderate, clear framework
89
+ 'uk': 65, # UK: Moderate, post-Brexit evolution
90
+ 'uae': 50 # UAE: Moderate-low, emerging framework
91
+ }
92
+
93
+ logger.info(f"RiskScorer initialized with weights: {self.weights}")
94
+
95
+ def calculate_risk(
96
+ self,
97
+ gaps: List[Dict],
98
+ token_classification: Optional[Dict] = None,
99
+ jurisdictions: Optional[List[str]] = None
100
+ ) -> Dict:
101
+ """
102
+ Calculate overall risk score from compliance gaps.
103
+
104
+ Args:
105
+ gaps: List of compliance gaps from ComplianceEngine
106
+ token_classification: Token classification result (if applicable)
107
+ jurisdictions: List of jurisdictions being analyzed
108
+
109
+ Returns:
110
+ Dictionary with risk score, level, and breakdown
111
+ """
112
+ factors = []
113
+
114
+ # Factor 1: Gap Severity
115
+ severity_score, severity_desc = self._score_gap_severity(gaps)
116
+ factors.append(RiskFactor(
117
+ name='Gap Severity',
118
+ score=severity_score,
119
+ weight=self.weights['gap_severity'],
120
+ weighted_score=severity_score * self.weights['gap_severity'],
121
+ description=severity_desc
122
+ ))
123
+
124
+ # Factor 2: Gap Count
125
+ count_score, count_desc = self._score_gap_count(gaps)
126
+ factors.append(RiskFactor(
127
+ name='Gap Count',
128
+ score=count_score,
129
+ weight=self.weights['gap_count'],
130
+ weighted_score=count_score * self.weights['gap_count'],
131
+ description=count_desc
132
+ ))
133
+
134
+ # Factor 3: Token Classification
135
+ token_score, token_desc = self._score_token_classification(token_classification)
136
+ factors.append(RiskFactor(
137
+ name='Token Classification',
138
+ score=token_score,
139
+ weight=self.weights['token_classification'],
140
+ weighted_score=token_score * self.weights['token_classification'],
141
+ description=token_desc
142
+ ))
143
+
144
+ # Factor 4: Urgency
145
+ urgency_score, urgency_desc = self._score_urgency(gaps)
146
+ factors.append(RiskFactor(
147
+ name='Urgency',
148
+ score=urgency_score,
149
+ weight=self.weights['urgency'],
150
+ weighted_score=urgency_score * self.weights['urgency'],
151
+ description=urgency_desc
152
+ ))
153
+
154
+ # Factor 5: Enforcement History
155
+ enforcement_score, enforcement_desc = self._score_enforcement(jurisdictions or [])
156
+ factors.append(RiskFactor(
157
+ name='Enforcement Risk',
158
+ score=enforcement_score,
159
+ weight=self.weights['enforcement_history'],
160
+ weighted_score=enforcement_score * self.weights['enforcement_history'],
161
+ description=enforcement_desc
162
+ ))
163
+
164
+ # Calculate total weighted score
165
+ total_score = sum(f.weighted_score for f in factors)
166
+ total_score = min(max(total_score, 0), 100) # Clamp to 0-100
167
+
168
+ # Determine risk level
169
+ risk_level = self._get_risk_level(total_score)
170
+
171
+ result = {
172
+ 'risk_score': round(total_score, 2),
173
+ 'risk_level': risk_level.value,
174
+ 'risk_category': risk_level.name,
175
+ 'factors': [f.to_dict() for f in factors],
176
+ 'summary': self._generate_summary(total_score, risk_level, factors),
177
+ 'recommendations': self._generate_risk_recommendations(total_score, factors),
178
+ 'calculated_at': datetime.now().isoformat()
179
+ }
180
+
181
+ logger.info(
182
+ f"Risk calculated: {total_score:.2f} ({risk_level.value}) from "
183
+ f"{len(gaps)} gaps, {len(jurisdictions or [])} jurisdictions"
184
+ )
185
+
186
+ return result
187
+
188
+ def _score_gap_severity(self, gaps: List[Dict]) -> tuple[float, str]:
189
+ """Score based on gap severity distribution."""
190
+ if not gaps:
191
+ return 0.0, "No compliance gaps identified"
192
+
193
+ severity_weights = {
194
+ 'critical': 100,
195
+ 'high': 75,
196
+ 'medium': 50,
197
+ 'low': 25
198
+ }
199
+
200
+ # Calculate weighted average severity
201
+ total_weight = 0
202
+ weighted_sum = 0
203
+
204
+ severity_counts = {'critical': 0, 'high': 0, 'medium': 0, 'low': 0}
205
+
206
+ for gap in gaps:
207
+ severity = gap.get('severity', 'medium')
208
+ severity_counts[severity] = severity_counts.get(severity, 0) + 1
209
+ weight = severity_weights.get(severity, 50)
210
+ weighted_sum += weight
211
+ total_weight += 1
212
+
213
+ score = weighted_sum / total_weight if total_weight > 0 else 0
214
+
215
+ desc = f"{severity_counts['critical']} critical, {severity_counts['high']} high, " \
216
+ f"{severity_counts['medium']} medium, {severity_counts['low']} low severity gaps"
217
+
218
+ return score, desc
219
+
220
+ def _score_gap_count(self, gaps: List[Dict]) -> tuple[float, str]:
221
+ """Score based on number of gaps."""
222
+ count = len(gaps)
223
+
224
+ # Score curve: 0 gaps = 0, 1-2 = 20, 3-4 = 40, 5-6 = 60, 7-8 = 80, 9+ = 100
225
+ if count == 0:
226
+ score = 0
227
+ elif count <= 2:
228
+ score = count * 10
229
+ elif count <= 4:
230
+ score = 20 + (count - 2) * 10
231
+ elif count <= 6:
232
+ score = 40 + (count - 4) * 10
233
+ elif count <= 8:
234
+ score = 60 + (count - 6) * 10
235
+ else:
236
+ score = min(80 + (count - 8) * 5, 100)
237
+
238
+ desc = f"{count} compliance gaps require remediation"
239
+
240
+ return score, desc
241
+
242
+ def _score_token_classification(
243
+ self,
244
+ classification: Optional[Dict]
245
+ ) -> tuple[float, str]:
246
+ """Score based on token classification."""
247
+ if not classification:
248
+ return 0.0, "No token issuance"
249
+
250
+ # Check if classified as security in any jurisdiction
251
+ is_security = False
252
+ security_jurisdictions = []
253
+
254
+ for jurisdiction, result in classification.items():
255
+ if result.get('classification') in ['security', 'capital markets product']:
256
+ is_security = True
257
+ security_jurisdictions.append(jurisdiction.upper())
258
+
259
+ if is_security:
260
+ score = 90.0 # Very high risk for unregistered securities
261
+ desc = f"Token classified as security in {', '.join(security_jurisdictions)}"
262
+ else:
263
+ score = 20.0 # Still some risk for utility tokens
264
+ desc = "Token classified as utility/non-security"
265
+
266
+ return score, desc
267
+
268
+ def _score_urgency(self, gaps: List[Dict]) -> tuple[float, str]:
269
+ """Score based on deadline urgency."""
270
+ if not gaps:
271
+ return 0.0, "No urgent deadlines"
272
+
273
+ urgency_weights = {
274
+ 'immediate': 100,
275
+ 'urgent': 75,
276
+ 'moderate': 50,
277
+ 'planning': 25
278
+ }
279
+
280
+ # Calculate weighted average urgency
281
+ total_weight = 0
282
+ weighted_sum = 0
283
+
284
+ urgency_counts = {'immediate': 0, 'urgent': 0, 'moderate': 0, 'planning': 0}
285
+
286
+ for gap in gaps:
287
+ urgency = gap.get('urgency', 'moderate')
288
+ urgency_counts[urgency] = urgency_counts.get(urgency, 0) + 1
289
+ weight = urgency_weights.get(urgency, 50)
290
+ weighted_sum += weight
291
+ total_weight += 1
292
+
293
+ score = weighted_sum / total_weight if total_weight > 0 else 0
294
+
295
+ desc = f"{urgency_counts['immediate']} immediate, {urgency_counts['urgent']} urgent deadlines"
296
+
297
+ return score, desc
298
+
299
+ def _score_enforcement(self, jurisdictions: List[str]) -> tuple[float, str]:
300
+ """Score based on enforcement risk of jurisdictions."""
301
+ if not jurisdictions:
302
+ return 0.0, "No jurisdictions specified"
303
+
304
+ # Average enforcement risk across jurisdictions
305
+ enforcement_scores = [
306
+ self.enforcement_risk.get(j, 50) for j in jurisdictions
307
+ ]
308
+
309
+ score = sum(enforcement_scores) / len(enforcement_scores)
310
+
311
+ highest_risk = max(jurisdictions, key=lambda j: self.enforcement_risk.get(j, 0))
312
+
313
+ desc = f"Operating in {len(jurisdictions)} jurisdictions, highest risk: {highest_risk.upper()}"
314
+
315
+ return score, desc
316
+
317
+ def _get_risk_level(self, score: float) -> RiskLevel:
318
+ """Determine risk level from score."""
319
+ if score >= 86:
320
+ return RiskLevel.CRITICAL
321
+ elif score >= 71:
322
+ return RiskLevel.HIGH
323
+ elif score >= 41:
324
+ return RiskLevel.MEDIUM
325
+ else:
326
+ return RiskLevel.LOW
327
+
328
+ def _generate_summary(
329
+ self,
330
+ score: float,
331
+ level: RiskLevel,
332
+ factors: List[RiskFactor]
333
+ ) -> str:
334
+ """Generate human-readable summary."""
335
+ top_factor = max(factors, key=lambda f: f.weighted_score)
336
+
337
+ if level == RiskLevel.CRITICAL:
338
+ return f"CRITICAL RISK ({score:.0f}/100): Immediate action required. " \
339
+ f"Primary risk: {top_factor.name} ({top_factor.score:.0f}/100)."
340
+ elif level == RiskLevel.HIGH:
341
+ return f"HIGH RISK ({score:.0f}/100): Urgent compliance measures needed. " \
342
+ f"Primary concern: {top_factor.name}."
343
+ elif level == RiskLevel.MEDIUM:
344
+ return f"MEDIUM RISK ({score:.0f}/100): Compliance improvements recommended. " \
345
+ f"Focus on: {top_factor.name}."
346
+ else:
347
+ return f"LOW RISK ({score:.0f}/100): Minimal compliance concerns. " \
348
+ f"Continue monitoring: {top_factor.name}."
349
+
350
+ def _generate_risk_recommendations(
351
+ self,
352
+ score: float,
353
+ factors: List[RiskFactor]
354
+ ) -> List[str]:
355
+ """Generate recommendations based on risk factors."""
356
+ recommendations = []
357
+
358
+ # Sort factors by weighted contribution
359
+ sorted_factors = sorted(factors, key=lambda f: f.weighted_score, reverse=True)
360
+
361
+ for factor in sorted_factors:
362
+ if factor.weighted_score > 15: # Significant contributor
363
+ if factor.name == 'Gap Severity':
364
+ recommendations.append(
365
+ "Address critical severity gaps immediately with legal counsel"
366
+ )
367
+ elif factor.name == 'Gap Count':
368
+ recommendations.append(
369
+ "Develop systematic compliance roadmap to address multiple gaps"
370
+ )
371
+ elif factor.name == 'Token Classification':
372
+ recommendations.append(
373
+ "Consult securities lawyer immediately for token registration strategy"
374
+ )
375
+ elif factor.name == 'Urgency':
376
+ recommendations.append(
377
+ "Prioritize urgent deadlines to avoid enforcement action"
378
+ )
379
+ elif factor.name == 'Enforcement Risk':
380
+ recommendations.append(
381
+ "Consider jurisdiction risk in business strategy and timeline"
382
+ )
383
+
384
+ # Overall recommendation based on score
385
+ if score >= 71:
386
+ recommendations.insert(
387
+ 0,
388
+ "URGENT: Engage compliance counsel and consider delaying launch until gaps addressed"
389
+ )
390
+ elif score >= 41:
391
+ recommendations.insert(
392
+ 0,
393
+ "Develop 90-day compliance action plan with clear milestones"
394
+ )
395
+
396
+ return recommendations[:5] # Top 5 recommendations
397
+
398
+
399
+ # Convenience function
400
+ def calculate_risk(
401
+ gaps: List[Dict],
402
+ token_classification: Optional[Dict] = None,
403
+ jurisdictions: Optional[List[str]] = None
404
+ ) -> Dict:
405
+ """Quick risk calculation."""
406
+ scorer = RiskScorer()
407
+ return scorer.calculate_risk(gaps, token_classification, jurisdictions)
408
+
409
+
410
+ if __name__ == "__main__":
411
+ # Test the scorer
412
+ print("\n=== Testing Risk Scorer ===\n")
413
+
414
+ sample_gaps = [
415
+ {
416
+ 'requirement': 'FinCEN MSB Registration',
417
+ 'severity': 'critical',
418
+ 'urgency': 'immediate',
419
+ 'jurisdiction': 'us'
420
+ },
421
+ {
422
+ 'requirement': 'State MTL Licenses',
423
+ 'severity': 'critical',
424
+ 'urgency': 'urgent',
425
+ 'jurisdiction': 'us'
426
+ },
427
+ {
428
+ 'requirement': 'MiCA CASP Authorization',
429
+ 'severity': 'high',
430
+ 'urgency': 'moderate',
431
+ 'jurisdiction': 'eu'
432
+ }
433
+ ]
434
+
435
+ result = calculate_risk(
436
+ gaps=sample_gaps,
437
+ token_classification={'us': {'classification': 'security'}},
438
+ jurisdictions=['us', 'eu']
439
+ )
440
+
441
+ print(f"Risk Score: {result['risk_score']}/100")
442
+ print(f"Risk Level: {result['risk_level'].upper()}")
443
+ print(f"\nSummary: {result['summary']}")
444
+
445
+ print(f"\nRisk Factors:")
446
+ for factor in result['factors']:
447
+ print(f" - {factor['name']}: {factor['score']:.0f}/100 "
448
+ f"(weight: {factor['weight']:.0%}, contribution: {factor['weighted_score']:.1f})")
449
+
450
+ print(f"\nRecommendations:")
451
+ for i, rec in enumerate(result['recommendations'], 1):
452
+ print(f" {i}. {rec}")
453
+
454
+ print("\n=== Scorer test complete! ===\n")
src/analysis/token_classifier.py ADDED
@@ -0,0 +1,487 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Token classification using the Howey Test and other regulatory frameworks.
3
+ Determines if a crypto token is a security or utility token.
4
+ """
5
+
6
+ import logging
7
+ from typing import Dict, List, Optional, Tuple
8
+ from datetime import datetime
9
+ import re
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class HoweyTestAnalyzer:
15
+ """
16
+ Analyzes tokens using the SEC's Howey Test.
17
+
18
+ The Howey Test has 4 prongs:
19
+ 1. Investment of money
20
+ 2. In a common enterprise
21
+ 3. With an expectation of profits
22
+ 4. Derived from the efforts of others
23
+
24
+ If all 4 are met, the token is likely a security.
25
+ """
26
+
27
+ def __init__(self):
28
+ """Initialize Howey Test analyzer."""
29
+ self.test_criteria = {
30
+ 'investment_of_money': {
31
+ 'keywords': [
32
+ 'purchase', 'buy', 'invest', 'sale', 'ico', 'token sale',
33
+ 'presale', 'crowdsale', 'fundraising', 'payment', 'contribute'
34
+ ],
35
+ 'weight': 0.25
36
+ },
37
+ 'common_enterprise': {
38
+ 'keywords': [
39
+ 'pool', 'pooled', 'combined', 'collective', 'together',
40
+ 'treasury', 'ecosystem', 'platform', 'network', 'protocol'
41
+ ],
42
+ 'weight': 0.25
43
+ },
44
+ 'expectation_of_profits': {
45
+ 'keywords': [
46
+ 'profit', 'returns', 'gains', 'appreciation', 'yield',
47
+ 'rewards', 'earnings', 'income', 'dividend', 'interest',
48
+ 'roi', 'return on investment', 'price increase'
49
+ ],
50
+ 'weight': 0.25
51
+ },
52
+ 'efforts_of_others': {
53
+ 'keywords': [
54
+ 'team', 'development', 'management', 'founders', 'developers',
55
+ 'operated by', 'managed by', 'governance', 'roadmap',
56
+ 'build', 'create', 'maintain', 'improve', 'update'
57
+ ],
58
+ 'weight': 0.25
59
+ }
60
+ }
61
+
62
+ def analyze_prong(self, text: str, prong_name: str) -> Tuple[bool, float, List[str]]:
63
+ """
64
+ Analyze a single Howey Test prong.
65
+
66
+ Args:
67
+ text: Token description/whitepaper text
68
+ prong_name: Name of the prong to analyze
69
+
70
+ Returns:
71
+ Tuple of (prong_met, confidence, evidence_keywords)
72
+ """
73
+ if prong_name not in self.test_criteria:
74
+ raise ValueError(f"Invalid prong: {prong_name}")
75
+
76
+ criteria = self.test_criteria[prong_name]
77
+ keywords = criteria['keywords']
78
+ text_lower = text.lower()
79
+
80
+ # Find matching keywords
81
+ matches = []
82
+ for keyword in keywords:
83
+ pattern = r'\b' + re.escape(keyword) + r'\b'
84
+ if re.search(pattern, text_lower):
85
+ matches.append(keyword)
86
+
87
+ # Calculate confidence based on match density
88
+ match_count = len(matches)
89
+ word_count = len(text_lower.split())
90
+ match_density = (match_count / (word_count / 100)) if word_count > 0 else 0
91
+
92
+ # Prong is "met" if we have multiple keyword matches
93
+ prong_met = match_count >= 2
94
+ confidence = min(match_density / 5, 1.0) # Normalize to 0-1
95
+
96
+ return prong_met, confidence, matches
97
+
98
+ def run_howey_test(self, text: str) -> Dict:
99
+ """
100
+ Run full Howey Test analysis on token description.
101
+
102
+ Args:
103
+ text: Token description/whitepaper text
104
+
105
+ Returns:
106
+ Dictionary with test results
107
+ """
108
+ results = {
109
+ 'prongs': {},
110
+ 'prongs_met': 0,
111
+ 'is_security': False,
112
+ 'overall_confidence': 0.0,
113
+ 'evidence': {},
114
+ 'analysis_timestamp': datetime.now().isoformat()
115
+ }
116
+
117
+ # Analyze each prong
118
+ for prong_name in self.test_criteria.keys():
119
+ met, confidence, evidence = self.analyze_prong(text, prong_name)
120
+
121
+ results['prongs'][prong_name] = {
122
+ 'met': met,
123
+ 'confidence': confidence,
124
+ 'evidence_count': len(evidence)
125
+ }
126
+
127
+ results['evidence'][prong_name] = evidence
128
+
129
+ if met:
130
+ results['prongs_met'] += 1
131
+
132
+ # Token is a security if all 4 prongs are met
133
+ results['is_security'] = results['prongs_met'] == 4
134
+
135
+ # Calculate overall confidence (average of prong confidences)
136
+ confidences = [p['confidence'] for p in results['prongs'].values()]
137
+ results['overall_confidence'] = sum(confidences) / len(confidences)
138
+
139
+ # Adjust confidence based on prongs met
140
+ if results['prongs_met'] < 4:
141
+ # Reduce confidence if not all prongs met
142
+ results['overall_confidence'] *= (results['prongs_met'] / 4)
143
+
144
+ logger.info(
145
+ f"Howey Test: {results['prongs_met']}/4 prongs met, "
146
+ f"is_security={results['is_security']}, "
147
+ f"confidence={results['overall_confidence']:.2f}"
148
+ )
149
+
150
+ return results
151
+
152
+
153
+ class TokenClassifier:
154
+ """
155
+ Comprehensive token classifier using multiple frameworks.
156
+ - US: Howey Test
157
+ - EU: MiCA classification
158
+ - Singapore: DPT classification
159
+ """
160
+
161
+ def __init__(self):
162
+ """Initialize token classifier."""
163
+ self.howey_analyzer = HoweyTestAnalyzer()
164
+ logger.info("TokenClassifier initialized")
165
+
166
+ def classify_us(self, token_description: str) -> Dict:
167
+ """
168
+ Classify token under US law (SEC Howey Test).
169
+
170
+ Args:
171
+ token_description: Description of token mechanics
172
+
173
+ Returns:
174
+ Classification result
175
+ """
176
+ howey_result = self.howey_analyzer.run_howey_test(token_description)
177
+
178
+ classification = {
179
+ 'jurisdiction': 'us',
180
+ 'framework': 'SEC Howey Test',
181
+ 'classification': 'security' if howey_result['is_security'] else 'utility',
182
+ 'confidence': howey_result['overall_confidence'],
183
+ 'howey_test': howey_result,
184
+ 'regulatory_implications': self._get_us_implications(howey_result)
185
+ }
186
+
187
+ return classification
188
+
189
+ def classify_eu(self, token_description: str) -> Dict:
190
+ """
191
+ Classify token under EU MiCA framework.
192
+
193
+ Args:
194
+ token_description: Token description
195
+
196
+ Returns:
197
+ Classification result
198
+ """
199
+ text_lower = token_description.lower()
200
+
201
+ # MiCA categories
202
+ is_utility_token = any([
203
+ 'access' in text_lower,
204
+ 'usage' in text_lower,
205
+ 'service' in text_lower,
206
+ 'platform access' in text_lower
207
+ ])
208
+
209
+ is_asset_referenced = any([
210
+ 'backed' in text_lower,
211
+ 'pegged' in text_lower,
212
+ 'collateralized' in text_lower,
213
+ 'reserve' in text_lower
214
+ ])
215
+
216
+ is_e_money = any([
217
+ 'fiat' in text_lower,
218
+ 'currency' in text_lower,
219
+ 'stablecoin' in text_lower,
220
+ 'payment' in text_lower
221
+ ])
222
+
223
+ # Determine primary category
224
+ if is_e_money:
225
+ category = 'e-money token'
226
+ elif is_asset_referenced:
227
+ category = 'asset-referenced token'
228
+ elif is_utility_token:
229
+ category = 'utility token'
230
+ else:
231
+ category = 'crypto-asset' # Default MiCA category
232
+
233
+ classification = {
234
+ 'jurisdiction': 'eu',
235
+ 'framework': 'MiCA',
236
+ 'classification': category,
237
+ 'confidence': 0.6, # Lower confidence for heuristic classification
238
+ 'mica_categories': {
239
+ 'utility_token': is_utility_token,
240
+ 'asset_referenced_token': is_asset_referenced,
241
+ 'e_money_token': is_e_money
242
+ },
243
+ 'regulatory_implications': self._get_eu_implications(category)
244
+ }
245
+
246
+ return classification
247
+
248
+ def classify_singapore(self, token_description: str) -> Dict:
249
+ """
250
+ Classify token under Singapore MAS framework.
251
+
252
+ Args:
253
+ token_description: Token description
254
+
255
+ Returns:
256
+ Classification result
257
+ """
258
+ text_lower = token_description.lower()
259
+
260
+ # MAS Payment Services Act - Digital Payment Token (DPT)
261
+ is_dpt = any([
262
+ 'payment' in text_lower,
263
+ 'medium of exchange' in text_lower,
264
+ 'store of value' in text_lower,
265
+ 'transfer' in text_lower
266
+ ])
267
+
268
+ is_capital_markets_product = any([
269
+ 'security' in text_lower,
270
+ 'investment' in text_lower,
271
+ 'profit' in text_lower,
272
+ 'return' in text_lower,
273
+ 'dividend' in text_lower
274
+ ])
275
+
276
+ # Determine category
277
+ if is_capital_markets_product:
278
+ category = 'capital markets product'
279
+ elif is_dpt:
280
+ category = 'digital payment token'
281
+ else:
282
+ category = 'unregulated token'
283
+
284
+ classification = {
285
+ 'jurisdiction': 'singapore',
286
+ 'framework': 'MAS PSA',
287
+ 'classification': category,
288
+ 'confidence': 0.6,
289
+ 'mas_categories': {
290
+ 'digital_payment_token': is_dpt,
291
+ 'capital_markets_product': is_capital_markets_product
292
+ },
293
+ 'regulatory_implications': self._get_singapore_implications(category)
294
+ }
295
+
296
+ return classification
297
+
298
+ def classify_all_jurisdictions(self, token_description: str) -> Dict:
299
+ """
300
+ Classify token across all supported jurisdictions.
301
+
302
+ Args:
303
+ token_description: Token description/whitepaper text
304
+
305
+ Returns:
306
+ Dictionary of classifications per jurisdiction
307
+ """
308
+ return {
309
+ 'us': self.classify_us(token_description),
310
+ 'eu': self.classify_eu(token_description),
311
+ 'singapore': self.classify_singapore(token_description),
312
+ 'summary': self._generate_summary(token_description)
313
+ }
314
+
315
+ def _get_us_implications(self, howey_result: Dict) -> List[str]:
316
+ """Get regulatory implications for US classification."""
317
+ implications = []
318
+
319
+ if howey_result['is_security']:
320
+ implications.extend([
321
+ "Token is likely a security under US law",
322
+ "Must register with SEC or qualify for exemption",
323
+ "Consider Regulation D (private placement) or Regulation A+",
324
+ "Must comply with securities laws for trading",
325
+ "May need broker-dealer registration for exchanges"
326
+ ])
327
+ else:
328
+ implications.extend([
329
+ "Token may be a utility token (not a security)",
330
+ "Still subject to FinCEN MSB registration if used for payments",
331
+ "State money transmitter licenses may be required",
332
+ "Consumer protection laws still apply",
333
+ "Monitor SEC guidance - classification can change"
334
+ ])
335
+
336
+ return implications
337
+
338
+ def _get_eu_implications(self, category: str) -> List[str]:
339
+ """Get regulatory implications for EU classification."""
340
+ implications_map = {
341
+ 'e-money token': [
342
+ "Subject to strict MiCA e-money token requirements",
343
+ "Need authorization as e-money institution",
344
+ "Must maintain 1:1 backing with fiat reserves",
345
+ "Enhanced consumer protection requirements",
346
+ "Effective from June 2024"
347
+ ],
348
+ 'asset-referenced token': [
349
+ "Subject to MiCA asset-referenced token regime",
350
+ "Must maintain reserve of referenced assets",
351
+ "Requires authorization from regulator",
352
+ "Ongoing reporting and transparency requirements",
353
+ "White paper must be approved"
354
+ ],
355
+ 'utility token': [
356
+ "Lower regulatory burden under MiCA",
357
+ "Still requires white paper publication",
358
+ "Consumer protection rules apply",
359
+ "Marketing restrictions apply",
360
+ "Effective from July 2024"
361
+ ],
362
+ 'crypto-asset': [
363
+ "General MiCA crypto-asset rules apply",
364
+ "CASP authorization needed for services",
365
+ "White paper required for public offerings",
366
+ "AML/CTF compliance mandatory"
367
+ ]
368
+ }
369
+
370
+ return implications_map.get(category, ["MiCA framework applies"])
371
+
372
+ def _get_singapore_implications(self, category: str) -> List[str]:
373
+ """Get regulatory implications for Singapore classification."""
374
+ implications_map = {
375
+ 'digital payment token': [
376
+ "Requires DPT service provider license from MAS",
377
+ "Must comply with Payment Services Act",
378
+ "AML/CFT requirements apply",
379
+ "Technology risk management guidelines",
380
+ "Fit and proper criteria for operators"
381
+ ],
382
+ 'capital markets product': [
383
+ "Subject to Securities and Futures Act",
384
+ "Requires CMS license from MAS",
385
+ "Prospectus or exemption required",
386
+ "Ongoing reporting obligations",
387
+ "Higher regulatory scrutiny"
388
+ ],
389
+ 'unregulated token': [
390
+ "May not require MAS licensing",
391
+ "Still subject to general laws",
392
+ "Monitor for regulatory changes",
393
+ "Consumer protection laws apply"
394
+ ]
395
+ }
396
+
397
+ return implications_map.get(category, ["Review MAS guidelines"])
398
+
399
+ def _generate_summary(self, token_description: str) -> Dict:
400
+ """Generate summary across jurisdictions."""
401
+ us_result = self.classify_us(token_description)
402
+ eu_result = self.classify_eu(token_description)
403
+ sg_result = self.classify_singapore(token_description)
404
+
405
+ is_security_anywhere = (
406
+ us_result['classification'] == 'security' or
407
+ sg_result['classification'] == 'capital markets product'
408
+ )
409
+
410
+ return {
411
+ 'is_security_anywhere': is_security_anywhere,
412
+ 'most_restrictive_jurisdiction': 'us' if us_result['classification'] == 'security' else 'eu',
413
+ 'classifications': {
414
+ 'us': us_result['classification'],
415
+ 'eu': eu_result['classification'],
416
+ 'singapore': sg_result['classification']
417
+ },
418
+ 'recommendation': (
419
+ "Consult securities lawyer immediately - token appears to be a security"
420
+ if is_security_anywhere else
421
+ "Token may qualify as utility token, but verify with legal counsel"
422
+ )
423
+ }
424
+
425
+
426
+ # Convenience function
427
+ def classify_token(token_description: str, jurisdiction: Optional[str] = None) -> Dict:
428
+ """
429
+ Quick classify a token.
430
+
431
+ Args:
432
+ token_description: Token description/whitepaper
433
+ jurisdiction: Specific jurisdiction ('us', 'eu', 'singapore') or None for all
434
+
435
+ Returns:
436
+ Classification result
437
+ """
438
+ classifier = TokenClassifier()
439
+
440
+ if jurisdiction:
441
+ if jurisdiction == 'us':
442
+ return classifier.classify_us(token_description)
443
+ elif jurisdiction == 'eu':
444
+ return classifier.classify_eu(token_description)
445
+ elif jurisdiction == 'singapore':
446
+ return classifier.classify_singapore(token_description)
447
+ else:
448
+ raise ValueError(f"Unsupported jurisdiction: {jurisdiction}")
449
+ else:
450
+ return classifier.classify_all_jurisdictions(token_description)
451
+
452
+
453
+ if __name__ == "__main__":
454
+ # Example usage
455
+ sample_token = """
456
+ Our governance token allows holders to vote on protocol upgrades and earn
457
+ rewards from transaction fees. Tokens are sold in a public sale at $0.50 each.
458
+ The development team will use funds to build the platform and market the product.
459
+ Early investors expect significant returns as the platform grows and token value
460
+ appreciates. The team manages the treasury and executes the roadmap.
461
+ """
462
+
463
+ print("\n=== Token Classification ===\n")
464
+
465
+ # Full analysis
466
+ results = classify_token(sample_token)
467
+
468
+ print("US Classification:")
469
+ us = results['us']
470
+ print(f" Classification: {us['classification']}")
471
+ print(f" Confidence: {us['confidence']:.2f}")
472
+ print(f" Howey Test: {us['howey_test']['prongs_met']}/4 prongs met")
473
+
474
+ print("\nEU Classification:")
475
+ eu = results['eu']
476
+ print(f" Classification: {eu['classification']}")
477
+ print(f" Confidence: {eu['confidence']:.2f}")
478
+
479
+ print("\nSingapore Classification:")
480
+ sg = results['singapore']
481
+ print(f" Classification: {sg['classification']}")
482
+ print(f" Confidence: {sg['confidence']:.2f}")
483
+
484
+ print("\nSummary:")
485
+ summary = results['summary']
486
+ print(f" Is security anywhere: {summary['is_security_anywhere']}")
487
+ print(f" Recommendation: {summary['recommendation']}")
src/config.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Configuration module for the Crypto Compliance Intelligence Agent.
3
+ Loads environment variables and provides centralized configuration.
4
+ """
5
+
6
+ import os
7
+ from dotenv import load_dotenv
8
+ from pathlib import Path
9
+
10
+ # Load environment variables from .env file
11
+ load_dotenv()
12
+
13
+ class Config:
14
+ """
15
+ Central configuration class for the application.
16
+ All configuration parameters are loaded from environment variables.
17
+ """
18
+
19
+ # API Keys
20
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
21
+ HF_TOKEN = os.getenv("HF_TOKEN")
22
+ GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
23
+
24
+ # Model Configuration
25
+ EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "sentence-transformers/all-MiniLM-L6-v2")
26
+ FINBERT_MODEL = os.getenv("FINBERT_MODEL", "ProsusAI/finbert")
27
+ LEGAL_BERT_MODEL = os.getenv("LEGAL_BERT_MODEL", "nlpaueb/legal-bert-base-uncased")
28
+
29
+ # ChromaDB Configuration
30
+ CHROMA_PERSIST_DIR = os.getenv("CHROMA_PERSIST_DIR", "./data/chroma_db")
31
+ COLLECTION_NAME = os.getenv("COLLECTION_NAME", "regulations_kb")
32
+
33
+ # Gemini Configuration
34
+ GEMINI_MODEL = os.getenv("GEMINI_MODEL", "gemini-2.0-flash-exp")
35
+ GEMINI_TEMPERATURE = float(os.getenv("GEMINI_TEMPERATURE", "0.1"))
36
+ GEMINI_MAX_TOKENS = int(os.getenv("GEMINI_MAX_TOKENS", "8192"))
37
+
38
+ # Application Settings
39
+ PROJECT_ROOT = Path(__file__).parent.parent
40
+ DATA_DIR = PROJECT_ROOT / "data"
41
+ REGULATIONS_DIR = DATA_DIR / "regulations"
42
+
43
+ # Jurisdictions supported
44
+ SUPPORTED_JURISDICTIONS = ["us", "eu", "singapore", "uk", "uae"]
45
+
46
+ # Logging Configuration
47
+ LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
48
+ LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
49
+
50
+ @classmethod
51
+ def validate(cls):
52
+ """
53
+ Validate that all required configuration is present.
54
+ Raises ValueError if critical configuration is missing.
55
+ """
56
+ if not cls.GEMINI_API_KEY:
57
+ raise ValueError("GEMINI_API_KEY is required but not set in environment variables")
58
+
59
+ # Ensure required directories exist
60
+ cls.DATA_DIR.mkdir(exist_ok=True, parents=True)
61
+ cls.REGULATIONS_DIR.mkdir(exist_ok=True, parents=True)
62
+
63
+ for jurisdiction in cls.SUPPORTED_JURISDICTIONS:
64
+ jurisdiction_dir = cls.REGULATIONS_DIR / jurisdiction
65
+ jurisdiction_dir.mkdir(exist_ok=True, parents=True)
66
+
67
+ return True
68
+
69
+ # Validate configuration on import
70
+ Config.validate()
src/data/__init__.py ADDED
File without changes
src/data/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (196 Bytes). View file
 
src/data/__pycache__/vectordb.cpython-313.pyc ADDED
Binary file (14.7 kB). View file
 
src/data/regulations/eu/mica-asset-referenced-tokens-2024.json ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "eu-mica-asset-referenced-tokens-2024",
3
+ "jurisdiction": "eu",
4
+ "agency": "ESMA (European Securities and Markets Authority) / MiCA Framework",
5
+ "title": "MiCA Regulation - Asset-Referenced Tokens (ARTs) for Real Estate",
6
+ "summary": "EU Markets in Crypto-Assets (MiCA) regulation requirements for asset-referenced tokens backed by real estate or other assets. Covers authorization requirements, reserve management, white paper publication, and ongoing supervision for ARTs. Effective June 30, 2024 with transitional period through December 2024.",
7
+ "status": "effective",
8
+ "announced_date": "2023-05-31",
9
+ "effective_date": "2024-06-30",
10
+ "last_updated": "2024-09-01",
11
+ "source_url": "https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32023R1114",
12
+ "full_text_url": "https://eur-lex.europa.eu/legal-content/EN/TXT/PDF/?uri=CELEX:32023R1114",
13
+ "crypto_activities_affected": [
14
+ "tokenization",
15
+ "asset-referenced-tokens",
16
+ "issuance",
17
+ "custody",
18
+ "secondary-markets"
19
+ ],
20
+ "tags": [
21
+ "mica",
22
+ "asset-referenced-tokens",
23
+ "real-estate",
24
+ "authorization",
25
+ "white-paper",
26
+ "reserve-requirements",
27
+ "esma"
28
+ ],
29
+ "requirements": [
30
+ {
31
+ "requirement": "ART Issuer Authorization from National Competent Authority",
32
+ "description": "Must obtain authorization as ART issuer from national competent authority (NCA) in home member state (e.g., BaFin in Germany, AMF in France). Application requires: business plan, governance arrangements, risk management framework, IT systems description, recovery plan. Authorization process: 6-12 months. Application fee varies by country (€10K-50K). Issuers established in third countries must appoint legal representative in EU.",
33
+ "mandatory": true,
34
+ "deadline_days": 0,
35
+ "estimated_cost_usd": {
36
+ "min": 75000,
37
+ "max": 150000,
38
+ "currency": "EUR",
39
+ "notes": "NCA application fee (€10K-50K) + legal preparation (€50K-100K) + consultancy fees"
40
+ },
41
+ "severity": "critical",
42
+ "exemptions": ["Credit institutions and electronic money institutions already authorized under existing EU framework"]
43
+ },
44
+ {
45
+ "requirement": "Minimum Own Funds Requirement",
46
+ "description": "ART issuers must maintain own funds equal to greater of: (1) €350,000, OR (2) 2% of average amount of reserve assets, OR (3) 25% of fixed overheads of preceding year. Own funds must be held in cash or highly liquid financial instruments. For real estate-backed ARTs exceeding €100M circulation, requirement may increase to 3% of reserves. Annual audit required.",
47
+ "mandatory": true,
48
+ "deadline_days": 0,
49
+ "estimated_cost_usd": {
50
+ "min": 350000,
51
+ "max": 2000000,
52
+ "currency": "EUR",
53
+ "notes": "Minimum €350K but scales with reserve size - real estate ARTs typically €500K-2M"
54
+ },
55
+ "severity": "critical",
56
+ "exemptions": []
57
+ },
58
+ {
59
+ "requirement": "Crypto-Asset White Paper Publication and Notification",
60
+ "description": "Must prepare and publish white paper containing: (1) issuer information and governance, (2) detailed description of real estate assets backing tokens, (3) rights and obligations of token holders, (4) stabilization mechanism, (5) reserve management policy, (6) comprehensive risk warnings (minimum 15 categories), (7) environmental/climate impact. White paper must be notified to NCA 20 business days before publication. NCA may prohibit or suspend offering. White paper valid for 12 months. Filing fee: €5K-20K per jurisdiction.",
61
+ "mandatory": true,
62
+ "deadline_days": 60,
63
+ "estimated_cost_usd": {
64
+ "min": 40000,
65
+ "max": 100000,
66
+ "currency": "EUR",
67
+ "notes": "Legal drafting (€30K-70K) + NCA notification fees (€5K-20K) + translations for multi-country offerings"
68
+ },
69
+ "severity": "critical",
70
+ "exemptions": ["Offerings to qualified investors only (but must still file white paper with NCA)"]
71
+ },
72
+ {
73
+ "requirement": "Reserve of Assets Management and Segregation",
74
+ "description": "Must maintain reserve of assets with value equal to at least 100% of circulation value of ARTs. Reserve composition: (1) at least 30% in cash deposits at credit institutions, (2) remainder in highly liquid financial instruments with minimal credit risk. For real estate ARTs: property valuations must be updated quarterly by independent appraiser. Reserve assets must be legally segregated and held by authorized custodian. Daily reconciliation and monthly reports to NCA required. Reserve cannot be pledged or encumbered.",
75
+ "mandatory": true,
76
+ "deadline_days": 90,
77
+ "estimated_cost_usd": {
78
+ "min": 60000,
79
+ "max": 150000,
80
+ "currency": "EUR",
81
+ "notes": "Custodian setup + quarterly property valuations (€15K-40K each) + compliance systems + legal segregation structure"
82
+ },
83
+ "severity": "critical",
84
+ "exemptions": []
85
+ },
86
+ {
87
+ "requirement": "Redemption Rights and Mechanisms",
88
+ "description": "Token holders must have right to redeem ARTs for reserve assets or fiat equivalent at any time, at least once per month. Redemption price based on fair market value of underlying real estate (minus reasonable fees up to 5%). Must maintain liquid reserves (30% of total) to meet redemption requests. If redemptions exceed 25% of reserves in 7-day period, issuer may suspend redemptions for up to 3 months with NCA approval. Smart contract must enforce redemption rights automatically.",
89
+ "mandatory": true,
90
+ "deadline_days": 90,
91
+ "estimated_cost_usd": {
92
+ "min": 25000,
93
+ "max": 60000,
94
+ "currency": "EUR",
95
+ "notes": "Smart contract development for redemption mechanism + liquidity management systems"
96
+ },
97
+ "severity": "high",
98
+ "exemptions": []
99
+ },
100
+ {
101
+ "requirement": "Conflicts of Interest and Governance Requirements",
102
+ "description": "Must establish management body with at least 2 members (4 if ART circulation exceeds €100M). Independent audit committee required. Conflict of interest policy must address: related party transactions, property valuations, reserve management. AML/CFT compliance officer required (separate from management). Internal audit function required for issuers exceeding €50M circulation. Fit and proper assessments for all board members.",
103
+ "mandatory": true,
104
+ "deadline_days": 120,
105
+ "estimated_cost_usd": {
106
+ "min": 80000,
107
+ "max": 200000,
108
+ "currency": "EUR",
109
+ "notes": "First year: board member recruitment + compliance officer salary (€60K-120K) + governance setup + fit-and-proper assessments"
110
+ },
111
+ "severity": "high",
112
+ "exemptions": []
113
+ },
114
+ {
115
+ "requirement": "Marketing Communications and Advertising Rules",
116
+ "description": "All marketing must: (1) be clearly identifiable as such, (2) be fair, clear, not misleading, (3) include risk warnings, (4) state ART issuer name and authorization status, (5) reference white paper. Prohibited claims: capital protection, guaranteed returns, comparison to securities unless fair. Marketing via social media influencers must disclose commercial relationship. NCA may require pre-approval of marketing materials. Non-compliant ads subject to €5M fines or 3% of annual turnover.",
117
+ "mandatory": true,
118
+ "deadline_days": 45,
119
+ "estimated_cost_usd": {
120
+ "min": 20000,
121
+ "max": 50000,
122
+ "currency": "EUR",
123
+ "notes": "Legal review of all marketing materials + compliance procedures + risk warning templates"
124
+ },
125
+ "severity": "medium",
126
+ "exemptions": []
127
+ },
128
+ {
129
+ "requirement": "Orderly Wind-Down Plan and Recovery Arrangements",
130
+ "description": "Must maintain detailed plan for orderly wind-down in case of insolvency or license revocation. Plan must include: token redemption process, reserve asset liquidation timeline (realistic for real estate: 12-24 months), communication to token holders, appointment of liquidator. Recovery plan required for ARTs exceeding €100M circulation, including stress testing scenarios (property value decline, mass redemptions, custody failure). Annual plan updates required.",
131
+ "mandatory": true,
132
+ "deadline_days": 180,
133
+ "estimated_cost_usd": {
134
+ "min": 30000,
135
+ "max": 80000,
136
+ "currency": "EUR",
137
+ "notes": "Legal and financial advisors for wind-down and recovery planning + stress testing"
138
+ },
139
+ "severity": "high",
140
+ "exemptions": []
141
+ },
142
+ {
143
+ "requirement": "Reporting and Transparency Obligations",
144
+ "description": "Ongoing reporting requirements: (1) quarterly reports to NCA (reserve composition, token circulation, redemptions), (2) annual audited financial statements, (3) publish quarterly reserve attestation on website, (4) immediate notification of material events (property damage, valuation changes >10%, technical failures, security breaches). Token holder reporting: quarterly updates on property performance and valuations. Public disclosure of any changes to white paper within 24 hours.",
145
+ "mandatory": true,
146
+ "deadline_days": 0,
147
+ "estimated_cost_usd": {
148
+ "min": 40000,
149
+ "max": 100000,
150
+ "currency": "EUR",
151
+ "notes": "Annual ongoing: external audit (€20K-50K) + quarterly reporting + compliance monitoring + disclosure systems"
152
+ },
153
+ "severity": "medium",
154
+ "exemptions": []
155
+ },
156
+ {
157
+ "requirement": "Cybersecurity and Operational Resilience (DORA Compliance)",
158
+ "description": "Must comply with Digital Operational Resilience Act (DORA) requirements: (1) ICT risk management framework, (2) incident reporting (within 4 hours for major incidents), (3) digital operational resilience testing (including penetration tests), (4) third-party ICT risk management, (5) threat-led penetration testing (TLPT) for significant ARTs. DORA compliance deadline: January 17, 2025. Non-compliance penalties up to ���10M or 2% of global turnover.",
159
+ "mandatory": true,
160
+ "deadline_days": 365,
161
+ "estimated_cost_usd": {
162
+ "min": 75000,
163
+ "max": 200000,
164
+ "currency": "EUR",
165
+ "notes": "DORA compliance program + penetration testing + incident response systems + third-party risk management"
166
+ },
167
+ "severity": "high",
168
+ "exemptions": []
169
+ },
170
+ {
171
+ "requirement": "AML/CFT Compliance Under 6AMLD",
172
+ "description": "Must comply with EU's 6th Anti-Money Laundering Directive (6AMLD) and Transfer of Funds Regulation (TFR). Requirements: (1) customer due diligence (CDD) for all token holders, (2) enhanced due diligence (EDD) for high-risk customers (PEPs, high-value transactions >€1000), (3) transaction monitoring and suspicious activity reporting (SAR) to FIU, (4) Travel Rule compliance for transfers >€1000, (5) sanctions screening (EU, OFAC, UN), (6) record retention 5 years. AML officer and independent audit required.",
173
+ "mandatory": true,
174
+ "deadline_days": 120,
175
+ "estimated_cost_usd": {
176
+ "min": 60000,
177
+ "max": 150000,
178
+ "currency": "EUR",
179
+ "notes": "First year: AML/KYC system (€30K-70K) + compliance officer + training + transaction monitoring tools"
180
+ },
181
+ "severity": "critical",
182
+ "exemptions": []
183
+ }
184
+ ],
185
+ "penalties": [
186
+ {
187
+ "violation": "Operating Without ART Issuer Authorization",
188
+ "penalty_type": "Administrative Fines + Criminal Prosecution",
189
+ "amount_usd": {
190
+ "min": 500000,
191
+ "max": 5000000,
192
+ "notes": "Administrative fines up to €5M or 3% of total annual turnover + criminal prosecution in some member states (imprisonment up to 5 years) + disgorgement of profits"
193
+ },
194
+ "additional_consequences": [
195
+ "Immediate cease-and-desist order",
196
+ "Token holder redemption rights at issuance price",
197
+ "Criminal prosecution (varies by member state)",
198
+ "Permanent ban from crypto-asset activities in EU",
199
+ "Asset seizure and freezing orders"
200
+ ]
201
+ },
202
+ {
203
+ "violation": "Inadequate Reserve Management or Commingling",
204
+ "penalty_type": "Administrative Fines + License Revocation",
205
+ "amount_usd": {
206
+ "min": 250000,
207
+ "max": 2500000,
208
+ "notes": "Fines up to €2.5M or 2% of annual turnover + license revocation + mandatory wind-down + civil liability to token holders"
209
+ },
210
+ "additional_consequences": [
211
+ "Authorization revocation and mandatory wind-down",
212
+ "Civil liability for token holder losses",
213
+ "Enhanced supervision and restrictions",
214
+ "NCA-appointed special administrator"
215
+ ]
216
+ },
217
+ {
218
+ "violation": "False or Misleading White Paper Information",
219
+ "penalty_type": "Administrative Fines + Civil Liability",
220
+ "amount_usd": {
221
+ "min": 100000,
222
+ "max": 1000000,
223
+ "notes": "Fines up to €1M or 1% of annual turnover + civil liability to investors who relied on false information + potential criminal fraud charges"
224
+ },
225
+ "additional_consequences": [
226
+ "Suspension of ART offerings",
227
+ "Civil lawsuits from token holders (rescission + damages)",
228
+ "Mandatory white paper corrections and re-publication",
229
+ "Reputational damage and loss of authorization"
230
+ ]
231
+ },
232
+ {
233
+ "violation": "Failure to Comply with AML/CFT Requirements",
234
+ "penalty_type": "Administrative Fines + Criminal Penalties",
235
+ "amount_usd": {
236
+ "min": 500000,
237
+ "max": 5000000,
238
+ "notes": "AML fines up to €5M or 10% of annual turnover (whichever higher) + criminal prosecution for money laundering facilitation + license revocation"
239
+ },
240
+ "additional_consequences": [
241
+ "Criminal prosecution for money laundering (imprisonment up to 10 years)",
242
+ "Immediate license suspension or revocation",
243
+ "Financial intelligence unit (FIU) investigation",
244
+ "Permanent industry ban",
245
+ "Sanctions screening violations may trigger additional EU/UN penalties"
246
+ ]
247
+ }
248
+ ],
249
+ "regulatory_guidance": [
250
+ "MiCA distinguishes ARTs from e-money tokens (EMTs) - real estate tokens typically classified as ARTs",
251
+ "If ART is also deemed financial instrument under MiFID II, additional requirements may apply",
252
+ "Issuers may choose home member state for authorization (jurisdiction shopping permitted)",
253
+ "Once authorized in one EU member state, passporting rights allow operations across entire EU/EEA",
254
+ "Real estate ARTs face challenges with reserve management due to property illiquidity - NCAs may require higher liquid reserves (40-50%)",
255
+ "ESMA developing regulatory technical standards (RTS) for reserve management - expected Q1 2025",
256
+ "Transitional period until December 30, 2024 for existing issuers to achieve compliance"
257
+ ],
258
+ "related_regulations": [
259
+ "eu-mica-casp-requirements-2024",
260
+ "eu-dora-operational-resilience-2025",
261
+ "eu-6amld-aml-2024",
262
+ "eu-tfr-travel-rule-2024"
263
+ ],
264
+ "confidence": 0.92,
265
+ "notes": "MiCA's ART framework presents significant challenges for real estate tokenization due to illiquid nature of property assets versus required liquid reserves and monthly redemption rights. Total first-year compliance costs for real estate ART: €800K-2M. Ongoing annual costs: €300K-600K. Many real estate tokenization projects may instead pursue MiFID II 'financial instrument' classification or limit offerings to qualified investors to reduce compliance burden."
266
+ }
src/data/regulations/schema.json ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "Regulatory Rule Schema",
4
+ "description": "Schema for crypto regulatory rules across jurisdictions",
5
+ "type": "object",
6
+ "required": ["id", "title", "jurisdiction", "agency", "status", "summary"],
7
+ "properties": {
8
+ "id": {
9
+ "type": "string",
10
+ "description": "Unique identifier: {jurisdiction}-{year}-{short-name}",
11
+ "pattern": "^(us|eu|singapore|uk|uae)-[0-9]{4}-[a-z0-9-]+$",
12
+ "examples": ["us-2024-custody", "eu-2024-mica-implementation"]
13
+ },
14
+ "title": {
15
+ "type": "string",
16
+ "description": "Official title of the regulation",
17
+ "examples": ["Crypto Custody Rule", "Markets in Crypto-Assets Regulation"]
18
+ },
19
+ "jurisdiction": {
20
+ "type": "string",
21
+ "enum": ["us", "eu", "singapore", "uk", "uae"],
22
+ "description": "Jurisdiction where regulation applies"
23
+ },
24
+ "agency": {
25
+ "type": "string",
26
+ "description": "Regulatory agency issuing the rule",
27
+ "examples": ["SEC", "MiCA", "MAS", "FCA", "VARA"]
28
+ },
29
+ "status": {
30
+ "type": "string",
31
+ "enum": ["proposed", "effective", "repealed", "under_review"],
32
+ "description": "Current status of the regulation"
33
+ },
34
+ "announced_date": {
35
+ "type": "string",
36
+ "format": "date",
37
+ "description": "Date regulation was announced (ISO 8601)"
38
+ },
39
+ "comment_deadline": {
40
+ "type": "string",
41
+ "format": "date",
42
+ "description": "Deadline for public comments (if applicable)"
43
+ },
44
+ "effective_date": {
45
+ "type": "string",
46
+ "description": "When regulation becomes effective (ISO date or 'Q1 2025')"
47
+ },
48
+ "summary": {
49
+ "type": "string",
50
+ "description": "Brief summary of the regulation (2-3 sentences)"
51
+ },
52
+ "full_text_url": {
53
+ "type": "string",
54
+ "format": "uri",
55
+ "description": "URL to official regulation text"
56
+ },
57
+ "crypto_activities_affected": {
58
+ "type": "array",
59
+ "items": {
60
+ "type": "string",
61
+ "enum": [
62
+ "exchange",
63
+ "custody",
64
+ "staking",
65
+ "lending",
66
+ "borrowing",
67
+ "nft_marketplace",
68
+ "defi_protocol",
69
+ "payment_processing",
70
+ "token_issuance",
71
+ "mining",
72
+ "wallet_services",
73
+ "otc_trading",
74
+ "derivatives",
75
+ "dao"
76
+ ]
77
+ },
78
+ "description": "List of crypto activities this regulation affects"
79
+ },
80
+ "requirements": {
81
+ "type": "array",
82
+ "items": {
83
+ "type": "object",
84
+ "properties": {
85
+ "requirement": {
86
+ "type": "string",
87
+ "description": "Specific requirement text"
88
+ },
89
+ "applies_to": {
90
+ "type": "array",
91
+ "items": { "type": "string" },
92
+ "description": "Which activities this requirement applies to"
93
+ },
94
+ "deadline": {
95
+ "type": "string",
96
+ "description": "Deadline for compliance"
97
+ }
98
+ }
99
+ },
100
+ "description": "Specific compliance requirements"
101
+ },
102
+ "penalties": {
103
+ "type": "object",
104
+ "properties": {
105
+ "fines_min": {
106
+ "type": "number",
107
+ "description": "Minimum fine amount (USD)"
108
+ },
109
+ "fines_max": {
110
+ "type": "number",
111
+ "description": "Maximum fine amount (USD)"
112
+ },
113
+ "other_penalties": {
114
+ "type": "array",
115
+ "items": { "type": "string" },
116
+ "description": "Non-monetary penalties (e.g., 'Business shutdown', 'Criminal charges')"
117
+ }
118
+ },
119
+ "description": "Penalties for non-compliance"
120
+ },
121
+ "exemptions": {
122
+ "type": "array",
123
+ "items": {
124
+ "type": "object",
125
+ "properties": {
126
+ "exemption_type": { "type": "string" },
127
+ "description": { "type": "string" },
128
+ "eligibility_criteria": {
129
+ "type": "array",
130
+ "items": { "type": "string" }
131
+ }
132
+ }
133
+ },
134
+ "description": "Available exemptions or safe harbors"
135
+ },
136
+ "confidence": {
137
+ "type": "number",
138
+ "minimum": 0.0,
139
+ "maximum": 1.0,
140
+ "description": "Confidence score for this regulation data (0.0-1.0)"
141
+ },
142
+ "source": {
143
+ "type": "object",
144
+ "properties": {
145
+ "type": {
146
+ "type": "string",
147
+ "enum": ["official", "news", "analysis", "scraped"],
148
+ "description": "Source type"
149
+ },
150
+ "url": {
151
+ "type": "string",
152
+ "format": "uri"
153
+ },
154
+ "retrieved_date": {
155
+ "type": "string",
156
+ "format": "date-time"
157
+ }
158
+ },
159
+ "description": "Data source information"
160
+ },
161
+ "related_regulations": {
162
+ "type": "array",
163
+ "items": {
164
+ "type": "string",
165
+ "description": "IDs of related regulations"
166
+ },
167
+ "description": "References to related regulatory rules"
168
+ },
169
+ "tags": {
170
+ "type": "array",
171
+ "items": {
172
+ "type": "string"
173
+ },
174
+ "description": "Keywords/tags for categorization",
175
+ "examples": [["kyc", "aml", "reporting"], ["securities", "howey-test"]]
176
+ }
177
+ }
178
+ }
src/data/regulations/singapore/mas-cmp-real-estate-tokens-2024.json ADDED
@@ -0,0 +1,275 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "singapore-mas-cmp-real-estate-tokens-2024",
3
+ "jurisdiction": "singapore",
4
+ "agency": "MAS (Monetary Authority of Singapore)",
5
+ "title": "Capital Markets Products Framework for Real Estate Security Tokens",
6
+ "summary": "MAS regulatory framework for digital tokens representing interests in real estate under Securities and Futures Act (SFA). Covers licensing requirements for offers, custody, and secondary trading of real estate security tokens. Includes guidelines on prospectus requirements, exemptions for sophisticated investors, and DPT service licensing.",
7
+ "status": "effective",
8
+ "announced_date": "2020-01-28",
9
+ "effective_date": "2020-01-28",
10
+ "last_updated": "2024-08-30",
11
+ "source_url": "https://www.mas.gov.sg/regulation/capital-markets/digital-tokens",
12
+ "full_text_url": "https://www.mas.gov.sg/-/media/mas/regulations-and-financial-stability/regulations-guidance-and-licensing/securities-futures-and-fund-management/guidelines/a-guide-to-digital-token-offerings.pdf",
13
+ "crypto_activities_affected": [
14
+ "tokenization",
15
+ "securities-offering",
16
+ "custody",
17
+ "secondary-trading",
18
+ "payment-services"
19
+ ],
20
+ "tags": [
21
+ "capital-markets-products",
22
+ "security-tokens",
23
+ "real-estate",
24
+ "sfa",
25
+ "prospectus",
26
+ "cms-license",
27
+ "dpt-services"
28
+ ],
29
+ "requirements": [
30
+ {
31
+ "requirement": "Capital Markets Product (CMP) Classification and Legal Opinion",
32
+ "description": "Real estate tokens representing ownership, profit-sharing, or derivative interests are regulated as 'capital markets products' (CMP) under SFA. Must obtain legal opinion from Singapore law firm confirming: (1) token is CMP (typically 'units in collective investment scheme' or 'debentures'), (2) appropriate exemptions apply, (3) compliance roadmap. Legal opinion required for MAS submissions. Cost: SGD $30K-60K ($22K-45K USD).",
33
+ "mandatory": true,
34
+ "deadline_days": 0,
35
+ "estimated_cost_usd": {
36
+ "min": 22000,
37
+ "max": 45000,
38
+ "currency": "USD",
39
+ "notes": "Singapore securities law firm legal opinion on CMP classification"
40
+ },
41
+ "severity": "critical",
42
+ "exemptions": ["Tokens that are pure payment tokens or utility tokens (not investment products)"]
43
+ },
44
+ {
45
+ "requirement": "Capital Markets Services (CMS) License for Issuance Activities",
46
+ "description": "Entities making offers of real estate security tokens must hold CMS license for 'dealing in capital markets products' OR rely on prospectus exemptions. CMS license application requires: (1) SGD $250K base capital + SGD $150K liquid assets, (2) fit and proper directors/shareholders, (3) business plan, (4) compliance arrangements, (5) office in Singapore. Application process: 6-9 months. Application fee: SGD $10K. Annual license fee: SGD $8K-15K based on activity.",
47
+ "mandatory": true,
48
+ "deadline_days": 0,
49
+ "estimated_cost_usd": {
50
+ "min": 300000,
51
+ "max": 400000,
52
+ "currency": "USD",
53
+ "notes": "SGD $250K base capital + SGD $150K liquid capital + SGD $10K application fee + legal/consultancy (SGD $50K-100K)"
54
+ },
55
+ "severity": "critical",
56
+ "exemptions": [
57
+ "Private placement to institutional/accredited investors only (up to 50 persons in 12 months)",
58
+ "Offers via licensed intermediary who holds CMS license"
59
+ ]
60
+ },
61
+ {
62
+ "requirement": "Prospectus Registration or Exemption Reliance",
63
+ "description": "Public offers of real estate security tokens require prospectus registered with MAS unless exempt. Prospectus must include: property details and valuations, financial forecasts, risk factors (minimum 20 factors), legal structure, use of proceeds, management bios, audited financials. Registration process: 3-6 months. Most issuers rely on exemptions: (1) offers to institutional/accredited investors only, (2) private placement exemption (up to 50 persons), (3) small offers exemption (up to SGD $5M in 12 months). Exemption reliance requires filing Form 45 with MAS.",
64
+ "mandatory": true,
65
+ "deadline_days": 14,
66
+ "estimated_cost_usd": {
67
+ "min": 8000,
68
+ "max": 80000,
69
+ "currency": "USD",
70
+ "notes": "If full prospectus: SGD $80K-120K. If exemption: Form 45 filing + legal (SGD $10K-15K)"
71
+ },
72
+ "severity": "critical",
73
+ "exemptions": [
74
+ "Offers to institutional investors only",
75
+ "Offers to accredited investors (individual net assets >SGD $2M OR income >SGD $300K)",
76
+ "Private placement (≤50 persons in 12 months)"
77
+ ]
78
+ },
79
+ {
80
+ "requirement": "Digital Payment Token (DPT) Service License (if applicable)",
81
+ "description": "If real estate tokens can be used for payment or exchange (not purely investment), may require DPT service license under Payment Services Act. Activities requiring DPT license: (1) operating exchange for DPT trading, (2) facilitating DPT transfers, (3) providing DPT custody wallet services. License requires: SGD $250K base capital (higher for exchange/custody: up to SGD $1.5M), fit and proper, AML/CFT compliance, technology risk management. Application: 6-12 months. Most real estate tokens qualify for exemption as they are purely securities, not payment instruments.",
82
+ "mandatory": false,
83
+ "deadline_days": 90,
84
+ "estimated_cost_usd": {
85
+ "min": 200000,
86
+ "max": 1200000,
87
+ "currency": "USD",
88
+ "notes": "If DPT license required: SGD $250K-1.5M capital + application and compliance costs (SGD $50K-100K)"
89
+ },
90
+ "severity": "high",
91
+ "exemptions": ["Security tokens used solely for investment (not payment) are exempt from PSA"]
92
+ },
93
+ {
94
+ "requirement": "Technology Risk Management (TRM) Guidelines Compliance",
95
+ "description": "Must comply with MAS Technology Risk Management Guidelines including: (1) cybersecurity controls and testing, (2) system availability targets (>99.5% for critical systems), (3) data security and encryption, (4) incident management and reporting (notify MAS within 1 hour for severe incidents), (5) business continuity plan (RTO <4 hours), (6) change management procedures, (7) third-party vendor risk management. Annual independent audit required. TRM non-compliance can result in license suspension.",
96
+ "mandatory": true,
97
+ "deadline_days": 180,
98
+ "estimated_cost_usd": {
99
+ "min": 40000,
100
+ "max": 100000,
101
+ "currency": "USD",
102
+ "notes": "First year: cybersecurity assessment + penetration testing + incident response + BCP development. Annual: SGD $30K-60K"
103
+ },
104
+ "severity": "high",
105
+ "exemptions": []
106
+ },
107
+ {
108
+ "requirement": "Anti-Money Laundering and Countering Financing of Terrorism (AML/CFT)",
109
+ "description": "Must comply with MAS Notice SFA04-N02 (AML/CFT for Capital Markets) including: (1) customer due diligence (CDD) - verify identity, source of funds, (2) enhanced due diligence (EDD) for high-risk customers (PEPs, countries on FATF list, transactions >SGD $20K), (3) ongoing monitoring and transaction screening, (4) suspicious transaction reporting (STR) to STRO within 15 days, (5) record keeping (6 years), (6) AML/CFT officer appointment, (7) regular staff training (minimum annually). Independent audit every 2 years.",
110
+ "mandatory": true,
111
+ "deadline_days": 90,
112
+ "estimated_cost_usd": {
113
+ "min": 35000,
114
+ "max": 85000,
115
+ "currency": "USD",
116
+ "notes": "First year: AML/CFT system setup (SGD $25K-50K) + compliance officer + training + screening tools. Annual ongoing: SGD $20K-40K"
117
+ },
118
+ "severity": "critical",
119
+ "exemptions": []
120
+ },
121
+ {
122
+ "requirement": "Custody and Safekeeping Arrangements (if providing custody)",
123
+ "description": "If issuer provides custody of tokens (holding private keys on behalf of investors), must either: (1) obtain CMS license for 'providing custodian services for securities', OR (2) appoint licensed custodian. Licensed custody requires: SGD $1M base capital + SGD $500K liquid capital, segregation of client assets, insurance coverage (minimum SGD $1M or 5% of AUM), annual audit, cybersecurity controls. Alternative: use third-party licensed custodian (fees 0.1-0.5% of AUM annually).",
124
+ "mandatory": true,
125
+ "deadline_days": 120,
126
+ "estimated_cost_usd": {
127
+ "min": 50000,
128
+ "max": 1200000,
129
+ "currency": "USD",
130
+ "notes": "If self-custody: SGD $1.5M capital + compliance infrastructure. If third-party: integration + fees (0.1-0.5% AUM)"
131
+ },
132
+ "severity": "high",
133
+ "exemptions": ["If investors hold their own private keys (non-custodial model) - but must provide clear disclosures"]
134
+ },
135
+ {
136
+ "requirement": "Approved Exchange or Recognized Market Operator (if secondary trading)",
137
+ "description": "Secondary trading of real estate security tokens must occur on: (1) Approved Exchange (AE) licensed by MAS, OR (2) Recognized Market Operator (RMO), OR (3) exempt organized market. Operating unlicensed exchange is criminal offense. AE/RMO license requires: SGD $5M base capital, technology infrastructure, market surveillance, clearing arrangements, business rules approved by MAS. Application: 12-24 months. Most issuers restrict secondary trading or partner with licensed exchanges like iSTOX, Fundnel.",
138
+ "mandatory": true,
139
+ "deadline_days": 0,
140
+ "estimated_cost_usd": {
141
+ "min": 30000,
142
+ "max": 5000000,
143
+ "currency": "USD",
144
+ "notes": "If operate own exchange: SGD $5M+ capital + infrastructure. If use third-party: integration fees (SGD $30K-100K) + listing fees"
145
+ },
146
+ "severity": "high",
147
+ "exemptions": ["Transfers restricted to original purchasers/affiliates", "Over-the-counter bilateral transfers (must still comply with securities laws)"]
148
+ },
149
+ {
150
+ "requirement": "Advertising and Marketing Guidelines",
151
+ "description": "All marketing materials must comply with MAS FAA Notice FAA-N03 (Advertising Guidelines). Requirements: (1) fair, balanced, not misleading, (2) risk warnings prominently displayed, (3) past performance disclaimers, (4) avoid unsubstantiated claims or guarantees, (5) specify target investor type (retail/accredited/institutional), (6) include license number and regulatory status. Marketing to retail investors requires additional disclosures and may require prospectus. Social media posts and influencer marketing subject to same rules. Non-compliant ads can result in SGD $50K-250K fines.",
152
+ "mandatory": true,
153
+ "deadline_days": 30,
154
+ "estimated_cost_usd": {
155
+ "min": 15000,
156
+ "max": 40000,
157
+ "currency": "USD",
158
+ "notes": "Legal review of all marketing materials + compliance procedures + staff training"
159
+ },
160
+ "severity": "medium",
161
+ "exemptions": []
162
+ },
163
+ {
164
+ "requirement": "Continuous Disclosure and Ongoing Reporting",
165
+ "description": "Ongoing obligations after offering: (1) material event disclosure within 24 hours (property damage, valuation changes >15%, management changes, breaches), (2) semi-annual unaudited financial statements, (3) annual audited financial statements within 5 months of FY-end, (4) annual property valuations by independent valuer, (5) quarterly updates to token holders on property performance. If token holders exceed 50, must appoint share registrar. Records must be kept for 5 years. Failure to disclose material events can trigger civil liability.",
166
+ "mandatory": true,
167
+ "deadline_days": 0,
168
+ "estimated_cost_usd": {
169
+ "min": 30000,
170
+ "max": 80000,
171
+ "currency": "USD",
172
+ "notes": "Annual ongoing: audit (SGD $20K-50K) + property valuation (SGD $10K-30K) + reporting systems + compliance staff"
173
+ },
174
+ "severity": "medium",
175
+ "exemptions": []
176
+ },
177
+ {
178
+ "requirement": "Product Due Diligence and Suitability Assessment",
179
+ "description": "If offering to non-institutional investors, must conduct: (1) product due diligence to assess risks and suitability, (2) customer knowledge assessment (KYA) to understand investor profile, (3) suitability assessment matching product to customer, (4) enhanced warnings for complex products or retail investors, (5) cooling-off period (7 days for retail investors). Must document all assessments. Mis-selling can result in investor restitution orders + MAS penalties (up to SGD $2M).",
180
+ "mandatory": true,
181
+ "deadline_days": 0,
182
+ "estimated_cost_usd": {
183
+ "min": 20000,
184
+ "max": 50000,
185
+ "currency": "USD",
186
+ "notes": "Suitability assessment system development + compliance procedures + staff training"
187
+ },
188
+ "severity": "high",
189
+ "exemptions": ["Offers to institutional and accredited investors only (exempt from suitability requirements)"]
190
+ }
191
+ ],
192
+ "penalties": [
193
+ {
194
+ "violation": "Unlicensed Capital Markets Services (Unauthorized Dealing)",
195
+ "penalty_type": "Criminal Prosecution + Civil Penalties",
196
+ "amount_usd": {
197
+ "min": 75000,
198
+ "max": 150000,
199
+ "notes": "Criminal fine up to SGD $150K OR imprisonment up to 3 years + civil penalties up to SGD $2M + disgorgement of profits + investor restitution"
200
+ },
201
+ "additional_consequences": [
202
+ "Criminal conviction and imprisonment (up to 3 years)",
203
+ "Civil penalty orders up to SGD $2M",
204
+ "Director disqualification orders (up to 5 years)",
205
+ "Investor rescission rights (full refund + interest)",
206
+ "Permanent ban from financial services industry in Singapore"
207
+ ]
208
+ },
209
+ {
210
+ "violation": "False or Misleading Prospectus/Offering Document",
211
+ "penalty_type": "Criminal Prosecution + Civil Liability",
212
+ "amount_usd": {
213
+ "min": 112500,
214
+ "max": 225000,
215
+ "notes": "Criminal fine up to SGD $150K OR imprisonment up to 2 years + civil compensation to investors + MAS enforcement action"
216
+ },
217
+ "additional_consequences": [
218
+ "Criminal prosecution (up to 2 years imprisonment)",
219
+ "Civil liability to all investors who relied on document",
220
+ "MAS prohibition orders preventing future fundraising",
221
+ "Director personal liability for losses",
222
+ "Reputational damage and business closure"
223
+ ]
224
+ },
225
+ {
226
+ "violation": "AML/CFT Breaches or Inadequate Controls",
227
+ "penalty_type": "Civil Penalties + License Revocation",
228
+ "amount_usd": {
229
+ "min": 75000,
230
+ "max": 750000,
231
+ "notes": "Civil penalties up to SGD $1M + license suspension or revocation + criminal prosecution for willful breaches (up to SGD $500K fine + 10 years imprisonment)"
232
+ },
233
+ "additional_consequences": [
234
+ "License suspension (30-90 days) or revocation",
235
+ "Criminal prosecution for willful AML violations",
236
+ "Enhanced supervision and remediation orders",
237
+ "Mandatory independent compliance audit at issuer's expense",
238
+ "Reputational damage and loss of banking relationships"
239
+ ]
240
+ },
241
+ {
242
+ "violation": "Operating Unlicensed Exchange or Market",
243
+ "penalty_type": "Criminal Prosecution + Shutdown Order",
244
+ "amount_usd": {
245
+ "min": 37500,
246
+ "max": 112500,
247
+ "notes": "Criminal fine up to SGD $150K OR imprisonment up to 2 years + immediate shutdown order + disgorgement of trading fees + investor restitution"
248
+ },
249
+ "additional_consequences": [
250
+ "Criminal conviction and imprisonment (up to 2 years)",
251
+ "Immediate cease-and-desist and platform shutdown",
252
+ "Asset freezing and seizure orders",
253
+ "Disgorgement of all trading fees and profits",
254
+ "Permanent industry ban"
255
+ ]
256
+ }
257
+ ],
258
+ "regulatory_guidance": [
259
+ "MAS 'Guide to Digital Token Offerings' (2020) is primary guidance for token classification",
260
+ "Real estate tokens typically classified as 'units in collective investment scheme' or 'debentures' under SFA",
261
+ "Most issuers rely on private placement exemption (≤50 investors) or accredited investor exemption to avoid prospectus",
262
+ "MAS takes substance-over-form approach - classification based on economic reality, not label",
263
+ "Secondary trading restrictions common - many issuers prohibit transfers or limit to accredited investors",
264
+ "Singapore has several licensed digital securities platforms: iSTOX, Fundnel, ADDX - recommended to partner rather than build own",
265
+ "MAS Project Guardian exploring use cases for tokenized assets including real estate - potential regulatory sandbox participation",
266
+ "Singapore-based issuers targeting global investors must comply with both SFA and foreign securities laws"
267
+ ],
268
+ "related_regulations": [
269
+ "singapore-mas-psa-dpt-2024",
270
+ "singapore-mas-cms-custody-2024",
271
+ "singapore-mas-trm-guidelines-2024"
272
+ ],
273
+ "confidence": 0.94,
274
+ "notes": "Singapore's regulatory framework for real estate security tokens is well-developed and clear. Total first-year costs for compliant real estate token offering: SGD $500K-1.5M (USD $375K-1.1M) depending on licensing path. Private placements to accredited investors offer most cost-effective route (SGD $100K-200K compliance costs). Retail offerings require full prospectus and CMS license (SGD $800K-1.5M). Singapore's Project Guardian and supportive regulatory approach make it attractive jurisdiction for tokenization pilots."
275
+ }
src/data/regulations/uae/vara-sto-real-estate-2024.json ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "uae-vara-sto-real-estate-2024",
3
+ "jurisdiction": "uae",
4
+ "agency": "VARA (Virtual Asset Regulatory Authority)",
5
+ "title": "Security Token Offering (STO) Requirements for Real Estate Tokenization",
6
+ "summary": "VARA regulations for tokenizing real estate assets through security token offerings in Dubai. Covers licensing, capital requirements, property valuation, and investor protection measures for fractionalized real estate ownership.",
7
+ "status": "effective",
8
+ "announced_date": "2023-06-15",
9
+ "effective_date": "2023-10-01",
10
+ "last_updated": "2024-08-20",
11
+ "source_url": "https://www.vara.ae/en/regulations/sto-framework",
12
+ "full_text_url": "https://www.vara.ae/media/regulations/sto-guidance.pdf",
13
+ "crypto_activities_affected": [
14
+ "tokenization",
15
+ "custody",
16
+ "exchange",
17
+ "advisory"
18
+ ],
19
+ "tags": [
20
+ "security-tokens",
21
+ "real-estate",
22
+ "sto",
23
+ "licensing",
24
+ "capital-requirements",
25
+ "property-valuation"
26
+ ],
27
+ "requirements": [
28
+ {
29
+ "requirement": "VARA STO License Application",
30
+ "description": "Entities offering security tokens backed by real estate must obtain a VARA Security Token Offering (STO) License, not the standard Virtual Asset License. Application process takes 12-18 months and requires detailed business plan, technical infrastructure assessment, and compliance framework.",
31
+ "mandatory": true,
32
+ "deadline_days": 0,
33
+ "estimated_cost_usd": {
34
+ "min": 50000,
35
+ "max": 75000,
36
+ "currency": "USD",
37
+ "notes": "License application fee: AED 50,000 + legal preparation AED 100,000-200,000"
38
+ },
39
+ "severity": "critical",
40
+ "exemptions": []
41
+ },
42
+ {
43
+ "requirement": "Minimum Capital Requirement",
44
+ "description": "AED 2,000,000 (approximately USD $545,000) minimum paid-up capital required for STO license holders. Capital must be held in UAE-based bank and cannot be used for operational expenses during first 12 months.",
45
+ "mandatory": true,
46
+ "deadline_days": 0,
47
+ "estimated_cost_usd": {
48
+ "min": 545000,
49
+ "max": 545000,
50
+ "currency": "USD",
51
+ "notes": "AED 2M regulatory capital requirement"
52
+ },
53
+ "severity": "critical",
54
+ "exemptions": []
55
+ },
56
+ {
57
+ "requirement": "Independent Property Valuation",
58
+ "description": "All real estate assets must be independently valued by VARA-approved property valuers within 90 days of tokenization. Revaluation required annually. Valuation report must include: fair market value, rental income analysis, comparable sales, and tokenization suitability assessment.",
59
+ "mandatory": true,
60
+ "deadline_days": 90,
61
+ "estimated_cost_usd": {
62
+ "min": 25000,
63
+ "max": 75000,
64
+ "currency": "USD",
65
+ "notes": "Per property valuation: AED 25K-75K depending on property value. Annual revaluation: AED 15K-40K"
66
+ },
67
+ "severity": "high",
68
+ "exemptions": []
69
+ },
70
+ {
71
+ "requirement": "Token Structure Documentation",
72
+ "description": "Detailed token economics documentation required: rights attached to tokens (voting, dividend, redemption), smart contract audit by VARA-approved auditor, token supply management, and lock-up periods for founding team and insiders.",
73
+ "mandatory": true,
74
+ "deadline_days": 60,
75
+ "estimated_cost_usd": {
76
+ "min": 40000,
77
+ "max": 100000,
78
+ "currency": "USD",
79
+ "notes": "Smart contract audit: AED 50K-150K + legal documentation: AED 75K-200K"
80
+ },
81
+ "severity": "high",
82
+ "exemptions": []
83
+ },
84
+ {
85
+ "requirement": "Investor Protection Framework",
86
+ "description": "Implement investor protection measures including: minimum investment thresholds (AED 50,000 for retail, AED 500,000 for qualified investors), suitability assessments, risk disclosure documents (minimum 25 pages), cooling-off period (14 days), and investor complaint resolution mechanism.",
87
+ "mandatory": true,
88
+ "deadline_days": 90,
89
+ "estimated_cost_usd": {
90
+ "min": 30000,
91
+ "max": 60000,
92
+ "currency": "USD",
93
+ "notes": "Documentation preparation + compliance systems setup"
94
+ },
95
+ "severity": "high",
96
+ "exemptions": []
97
+ },
98
+ {
99
+ "requirement": "AML/CTF Compliance Program",
100
+ "description": "Comprehensive Anti-Money Laundering and Counter-Terrorist Financing program aligned with FATF recommendations. Must include: KYC procedures, transaction monitoring system, suspicious activity reporting (SAR), record retention (7 years), and annual independent audit.",
101
+ "mandatory": true,
102
+ "deadline_days": 120,
103
+ "estimated_cost_usd": {
104
+ "min": 75000,
105
+ "max": 150000,
106
+ "currency": "USD",
107
+ "notes": "First year: system setup + compliance officer + training. Annual ongoing: AED 100K-200K"
108
+ },
109
+ "severity": "critical",
110
+ "exemptions": []
111
+ },
112
+ {
113
+ "requirement": "Custody and Escrow Arrangements",
114
+ "description": "Property title deeds must be held in escrow by UAE-licensed escrow agent. Digital tokens must be custodied by VARA-licensed custodian with insurance coverage (minimum 125% of token value). Multi-signature wallet arrangements required with at least 3-of-5 key holders.",
115
+ "mandatory": true,
116
+ "deadline_days": 60,
117
+ "estimated_cost_usd": {
118
+ "min": 50000,
119
+ "max": 100000,
120
+ "currency": "USD",
121
+ "notes": "Escrow setup + custody fees (0.5-1% annually of AUM) + insurance premiums"
122
+ },
123
+ "severity": "high",
124
+ "exemptions": []
125
+ },
126
+ {
127
+ "requirement": "Marketing and Disclosure Requirements",
128
+ "description": "All marketing materials must be pre-approved by VARA (15-day review period). Required disclosures: property location and details, token economics, historical performance, fees and charges, liquidity risks, property management details, and regulatory status. False or misleading statements prohibited (penalties up to AED 10M).",
129
+ "mandatory": true,
130
+ "deadline_days": 45,
131
+ "estimated_cost_usd": {
132
+ "min": 20000,
133
+ "max": 40000,
134
+ "currency": "USD",
135
+ "notes": "Legal review + marketing compliance + VARA review fees"
136
+ },
137
+ "severity": "medium",
138
+ "exemptions": []
139
+ },
140
+ {
141
+ "requirement": "Ongoing Reporting Obligations",
142
+ "description": "Quarterly financial reports, semi-annual property performance updates, annual audited financial statements, token holder registry updates (within 5 business days of transfers), and material event notifications (within 24 hours). All reports submitted via VARA's digital portal.",
143
+ "mandatory": true,
144
+ "deadline_days": 0,
145
+ "estimated_cost_usd": {
146
+ "min": 40000,
147
+ "max": 80000,
148
+ "currency": "USD",
149
+ "notes": "Annual ongoing: compliance staff + audit fees + reporting systems"
150
+ },
151
+ "severity": "medium",
152
+ "exemptions": []
153
+ },
154
+ {
155
+ "requirement": "Technology and Cybersecurity Standards",
156
+ "description": "ISO 27001 certification required within 12 months of license grant. Systems must have: penetration testing (quarterly), disaster recovery plan (RTO < 4 hours), encryption of all customer data, and incident response procedures. Annual external cybersecurity audit mandatory.",
157
+ "mandatory": true,
158
+ "deadline_days": 365,
159
+ "estimated_cost_usd": {
160
+ "min": 60000,
161
+ "max": 120000,
162
+ "currency": "USD",
163
+ "notes": "ISO certification + penetration testing + ongoing security measures"
164
+ },
165
+ "severity": "high",
166
+ "exemptions": []
167
+ }
168
+ ],
169
+ "penalties": [
170
+ {
171
+ "violation": "Operating without STO License",
172
+ "penalty_type": "Administrative fine + Criminal prosecution",
173
+ "amount_usd": {
174
+ "min": 270000,
175
+ "max": 2700000,
176
+ "notes": "AED 1M-10M fine + possible imprisonment (up to 10 years) + asset seizure"
177
+ },
178
+ "additional_consequences": [
179
+ "Permanent ban from UAE virtual asset sector",
180
+ "Seizure of all tokenized assets",
181
+ "Investor restitution orders",
182
+ "Public disclosure of violation"
183
+ ]
184
+ },
185
+ {
186
+ "violation": "Inadequate AML/CTF controls",
187
+ "penalty_type": "Administrative fine + Remediation order",
188
+ "amount_usd": {
189
+ "min": 135000,
190
+ "max": 1350000,
191
+ "notes": "AED 500K-5M depending on severity + mandatory compliance officer replacement"
192
+ },
193
+ "additional_consequences": [
194
+ "Enhanced supervision (12-24 months)",
195
+ "License suspension (30-90 days)",
196
+ "Mandatory independent compliance review"
197
+ ]
198
+ },
199
+ {
200
+ "violation": "False or misleading marketing",
201
+ "penalty_type": "Administrative fine + Corrective disclosure",
202
+ "amount_usd": {
203
+ "min": 54000,
204
+ "max": 540000,
205
+ "notes": "AED 200K-2M + mandatory corrective advertising at issuer's expense"
206
+ },
207
+ "additional_consequences": [
208
+ "Marketing pre-approval required for 24 months",
209
+ "Investor compensation for losses",
210
+ "Public censure"
211
+ ]
212
+ }
213
+ ],
214
+ "regulatory_guidance": [
215
+ "VARA considers real estate tokenization as securities offering, not virtual asset trading",
216
+ "Tokens representing fractional property ownership are 'Capital Market Products' under UAE law",
217
+ "Cross-border offerings require additional approvals from UAE Securities and Commodities Authority (SCA)",
218
+ "Property ownership transfer must comply with Dubai Land Department (DLD) requirements",
219
+ "Rental income distribution to token holders subject to UAE corporate tax (9% from June 2023)"
220
+ ],
221
+ "related_regulations": [
222
+ "uae-vara-custody-2023",
223
+ "uae-sca-securities-2020",
224
+ "uae-dld-property-tokenization-2024",
225
+ "uae-aml-ctf-2022"
226
+ ],
227
+ "confidence": 0.95,
228
+ "notes": "Real estate tokenization in UAE requires coordination between VARA (digital asset regulation), SCA (securities regulation), and DLD (property registration). Most stringent requirements apply to retail investor offerings. Qualified investor offerings may have reduced requirements."
229
+ }
src/data/regulations/uk/fca-cis-property-tokens-2024.json ADDED
@@ -0,0 +1,282 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "uk-fca-cis-property-tokens-2024",
3
+ "jurisdiction": "uk",
4
+ "agency": "FCA (Financial Conduct Authority)",
5
+ "title": "Collective Investment Schemes Regime for Tokenized Property",
6
+ "summary": "FCA regulatory framework for tokenized real estate under collective investment schemes (CIS) regime and Financial Services and Markets Act 2000 (FSMA). Covers authorization requirements, prospectus rules, restrictions on retail promotion, and ongoing supervision. Applies to tokens representing fractional property ownership offered to UK investors.",
7
+ "status": "effective",
8
+ "announced_date": "2019-01-23",
9
+ "effective_date": "2019-01-23",
10
+ "last_updated": "2024-10-01",
11
+ "source_url": "https://www.fca.org.uk/publication/policy/ps19-22.pdf",
12
+ "full_text_url": "https://www.fca.org.uk/firms/financial-promotions-regime",
13
+ "crypto_activities_affected": [
14
+ "tokenization",
15
+ "collective-investment-schemes",
16
+ "securities-offering",
17
+ "custody",
18
+ "financial-promotions"
19
+ ],
20
+ "tags": [
21
+ "collective-investment-schemes",
22
+ "real-estate",
23
+ "security-tokens",
24
+ "specified-investments",
25
+ "financial-promotions",
26
+ "prospectus",
27
+ "fca-authorization"
28
+ ],
29
+ "requirements": [
30
+ {
31
+ "requirement": "Collective Investment Scheme (CIS) Classification Assessment",
32
+ "description": "Tokenized real estate structures representing pooled investments where investors do not have day-to-day control are likely 'collective investment schemes' (CIS) under FSMA Section 235. CIS characteristics: (1) participants pool contributions, (2) property acquired/managed as whole, (3) participants do not have day-to-day control, (4) profits/income pooled and shared. Must obtain legal opinion from UK solicitor confirming CIS status and regulatory treatment. Cost: £25K-50K. Alternative structures: direct property ownership tokens (not CIS) or unregulated alternative investment fund (but restricted to sophisticated/high net worth investors only).",
33
+ "mandatory": true,
34
+ "deadline_days": 0,
35
+ "estimated_cost_usd": {
36
+ "min": 31000,
37
+ "max": 62000,
38
+ "currency": "USD",
39
+ "notes": "UK law firm legal opinion on CIS classification and FSMA compliance roadmap (£25K-50K at £1.24/GBP)"
40
+ },
41
+ "severity": "critical",
42
+ "exemptions": ["Single property direct ownership tokens where investors have day-to-day control (not CIS)"]
43
+ },
44
+ {
45
+ "requirement": "FCA Authorization as CIS Operator or AIFM",
46
+ "description": "Operating a CIS requires FCA authorization as: (1) Operator of CIS (if UCITS-compliant fund), OR (2) Alternative Investment Fund Manager (AIFM) if Alternative Investment Fund (AIF). Real estate funds typically AIFs. AIFM authorization requires: £125K initial capital (full-scope) or £50K (sub-threshold), fit and proper senior managers, compliance and risk functions, depositaries, valuation procedures, AIFMD compliance. Application process: 6-12 months. Application fee: £25K. Annual fees: £10K-50K based on AUM.",
47
+ "mandatory": true,
48
+ "deadline_days": 0,
49
+ "estimated_cost_usd": {
50
+ "min": 155000,
51
+ "max": 310000,
52
+ "currency": "USD",
53
+ "notes": "£125K regulatory capital + £25K application fee + legal/consultancy (£50K-100K). Sub-threshold AIFM only £50K capital if AUM <£100M."
54
+ },
55
+ "severity": "critical",
56
+ "exemptions": [
57
+ "Sub-threshold AIFM (AUM <€100M for unleveraged funds OR <€500M for leveraged funds) - reduced capital and disclosure requirements",
58
+ "Marketing only to professional/high net worth investors (still need authorization but simplified process)"
59
+ ]
60
+ },
61
+ {
62
+ "requirement": "Prospectus or Exempted Document",
63
+ "description": "Offers of CIS units to UK public require prospectus approved by FCA unless exempt. Prospectus must comply with UK Prospectus Regulation including: property details and independent valuations, financial information (3 years audited financials), risk factors (minimum 20 categories), management team, use of proceeds, token structure and rights, taxation. Prospectus review and approval: 3-6 months. Most tokenized property offerings use exemptions: (1) qualified investors only, (2) offer to <150 persons (excluding qualified investors), (3) minimum investment ≥£100K, (4) total consideration <€8M in 12 months. Exempt offers still require clear and compliant information memorandum.",
64
+ "mandatory": true,
65
+ "deadline_days": 60,
66
+ "estimated_cost_usd": {
67
+ "min": 12400,
68
+ "max": 124000,
69
+ "currency": "USD",
70
+ "notes": "Full prospectus: £80K-100K. Exempt offer information memorandum: £10K-30K"
71
+ },
72
+ "severity": "critical",
73
+ "exemptions": [
74
+ "Offers to professional/qualified investors only",
75
+ "Offers to <150 persons (excluding qualified investors) per 12 months",
76
+ "Minimum investment ≥£100,000 per investor",
77
+ "Total consideration <€8M in 12 months"
78
+ ]
79
+ },
80
+ {
81
+ "requirement": "Financial Promotions Approval and Restrictions",
82
+ "description": "All financial promotions (marketing) for CIS units must be: (1) approved by FCA-authorized firm, OR (2) fall within exemption (e.g., to certified high net worth, sophisticated investors, or investment professionals only). Retail promotion of real estate tokens is PROHIBITED since October 2023 under FCA PS23/6 unless: (a) promoted as security token admitted to trading on recognized investment exchange, OR (b) issuer holds relevant FCA permissions. Promotion to retail without approval: criminal offense (up to 2 years imprisonment + unlimited fines). High net worth certification: individual net assets >£250K (excluding primary residence) OR income >£100K.",
83
+ "mandatory": true,
84
+ "deadline_days": 0,
85
+ "estimated_cost_usd": {
86
+ "min": 18600,
87
+ "max": 62000,
88
+ "currency": "USD",
89
+ "notes": "FCA-authorized firm approval fees (£5K-20K per campaign) + legal review (£10K-30K) + compliance procedures"
90
+ },
91
+ "severity": "critical",
92
+ "exemptions": [
93
+ "Promotions to certified high net worth individuals (net assets >£250K or income >£100K)",
94
+ "Promotions to certified sophisticated investors (self-certified knowledge and experience)",
95
+ "Promotions to investment professionals only"
96
+ ]
97
+ },
98
+ {
99
+ "requirement": "Depositary Appointment (AIFMD Requirement)",
100
+ "description": "AIFs must appoint eligible depositary (credit institution, MiFID investment firm, or authorized AIFM depositary) to: (1) hold scheme assets or verify ownership, (2) monitor cash flows, (3) oversight of valuation, (4) carry out depositary's instructions unless unlawful. Depositary liable for loss of financial instruments held in custody. For tokenized real estate: depositary holds property title deeds and oversees token ledger integrity. Depositary fees: 0.02-0.10% of NAV annually (minimum £25K-50K annually). Depositary agreement and appointment required before fund launch.",
101
+ "mandatory": true,
102
+ "deadline_days": 90,
103
+ "estimated_cost_usd": {
104
+ "min": 31000,
105
+ "max": 124000,
106
+ "currency": "USD",
107
+ "notes": "Annual depositary fees (£25K-100K depending on NAV) + setup and legal agreements (£10K-20K)"
108
+ },
109
+ "severity": "high",
110
+ "exemptions": ["Sub-threshold AIFMs with simplified AIFMD compliance may have reduced depositary requirements"]
111
+ },
112
+ {
113
+ "requirement": "Independent Valuation and Valuer Appointment",
114
+ "description": "Real estate assets must be independently valued: (1) before initial investment or property acquisition, (2) at least annually thereafter, (3) when material event affects valuation (damage, rezoning, market changes >10%). Valuer must be: (a) independent external valuer (e.g., RICS-qualified surveyor), OR (b) internal valuer functionally independent of portfolio management. Valuation must follow RICS Red Book standards. Valuation reports must be provided to depositary and disclosed to investors. Valuation frequency for daily-traded funds: monthly. Cost: £5K-30K per property per valuation.",
115
+ "mandatory": true,
116
+ "deadline_days": 90,
117
+ "estimated_cost_usd": {
118
+ "min": 12400,
119
+ "max": 62000,
120
+ "currency": "USD",
121
+ "notes": "Initial valuations (£10K-30K per property) + annual revaluations (£5K-20K). Multi-property portfolios: £50K-100K annually."
122
+ },
123
+ "severity": "high",
124
+ "exemptions": []
125
+ },
126
+ {
127
+ "requirement": "Senior Managers and Certification Regime (SMCR) Compliance",
128
+ "description": "AIFM must comply with SMCR including: (1) identify and allocate Senior Management Functions (SMFs) - e.g., CEO, compliance oversight, money laundering reporting officer (MLRO), (2) obtain regulatory approval for SMF appointments (3-6 months), (3) certify other staff performing Certification Functions annually, (4) implement Conduct Rules for all staff, (5) maintain records (responsibilities maps, handover procedures). Fit and proper assessments for all SMFs. Penalties for SMCR breaches: up to £1M per individual + prohibition orders.",
129
+ "mandatory": true,
130
+ "deadline_days": 120,
131
+ "estimated_cost_usd": {
132
+ "min": 31000,
133
+ "max": 93000,
134
+ "currency": "USD",
135
+ "notes": "First year: SMCR gap analysis + SMF approval applications (£10K-30K) + governance framework + training (£15K-45K)"
136
+ },
137
+ "severity": "high",
138
+ "exemptions": []
139
+ },
140
+ {
141
+ "requirement": "Anti-Money Laundering (AML) and Counter-Terrorism Financing (CTF)",
142
+ "description": "Must comply with Money Laundering Regulations 2017 (MLR 2017) including: (1) risk-based approach and enterprise-wide risk assessment, (2) customer due diligence (CDD) - verify identity and source of funds, (3) enhanced due diligence (EDD) for PEPs and high-risk customers (investments >£10K), (4) ongoing monitoring, (5) suspicious activity reports (SARs) to National Crime Agency (NCA), (6) sanctions screening (OFSI, EU, UN lists), (7) appoint nominated officer (Money Laundering Reporting Officer), (8) staff training (annual), (9) record retention (5 years). Independent AML audit every 2 years.",
143
+ "mandatory": true,
144
+ "deadline_days": 90,
145
+ "estimated_cost_usd": {
146
+ "min": 37200,
147
+ "max": 93000,
148
+ "currency": "USD",
149
+ "notes": "First year: AML/CTF system (£20K-50K) + MLRO appointment + training + screening tools. Annual ongoing: £15K-40K"
150
+ },
151
+ "severity": "critical",
152
+ "exemptions": []
153
+ },
154
+ {
155
+ "requirement": "Ongoing Reporting and Disclosure Obligations",
156
+ "description": "Authorized AIFMs must provide: (1) annual report to investors within 6 months of year-end (audited accounts, valuation reports, remuneration disclosure), (2) semi-annual reports (if required by fund rules), (3) quarterly investor updates on property performance, (4) AIFMD Annex IV reporting to FCA (annually or quarterly if AUM >€1B), (5) material event notifications within 14 days, (6) respond to investor information requests within reasonable time. Retail funds: additional COLL Sourcebook disclosure requirements. Failure to report triggers supervisory action.",
157
+ "mandatory": true,
158
+ "deadline_days": 0,
159
+ "estimated_cost_usd": {
160
+ "min": 31000,
161
+ "max": 93000,
162
+ "currency": "USD",
163
+ "notes": "Annual ongoing: external audit (£20K-50K) + valuations + investor reporting systems + compliance staff (£25K-75K)"
164
+ },
165
+ "severity": "medium",
166
+ "exemptions": []
167
+ },
168
+ {
169
+ "requirement": "Operational Resilience and Cybersecurity",
170
+ "description": "Must comply with FCA operational resilience requirements (effective March 2022) including: (1) identify important business services (e.g., token custody, investor reporting), (2) set impact tolerances (maximum disruption before unacceptable harm), (3) mapping and testing (scenario testing, including severe but plausible disruptions), (4) communication plans, (5) self-assessment annually. Cybersecurity: implement controls aligned with NIST/ISO 27001, penetration testing (annually), incident response plan, data encryption. Operational incidents must be reported to FCA within defined timeframes (critical: immediately).",
171
+ "mandatory": true,
172
+ "deadline_days": 180,
173
+ "estimated_cost_usd": {
174
+ "min": 37200,
175
+ "max": 124000,
176
+ "currency": "USD",
177
+ "notes": "First year: operational resilience framework (£15K-40K) + cybersecurity assessment + pen testing + BCP (£15K-60K)"
178
+ },
179
+ "severity": "high",
180
+ "exemptions": []
181
+ },
182
+ {
183
+ "requirement": "Secondary Market and Transfer Restrictions",
184
+ "description": "If providing secondary market for CIS units/tokens, must either: (1) list on FCA-recognized investment exchange (e.g., LSE, Aquis), OR (2) operate as Multilateral Trading Facility (MTF) or Organized Trading Facility (OTF) - requires MiFID investment firm authorization (£50K-500K costs). Most tokenized real estate offerings restrict transfers: (a) 12-month lock-up, (b) transfers only with manager approval, (c) minimum holding period. Smart contract must enforce transfer restrictions. Unlisted CIS units difficult to transfer - liquidity risk must be disclosed prominently.",
185
+ "mandatory": false,
186
+ "deadline_days": 0,
187
+ "estimated_cost_usd": {
188
+ "min": 18600,
189
+ "max": 620000,
190
+ "currency": "USD",
191
+ "notes": "If exchange listing: £50K-150K setup + ongoing fees. If operate MTF: £100K-500K+ for MiFID authorization. If restricted transfers: £15K legal documentation."
192
+ },
193
+ "severity": "medium",
194
+ "exemptions": ["Transfers restricted to original investors or with manager approval - no MTF/exchange needed"]
195
+ }
196
+ ],
197
+ "penalties": [
198
+ {
199
+ "violation": "Operating Unauthorized CIS or AIFM",
200
+ "penalty_type": "Criminal Prosecution + Unlimited Fines",
201
+ "amount_usd": {
202
+ "min": 0,
203
+ "max": 99999999,
204
+ "notes": "Criminal offense under FSMA Section 23: unlimited fines + imprisonment up to 2 years + investor restitution orders + disgorgement of all fees"
205
+ },
206
+ "additional_consequences": [
207
+ "Criminal conviction and imprisonment (up to 2 years)",
208
+ "Unlimited fines (no statutory cap)",
209
+ "Investor restitution orders (full refund of investments)",
210
+ "Director disqualification (2-15 years)",
211
+ "Permanent ban from UK financial services",
212
+ "Asset freezing and restraint orders"
213
+ ]
214
+ },
215
+ {
216
+ "violation": "Illegal Financial Promotion to Retail Investors",
217
+ "penalty_type": "Criminal Prosecution + Civil Penalties",
218
+ "amount_usd": {
219
+ "min": 0,
220
+ "max": 99999999,
221
+ "notes": "Criminal offense under FSMA Section 21: unlimited fines + imprisonment up to 2 years + FCA civil penalties up to £1M or higher of disgorgement"
222
+ },
223
+ "additional_consequences": [
224
+ "Criminal prosecution (up to 2 years imprisonment)",
225
+ "Unlimited criminal fines",
226
+ "FCA public censure and financial penalties",
227
+ "Investor compensation orders",
228
+ "Prohibition orders preventing industry participation"
229
+ ]
230
+ },
231
+ {
232
+ "violation": "Misleading Prospectus or Material Omissions",
233
+ "penalty_type": "Criminal Prosecution + Civil Liability",
234
+ "amount_usd": {
235
+ "min": 62000,
236
+ "max": 99999999,
237
+ "notes": "Criminal liability under FSMA: unlimited fines + up to 7 years imprisonment + civil compensation to investors who relied on prospectus"
238
+ },
239
+ "additional_consequences": [
240
+ "Criminal prosecution under FSMA s.90/s.397 (up to 7 years imprisonment)",
241
+ "Civil liability to all investors (rescission + damages)",
242
+ "FCA enforcement action and fines",
243
+ "Director personal liability",
244
+ "Fraud Act 2006 prosecution if dishonest"
245
+ ]
246
+ },
247
+ {
248
+ "violation": "AML/CTF Breaches or Inadequate Controls",
249
+ "penalty_type": "Criminal Prosecution + Civil Penalties",
250
+ "amount_usd": {
251
+ "min": 124000,
252
+ "max": 99999999,
253
+ "notes": "Criminal penalties under MLR 2017: unlimited fines + imprisonment up to 2 years + FCA financial penalties (up to £5M or higher for serious breaches) + authorization withdrawal"
254
+ },
255
+ "additional_consequences": [
256
+ "Criminal prosecution (up to 2 years imprisonment)",
257
+ "FCA authorization revocation or suspension",
258
+ "Serious Fraud Office (SFO) investigation if money laundering facilitated",
259
+ "SMCR prohibition orders for senior managers",
260
+ "Enhanced supervision and mandatory remediation"
261
+ ]
262
+ }
263
+ ],
264
+ "regulatory_guidance": [
265
+ "FCA PS19/22 (2019) confirms cryptoassets representing security tokens are regulated under FSMA",
266
+ "Most tokenized real estate structures are CIS under Section 235 FSMA - requires authorization",
267
+ "FCA PS23/6 (October 2023) banned retail promotion of most cryptoassets including real estate tokens - high net worth/sophisticated investors only",
268
+ "Real estate tokens typically classified as 'units in collective investment scheme' or 'alternative investment fund'",
269
+ "FCA Perimeter Guidance PERG 9 provides guidance on CIS classification",
270
+ "Direct property ownership tokens (where investor has day-to-day control) may escape CIS classification but rare in practice",
271
+ "UK regulatory approach conservative compared to Singapore/Switzerland - retail access highly restricted",
272
+ "Brexit impact: UK Prospectus Regulation post-Brexit allows offers <£8M without prospectus (previously €8M)"
273
+ ],
274
+ "related_regulations": [
275
+ "uk-fca-cryptoasset-promotion-ban-2023",
276
+ "uk-fca-smcr-2024",
277
+ "uk-mlr-aml-2024",
278
+ "uk-fca-operational-resilience-2022"
279
+ ],
280
+ "confidence": 0.93,
281
+ "notes": "UK's regulatory framework for tokenized real estate is stringent and protective of retail investors. Post-October 2023, retail promotion of real estate tokens effectively banned unless listed on recognized exchange. Total first-year compliance costs for UK real estate token offering: £500K-1.2M (USD $620K-1.5M). Ongoing annual costs: £150K-400K. Most cost-effective approach: market only to high net worth/sophisticated investors (reduces prospectus and authorization complexity). Full retail authorization path extremely expensive and time-consuming (12-24 months)."
282
+ }