cnhkmcp 1.4.0__py3-none-any.whl → 1.4.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cnhkmcp/__init__.py +1 -1
- cnhkmcp/untracked/platform_functions.py +140 -77
- cnhkmcp/untracked//321/207/320/264/342/225/221/321/204/342/225/233/320/233/321/205/342/225/226/320/265/321/204/342/225/234/320/254/321/206/342/225/241/320/221_daily_report_workflow.md +2 -2
- {cnhkmcp-1.4.0.dist-info → cnhkmcp-1.4.2.dist-info}/METADATA +1 -1
- {cnhkmcp-1.4.0.dist-info → cnhkmcp-1.4.2.dist-info}/RECORD +9 -9
- {cnhkmcp-1.4.0.dist-info → cnhkmcp-1.4.2.dist-info}/WHEEL +0 -0
- {cnhkmcp-1.4.0.dist-info → cnhkmcp-1.4.2.dist-info}/entry_points.txt +0 -0
- {cnhkmcp-1.4.0.dist-info → cnhkmcp-1.4.2.dist-info}/licenses/LICENSE +0 -0
- {cnhkmcp-1.4.0.dist-info → cnhkmcp-1.4.2.dist-info}/top_level.txt +0 -0
cnhkmcp/__init__.py
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
2
3
|
"""
|
|
3
4
|
WorldQuant BRAIN MCP Server - Python Version
|
|
4
5
|
A comprehensive Model Context Protocol (MCP) server for WorldQuant BRAIN platform integration.
|
|
5
6
|
"""
|
|
6
7
|
|
|
8
|
+
# Ensure proper encoding handling for Windows
|
|
9
|
+
import sys
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
# Note: We'll handle encoding issues in individual functions rather than
|
|
13
|
+
# overriding system streams to avoid conflicts with MCP server
|
|
14
|
+
|
|
7
15
|
import json
|
|
8
16
|
import time
|
|
9
17
|
import asyncio
|
|
@@ -11,8 +19,6 @@ import logging
|
|
|
11
19
|
from typing import Dict, List, Optional, Any, Union
|
|
12
20
|
from dataclasses import dataclass, asdict
|
|
13
21
|
from datetime import datetime, timedelta
|
|
14
|
-
import os
|
|
15
|
-
import sys
|
|
16
22
|
import math
|
|
17
23
|
from time import sleep
|
|
18
24
|
|
|
@@ -82,7 +88,20 @@ class BrainApiClient:
|
|
|
82
88
|
|
|
83
89
|
def log(self, message: str, level: str = "INFO"):
|
|
84
90
|
"""Log messages to stderr to avoid MCP protocol interference."""
|
|
85
|
-
|
|
91
|
+
try:
|
|
92
|
+
# Try to print with original message first
|
|
93
|
+
print(f"[{level}] {message}", file=sys.stderr)
|
|
94
|
+
except UnicodeEncodeError:
|
|
95
|
+
# Fallback: remove problematic characters and try again
|
|
96
|
+
try:
|
|
97
|
+
safe_message = message.encode('ascii', 'ignore').decode('ascii')
|
|
98
|
+
print(f"[{level}] {safe_message}", file=sys.stderr)
|
|
99
|
+
except Exception:
|
|
100
|
+
# Final fallback: just print the level and a safe message
|
|
101
|
+
print(f"[{level}] Log message", file=sys.stderr)
|
|
102
|
+
except Exception:
|
|
103
|
+
# Final fallback: just print the level and a safe message
|
|
104
|
+
print(f"[{level}] Log message", file=sys.stderr)
|
|
86
105
|
|
|
87
106
|
async def authenticate(self, email: str, password: str) -> Dict[str, Any]:
|
|
88
107
|
"""Authenticate with WorldQuant BRAIN platform with biometric support."""
|
|
@@ -716,68 +735,75 @@ class BrainApiClient:
|
|
|
716
735
|
|
|
717
736
|
from typing import Tuple
|
|
718
737
|
def process_description(desc: str, message_id: str) -> Tuple[str, List[str]]:
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
save_dir.mkdir(parents=True, exist_ok=True)
|
|
739
|
-
except Exception as e:
|
|
740
|
-
self.log(f"Could not create image save directory: {e}", "WARNING")
|
|
741
|
-
|
|
742
|
-
new_desc = desc
|
|
743
|
-
for idx, match in enumerate(matches, start=1):
|
|
744
|
-
data_uri = match.group(1) # data:image/...;base64,XXXX
|
|
745
|
-
if not data_uri.lower().startswith("data:image"):
|
|
746
|
-
continue
|
|
747
|
-
# Split header and base64 payload
|
|
748
|
-
if "," not in data_uri:
|
|
749
|
-
continue
|
|
750
|
-
header, b64_data = data_uri.split(",", 1)
|
|
751
|
-
mime_part = header.split(";")[0] # data:image/png
|
|
752
|
-
ext = "png"
|
|
753
|
-
if "/" in mime_part:
|
|
754
|
-
ext = mime_part.split("/")[1]
|
|
755
|
-
safe_ext = (ext or "img").split("?")[0]
|
|
756
|
-
placeholder_text = "[Embedded image]"
|
|
757
|
-
if image_handling == "ignore":
|
|
758
|
-
replacement = f"[Image removed: {safe_ext}]"
|
|
759
|
-
elif image_handling == "placeholder":
|
|
760
|
-
# Try decode & save
|
|
761
|
-
file_name = f"{message_id}_{idx}.{safe_ext}"
|
|
762
|
-
file_path = save_dir / file_name
|
|
738
|
+
try:
|
|
739
|
+
if not desc or image_handling == "keep":
|
|
740
|
+
return desc, []
|
|
741
|
+
attachments: List[str] = []
|
|
742
|
+
# Regex to capture full <img ...> tag with data URI
|
|
743
|
+
img_tag_pattern = re.compile(r"<img[^>]+src=\"(data:image/[^\"]+)\"[^>]*>", re.IGNORECASE)
|
|
744
|
+
# Iterate over unique matches to avoid double work
|
|
745
|
+
matches = list(img_tag_pattern.finditer(desc))
|
|
746
|
+
if not matches:
|
|
747
|
+
# Additional heuristic: very long base64-looking token inside quotes followed by </img>
|
|
748
|
+
# (legacy format noted by user sample). Replace with placeholder.
|
|
749
|
+
heuristic_pattern = re.compile(r"([A-Za-z0-9+/]{500,}={0,2})\"\s*</img>")
|
|
750
|
+
if image_handling != "keep" and heuristic_pattern.search(desc):
|
|
751
|
+
placeholder = "[Embedded image removed - large base64 sequence truncated]"
|
|
752
|
+
return heuristic_pattern.sub(placeholder + "</img>", desc), []
|
|
753
|
+
return desc, []
|
|
754
|
+
|
|
755
|
+
# Ensure save directory exists only if we will store something
|
|
756
|
+
if image_handling == "placeholder" and not save_dir.exists():
|
|
763
757
|
try:
|
|
764
|
-
|
|
765
|
-
if len(b64_data) > 7_000_000:
|
|
766
|
-
raise ValueError("Image too large to decode safely")
|
|
767
|
-
with open(file_path, "wb") as f:
|
|
768
|
-
f.write(base64.b64decode(b64_data))
|
|
769
|
-
attachments.append(str(file_path))
|
|
770
|
-
replacement = f"[Image extracted -> {file_path}]"
|
|
758
|
+
save_dir.mkdir(parents=True, exist_ok=True)
|
|
771
759
|
except Exception as e:
|
|
772
|
-
self.log(f"
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
760
|
+
self.log(f"Could not create image save directory: {e}", "WARNING")
|
|
761
|
+
|
|
762
|
+
new_desc = desc
|
|
763
|
+
for idx, match in enumerate(matches, start=1):
|
|
764
|
+
data_uri = match.group(1) # data:image/...;base64,XXXX
|
|
765
|
+
if not data_uri.lower().startswith("data:image"):
|
|
766
|
+
continue
|
|
767
|
+
# Split header and base64 payload
|
|
768
|
+
if "," not in data_uri:
|
|
769
|
+
continue
|
|
770
|
+
header, b64_data = data_uri.split(",", 1)
|
|
771
|
+
mime_part = header.split(";")[0] # data:image/png
|
|
772
|
+
ext = "png"
|
|
773
|
+
if "/" in mime_part:
|
|
774
|
+
ext = mime_part.split("/")[1]
|
|
775
|
+
safe_ext = (ext or "img").split("?")[0]
|
|
776
|
+
placeholder_text = "[Embedded image]"
|
|
777
|
+
if image_handling == "ignore":
|
|
778
|
+
replacement = f"[Image removed: {safe_ext}]"
|
|
779
|
+
elif image_handling == "placeholder":
|
|
780
|
+
# Try decode & save
|
|
781
|
+
file_name = f"{message_id}_{idx}.{safe_ext}"
|
|
782
|
+
file_path = save_dir / file_name
|
|
783
|
+
try:
|
|
784
|
+
# Guard extremely large strings (>5MB ~ 6.7M base64 chars) to avoid memory blow
|
|
785
|
+
if len(b64_data) > 7_000_000:
|
|
786
|
+
raise ValueError("Image too large to decode safely")
|
|
787
|
+
with open(file_path, "wb") as f:
|
|
788
|
+
f.write(base64.b64decode(b64_data))
|
|
789
|
+
attachments.append(str(file_path))
|
|
790
|
+
replacement = f"[Image extracted -> {file_path}]"
|
|
791
|
+
except Exception as e:
|
|
792
|
+
self.log(f"Failed to decode embedded image in message {message_id}: {e}", "WARNING")
|
|
793
|
+
replacement = "[Image extraction failed - content omitted]"
|
|
794
|
+
else: # keep
|
|
795
|
+
replacement = placeholder_text # shouldn't be used since early return, but safe
|
|
796
|
+
# Replace only the matched tag (not global) – use re.sub with count=1 on substring slice
|
|
797
|
+
# Safer to operate on new_desc using the exact matched string
|
|
798
|
+
original_tag = match.group(0)
|
|
799
|
+
new_desc = new_desc.replace(original_tag, replacement, 1)
|
|
800
|
+
return new_desc, attachments
|
|
801
|
+
except UnicodeEncodeError as ue:
|
|
802
|
+
self.log(f"Unicode encoding error in process_description: {ue}", "WARNING")
|
|
803
|
+
return desc, []
|
|
804
|
+
except Exception as e:
|
|
805
|
+
self.log(f"Error in process_description: {e}", "WARNING")
|
|
806
|
+
return desc, []
|
|
781
807
|
|
|
782
808
|
try:
|
|
783
809
|
params = {}
|
|
@@ -803,11 +829,18 @@ class BrainApiClient:
|
|
|
803
829
|
else:
|
|
804
830
|
# If changed but no attachments (ignore mode) mark sanitized
|
|
805
831
|
msg['sanitized'] = True
|
|
832
|
+
except UnicodeEncodeError as ue:
|
|
833
|
+
self.log(f"Unicode encoding error sanitizing message {msg.get('id')}: {ue}", "WARNING")
|
|
834
|
+
# Keep original description if encoding fails
|
|
835
|
+
continue
|
|
806
836
|
except Exception as inner_e:
|
|
807
837
|
self.log(f"Failed to sanitize message {msg.get('id')}: {inner_e}", "WARNING")
|
|
808
838
|
data['results'] = results
|
|
809
839
|
data['image_handling'] = image_handling
|
|
810
840
|
return data
|
|
841
|
+
except UnicodeEncodeError as ue:
|
|
842
|
+
self.log(f"Failed to get messages due to encoding error: {str(ue)}", "ERROR")
|
|
843
|
+
raise
|
|
811
844
|
except Exception as e:
|
|
812
845
|
self.log(f"Failed to get messages: {str(e)}", "ERROR")
|
|
813
846
|
raise
|
|
@@ -1194,12 +1227,13 @@ class BrainApiClient:
|
|
|
1194
1227
|
self.log(f"Failed to get pyramid multipliers: {str(e)}", "ERROR")
|
|
1195
1228
|
raise
|
|
1196
1229
|
|
|
1197
|
-
async def
|
|
1230
|
+
async def value_factor_trendScore(self, start_date: str, end_date: str) -> Dict[str, Any]:
|
|
1198
1231
|
"""Compute diversity score for regular alphas in a date range.
|
|
1199
1232
|
|
|
1200
1233
|
Description:
|
|
1201
|
-
This diversity
|
|
1202
|
-
|
|
1234
|
+
This function calculate the diversity of the users' submission, by checking the diversity, we can have a good understanding on the valuefactor's trend.
|
|
1235
|
+
value factor of a user is defiend by This diversity score, which measures three key aspects of work output: the proportion of works
|
|
1236
|
+
with the "Atom" tag (S_A), atom proportion, the breadth of pyramids covered (S_P), and how evenly works
|
|
1203
1237
|
are distributed across those pyramids (S_H). Calculated as their product, it rewards
|
|
1204
1238
|
strong performance across all three dimensions—encouraging more Atom-tagged works,
|
|
1205
1239
|
wider pyramid coverage, and balanced distribution—with weaknesses in any area lowering
|
|
@@ -1660,10 +1694,10 @@ async def authenticate(email: Optional[str] = "", password: Optional[str] = "")
|
|
|
1660
1694
|
|
|
1661
1695
|
|
|
1662
1696
|
@mcp.tool()
|
|
1663
|
-
async def
|
|
1697
|
+
async def value_factor_trendScore(start_date: str, end_date: str) -> Dict[str, Any]:
|
|
1664
1698
|
"""Compute and return the diversity score for REGULAR alphas in a submission-date window.
|
|
1665
|
-
|
|
1666
|
-
This MCP tool wraps BrainApiClient.
|
|
1699
|
+
This function calculate the diversity of the users' submission, by checking the diversity, we can have a good understanding on the valuefactor's trend.
|
|
1700
|
+
This MCP tool wraps BrainApiClient.value_factor_trendScore and always uses submission dates (OS).
|
|
1667
1701
|
|
|
1668
1702
|
Inputs:
|
|
1669
1703
|
- start_date: ISO UTC start datetime (e.g. '2025-08-14T00:00:00Z')
|
|
@@ -1673,7 +1707,7 @@ async def get_diversity_score(start_date: str, end_date: str) -> Dict[str, Any]:
|
|
|
1673
1707
|
Returns: compact JSON with diversity_score, N, A, P, P_max, S_A, S_P, S_H, per_pyramid_counts
|
|
1674
1708
|
"""
|
|
1675
1709
|
try:
|
|
1676
|
-
return await brain_client.
|
|
1710
|
+
return await brain_client.value_factor_trendScore(start_date=start_date, end_date=end_date)
|
|
1677
1711
|
except Exception as e:
|
|
1678
1712
|
return {"error": str(e)}
|
|
1679
1713
|
|
|
@@ -2109,9 +2143,9 @@ async def get_documentations() -> Dict[str, Any]:
|
|
|
2109
2143
|
# get_messages_summary MCP tool removed as requested
|
|
2110
2144
|
|
|
2111
2145
|
@mcp.tool()
|
|
2112
|
-
async def get_messages(limit: Optional[int] =
|
|
2146
|
+
async def get_messages(limit: Optional[int] = 0, offset: int = 0) -> Dict[str, Any]:
|
|
2113
2147
|
"""
|
|
2114
|
-
|
|
2148
|
+
Get messages for the current user with optional pagination.
|
|
2115
2149
|
|
|
2116
2150
|
Args:
|
|
2117
2151
|
limit: Maximum number of messages to return (e.g., 10 for top 10 messages)
|
|
@@ -2122,6 +2156,7 @@ async def get_messages(limit: Optional[int] = None, offset: int = 0) -> Dict[str
|
|
|
2122
2156
|
Returns:
|
|
2123
2157
|
Messages for the current user, optionally limited by count
|
|
2124
2158
|
"""
|
|
2159
|
+
# Wrap the entire function in a try-catch to handle any encoding issues
|
|
2125
2160
|
try:
|
|
2126
2161
|
# Enhanced parameter validation and conversion
|
|
2127
2162
|
validated_limit = None
|
|
@@ -2161,8 +2196,11 @@ async def get_messages(limit: Optional[int] = None, offset: int = 0) -> Dict[str
|
|
|
2161
2196
|
if validated_offset < 0:
|
|
2162
2197
|
return {"error": f"Offset must be non-negative, got: {offset}"}
|
|
2163
2198
|
|
|
2164
|
-
# Log the validated parameters for debugging
|
|
2165
|
-
|
|
2199
|
+
# Log the validated parameters for debugging (without emojis to avoid encoding issues)
|
|
2200
|
+
try:
|
|
2201
|
+
print(f"get_messages called with validated parameters: limit={validated_limit}, offset={validated_offset}")
|
|
2202
|
+
except Exception:
|
|
2203
|
+
print(f"get_messages called with parameters: limit={validated_limit}, offset={validated_offset}")
|
|
2166
2204
|
|
|
2167
2205
|
# Call the brain client with validated parameters
|
|
2168
2206
|
result = await brain_client.get_messages(validated_limit, validated_offset)
|
|
@@ -2182,11 +2220,32 @@ async def get_messages(limit: Optional[int] = None, offset: int = 0) -> Dict[str
|
|
|
2182
2220
|
|
|
2183
2221
|
return result
|
|
2184
2222
|
|
|
2223
|
+
except UnicodeEncodeError as ue:
|
|
2224
|
+
# Handle encoding errors specifically
|
|
2225
|
+
error_msg = f"get_messages failed due to encoding error: {str(ue)}"
|
|
2226
|
+
try:
|
|
2227
|
+
print(f"ENCODING ERROR: {error_msg}")
|
|
2228
|
+
except Exception:
|
|
2229
|
+
print(f"get_messages encoding error: {str(ue)}")
|
|
2230
|
+
return {
|
|
2231
|
+
"error": error_msg,
|
|
2232
|
+
"error_type": "UnicodeEncodeError",
|
|
2233
|
+
"original_params": {
|
|
2234
|
+
"limit": limit,
|
|
2235
|
+
"offset": offset,
|
|
2236
|
+
"limit_type": str(type(limit)),
|
|
2237
|
+
"offset_type": str(type(offset))
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2185
2240
|
except Exception as e:
|
|
2186
2241
|
error_msg = f"get_messages failed: {str(e)}"
|
|
2187
|
-
|
|
2242
|
+
try:
|
|
2243
|
+
print(f"ERROR: {error_msg}")
|
|
2244
|
+
except Exception:
|
|
2245
|
+
print(f"get_messages failed: {str(e)}")
|
|
2188
2246
|
return {
|
|
2189
2247
|
"error": error_msg,
|
|
2248
|
+
"error_type": type(e).__name__,
|
|
2190
2249
|
"original_params": {
|
|
2191
2250
|
"limit": limit,
|
|
2192
2251
|
"offset": offset,
|
|
@@ -2779,6 +2838,10 @@ async def get_SimError_detail(locations: Sequence[str]) -> dict:
|
|
|
2779
2838
|
return {"results": results}
|
|
2780
2839
|
|
|
2781
2840
|
if __name__ == "__main__":
|
|
2782
|
-
|
|
2783
|
-
|
|
2841
|
+
try:
|
|
2842
|
+
print("WorldQuant BRAIN MCP Server Starting...", file=sys.stderr)
|
|
2843
|
+
mcp.run()
|
|
2844
|
+
except Exception as e:
|
|
2845
|
+
print(f"Failed to start MCP server: {e}", file=sys.stderr)
|
|
2846
|
+
sys.exit(1)
|
|
2784
2847
|
|
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
2. **社区动态**:从消息中提取社区相关信息,如研究论文或热门话题。
|
|
35
35
|
3. **排行榜变化**:记录用户位置变化。
|
|
36
36
|
- 使用工具:`mcp_brain-api_get_leaderboard`(设置 `user_id` 为用户 ID,如 "CQ89422")。
|
|
37
|
-
4.
|
|
37
|
+
4. **多样性分数**:收集用户最近一个季度的多样性分数,获知其value factor趋势,该分数捕捉用户提交Alpha的多样性,来判断其value factor的变化趋势,在0-1之间,越高越好,据此提出具体建议。
|
|
38
38
|
- **使用的 MCP 工具**:
|
|
39
39
|
- `mcp_brain-api_get_messages`:获取平台公告和社区动态。
|
|
40
40
|
- `mcp_brain-api_get_leaderboard`:获取用户排行榜统计。
|
|
41
|
-
- `mcp_brain-
|
|
41
|
+
- `mcp_brain-api_value_factor_trendScore`:用户value factor趋势,又名多样性分数。
|
|
42
42
|
|
|
43
43
|
### 3. 比赛参与与进度
|
|
44
44
|
- **步骤**:
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
cnhkmcp/__init__.py,sha256=
|
|
1
|
+
cnhkmcp/__init__.py,sha256=wqKNYTIOL4EgYpqCzY0MEiTP74yW4ZY6d01KFoo3GU0,2758
|
|
2
2
|
cnhkmcp/untracked/arXiv_API_Tool_Manual.md,sha256=I3hvI5mpmIjBuWptufoVSWFnuhyUc67oCGHEuR0p_xs,13552
|
|
3
3
|
cnhkmcp/untracked/arxiv_api.py,sha256=-E-Ub9K-DXAYaCjrbobyfQ9H97gaZBc7pL6xPEyVHec,9020
|
|
4
4
|
cnhkmcp/untracked/forum_functions.py,sha256=QW-CplAsqDkw-Wcwq-1tuZBq48dEO-vXZ8xw7X65EuE,42303
|
|
5
|
-
cnhkmcp/untracked/platform_functions.py,sha256=
|
|
5
|
+
cnhkmcp/untracked/platform_functions.py,sha256=lf3i7q7zZarIX4fUgutm8TmU82QKtKU1JxPvUElIj_o,119842
|
|
6
6
|
cnhkmcp/untracked/sample_mcp_config.json,sha256=QSFvZ086bxUQsvmLjcE6pL9ObzKn4FGnt9npWPo7Eps,1044
|
|
7
7
|
cnhkmcp/untracked/user_config.json,sha256=_INn1X1qIsITrmEno-BRlQOAGm9wnNCw-6B333DEvnk,695
|
|
8
8
|
cnhkmcp/untracked/示例参考文档_BRAIN_Alpha_Test_Requirements_and_Tips.md,sha256=W4dtQrqoTN72UyvIsvkGRF0HFOJLHSDeeSlbR3gqQg0,17133
|
|
@@ -10,7 +10,7 @@ cnhkmcp/untracked/示例工作流_Alpha_explaination_workflow.md,sha256=QukeT9gI
|
|
|
10
10
|
cnhkmcp/untracked/示例工作流_BRAIN_6_Tips_Datafield_Exploration_Guide.md,sha256=YyMX1MIsDTJTduxulY-fYipNHvRihshQy9Q2j6Zxg2Q,9123
|
|
11
11
|
cnhkmcp/untracked/示例工作流_BRAIN_Alpha_Improvement_Workflow.md,sha256=XlWYREd_qXe1skdXIhkiGY05oDr_6KiBs1WkerY4S8U,5092
|
|
12
12
|
cnhkmcp/untracked/示例工作流_Dataset_Exploration_Expert_Manual.md,sha256=-C4fWdaBe9UzA5BDZz0Do2z8RaPWLslb6D0nTz6fqk4,24403
|
|
13
|
-
cnhkmcp/untracked/示例工作流_daily_report_workflow.md,sha256=
|
|
13
|
+
cnhkmcp/untracked/示例工作流_daily_report_workflow.md,sha256=6aNmPqWRn09XdQMRxoVTka9IYVOUv5LcWparHu16EfQ,9460
|
|
14
14
|
cnhkmcp/untracked/APP/.gitignore,sha256=oPCoVTNo82bhkN0c671LdjCpOTVpVhZI5NR75ztcg48,317
|
|
15
15
|
cnhkmcp/untracked/APP/MODULAR_STRUCTURE.md,sha256=Ji4VeRZjeMWRX6cvEHxyR_gmoorIEEdqwsXTCVIr5_0,4331
|
|
16
16
|
cnhkmcp/untracked/APP/README.md,sha256=vb7hmQX0sH5aFNBmDCN5szMSDHm1_h2VKY4UKCt0aMk,11676
|
|
@@ -60,9 +60,9 @@ cnhkmcp/untracked/APP/templates/inspiration_house.html,sha256=CZMHj_ild-mUDRvNc5
|
|
|
60
60
|
cnhkmcp/untracked/APP/templates/paper_analysis.html,sha256=7HgltBpZtmZCYCF7JU4wU9F5NMxUKyCXg7bCYCRfQDs,4139
|
|
61
61
|
cnhkmcp/untracked/APP/templates/simulator.html,sha256=Y3-w-mZyvRiOdBiFe705CqBPAUI07KNY2WJ5meNzHM8,12508
|
|
62
62
|
cnhkmcp/untracked/__pycache__/forum_functions.cpython-313.pyc,sha256=agARlP2x36scUU0TIYnrHB3EpEx4OOuHrB42_B1XR3k,45049
|
|
63
|
-
cnhkmcp-1.4.
|
|
64
|
-
cnhkmcp-1.4.
|
|
65
|
-
cnhkmcp-1.4.
|
|
66
|
-
cnhkmcp-1.4.
|
|
67
|
-
cnhkmcp-1.4.
|
|
68
|
-
cnhkmcp-1.4.
|
|
63
|
+
cnhkmcp-1.4.2.dist-info/licenses/LICENSE,sha256=QLxO2eNMnJQEdI_R1UV2AOD-IvuA8zVrkHWA4D9gtoc,1081
|
|
64
|
+
cnhkmcp-1.4.2.dist-info/METADATA,sha256=QB_7K08v3JYQQVvg57c85NoiZrnD9A-Qv_k9Fx1gCm8,5171
|
|
65
|
+
cnhkmcp-1.4.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
66
|
+
cnhkmcp-1.4.2.dist-info/entry_points.txt,sha256=lTQieVyIvjhSMK4fT-XwnccY-JBC1H4vVQ3V9dDM-Pc,70
|
|
67
|
+
cnhkmcp-1.4.2.dist-info/top_level.txt,sha256=x--ibUcSgOS9Z_RWK2Qc-vfs7DaXQN-WMaaxEETJ1Bw,8
|
|
68
|
+
cnhkmcp-1.4.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|