dewmisam commited on
Commit
1067825
·
verified ·
1 Parent(s): 88876aa

Upload 19 files

Browse files
Files changed (19) hide show
  1. .gitignore +9 -0
  2. aiService.py +31 -0
  3. clusterService.py +114 -0
  4. clustering.py +23 -0
  5. correlation.py +24 -0
  6. correlationService.py +136 -0
  7. explain.py +26 -0
  8. explainService.py +65 -0
  9. main.py +26 -0
  10. package-lock.json +1293 -0
  11. package.json +6 -0
  12. plotService.py +45 -0
  13. profile.py +19 -0
  14. profileService.py +37 -0
  15. promptService.py +111 -0
  16. reportService.py +85 -0
  17. requirements.txt +10 -0
  18. state.py +1 -0
  19. upload.py +53 -0
.gitignore ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ venv/
2
+ .env
3
+ __pycache__/
4
+ *.pyc
5
+ .DS_Store
6
+
7
+ node_modules/
8
+ .vite/
9
+ dist/
aiService.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import os
3
+ from dotenv import load_dotenv
4
+
5
+ load_dotenv()
6
+
7
+
8
+ def call_ai(prompt):
9
+
10
+ api_key = os.getenv("OPENROUTER_API_KEY")
11
+
12
+ response = requests.post(
13
+ url="https://openrouter.ai/api/v1/chat/completions",
14
+ headers={
15
+ "Authorization": f"Bearer {api_key}",
16
+ "Content-Type": "application/json",
17
+ },
18
+ json={
19
+ "model": "openai/gpt-4o-mini-2024-07-18",
20
+ "messages": [
21
+ {"role": "user", "content": prompt}
22
+ ]
23
+ }
24
+ )
25
+
26
+ result = response.json()
27
+
28
+ if "choices" in result:
29
+ return result["choices"][0]["message"]["content"]
30
+
31
+ return "AI unavailable"
clusterService.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ from sklearn.preprocessing import StandardScaler
3
+ from sklearn.cluster import KMeans
4
+ from sklearn.metrics import silhouette_score
5
+
6
+
7
+ def get_clusters(df):
8
+
9
+ df_processed = df.copy()
10
+
11
+ # --- Step 1: Drop ID columns ---
12
+ id_columns = [col for col in df_processed.columns if col.lower() == "id"]
13
+ df_processed = df_processed.drop(columns=id_columns)
14
+
15
+ # --- Step 2: Fill missing values ---
16
+ for col in df_processed.columns:
17
+ if pd.api.types.is_numeric_dtype(df_processed[col]):
18
+ df_processed[col] = df_processed[col].fillna(df_processed[col].mean())
19
+ else:
20
+ df_processed[col] = df_processed[col].fillna(df_processed[col].mode()[0])
21
+
22
+ # --- Step 3: Encode categorical columns (tiered strategy) ---
23
+ # One-Hot for low cardinality (safe, equal distances)
24
+ # Frequency for medium cardinality (no dimension explosion)
25
+ # Drop for high cardinality (likely ID-like, not useful)
26
+ encoded_columns = []
27
+ for col in df_processed.select_dtypes(include="object").columns:
28
+ n_unique = df_processed[col].nunique()
29
+
30
+ if n_unique <= 10:
31
+ # One-Hot: each category becomes a binary column
32
+ # All categories have equal distance (√2) from each other
33
+ dummies = pd.get_dummies(df_processed[col], prefix=col, dtype=int)
34
+ df_processed = pd.concat([df_processed.drop(columns=[col]), dummies], axis=1)
35
+ encoded_columns.append({"column": col, "method": "one-hot", "new_columns": n_unique})
36
+
37
+ elif n_unique <= 50:
38
+ # Frequency: replace category with how often it appears (proportion)
39
+ # No fake ordinal relationship, only 1 column
40
+ freq_map = df_processed[col].value_counts(normalize=True)
41
+ df_processed[col] = df_processed[col].map(freq_map).astype(float)
42
+ encoded_columns.append({"column": col, "method": "frequency", "unique_values": n_unique})
43
+
44
+ else:
45
+ # Drop: too many unique values (likely names, emails, IDs)
46
+ df_processed = df_processed.drop(columns=[col])
47
+ encoded_columns.append({"column": col, "method": "dropped", "reason": "too many unique values"})
48
+
49
+ # --- Step 4: Select numeric columns ---
50
+ df_numeric = df_processed.select_dtypes(include="number")
51
+
52
+ if df_numeric.shape[1] < 2:
53
+ return {
54
+ "status": "skipped",
55
+ "reason": "Not enough usable columns for clustering after encoding"
56
+ }
57
+
58
+ if df_numeric.var().mean() < 1e-3:
59
+ return {
60
+ "status": "skipped",
61
+ "reason": "Data has very low variance (no meaningful clusters)"
62
+ }
63
+
64
+ # --- Step 5: Scale features ---
65
+ # StandardScaler: mean=0, std=1 for each feature
66
+ # Ensures all features contribute equally to distance calculations
67
+ scaler = StandardScaler()
68
+ scaled = scaler.fit_transform(df_numeric)
69
+
70
+ # --- Step 6: Find optimal k using Silhouette Score ---
71
+ # Silhouette measures how well each point fits in its cluster vs nearest other cluster
72
+ # Score ranges from -1 (wrong cluster) to +1 (perfect cluster)
73
+ # Pick the k with the highest average silhouette score
74
+ max_k = min(11, len(df_numeric)) # can't have more clusters than data points
75
+ if max_k < 3:
76
+ return {
77
+ "status": "skipped",
78
+ "reason": "Not enough data points for meaningful clustering"
79
+ }
80
+
81
+ silhouette_scores = []
82
+ for k in range(2, max_k):
83
+ km = KMeans(n_clusters=k, random_state=42, n_init=10)
84
+ labels = km.fit_predict(scaled)
85
+ score = silhouette_score(scaled, labels)
86
+ silhouette_scores.append(score)
87
+
88
+ best_k = silhouette_scores.index(max(silhouette_scores)) + 2
89
+
90
+ if max(silhouette_scores) < 0.15:
91
+ return {
92
+ "status": "skipped",
93
+ "reason": "No strong cluster separation found"
94
+ }
95
+
96
+ # --- Step 7: Final clustering with best k ---
97
+ km = KMeans(n_clusters=best_k, random_state=42, n_init=10)
98
+ km.fit(scaled)
99
+ df_numeric = df_numeric.copy()
100
+ df_numeric["cluster"] = km.labels_
101
+ cluster_summary = df_numeric.groupby("cluster").mean()
102
+ cluster_sizes = {
103
+ int(k): int(v)
104
+ for k, v in df_numeric["cluster"].value_counts().items()
105
+ }
106
+
107
+ return {
108
+ "status": "success",
109
+ "best_k": best_k,
110
+ "silhouette_score": round(max(silhouette_scores), 4),
111
+ "encoded_columns": encoded_columns,
112
+ "cluster_summary": cluster_summary.to_dict(),
113
+ "cluster_sizes": cluster_sizes
114
+ }
clustering.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException
2
+ from pydantic import BaseModel
3
+ from backend.state import session_store
4
+ from backend.services.clusterService import get_clusters
5
+
6
+ router = APIRouter()
7
+
8
+ class SessionRequest(BaseModel):
9
+ session_id: str
10
+
11
+ @router.post("/clustering")
12
+ async def clusters(request: SessionRequest):
13
+ session_id = request.session_id
14
+
15
+ if session_id not in session_store:
16
+ raise HTTPException(status_code=400, detail="session code not found")
17
+
18
+ df = session_store[session_id]["df"]
19
+
20
+ try:
21
+ return get_clusters(df)
22
+ except ValueError as e:
23
+ raise HTTPException(status_code=400, detail=str(e))
correlation.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException
2
+ from pydantic import BaseModel
3
+ from backend.state import session_store
4
+ from backend.services.correlationService import get_correlation
5
+ router = APIRouter()
6
+
7
+ class SessionRequest(BaseModel):
8
+ session_id: str
9
+ target: str | None = None
10
+
11
+ @router.post("/correlation")
12
+ async def correlation(request: SessionRequest):
13
+ session_id = request.session_id
14
+ target = request.target
15
+
16
+ if session_id not in session_store:
17
+ raise HTTPException(status_code=400, detail="session code not found")
18
+
19
+ df = session_store[session_id]["df"]
20
+
21
+ try:
22
+ return get_correlation(df, target)
23
+ except ValueError as e:
24
+ raise HTTPException(status_code=400, detail=str(e))
correlationService.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import math
3
+ import numpy as np
4
+ from sklearn.preprocessing import LabelEncoder
5
+ from sklearn.ensemble import RandomForestClassifier
6
+ from typing import Any, Dict, cast
7
+
8
+ import math
9
+ import numpy as np
10
+
11
+ def clean_data(data):
12
+ '''
13
+ Make the API response JSON-safe.
14
+ FastAPI cannot return NaN/Inf in JSON.
15
+ '''
16
+ if isinstance(data, dict):
17
+ return {k: clean_data(v) for k, v in data.items()}
18
+
19
+ elif isinstance(data, list):
20
+ return [clean_data(v) for v in data]
21
+
22
+ elif isinstance(data, (np.integer, np.floating)):
23
+ value = float(data)
24
+ if math.isnan(value) or math.isinf(value):
25
+ return None
26
+ return value
27
+
28
+ elif isinstance(data, float):
29
+ if math.isnan(data) or math.isinf(data):
30
+ return None
31
+ return data
32
+
33
+ return data
34
+
35
+
36
+ def get_correlation(df, target)-> Dict[str, Any]:
37
+
38
+ '''
39
+ Says this returns a dictionary
40
+ '''
41
+
42
+ df_processed = df.copy()
43
+
44
+ id_columns = [col for col in df_processed.columns if col.lower() == "id"]
45
+ df_processed = df_processed.drop(columns=id_columns)
46
+
47
+ for col in df_processed.columns:
48
+ if pd.api.types.is_numeric_dtype(df_processed[col]):
49
+ df_processed[col] = df_processed[col].fillna(df_processed[col].mean())
50
+ else:
51
+ df_processed[col] = df_processed[col].fillna(df_processed[col].mode()[0])
52
+
53
+ encoded_columns = []
54
+ le = LabelEncoder()
55
+ for col in df_processed.select_dtypes(include="object").columns:
56
+ df_processed[col] = le.fit_transform(df_processed[col])
57
+ encoded_columns.append(col)
58
+
59
+ # keep Numeric data
60
+ df_numeric = df_processed.select_dtypes(include="number")
61
+
62
+ # Remove constant columns
63
+ df_numeric = df_numeric.loc[:, df_numeric.nunique() > 1]
64
+
65
+ if df_numeric.shape[1] < 2:
66
+ return {
67
+ "message": "Not enough numeric columns for correlation"
68
+ }
69
+
70
+ # Always compute correlation
71
+ pearson_df = df_numeric.corr()
72
+ pearson_df = pearson_df.replace([np.inf, -np.inf], np.nan)
73
+ pearson_df = pearson_df.fillna(0)
74
+
75
+ pearson_corr = pearson_df.to_dict()
76
+
77
+ spearman_df = df_numeric.corr(method="spearman")
78
+ spearman_df = spearman_df.replace([np.inf, -np.inf], np.nan)
79
+ spearman_df = spearman_df.fillna(0)
80
+
81
+ spearman_corr = spearman_df.to_dict()
82
+
83
+ # EDA
84
+
85
+ if not target:
86
+ '''
87
+ cast(type, value) = “pretend this value is this type”
88
+ '''
89
+ return cast(Dict[str, Any], clean_data({
90
+ "mode": "eda",
91
+ "rows": df.shape[0],
92
+ "columns": df.shape[1],
93
+ "column_names": df.columns.to_list(),
94
+ "encoded_columns": encoded_columns,
95
+ "final_column_count": df_numeric.shape[1],
96
+ "pearson": pearson_corr,
97
+ "spearman": spearman_corr
98
+ }))
99
+
100
+ # ML MODE
101
+
102
+ if target not in df_processed.columns:
103
+ raise ValueError(f"Target column '{target}' not found")
104
+
105
+ # Encode target if needed
106
+ if not pd.api.types.is_numeric_dtype(df_processed[target]):
107
+ df_processed[target] = LabelEncoder().fit_transform(df_processed[target])
108
+
109
+ X = df_processed.drop(columns=[target]).select_dtypes(include="number")
110
+ y = df_processed[target]
111
+
112
+ model = RandomForestClassifier(n_estimators=100, random_state=42)
113
+ model.fit(X, y)
114
+
115
+ feature_importance = {
116
+ col: round(float(imp), 4)
117
+ for col, imp in zip(X.columns, model.feature_importances_)
118
+ }
119
+
120
+ feature_importance = dict(
121
+ sorted(feature_importance.items(), key=lambda x: x[1], reverse=True)
122
+ )
123
+
124
+ return cast(Dict[str, Any], clean_data({
125
+ "mode": "ml",
126
+ "rows": df.shape[0],
127
+ "columns": df.shape[1],
128
+ "column_names": df.columns.to_list(),
129
+ "encoded_columns": encoded_columns,
130
+ "final_column_count": df_numeric.shape[1],
131
+ "pearson": pearson_corr,
132
+ "spearman": spearman_corr,
133
+ "feature_importance": dict(list(feature_importance.items())[:5]),
134
+ }))
135
+
136
+
explain.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException
2
+ from pydantic import BaseModel
3
+ from backend.services.explainService import run_explain
4
+ from backend.services.reportService import generate_report_file
5
+
6
+ router = APIRouter()
7
+
8
+ class SessionRequest(BaseModel):
9
+ session_id: str
10
+ target: str | None = None
11
+
12
+
13
+ @router.post("/explain")
14
+ async def explain(request: SessionRequest):
15
+ try:
16
+ return run_explain(request.session_id, request.target)
17
+ except Exception as e:
18
+ raise HTTPException(status_code=500, detail=str(e))
19
+
20
+
21
+ @router.post("/report")
22
+ async def report(request: SessionRequest):
23
+ try:
24
+ return generate_report_file(request.session_id)
25
+ except Exception as e:
26
+ raise HTTPException(status_code=500, detail=str(e))
explainService.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from backend.state import session_store
2
+ from backend.services.profileService import run_profile
3
+ from backend.services.correlationService import get_correlation
4
+ from backend.services.clusterService import get_clusters
5
+ from backend.services.aiService import call_ai
6
+ from backend.services.promptService import build_prompt
7
+
8
+
9
+ def run_explain(session_id, target):
10
+
11
+ if session_id not in session_store:
12
+ raise Exception("session not found")
13
+
14
+ df = session_store[session_id]["df"]
15
+
16
+ if target and target in df.columns:
17
+ mode = "ml"
18
+ else:
19
+ mode = "eda"
20
+ target = None
21
+
22
+ profile = run_profile(df)
23
+
24
+ if mode == "ml":
25
+ correlation = get_correlation(df, target)
26
+ top_features = correlation["feature_importance"]
27
+ pearson = correlation.get("pearson", {}) # Get pearson if it exists, otherwise use an empty dictionary instead of crashing
28
+ spearman = correlation.get("spearman", {}) #{} is just a safe backup value when "pearson" or "spearman" is missing.
29
+ else:
30
+ correlation = get_correlation(df, None)
31
+ top_features = {}
32
+ pearson = correlation.get("pearson", {})
33
+ spearman = correlation.get("spearman", {})
34
+
35
+ try:
36
+ clusters = get_clusters(df)
37
+ except Exception:
38
+ clusters = {
39
+ "status": "skipped",
40
+ "reason": "Clustering failed"
41
+ }
42
+
43
+ summary = {
44
+ "rows": profile["rows"],
45
+ "columns": profile["columns"],
46
+ "numeric_columns": profile["numeric_column_count"],
47
+ "mode": mode,
48
+ "target": target,
49
+ "top_features": top_features,
50
+ "clusters": clusters,
51
+ "pearson": pearson,
52
+ "spearman": spearman
53
+ }
54
+
55
+ prompt = build_prompt(summary, profile)
56
+
57
+ ai_text = call_ai(prompt)
58
+
59
+ session_store[session_id]["summary"] = summary
60
+ session_store[session_id]["explanation"] = ai_text
61
+
62
+ return {
63
+ "summary": summary,
64
+ "explanation": ai_text
65
+ }
main.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from dotenv import load_dotenv
4
+ import os
5
+ from backend.routers import upload, profile, correlation, clustering, explain
6
+
7
+ load_dotenv()
8
+
9
+ app = FastAPI(title="Explain My Data API")
10
+
11
+ app.add_middleware(
12
+ CORSMiddleware,
13
+ allow_origins=["*"],
14
+ allow_methods=["*"],
15
+ allow_headers=["*"],
16
+ )
17
+
18
+ @app.get("/health")
19
+ def health_check():
20
+ return {"status": "ok", "message": "Server is running"}
21
+
22
+ app.include_router(upload.router, prefix="/api")
23
+ app.include_router(profile.router, prefix="/api")
24
+ app.include_router(correlation.router, prefix="/api")
25
+ app.include_router(clustering.router, prefix="/api")
26
+ app.include_router(explain.router, prefix="/api")
package-lock.json ADDED
@@ -0,0 +1,1293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "DataDecoder",
3
+ "lockfileVersion": 3,
4
+ "requires": true,
5
+ "packages": {
6
+ "": {
7
+ "dependencies": {
8
+ "@tailwindcss/typography": "^0.5.19",
9
+ "react-markdown": "^10.1.0"
10
+ }
11
+ },
12
+ "node_modules/@tailwindcss/typography": {
13
+ "version": "0.5.19",
14
+ "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz",
15
+ "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==",
16
+ "license": "MIT",
17
+ "dependencies": {
18
+ "postcss-selector-parser": "6.0.10"
19
+ },
20
+ "peerDependencies": {
21
+ "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
22
+ }
23
+ },
24
+ "node_modules/@types/debug": {
25
+ "version": "4.1.13",
26
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz",
27
+ "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==",
28
+ "license": "MIT",
29
+ "dependencies": {
30
+ "@types/ms": "*"
31
+ }
32
+ },
33
+ "node_modules/@types/estree": {
34
+ "version": "1.0.8",
35
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
36
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
37
+ "license": "MIT"
38
+ },
39
+ "node_modules/@types/estree-jsx": {
40
+ "version": "1.0.5",
41
+ "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz",
42
+ "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==",
43
+ "license": "MIT",
44
+ "dependencies": {
45
+ "@types/estree": "*"
46
+ }
47
+ },
48
+ "node_modules/@types/hast": {
49
+ "version": "3.0.4",
50
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
51
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
52
+ "license": "MIT",
53
+ "dependencies": {
54
+ "@types/unist": "*"
55
+ }
56
+ },
57
+ "node_modules/@types/mdast": {
58
+ "version": "4.0.4",
59
+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
60
+ "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
61
+ "license": "MIT",
62
+ "dependencies": {
63
+ "@types/unist": "*"
64
+ }
65
+ },
66
+ "node_modules/@types/ms": {
67
+ "version": "2.1.0",
68
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
69
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
70
+ "license": "MIT"
71
+ },
72
+ "node_modules/@types/react": {
73
+ "version": "19.2.14",
74
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
75
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
76
+ "license": "MIT",
77
+ "peer": true,
78
+ "dependencies": {
79
+ "csstype": "^3.2.2"
80
+ }
81
+ },
82
+ "node_modules/@types/unist": {
83
+ "version": "3.0.3",
84
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
85
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
86
+ "license": "MIT"
87
+ },
88
+ "node_modules/@ungap/structured-clone": {
89
+ "version": "1.3.0",
90
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
91
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
92
+ "license": "ISC"
93
+ },
94
+ "node_modules/bail": {
95
+ "version": "2.0.2",
96
+ "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
97
+ "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==",
98
+ "license": "MIT",
99
+ "funding": {
100
+ "type": "github",
101
+ "url": "https://github.com/sponsors/wooorm"
102
+ }
103
+ },
104
+ "node_modules/ccount": {
105
+ "version": "2.0.1",
106
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
107
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
108
+ "license": "MIT",
109
+ "funding": {
110
+ "type": "github",
111
+ "url": "https://github.com/sponsors/wooorm"
112
+ }
113
+ },
114
+ "node_modules/character-entities": {
115
+ "version": "2.0.2",
116
+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
117
+ "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
118
+ "license": "MIT",
119
+ "funding": {
120
+ "type": "github",
121
+ "url": "https://github.com/sponsors/wooorm"
122
+ }
123
+ },
124
+ "node_modules/character-entities-html4": {
125
+ "version": "2.1.0",
126
+ "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
127
+ "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
128
+ "license": "MIT",
129
+ "funding": {
130
+ "type": "github",
131
+ "url": "https://github.com/sponsors/wooorm"
132
+ }
133
+ },
134
+ "node_modules/character-entities-legacy": {
135
+ "version": "3.0.0",
136
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
137
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
138
+ "license": "MIT",
139
+ "funding": {
140
+ "type": "github",
141
+ "url": "https://github.com/sponsors/wooorm"
142
+ }
143
+ },
144
+ "node_modules/character-reference-invalid": {
145
+ "version": "2.0.1",
146
+ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
147
+ "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==",
148
+ "license": "MIT",
149
+ "funding": {
150
+ "type": "github",
151
+ "url": "https://github.com/sponsors/wooorm"
152
+ }
153
+ },
154
+ "node_modules/comma-separated-tokens": {
155
+ "version": "2.0.3",
156
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
157
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
158
+ "license": "MIT",
159
+ "funding": {
160
+ "type": "github",
161
+ "url": "https://github.com/sponsors/wooorm"
162
+ }
163
+ },
164
+ "node_modules/cssesc": {
165
+ "version": "3.0.0",
166
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
167
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
168
+ "license": "MIT",
169
+ "bin": {
170
+ "cssesc": "bin/cssesc"
171
+ },
172
+ "engines": {
173
+ "node": ">=4"
174
+ }
175
+ },
176
+ "node_modules/csstype": {
177
+ "version": "3.2.3",
178
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
179
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
180
+ "license": "MIT",
181
+ "peer": true
182
+ },
183
+ "node_modules/debug": {
184
+ "version": "4.4.3",
185
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
186
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
187
+ "license": "MIT",
188
+ "dependencies": {
189
+ "ms": "^2.1.3"
190
+ },
191
+ "engines": {
192
+ "node": ">=6.0"
193
+ },
194
+ "peerDependenciesMeta": {
195
+ "supports-color": {
196
+ "optional": true
197
+ }
198
+ }
199
+ },
200
+ "node_modules/decode-named-character-reference": {
201
+ "version": "1.3.0",
202
+ "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz",
203
+ "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==",
204
+ "license": "MIT",
205
+ "dependencies": {
206
+ "character-entities": "^2.0.0"
207
+ },
208
+ "funding": {
209
+ "type": "github",
210
+ "url": "https://github.com/sponsors/wooorm"
211
+ }
212
+ },
213
+ "node_modules/dequal": {
214
+ "version": "2.0.3",
215
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
216
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
217
+ "license": "MIT",
218
+ "engines": {
219
+ "node": ">=6"
220
+ }
221
+ },
222
+ "node_modules/devlop": {
223
+ "version": "1.1.0",
224
+ "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
225
+ "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
226
+ "license": "MIT",
227
+ "dependencies": {
228
+ "dequal": "^2.0.0"
229
+ },
230
+ "funding": {
231
+ "type": "github",
232
+ "url": "https://github.com/sponsors/wooorm"
233
+ }
234
+ },
235
+ "node_modules/estree-util-is-identifier-name": {
236
+ "version": "3.0.0",
237
+ "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
238
+ "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==",
239
+ "license": "MIT",
240
+ "funding": {
241
+ "type": "opencollective",
242
+ "url": "https://opencollective.com/unified"
243
+ }
244
+ },
245
+ "node_modules/extend": {
246
+ "version": "3.0.2",
247
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
248
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
249
+ "license": "MIT"
250
+ },
251
+ "node_modules/hast-util-to-jsx-runtime": {
252
+ "version": "2.3.6",
253
+ "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
254
+ "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==",
255
+ "license": "MIT",
256
+ "dependencies": {
257
+ "@types/estree": "^1.0.0",
258
+ "@types/hast": "^3.0.0",
259
+ "@types/unist": "^3.0.0",
260
+ "comma-separated-tokens": "^2.0.0",
261
+ "devlop": "^1.0.0",
262
+ "estree-util-is-identifier-name": "^3.0.0",
263
+ "hast-util-whitespace": "^3.0.0",
264
+ "mdast-util-mdx-expression": "^2.0.0",
265
+ "mdast-util-mdx-jsx": "^3.0.0",
266
+ "mdast-util-mdxjs-esm": "^2.0.0",
267
+ "property-information": "^7.0.0",
268
+ "space-separated-tokens": "^2.0.0",
269
+ "style-to-js": "^1.0.0",
270
+ "unist-util-position": "^5.0.0",
271
+ "vfile-message": "^4.0.0"
272
+ },
273
+ "funding": {
274
+ "type": "opencollective",
275
+ "url": "https://opencollective.com/unified"
276
+ }
277
+ },
278
+ "node_modules/hast-util-whitespace": {
279
+ "version": "3.0.0",
280
+ "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
281
+ "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
282
+ "license": "MIT",
283
+ "dependencies": {
284
+ "@types/hast": "^3.0.0"
285
+ },
286
+ "funding": {
287
+ "type": "opencollective",
288
+ "url": "https://opencollective.com/unified"
289
+ }
290
+ },
291
+ "node_modules/html-url-attributes": {
292
+ "version": "3.0.1",
293
+ "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
294
+ "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==",
295
+ "license": "MIT",
296
+ "funding": {
297
+ "type": "opencollective",
298
+ "url": "https://opencollective.com/unified"
299
+ }
300
+ },
301
+ "node_modules/inline-style-parser": {
302
+ "version": "0.2.7",
303
+ "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
304
+ "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==",
305
+ "license": "MIT"
306
+ },
307
+ "node_modules/is-alphabetical": {
308
+ "version": "2.0.1",
309
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
310
+ "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
311
+ "license": "MIT",
312
+ "funding": {
313
+ "type": "github",
314
+ "url": "https://github.com/sponsors/wooorm"
315
+ }
316
+ },
317
+ "node_modules/is-alphanumerical": {
318
+ "version": "2.0.1",
319
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
320
+ "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
321
+ "license": "MIT",
322
+ "dependencies": {
323
+ "is-alphabetical": "^2.0.0",
324
+ "is-decimal": "^2.0.0"
325
+ },
326
+ "funding": {
327
+ "type": "github",
328
+ "url": "https://github.com/sponsors/wooorm"
329
+ }
330
+ },
331
+ "node_modules/is-decimal": {
332
+ "version": "2.0.1",
333
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
334
+ "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
335
+ "license": "MIT",
336
+ "funding": {
337
+ "type": "github",
338
+ "url": "https://github.com/sponsors/wooorm"
339
+ }
340
+ },
341
+ "node_modules/is-hexadecimal": {
342
+ "version": "2.0.1",
343
+ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
344
+ "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==",
345
+ "license": "MIT",
346
+ "funding": {
347
+ "type": "github",
348
+ "url": "https://github.com/sponsors/wooorm"
349
+ }
350
+ },
351
+ "node_modules/is-plain-obj": {
352
+ "version": "4.1.0",
353
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
354
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
355
+ "license": "MIT",
356
+ "engines": {
357
+ "node": ">=12"
358
+ },
359
+ "funding": {
360
+ "url": "https://github.com/sponsors/sindresorhus"
361
+ }
362
+ },
363
+ "node_modules/longest-streak": {
364
+ "version": "3.1.0",
365
+ "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
366
+ "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==",
367
+ "license": "MIT",
368
+ "funding": {
369
+ "type": "github",
370
+ "url": "https://github.com/sponsors/wooorm"
371
+ }
372
+ },
373
+ "node_modules/mdast-util-from-markdown": {
374
+ "version": "2.0.3",
375
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz",
376
+ "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==",
377
+ "license": "MIT",
378
+ "dependencies": {
379
+ "@types/mdast": "^4.0.0",
380
+ "@types/unist": "^3.0.0",
381
+ "decode-named-character-reference": "^1.0.0",
382
+ "devlop": "^1.0.0",
383
+ "mdast-util-to-string": "^4.0.0",
384
+ "micromark": "^4.0.0",
385
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
386
+ "micromark-util-decode-string": "^2.0.0",
387
+ "micromark-util-normalize-identifier": "^2.0.0",
388
+ "micromark-util-symbol": "^2.0.0",
389
+ "micromark-util-types": "^2.0.0",
390
+ "unist-util-stringify-position": "^4.0.0"
391
+ },
392
+ "funding": {
393
+ "type": "opencollective",
394
+ "url": "https://opencollective.com/unified"
395
+ }
396
+ },
397
+ "node_modules/mdast-util-mdx-expression": {
398
+ "version": "2.0.1",
399
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
400
+ "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==",
401
+ "license": "MIT",
402
+ "dependencies": {
403
+ "@types/estree-jsx": "^1.0.0",
404
+ "@types/hast": "^3.0.0",
405
+ "@types/mdast": "^4.0.0",
406
+ "devlop": "^1.0.0",
407
+ "mdast-util-from-markdown": "^2.0.0",
408
+ "mdast-util-to-markdown": "^2.0.0"
409
+ },
410
+ "funding": {
411
+ "type": "opencollective",
412
+ "url": "https://opencollective.com/unified"
413
+ }
414
+ },
415
+ "node_modules/mdast-util-mdx-jsx": {
416
+ "version": "3.2.0",
417
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz",
418
+ "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==",
419
+ "license": "MIT",
420
+ "dependencies": {
421
+ "@types/estree-jsx": "^1.0.0",
422
+ "@types/hast": "^3.0.0",
423
+ "@types/mdast": "^4.0.0",
424
+ "@types/unist": "^3.0.0",
425
+ "ccount": "^2.0.0",
426
+ "devlop": "^1.1.0",
427
+ "mdast-util-from-markdown": "^2.0.0",
428
+ "mdast-util-to-markdown": "^2.0.0",
429
+ "parse-entities": "^4.0.0",
430
+ "stringify-entities": "^4.0.0",
431
+ "unist-util-stringify-position": "^4.0.0",
432
+ "vfile-message": "^4.0.0"
433
+ },
434
+ "funding": {
435
+ "type": "opencollective",
436
+ "url": "https://opencollective.com/unified"
437
+ }
438
+ },
439
+ "node_modules/mdast-util-mdxjs-esm": {
440
+ "version": "2.0.1",
441
+ "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz",
442
+ "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==",
443
+ "license": "MIT",
444
+ "dependencies": {
445
+ "@types/estree-jsx": "^1.0.0",
446
+ "@types/hast": "^3.0.0",
447
+ "@types/mdast": "^4.0.0",
448
+ "devlop": "^1.0.0",
449
+ "mdast-util-from-markdown": "^2.0.0",
450
+ "mdast-util-to-markdown": "^2.0.0"
451
+ },
452
+ "funding": {
453
+ "type": "opencollective",
454
+ "url": "https://opencollective.com/unified"
455
+ }
456
+ },
457
+ "node_modules/mdast-util-phrasing": {
458
+ "version": "4.1.0",
459
+ "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
460
+ "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
461
+ "license": "MIT",
462
+ "dependencies": {
463
+ "@types/mdast": "^4.0.0",
464
+ "unist-util-is": "^6.0.0"
465
+ },
466
+ "funding": {
467
+ "type": "opencollective",
468
+ "url": "https://opencollective.com/unified"
469
+ }
470
+ },
471
+ "node_modules/mdast-util-to-hast": {
472
+ "version": "13.2.1",
473
+ "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz",
474
+ "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==",
475
+ "license": "MIT",
476
+ "dependencies": {
477
+ "@types/hast": "^3.0.0",
478
+ "@types/mdast": "^4.0.0",
479
+ "@ungap/structured-clone": "^1.0.0",
480
+ "devlop": "^1.0.0",
481
+ "micromark-util-sanitize-uri": "^2.0.0",
482
+ "trim-lines": "^3.0.0",
483
+ "unist-util-position": "^5.0.0",
484
+ "unist-util-visit": "^5.0.0",
485
+ "vfile": "^6.0.0"
486
+ },
487
+ "funding": {
488
+ "type": "opencollective",
489
+ "url": "https://opencollective.com/unified"
490
+ }
491
+ },
492
+ "node_modules/mdast-util-to-markdown": {
493
+ "version": "2.1.2",
494
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz",
495
+ "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==",
496
+ "license": "MIT",
497
+ "dependencies": {
498
+ "@types/mdast": "^4.0.0",
499
+ "@types/unist": "^3.0.0",
500
+ "longest-streak": "^3.0.0",
501
+ "mdast-util-phrasing": "^4.0.0",
502
+ "mdast-util-to-string": "^4.0.0",
503
+ "micromark-util-classify-character": "^2.0.0",
504
+ "micromark-util-decode-string": "^2.0.0",
505
+ "unist-util-visit": "^5.0.0",
506
+ "zwitch": "^2.0.0"
507
+ },
508
+ "funding": {
509
+ "type": "opencollective",
510
+ "url": "https://opencollective.com/unified"
511
+ }
512
+ },
513
+ "node_modules/mdast-util-to-string": {
514
+ "version": "4.0.0",
515
+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
516
+ "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
517
+ "license": "MIT",
518
+ "dependencies": {
519
+ "@types/mdast": "^4.0.0"
520
+ },
521
+ "funding": {
522
+ "type": "opencollective",
523
+ "url": "https://opencollective.com/unified"
524
+ }
525
+ },
526
+ "node_modules/micromark": {
527
+ "version": "4.0.2",
528
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
529
+ "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==",
530
+ "funding": [
531
+ {
532
+ "type": "GitHub Sponsors",
533
+ "url": "https://github.com/sponsors/unifiedjs"
534
+ },
535
+ {
536
+ "type": "OpenCollective",
537
+ "url": "https://opencollective.com/unified"
538
+ }
539
+ ],
540
+ "license": "MIT",
541
+ "dependencies": {
542
+ "@types/debug": "^4.0.0",
543
+ "debug": "^4.0.0",
544
+ "decode-named-character-reference": "^1.0.0",
545
+ "devlop": "^1.0.0",
546
+ "micromark-core-commonmark": "^2.0.0",
547
+ "micromark-factory-space": "^2.0.0",
548
+ "micromark-util-character": "^2.0.0",
549
+ "micromark-util-chunked": "^2.0.0",
550
+ "micromark-util-combine-extensions": "^2.0.0",
551
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
552
+ "micromark-util-encode": "^2.0.0",
553
+ "micromark-util-normalize-identifier": "^2.0.0",
554
+ "micromark-util-resolve-all": "^2.0.0",
555
+ "micromark-util-sanitize-uri": "^2.0.0",
556
+ "micromark-util-subtokenize": "^2.0.0",
557
+ "micromark-util-symbol": "^2.0.0",
558
+ "micromark-util-types": "^2.0.0"
559
+ }
560
+ },
561
+ "node_modules/micromark-core-commonmark": {
562
+ "version": "2.0.3",
563
+ "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz",
564
+ "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==",
565
+ "funding": [
566
+ {
567
+ "type": "GitHub Sponsors",
568
+ "url": "https://github.com/sponsors/unifiedjs"
569
+ },
570
+ {
571
+ "type": "OpenCollective",
572
+ "url": "https://opencollective.com/unified"
573
+ }
574
+ ],
575
+ "license": "MIT",
576
+ "dependencies": {
577
+ "decode-named-character-reference": "^1.0.0",
578
+ "devlop": "^1.0.0",
579
+ "micromark-factory-destination": "^2.0.0",
580
+ "micromark-factory-label": "^2.0.0",
581
+ "micromark-factory-space": "^2.0.0",
582
+ "micromark-factory-title": "^2.0.0",
583
+ "micromark-factory-whitespace": "^2.0.0",
584
+ "micromark-util-character": "^2.0.0",
585
+ "micromark-util-chunked": "^2.0.0",
586
+ "micromark-util-classify-character": "^2.0.0",
587
+ "micromark-util-html-tag-name": "^2.0.0",
588
+ "micromark-util-normalize-identifier": "^2.0.0",
589
+ "micromark-util-resolve-all": "^2.0.0",
590
+ "micromark-util-subtokenize": "^2.0.0",
591
+ "micromark-util-symbol": "^2.0.0",
592
+ "micromark-util-types": "^2.0.0"
593
+ }
594
+ },
595
+ "node_modules/micromark-factory-destination": {
596
+ "version": "2.0.1",
597
+ "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
598
+ "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==",
599
+ "funding": [
600
+ {
601
+ "type": "GitHub Sponsors",
602
+ "url": "https://github.com/sponsors/unifiedjs"
603
+ },
604
+ {
605
+ "type": "OpenCollective",
606
+ "url": "https://opencollective.com/unified"
607
+ }
608
+ ],
609
+ "license": "MIT",
610
+ "dependencies": {
611
+ "micromark-util-character": "^2.0.0",
612
+ "micromark-util-symbol": "^2.0.0",
613
+ "micromark-util-types": "^2.0.0"
614
+ }
615
+ },
616
+ "node_modules/micromark-factory-label": {
617
+ "version": "2.0.1",
618
+ "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz",
619
+ "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==",
620
+ "funding": [
621
+ {
622
+ "type": "GitHub Sponsors",
623
+ "url": "https://github.com/sponsors/unifiedjs"
624
+ },
625
+ {
626
+ "type": "OpenCollective",
627
+ "url": "https://opencollective.com/unified"
628
+ }
629
+ ],
630
+ "license": "MIT",
631
+ "dependencies": {
632
+ "devlop": "^1.0.0",
633
+ "micromark-util-character": "^2.0.0",
634
+ "micromark-util-symbol": "^2.0.0",
635
+ "micromark-util-types": "^2.0.0"
636
+ }
637
+ },
638
+ "node_modules/micromark-factory-space": {
639
+ "version": "2.0.1",
640
+ "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz",
641
+ "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==",
642
+ "funding": [
643
+ {
644
+ "type": "GitHub Sponsors",
645
+ "url": "https://github.com/sponsors/unifiedjs"
646
+ },
647
+ {
648
+ "type": "OpenCollective",
649
+ "url": "https://opencollective.com/unified"
650
+ }
651
+ ],
652
+ "license": "MIT",
653
+ "dependencies": {
654
+ "micromark-util-character": "^2.0.0",
655
+ "micromark-util-types": "^2.0.0"
656
+ }
657
+ },
658
+ "node_modules/micromark-factory-title": {
659
+ "version": "2.0.1",
660
+ "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz",
661
+ "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==",
662
+ "funding": [
663
+ {
664
+ "type": "GitHub Sponsors",
665
+ "url": "https://github.com/sponsors/unifiedjs"
666
+ },
667
+ {
668
+ "type": "OpenCollective",
669
+ "url": "https://opencollective.com/unified"
670
+ }
671
+ ],
672
+ "license": "MIT",
673
+ "dependencies": {
674
+ "micromark-factory-space": "^2.0.0",
675
+ "micromark-util-character": "^2.0.0",
676
+ "micromark-util-symbol": "^2.0.0",
677
+ "micromark-util-types": "^2.0.0"
678
+ }
679
+ },
680
+ "node_modules/micromark-factory-whitespace": {
681
+ "version": "2.0.1",
682
+ "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz",
683
+ "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==",
684
+ "funding": [
685
+ {
686
+ "type": "GitHub Sponsors",
687
+ "url": "https://github.com/sponsors/unifiedjs"
688
+ },
689
+ {
690
+ "type": "OpenCollective",
691
+ "url": "https://opencollective.com/unified"
692
+ }
693
+ ],
694
+ "license": "MIT",
695
+ "dependencies": {
696
+ "micromark-factory-space": "^2.0.0",
697
+ "micromark-util-character": "^2.0.0",
698
+ "micromark-util-symbol": "^2.0.0",
699
+ "micromark-util-types": "^2.0.0"
700
+ }
701
+ },
702
+ "node_modules/micromark-util-character": {
703
+ "version": "2.1.1",
704
+ "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
705
+ "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
706
+ "funding": [
707
+ {
708
+ "type": "GitHub Sponsors",
709
+ "url": "https://github.com/sponsors/unifiedjs"
710
+ },
711
+ {
712
+ "type": "OpenCollective",
713
+ "url": "https://opencollective.com/unified"
714
+ }
715
+ ],
716
+ "license": "MIT",
717
+ "dependencies": {
718
+ "micromark-util-symbol": "^2.0.0",
719
+ "micromark-util-types": "^2.0.0"
720
+ }
721
+ },
722
+ "node_modules/micromark-util-chunked": {
723
+ "version": "2.0.1",
724
+ "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz",
725
+ "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==",
726
+ "funding": [
727
+ {
728
+ "type": "GitHub Sponsors",
729
+ "url": "https://github.com/sponsors/unifiedjs"
730
+ },
731
+ {
732
+ "type": "OpenCollective",
733
+ "url": "https://opencollective.com/unified"
734
+ }
735
+ ],
736
+ "license": "MIT",
737
+ "dependencies": {
738
+ "micromark-util-symbol": "^2.0.0"
739
+ }
740
+ },
741
+ "node_modules/micromark-util-classify-character": {
742
+ "version": "2.0.1",
743
+ "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz",
744
+ "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==",
745
+ "funding": [
746
+ {
747
+ "type": "GitHub Sponsors",
748
+ "url": "https://github.com/sponsors/unifiedjs"
749
+ },
750
+ {
751
+ "type": "OpenCollective",
752
+ "url": "https://opencollective.com/unified"
753
+ }
754
+ ],
755
+ "license": "MIT",
756
+ "dependencies": {
757
+ "micromark-util-character": "^2.0.0",
758
+ "micromark-util-symbol": "^2.0.0",
759
+ "micromark-util-types": "^2.0.0"
760
+ }
761
+ },
762
+ "node_modules/micromark-util-combine-extensions": {
763
+ "version": "2.0.1",
764
+ "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz",
765
+ "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==",
766
+ "funding": [
767
+ {
768
+ "type": "GitHub Sponsors",
769
+ "url": "https://github.com/sponsors/unifiedjs"
770
+ },
771
+ {
772
+ "type": "OpenCollective",
773
+ "url": "https://opencollective.com/unified"
774
+ }
775
+ ],
776
+ "license": "MIT",
777
+ "dependencies": {
778
+ "micromark-util-chunked": "^2.0.0",
779
+ "micromark-util-types": "^2.0.0"
780
+ }
781
+ },
782
+ "node_modules/micromark-util-decode-numeric-character-reference": {
783
+ "version": "2.0.2",
784
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz",
785
+ "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==",
786
+ "funding": [
787
+ {
788
+ "type": "GitHub Sponsors",
789
+ "url": "https://github.com/sponsors/unifiedjs"
790
+ },
791
+ {
792
+ "type": "OpenCollective",
793
+ "url": "https://opencollective.com/unified"
794
+ }
795
+ ],
796
+ "license": "MIT",
797
+ "dependencies": {
798
+ "micromark-util-symbol": "^2.0.0"
799
+ }
800
+ },
801
+ "node_modules/micromark-util-decode-string": {
802
+ "version": "2.0.1",
803
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz",
804
+ "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==",
805
+ "funding": [
806
+ {
807
+ "type": "GitHub Sponsors",
808
+ "url": "https://github.com/sponsors/unifiedjs"
809
+ },
810
+ {
811
+ "type": "OpenCollective",
812
+ "url": "https://opencollective.com/unified"
813
+ }
814
+ ],
815
+ "license": "MIT",
816
+ "dependencies": {
817
+ "decode-named-character-reference": "^1.0.0",
818
+ "micromark-util-character": "^2.0.0",
819
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
820
+ "micromark-util-symbol": "^2.0.0"
821
+ }
822
+ },
823
+ "node_modules/micromark-util-encode": {
824
+ "version": "2.0.1",
825
+ "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
826
+ "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
827
+ "funding": [
828
+ {
829
+ "type": "GitHub Sponsors",
830
+ "url": "https://github.com/sponsors/unifiedjs"
831
+ },
832
+ {
833
+ "type": "OpenCollective",
834
+ "url": "https://opencollective.com/unified"
835
+ }
836
+ ],
837
+ "license": "MIT"
838
+ },
839
+ "node_modules/micromark-util-html-tag-name": {
840
+ "version": "2.0.1",
841
+ "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz",
842
+ "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==",
843
+ "funding": [
844
+ {
845
+ "type": "GitHub Sponsors",
846
+ "url": "https://github.com/sponsors/unifiedjs"
847
+ },
848
+ {
849
+ "type": "OpenCollective",
850
+ "url": "https://opencollective.com/unified"
851
+ }
852
+ ],
853
+ "license": "MIT"
854
+ },
855
+ "node_modules/micromark-util-normalize-identifier": {
856
+ "version": "2.0.1",
857
+ "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz",
858
+ "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==",
859
+ "funding": [
860
+ {
861
+ "type": "GitHub Sponsors",
862
+ "url": "https://github.com/sponsors/unifiedjs"
863
+ },
864
+ {
865
+ "type": "OpenCollective",
866
+ "url": "https://opencollective.com/unified"
867
+ }
868
+ ],
869
+ "license": "MIT",
870
+ "dependencies": {
871
+ "micromark-util-symbol": "^2.0.0"
872
+ }
873
+ },
874
+ "node_modules/micromark-util-resolve-all": {
875
+ "version": "2.0.1",
876
+ "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz",
877
+ "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==",
878
+ "funding": [
879
+ {
880
+ "type": "GitHub Sponsors",
881
+ "url": "https://github.com/sponsors/unifiedjs"
882
+ },
883
+ {
884
+ "type": "OpenCollective",
885
+ "url": "https://opencollective.com/unified"
886
+ }
887
+ ],
888
+ "license": "MIT",
889
+ "dependencies": {
890
+ "micromark-util-types": "^2.0.0"
891
+ }
892
+ },
893
+ "node_modules/micromark-util-sanitize-uri": {
894
+ "version": "2.0.1",
895
+ "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
896
+ "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
897
+ "funding": [
898
+ {
899
+ "type": "GitHub Sponsors",
900
+ "url": "https://github.com/sponsors/unifiedjs"
901
+ },
902
+ {
903
+ "type": "OpenCollective",
904
+ "url": "https://opencollective.com/unified"
905
+ }
906
+ ],
907
+ "license": "MIT",
908
+ "dependencies": {
909
+ "micromark-util-character": "^2.0.0",
910
+ "micromark-util-encode": "^2.0.0",
911
+ "micromark-util-symbol": "^2.0.0"
912
+ }
913
+ },
914
+ "node_modules/micromark-util-subtokenize": {
915
+ "version": "2.1.0",
916
+ "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz",
917
+ "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==",
918
+ "funding": [
919
+ {
920
+ "type": "GitHub Sponsors",
921
+ "url": "https://github.com/sponsors/unifiedjs"
922
+ },
923
+ {
924
+ "type": "OpenCollective",
925
+ "url": "https://opencollective.com/unified"
926
+ }
927
+ ],
928
+ "license": "MIT",
929
+ "dependencies": {
930
+ "devlop": "^1.0.0",
931
+ "micromark-util-chunked": "^2.0.0",
932
+ "micromark-util-symbol": "^2.0.0",
933
+ "micromark-util-types": "^2.0.0"
934
+ }
935
+ },
936
+ "node_modules/micromark-util-symbol": {
937
+ "version": "2.0.1",
938
+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
939
+ "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
940
+ "funding": [
941
+ {
942
+ "type": "GitHub Sponsors",
943
+ "url": "https://github.com/sponsors/unifiedjs"
944
+ },
945
+ {
946
+ "type": "OpenCollective",
947
+ "url": "https://opencollective.com/unified"
948
+ }
949
+ ],
950
+ "license": "MIT"
951
+ },
952
+ "node_modules/micromark-util-types": {
953
+ "version": "2.0.2",
954
+ "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
955
+ "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
956
+ "funding": [
957
+ {
958
+ "type": "GitHub Sponsors",
959
+ "url": "https://github.com/sponsors/unifiedjs"
960
+ },
961
+ {
962
+ "type": "OpenCollective",
963
+ "url": "https://opencollective.com/unified"
964
+ }
965
+ ],
966
+ "license": "MIT"
967
+ },
968
+ "node_modules/ms": {
969
+ "version": "2.1.3",
970
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
971
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
972
+ "license": "MIT"
973
+ },
974
+ "node_modules/parse-entities": {
975
+ "version": "4.0.2",
976
+ "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
977
+ "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==",
978
+ "license": "MIT",
979
+ "dependencies": {
980
+ "@types/unist": "^2.0.0",
981
+ "character-entities-legacy": "^3.0.0",
982
+ "character-reference-invalid": "^2.0.0",
983
+ "decode-named-character-reference": "^1.0.0",
984
+ "is-alphanumerical": "^2.0.0",
985
+ "is-decimal": "^2.0.0",
986
+ "is-hexadecimal": "^2.0.0"
987
+ },
988
+ "funding": {
989
+ "type": "github",
990
+ "url": "https://github.com/sponsors/wooorm"
991
+ }
992
+ },
993
+ "node_modules/parse-entities/node_modules/@types/unist": {
994
+ "version": "2.0.11",
995
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
996
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
997
+ "license": "MIT"
998
+ },
999
+ "node_modules/postcss-selector-parser": {
1000
+ "version": "6.0.10",
1001
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
1002
+ "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
1003
+ "license": "MIT",
1004
+ "dependencies": {
1005
+ "cssesc": "^3.0.0",
1006
+ "util-deprecate": "^1.0.2"
1007
+ },
1008
+ "engines": {
1009
+ "node": ">=4"
1010
+ }
1011
+ },
1012
+ "node_modules/property-information": {
1013
+ "version": "7.1.0",
1014
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
1015
+ "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==",
1016
+ "license": "MIT",
1017
+ "funding": {
1018
+ "type": "github",
1019
+ "url": "https://github.com/sponsors/wooorm"
1020
+ }
1021
+ },
1022
+ "node_modules/react": {
1023
+ "version": "19.2.4",
1024
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
1025
+ "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
1026
+ "license": "MIT",
1027
+ "peer": true,
1028
+ "engines": {
1029
+ "node": ">=0.10.0"
1030
+ }
1031
+ },
1032
+ "node_modules/react-markdown": {
1033
+ "version": "10.1.0",
1034
+ "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz",
1035
+ "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==",
1036
+ "license": "MIT",
1037
+ "dependencies": {
1038
+ "@types/hast": "^3.0.0",
1039
+ "@types/mdast": "^4.0.0",
1040
+ "devlop": "^1.0.0",
1041
+ "hast-util-to-jsx-runtime": "^2.0.0",
1042
+ "html-url-attributes": "^3.0.0",
1043
+ "mdast-util-to-hast": "^13.0.0",
1044
+ "remark-parse": "^11.0.0",
1045
+ "remark-rehype": "^11.0.0",
1046
+ "unified": "^11.0.0",
1047
+ "unist-util-visit": "^5.0.0",
1048
+ "vfile": "^6.0.0"
1049
+ },
1050
+ "funding": {
1051
+ "type": "opencollective",
1052
+ "url": "https://opencollective.com/unified"
1053
+ },
1054
+ "peerDependencies": {
1055
+ "@types/react": ">=18",
1056
+ "react": ">=18"
1057
+ }
1058
+ },
1059
+ "node_modules/remark-parse": {
1060
+ "version": "11.0.0",
1061
+ "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
1062
+ "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==",
1063
+ "license": "MIT",
1064
+ "dependencies": {
1065
+ "@types/mdast": "^4.0.0",
1066
+ "mdast-util-from-markdown": "^2.0.0",
1067
+ "micromark-util-types": "^2.0.0",
1068
+ "unified": "^11.0.0"
1069
+ },
1070
+ "funding": {
1071
+ "type": "opencollective",
1072
+ "url": "https://opencollective.com/unified"
1073
+ }
1074
+ },
1075
+ "node_modules/remark-rehype": {
1076
+ "version": "11.1.2",
1077
+ "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz",
1078
+ "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==",
1079
+ "license": "MIT",
1080
+ "dependencies": {
1081
+ "@types/hast": "^3.0.0",
1082
+ "@types/mdast": "^4.0.0",
1083
+ "mdast-util-to-hast": "^13.0.0",
1084
+ "unified": "^11.0.0",
1085
+ "vfile": "^6.0.0"
1086
+ },
1087
+ "funding": {
1088
+ "type": "opencollective",
1089
+ "url": "https://opencollective.com/unified"
1090
+ }
1091
+ },
1092
+ "node_modules/space-separated-tokens": {
1093
+ "version": "2.0.2",
1094
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
1095
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
1096
+ "license": "MIT",
1097
+ "funding": {
1098
+ "type": "github",
1099
+ "url": "https://github.com/sponsors/wooorm"
1100
+ }
1101
+ },
1102
+ "node_modules/stringify-entities": {
1103
+ "version": "4.0.4",
1104
+ "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
1105
+ "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
1106
+ "license": "MIT",
1107
+ "dependencies": {
1108
+ "character-entities-html4": "^2.0.0",
1109
+ "character-entities-legacy": "^3.0.0"
1110
+ },
1111
+ "funding": {
1112
+ "type": "github",
1113
+ "url": "https://github.com/sponsors/wooorm"
1114
+ }
1115
+ },
1116
+ "node_modules/style-to-js": {
1117
+ "version": "1.1.21",
1118
+ "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz",
1119
+ "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==",
1120
+ "license": "MIT",
1121
+ "dependencies": {
1122
+ "style-to-object": "1.0.14"
1123
+ }
1124
+ },
1125
+ "node_modules/style-to-object": {
1126
+ "version": "1.0.14",
1127
+ "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz",
1128
+ "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==",
1129
+ "license": "MIT",
1130
+ "dependencies": {
1131
+ "inline-style-parser": "0.2.7"
1132
+ }
1133
+ },
1134
+ "node_modules/tailwindcss": {
1135
+ "version": "4.2.2",
1136
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz",
1137
+ "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==",
1138
+ "license": "MIT",
1139
+ "peer": true
1140
+ },
1141
+ "node_modules/trim-lines": {
1142
+ "version": "3.0.1",
1143
+ "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
1144
+ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
1145
+ "license": "MIT",
1146
+ "funding": {
1147
+ "type": "github",
1148
+ "url": "https://github.com/sponsors/wooorm"
1149
+ }
1150
+ },
1151
+ "node_modules/trough": {
1152
+ "version": "2.2.0",
1153
+ "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz",
1154
+ "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==",
1155
+ "license": "MIT",
1156
+ "funding": {
1157
+ "type": "github",
1158
+ "url": "https://github.com/sponsors/wooorm"
1159
+ }
1160
+ },
1161
+ "node_modules/unified": {
1162
+ "version": "11.0.5",
1163
+ "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
1164
+ "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
1165
+ "license": "MIT",
1166
+ "dependencies": {
1167
+ "@types/unist": "^3.0.0",
1168
+ "bail": "^2.0.0",
1169
+ "devlop": "^1.0.0",
1170
+ "extend": "^3.0.0",
1171
+ "is-plain-obj": "^4.0.0",
1172
+ "trough": "^2.0.0",
1173
+ "vfile": "^6.0.0"
1174
+ },
1175
+ "funding": {
1176
+ "type": "opencollective",
1177
+ "url": "https://opencollective.com/unified"
1178
+ }
1179
+ },
1180
+ "node_modules/unist-util-is": {
1181
+ "version": "6.0.1",
1182
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz",
1183
+ "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==",
1184
+ "license": "MIT",
1185
+ "dependencies": {
1186
+ "@types/unist": "^3.0.0"
1187
+ },
1188
+ "funding": {
1189
+ "type": "opencollective",
1190
+ "url": "https://opencollective.com/unified"
1191
+ }
1192
+ },
1193
+ "node_modules/unist-util-position": {
1194
+ "version": "5.0.0",
1195
+ "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
1196
+ "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
1197
+ "license": "MIT",
1198
+ "dependencies": {
1199
+ "@types/unist": "^3.0.0"
1200
+ },
1201
+ "funding": {
1202
+ "type": "opencollective",
1203
+ "url": "https://opencollective.com/unified"
1204
+ }
1205
+ },
1206
+ "node_modules/unist-util-stringify-position": {
1207
+ "version": "4.0.0",
1208
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
1209
+ "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
1210
+ "license": "MIT",
1211
+ "dependencies": {
1212
+ "@types/unist": "^3.0.0"
1213
+ },
1214
+ "funding": {
1215
+ "type": "opencollective",
1216
+ "url": "https://opencollective.com/unified"
1217
+ }
1218
+ },
1219
+ "node_modules/unist-util-visit": {
1220
+ "version": "5.1.0",
1221
+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz",
1222
+ "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==",
1223
+ "license": "MIT",
1224
+ "dependencies": {
1225
+ "@types/unist": "^3.0.0",
1226
+ "unist-util-is": "^6.0.0",
1227
+ "unist-util-visit-parents": "^6.0.0"
1228
+ },
1229
+ "funding": {
1230
+ "type": "opencollective",
1231
+ "url": "https://opencollective.com/unified"
1232
+ }
1233
+ },
1234
+ "node_modules/unist-util-visit-parents": {
1235
+ "version": "6.0.2",
1236
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz",
1237
+ "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==",
1238
+ "license": "MIT",
1239
+ "dependencies": {
1240
+ "@types/unist": "^3.0.0",
1241
+ "unist-util-is": "^6.0.0"
1242
+ },
1243
+ "funding": {
1244
+ "type": "opencollective",
1245
+ "url": "https://opencollective.com/unified"
1246
+ }
1247
+ },
1248
+ "node_modules/util-deprecate": {
1249
+ "version": "1.0.2",
1250
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
1251
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
1252
+ "license": "MIT"
1253
+ },
1254
+ "node_modules/vfile": {
1255
+ "version": "6.0.3",
1256
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
1257
+ "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
1258
+ "license": "MIT",
1259
+ "dependencies": {
1260
+ "@types/unist": "^3.0.0",
1261
+ "vfile-message": "^4.0.0"
1262
+ },
1263
+ "funding": {
1264
+ "type": "opencollective",
1265
+ "url": "https://opencollective.com/unified"
1266
+ }
1267
+ },
1268
+ "node_modules/vfile-message": {
1269
+ "version": "4.0.3",
1270
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz",
1271
+ "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==",
1272
+ "license": "MIT",
1273
+ "dependencies": {
1274
+ "@types/unist": "^3.0.0",
1275
+ "unist-util-stringify-position": "^4.0.0"
1276
+ },
1277
+ "funding": {
1278
+ "type": "opencollective",
1279
+ "url": "https://opencollective.com/unified"
1280
+ }
1281
+ },
1282
+ "node_modules/zwitch": {
1283
+ "version": "2.0.4",
1284
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
1285
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
1286
+ "license": "MIT",
1287
+ "funding": {
1288
+ "type": "github",
1289
+ "url": "https://github.com/sponsors/wooorm"
1290
+ }
1291
+ }
1292
+ }
1293
+ }
package.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "dependencies": {
3
+ "@tailwindcss/typography": "^0.5.19",
4
+ "react-markdown": "^10.1.0"
5
+ }
6
+ }
plotService.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import matplotlib.pyplot as plt
2
+ import seaborn as sns
3
+ import os
4
+
5
+ def create_correlation_heatmap(corr_data, session_id):
6
+ plt.figure(figsize=(6, 4))
7
+
8
+ sns.heatmap(corr_data, annot=False, cmap="coolwarm")
9
+
10
+ file_path = f"{session_id}_correlation.png"
11
+ plt.title("Correlation Heatmap")
12
+ plt.savefig(file_path, bbox_inches="tight")
13
+ plt.close()
14
+
15
+ return file_path
16
+
17
+ def create_feature_importance_chart(feature_data, session_id):
18
+ plt.figure(figsize=(6, 4))
19
+
20
+ names = list(feature_data.keys())
21
+ values = list(feature_data.values())
22
+
23
+ plt.barh(names, values)
24
+ plt.title("Top Features")
25
+
26
+ file_path = f"{session_id}_features.png"
27
+ plt.savefig(file_path, bbox_inches="tight")
28
+ plt.close()
29
+
30
+ return file_path
31
+
32
+ def create_cluster_chart(cluster_sizes, session_id):
33
+ plt.figure(figsize=(6, 4))
34
+
35
+ labels = list(cluster_sizes.keys())
36
+ values = list(cluster_sizes.values())
37
+
38
+ plt.bar(labels, values)
39
+ plt.title("Cluster Distribution")
40
+
41
+ file_path = f"{session_id}_clusters.png"
42
+ plt.savefig(file_path, bbox_inches="tight")
43
+ plt.close()
44
+
45
+ return file_path
profile.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException
2
+ from pydantic import BaseModel
3
+ from backend.state import session_store
4
+ from backend.services.profileService import run_profile
5
+
6
+ router = APIRouter()
7
+
8
+ class SessionRequest(BaseModel):
9
+ session_id: str
10
+
11
+ @router.post("/profile")
12
+ async def profile(request: SessionRequest):
13
+ session_id = request.session_id
14
+
15
+ if session_id not in session_store:
16
+ raise HTTPException(status_code=400, detail="session code not found")
17
+
18
+ df = session_store[session_id]["df"]
19
+ return run_profile(df)
profileService.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+
3
+ def run_profile(df):
4
+ rows, cols = df.shape
5
+ rows = int(rows)
6
+ cols = int(cols)
7
+ columns = df.columns.to_list()
8
+ column_details = []
9
+ numeric_count = 0
10
+
11
+ for col in columns:
12
+ null_count = int(df[col].isnull().sum())
13
+ null_percentage = round((null_count / rows) * 100, 2)
14
+
15
+ column = {
16
+ col: str(df[col].dtype),
17
+ "null_count": null_count,
18
+ "null_percentage": null_percentage,
19
+ "high_null_warning": null_percentage > 50
20
+ }
21
+
22
+ if pd.api.types.is_numeric_dtype(df[col]):
23
+ numeric_count += 1
24
+ column["mean"] = round(float(df[col].mean()), 2)
25
+ column["median"] = round(float(df[col].median()), 2)
26
+ column["min"] = round(float(df[col].min()), 2)
27
+ column["max"] = round(float(df[col].max()), 2)
28
+
29
+ column_details.append(column)
30
+
31
+ return {
32
+ "rows": rows,
33
+ "columns": cols,
34
+ "column_name": df.columns.to_list(),
35
+ "column_details": column_details,
36
+ "numeric_column_count": numeric_count
37
+ }
promptService.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def build_prompt(summary, profile):
2
+
3
+ column_names = ", ".join(profile["column_name"])
4
+
5
+ column_details = "\n".join([
6
+ f"- {list(col.keys())[0]} | type: {list(col.values())[0]}"
7
+ for col in profile["column_details"]
8
+ ])
9
+
10
+ features_text = "\n".join(
11
+ [f"- {k}: {v}" for k, v in summary["top_features"].items()]
12
+ )
13
+
14
+ pearson_text = "\n".join([
15
+ f"- {a} & {b}: {round(v, 2)}"
16
+ for a, b, v in summary.get("top_pearson", [])
17
+ ])
18
+
19
+ spearman_text = "\n".join([
20
+ f"- {a} & {b}: {round(v, 2)}"
21
+ for a, b, v in summary.get("top_spearman", [])
22
+ ])
23
+
24
+ clusters = summary["clusters"]
25
+
26
+ if clusters.get("status") == "success":
27
+ cluster_summary = clusters.get("cluster_summary", {})
28
+
29
+ cluster_summary_text = "\n".join([
30
+ f"Cluster {k}: {v}" for k, v in cluster_summary.items()
31
+ ])
32
+
33
+ cluster_text = f"""
34
+ Cluster Analysis:
35
+ Best k: {clusters.get('best_k')}
36
+
37
+ Cluster Sizes:
38
+ {clusters.get('cluster_sizes')}
39
+
40
+ Cluster Summary:
41
+ {cluster_summary_text}
42
+ """
43
+ else:
44
+ cluster_text = f"""
45
+ Cluster Analysis:
46
+ Not applied
47
+
48
+ Reason:
49
+ {clusters.get("reason")}
50
+ """
51
+
52
+ prompt = f"""
53
+ You are a professional data analyst.
54
+
55
+ Generate a structured data analysis report.
56
+
57
+ Format:
58
+ 1. Dataset Overview
59
+ 2. Key Features
60
+ 3. Cluster Analysis
61
+ 4. Key Insights
62
+ 5. Conclusion
63
+
64
+ Dataset:
65
+ - Rows: {summary['rows']}
66
+ - Columns: {summary['columns']}
67
+ - Numeric columns: {summary['numeric_columns']}
68
+
69
+ Column Names:
70
+ {column_names}
71
+
72
+ Column Details:
73
+ {column_details}
74
+ """
75
+
76
+ prompt += f"""
77
+ Correlation Analysis:
78
+
79
+ Top Pearson (linear relationships):
80
+ {pearson_text}
81
+
82
+ Top Spearman (rank relationships):
83
+ {spearman_text}
84
+ """
85
+
86
+ if summary["mode"] == "ml":
87
+ prompt += f"""
88
+ Target Column:
89
+ {summary['target']}
90
+
91
+ Top Features:
92
+ {features_text}
93
+ """
94
+ else:
95
+ prompt += """
96
+ No target column.
97
+
98
+ Focus on patterns and relationships.
99
+ """
100
+
101
+ prompt += cluster_text
102
+
103
+ prompt += """
104
+ Instructions:
105
+ - Use simple English
106
+ - No conversational text
107
+ - Use bullet points
108
+ - Use real column names
109
+ """
110
+
111
+ return prompt
reportService.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
2
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
3
+ from reportlab.lib.enums import TA_CENTER
4
+ from fastapi.responses import FileResponse
5
+ from backend.state import session_store
6
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
7
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
8
+
9
+ def generate_pdf(text, file_path):
10
+
11
+ from reportlab.lib.enums import TA_CENTER
12
+
13
+ doc = SimpleDocTemplate(file_path)
14
+ styles = getSampleStyleSheet()
15
+
16
+ title_style = ParagraphStyle(
17
+ name="Title",
18
+ parent=styles["Heading1"],
19
+ alignment=TA_CENTER,
20
+ spaceAfter=20
21
+ )
22
+
23
+ section_style = ParagraphStyle(
24
+ name="Section",
25
+ parent=styles["Heading2"],
26
+ spaceBefore=15,
27
+ spaceAfter=10
28
+ )
29
+
30
+ bullet_style = ParagraphStyle(
31
+ name="Bullet",
32
+ parent=styles["Normal"],
33
+ leftIndent=15,
34
+ spaceAfter=5
35
+ )
36
+
37
+ normal_style = styles["Normal"]
38
+
39
+ content = []
40
+
41
+ lines = text.split("\n")
42
+
43
+ for line in lines:
44
+ line = line.strip()
45
+
46
+ line = line.replace("**", "")
47
+
48
+ if not line:
49
+ content.append(Spacer(1, 10))
50
+ continue
51
+
52
+ if "data analysis report" in line.lower():
53
+ content.append(Paragraph(line, title_style))
54
+
55
+ elif line[0].isdigit() and "." in line:
56
+ content.append(Paragraph(line, section_style))
57
+
58
+ elif line.startswith("*") or line.startswith("-"):
59
+ bullet = line.lstrip("*- ").strip()
60
+ content.append(Paragraph(f"• {bullet}", bullet_style))
61
+
62
+ else:
63
+ content.append(Paragraph(line, normal_style))
64
+
65
+ doc.build(content)
66
+
67
+ def generate_report_file(session_id):
68
+
69
+ if session_id not in session_store:
70
+ raise Exception("session not found")
71
+
72
+ ai_text = session_store[session_id].get("explanation")
73
+
74
+ if not ai_text:
75
+ raise Exception("Run /explain first")
76
+
77
+ file_path = f"report_{session_id}.pdf"
78
+
79
+ generate_pdf(ai_text, file_path)
80
+
81
+ return FileResponse(
82
+ path=file_path,
83
+ filename="report.pdf",
84
+ media_type="application/pdf"
85
+ )
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+ python-dotenv
4
+ pandas
5
+ numpy
6
+ scikit-learn
7
+ scipy
8
+ python-multipart
9
+ requests
10
+ reportlab
state.py ADDED
@@ -0,0 +1 @@
 
 
1
+ session_store = {}
upload.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, UploadFile, File, HTTPException
2
+ import io
3
+ import pandas as pd
4
+ from backend.state import session_store
5
+ import uuid
6
+ import math
7
+
8
+ router = APIRouter()
9
+
10
+ @router.post("/upload")
11
+ async def upload_file(file: UploadFile=File(...)):
12
+ session_id = str(uuid.uuid4())
13
+ if not file.filename.endswith(".csv"): # type: ignore
14
+ raise HTTPException(status_code=400, detail="Only CSV files are accepted")
15
+
16
+ content = await file.read()
17
+
18
+ if len(content) > 20 * 1024 * 1024:
19
+ raise HTTPException(status_code=400, detail="File too large. Maximum size is 10MB")
20
+
21
+ try:
22
+ df = pd.read_csv(io.BytesIO(content), na_values=['?', 'NA', 'N/A', 'na', 'n/a', ''])
23
+ except Exception:
24
+ raise HTTPException(status_code=400, detail="Could not parse CSV. Check the file format")
25
+
26
+ def clean_nan(data):
27
+ if isinstance(data, dict):
28
+ return {k: clean_nan(v) for k, v in data.items()}
29
+ elif isinstance(data, list):
30
+ return [clean_nan(v) for v in data]
31
+ elif isinstance(data, float) and math.isnan(data):
32
+ return None
33
+ return data
34
+
35
+ rows, cols = df.shape
36
+ preview = clean_nan(df.head(5).to_dict(orient="records"))
37
+ session_store[session_id] = {
38
+ "df": df,
39
+ "summary": None,
40
+ "explanation": None
41
+ }
42
+
43
+ return {
44
+ "filename": file.filename,
45
+ "rows": rows,
46
+ "columns": cols,
47
+ "column_names": df.columns.tolist(),
48
+ "session_id": session_id,
49
+ "preview": preview
50
+ }
51
+
52
+
53
+