Spaces:
Sleeping
Sleeping
[KM-383] [CEX] [AI] Deployment AI Engine / BE
Browse files- .streamlit/config.toml +7 -0
- externals/observability/langfuse.py +5 -5
- pyproject.toml +5 -0
- services/agentic/profile_scoring.py +9 -5
- services/base/BaseGenerator.py +78 -30
- services/base/BaseGenerator_v2.py +169 -0
- services/knowledge/extract_profile.py +15 -5
- services/models/data_model.py +3 -3
- streamlit_app.py +725 -0
- utils/decorator.py +1 -1
- uv.lock +314 -0
.streamlit/config.toml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[server]
|
| 2 |
+
enableXsrfProtection = false
|
| 3 |
+
enableCORS = false
|
| 4 |
+
maxUploadSize = 3
|
| 5 |
+
|
| 6 |
+
[theme]
|
| 7 |
+
base="light"
|
externals/observability/langfuse.py
CHANGED
|
@@ -6,11 +6,11 @@ from langfuse import Langfuse
|
|
| 6 |
from langchain_core.callbacks.base import BaseCallbackHandler
|
| 7 |
|
| 8 |
|
| 9 |
-
langfuse = Langfuse(
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
)
|
| 14 |
|
| 15 |
|
| 16 |
langfuse_handler = BaseCallbackHandler()
|
|
|
|
| 6 |
from langchain_core.callbacks.base import BaseCallbackHandler
|
| 7 |
|
| 8 |
|
| 9 |
+
# langfuse = Langfuse(
|
| 10 |
+
# secret_key=os.environ.get('ss__langfuse__secret_key'),
|
| 11 |
+
# public_key=os.environ.get('ss__langfuse__public_key'),
|
| 12 |
+
# host=os.environ.get('langfuse__host'),
|
| 13 |
+
# )
|
| 14 |
|
| 15 |
|
| 16 |
langfuse_handler = BaseCallbackHandler()
|
pyproject.toml
CHANGED
|
@@ -29,3 +29,8 @@ dependencies = [
|
|
| 29 |
"sqlalchemy>=2.0.45",
|
| 30 |
"uvicorn>=0.40.0",
|
| 31 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
"sqlalchemy>=2.0.45",
|
| 30 |
"uvicorn>=0.40.0",
|
| 31 |
]
|
| 32 |
+
|
| 33 |
+
[dependency-groups]
|
| 34 |
+
dev = [
|
| 35 |
+
"streamlit>=1.54.0",
|
| 36 |
+
]
|
services/agentic/profile_scoring.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
import traceback
|
|
|
|
| 2 |
from typing import List
|
| 3 |
from langchain_core.prompts import ChatPromptTemplate
|
| 4 |
|
|
@@ -6,7 +7,8 @@ from config.constant import ProfileFieldTypes
|
|
| 6 |
# from externals.databases.pg_crud import get_criteria_id, create_cv_filter, create_cv_matching, get_matching_id, create_cv_score, get_scoring_id
|
| 7 |
from externals.databases.pg_models import CVProfile, CVWeight, CVFilter, CVScore, CVMatching
|
| 8 |
from services.llms.LLM import model_4o_2
|
| 9 |
-
from services.base.BaseGenerator import BaseAIGenerator, MetadataObservability
|
|
|
|
| 10 |
from services.models.data_model import AIProfile
|
| 11 |
from services.models.data_model import (
|
| 12 |
AIMatchProfile,
|
|
@@ -89,13 +91,14 @@ def comparison_parser(profile:AIProfile, criteria:Criteria):
|
|
| 89 |
for k, v in criteria.items():
|
| 90 |
print(f" key comparison: {k}")
|
| 91 |
op = helper_get_operator(k)
|
| 92 |
-
if 'gpa' in k or 'yoe' in k:
|
| 93 |
judges = helper_judge_scoring(a_profile=profile.get(k),
|
| 94 |
b_criteria=v,
|
| 95 |
rules=op)
|
|
|
|
| 96 |
else:
|
| 97 |
judges = '???'
|
| 98 |
-
|
| 99 |
return comparison
|
| 100 |
|
| 101 |
|
|
@@ -403,14 +406,15 @@ class AgenticScoringService:
|
|
| 403 |
llm = model_4o_2.with_structured_output(AIMatchProfile)
|
| 404 |
|
| 405 |
gen_ai = BaseAIGenerator(
|
| 406 |
-
task_name=
|
| 407 |
prompt=prompt,
|
| 408 |
llm=llm,
|
| 409 |
input_llm=input_llm,
|
| 410 |
metadata_observability=MetadataObservability(
|
| 411 |
fullname=self.user.full_name,
|
| 412 |
-
task_id=str(
|
| 413 |
agent=self._ai_matching.__name__,
|
|
|
|
| 414 |
)
|
| 415 |
)
|
| 416 |
|
|
|
|
| 1 |
import traceback
|
| 2 |
+
from uuid import uuid4
|
| 3 |
from typing import List
|
| 4 |
from langchain_core.prompts import ChatPromptTemplate
|
| 5 |
|
|
|
|
| 7 |
# from externals.databases.pg_crud import get_criteria_id, create_cv_filter, create_cv_matching, get_matching_id, create_cv_score, get_scoring_id
|
| 8 |
from externals.databases.pg_models import CVProfile, CVWeight, CVFilter, CVScore, CVMatching
|
| 9 |
from services.llms.LLM import model_4o_2
|
| 10 |
+
# from services.base.BaseGenerator import BaseAIGenerator, MetadataObservability
|
| 11 |
+
from services.base.BaseGenerator_v2 import BaseAIGenerator, MetadataObservability
|
| 12 |
from services.models.data_model import AIProfile
|
| 13 |
from services.models.data_model import (
|
| 14 |
AIMatchProfile,
|
|
|
|
| 91 |
for k, v in criteria.items():
|
| 92 |
print(f" key comparison: {k}")
|
| 93 |
op = helper_get_operator(k)
|
| 94 |
+
if ('gpa' in k or 'yoe' in k) and (profile.get(k) is not None) and (v is not None):
|
| 95 |
judges = helper_judge_scoring(a_profile=profile.get(k),
|
| 96 |
b_criteria=v,
|
| 97 |
rules=op)
|
| 98 |
+
comparison += f"\n| {k} | {profile.get(k)} | {v} | {op} | {judges} |"
|
| 99 |
else:
|
| 100 |
judges = '???'
|
| 101 |
+
comparison += f"\n| {k} | {profile.get(k)} | NA | NA | False |"
|
| 102 |
return comparison
|
| 103 |
|
| 104 |
|
|
|
|
| 406 |
llm = model_4o_2.with_structured_output(AIMatchProfile)
|
| 407 |
|
| 408 |
gen_ai = BaseAIGenerator(
|
| 409 |
+
task_name="ai matching",
|
| 410 |
prompt=prompt,
|
| 411 |
llm=llm,
|
| 412 |
input_llm=input_llm,
|
| 413 |
metadata_observability=MetadataObservability(
|
| 414 |
fullname=self.user.full_name,
|
| 415 |
+
task_id=str(uuid4()),
|
| 416 |
agent=self._ai_matching.__name__,
|
| 417 |
+
user_id=self.user.email
|
| 418 |
)
|
| 419 |
)
|
| 420 |
|
services/base/BaseGenerator.py
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from pydantic import BaseModel
|
| 2 |
from langchain_core.prompts import ChatPromptTemplate
|
| 3 |
from langchain_openai import AzureChatOpenAI
|
|
@@ -13,6 +16,7 @@ from typing import Dict
|
|
| 13 |
from services.llms.LLM import model_5mini, model_4omini
|
| 14 |
from utils.decorator import trace_runtime
|
| 15 |
from utils.logger import get_logger
|
|
|
|
| 16 |
|
| 17 |
logger = get_logger("base generator")
|
| 18 |
|
|
@@ -21,6 +25,7 @@ class MetadataObservability(BaseModel):
|
|
| 21 |
fullname: str
|
| 22 |
task_id: str
|
| 23 |
agent: str
|
|
|
|
| 24 |
|
| 25 |
|
| 26 |
class BaseAIGenerator:
|
|
@@ -31,26 +36,44 @@ class BaseAIGenerator:
|
|
| 31 |
metadata_observability: MetadataObservability,
|
| 32 |
llm: AzureChatOpenAI = model_5mini | model_4omini,
|
| 33 |
):
|
| 34 |
-
self.
|
| 35 |
self.llm = llm
|
| 36 |
self.prompt = prompt
|
| 37 |
self.input_llm = input_llm
|
| 38 |
-
self.
|
| 39 |
|
| 40 |
-
def
|
| 41 |
try:
|
| 42 |
-
import os
|
| 43 |
-
from config.constant import LangfuseConstants # adjust import path if needed
|
| 44 |
os.environ["LANGFUSE_PUBLIC_KEY"] = LangfuseConstants.PUBLIC_KEY
|
| 45 |
os.environ["LANGFUSE_SECRET_KEY"] = LangfuseConstants.SECRET_KEY
|
| 46 |
os.environ["LANGFUSE_HOST"] = LangfuseConstants.HOST or "https://us.cloud.langfuse.com"
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
return CallbackHandler()
|
| 50 |
except Exception as e:
|
| 51 |
logger.warning(f"⚠️ Langfuse unavailable, skipping observability: {e}")
|
| 52 |
return None
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
@retry(
|
| 55 |
reraise=True,
|
| 56 |
stop=stop_after_attempt(2),
|
|
@@ -72,35 +95,60 @@ class BaseAIGenerator:
|
|
| 72 |
@trace_runtime
|
| 73 |
async def agenerate(self):
|
| 74 |
try:
|
| 75 |
-
|
| 76 |
-
config = {"callbacks": [handler]} if handler else {}
|
| 77 |
chain = self.prompt | self.llm
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
logger.exception("❌ BaseGenerator agenerate error")
|
| 88 |
return None
|
| 89 |
|
| 90 |
@trace_runtime
|
| 91 |
async def generate(self):
|
| 92 |
try:
|
| 93 |
-
|
| 94 |
-
config = {"callbacks": [handler]} if handler else {}
|
| 95 |
chain = self.prompt | self.llm
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
logger.exception("❌ BaseGenerator generate error")
|
| 106 |
return None
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from config.constant import LangfuseConstants
|
| 3 |
+
from langfuse.langchain import CallbackHandler
|
| 4 |
from pydantic import BaseModel
|
| 5 |
from langchain_core.prompts import ChatPromptTemplate
|
| 6 |
from langchain_openai import AzureChatOpenAI
|
|
|
|
| 16 |
from services.llms.LLM import model_5mini, model_4omini
|
| 17 |
from utils.decorator import trace_runtime
|
| 18 |
from utils.logger import get_logger
|
| 19 |
+
from langfuse import get_client, Langfuse
|
| 20 |
|
| 21 |
logger = get_logger("base generator")
|
| 22 |
|
|
|
|
| 25 |
fullname: str
|
| 26 |
task_id: str
|
| 27 |
agent: str
|
| 28 |
+
user_id: str
|
| 29 |
|
| 30 |
|
| 31 |
class BaseAIGenerator:
|
|
|
|
| 36 |
metadata_observability: MetadataObservability,
|
| 37 |
llm: AzureChatOpenAI = model_5mini | model_4omini,
|
| 38 |
):
|
| 39 |
+
self.metadata_observability = metadata_observability
|
| 40 |
self.llm = llm
|
| 41 |
self.prompt = prompt
|
| 42 |
self.input_llm = input_llm
|
| 43 |
+
self.name = task_name
|
| 44 |
|
| 45 |
+
def _get_langfuse_client(self):
|
| 46 |
try:
|
|
|
|
|
|
|
| 47 |
os.environ["LANGFUSE_PUBLIC_KEY"] = LangfuseConstants.PUBLIC_KEY
|
| 48 |
os.environ["LANGFUSE_SECRET_KEY"] = LangfuseConstants.SECRET_KEY
|
| 49 |
os.environ["LANGFUSE_HOST"] = LangfuseConstants.HOST or "https://us.cloud.langfuse.com"
|
| 50 |
+
langfuse = Langfuse()
|
| 51 |
+
return langfuse
|
|
|
|
| 52 |
except Exception as e:
|
| 53 |
logger.warning(f"⚠️ Langfuse unavailable, skipping observability: {e}")
|
| 54 |
return None
|
| 55 |
|
| 56 |
+
def _get_langfuse_config(self):
|
| 57 |
+
try:
|
| 58 |
+
os.environ["LANGFUSE_PUBLIC_KEY"] = LangfuseConstants.PUBLIC_KEY
|
| 59 |
+
os.environ["LANGFUSE_SECRET_KEY"] = LangfuseConstants.SECRET_KEY
|
| 60 |
+
os.environ["LANGFUSE_HOST"] = LangfuseConstants.HOST or "https://us.cloud.langfuse.com"
|
| 61 |
+
|
| 62 |
+
handler = CallbackHandler(update_trace=True)
|
| 63 |
+
|
| 64 |
+
return {
|
| 65 |
+
"callbacks": [handler],
|
| 66 |
+
"metadata": {
|
| 67 |
+
"langfuse_session_id": self.metadata_observability.task_id,
|
| 68 |
+
"langfuse_user_id": self.metadata_observability.fullname,
|
| 69 |
+
"langfuse_tags": [self.metadata_observability.agent],
|
| 70 |
+
"langfuse_trace_name": self.name,
|
| 71 |
+
},
|
| 72 |
+
}
|
| 73 |
+
except Exception as e:
|
| 74 |
+
logger.warning(f"⚠️ Langfuse unavailable, skipping observability: {e}")
|
| 75 |
+
return {}
|
| 76 |
+
|
| 77 |
@retry(
|
| 78 |
reraise=True,
|
| 79 |
stop=stop_after_attempt(2),
|
|
|
|
| 95 |
@trace_runtime
|
| 96 |
async def agenerate(self):
|
| 97 |
try:
|
| 98 |
+
config = self._get_langfuse_config()
|
|
|
|
| 99 |
chain = self.prompt | self.llm
|
| 100 |
+
langfuse_client = self._get_langfuse_client()
|
| 101 |
+
trace_id = Langfuse.create_trace_id(seed=self.metadata_observability.task_id)
|
| 102 |
+
|
| 103 |
+
with langfuse_client.start_as_current_observation(
|
| 104 |
+
as_type='generation',
|
| 105 |
+
name=self.name,
|
| 106 |
+
metadata=self.metadata_observability,
|
| 107 |
+
input=self.input_llm,
|
| 108 |
+
trace_context={"trace_id": trace_id},
|
| 109 |
+
) as span:
|
| 110 |
+
span.update_trace(
|
| 111 |
+
name=self.name,
|
| 112 |
+
user_id=self.metadata_observability.user_id)
|
| 113 |
+
output = await self._asafe_invoke(
|
| 114 |
+
chain=chain,
|
| 115 |
+
input_llm=self.input_llm,
|
| 116 |
+
config=config,
|
| 117 |
+
)
|
| 118 |
+
span.update_trace(output=output)
|
| 119 |
+
return output
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
except Exception:
|
| 123 |
logger.exception("❌ BaseGenerator agenerate error")
|
| 124 |
return None
|
| 125 |
|
| 126 |
@trace_runtime
|
| 127 |
async def generate(self):
|
| 128 |
try:
|
| 129 |
+
config = self._get_langfuse_config()
|
|
|
|
| 130 |
chain = self.prompt | self.llm
|
| 131 |
+
langfuse_client = self._get_langfuse_client()
|
| 132 |
+
trace_id = Langfuse.create_trace_id(seed=self.metadata_observability.task_id)
|
| 133 |
+
|
| 134 |
+
with langfuse_client.start_as_current_observation(
|
| 135 |
+
as_type='generation',
|
| 136 |
+
name=self.name,
|
| 137 |
+
metadata=self.metadata_observability,
|
| 138 |
+
input=self.input_llm,
|
| 139 |
+
trace_context={"trace_id": trace_id},
|
| 140 |
+
) as span:
|
| 141 |
+
span.update_trace(
|
| 142 |
+
name=self.name,
|
| 143 |
+
user_id=self.metadata_observability.user_id)
|
| 144 |
+
output = self._safe_invoke(
|
| 145 |
+
chain=chain,
|
| 146 |
+
input_llm=self.input_llm,
|
| 147 |
+
config=config,
|
| 148 |
+
)
|
| 149 |
+
span.update_trace(output=output)
|
| 150 |
+
return output
|
| 151 |
+
|
| 152 |
+
except Exception:
|
| 153 |
logger.exception("❌ BaseGenerator generate error")
|
| 154 |
return None
|
services/base/BaseGenerator_v2.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from langfuse import get_client, Langfuse, propagate_attributes
|
| 2 |
+
from langfuse.langchain import CallbackHandler
|
| 3 |
+
import os
|
| 4 |
+
from config.constant import LangfuseConstants
|
| 5 |
+
from pydantic import BaseModel
|
| 6 |
+
from langchain_core.prompts import ChatPromptTemplate
|
| 7 |
+
from langchain_openai import AzureChatOpenAI
|
| 8 |
+
from tenacity import (
|
| 9 |
+
retry,
|
| 10 |
+
stop_after_attempt,
|
| 11 |
+
wait_exponential,
|
| 12 |
+
retry_if_exception_type
|
| 13 |
+
)
|
| 14 |
+
from typing import Dict
|
| 15 |
+
from services.llms.LLM import model_5mini, model_4omini
|
| 16 |
+
from utils.decorator import trace_runtime
|
| 17 |
+
from utils.logger import get_logger
|
| 18 |
+
|
| 19 |
+
logger = get_logger("base generator")
|
| 20 |
+
|
| 21 |
+
# Set environment variables at module level
|
| 22 |
+
os.environ["LANGFUSE_PUBLIC_KEY"] = LangfuseConstants.PUBLIC_KEY
|
| 23 |
+
os.environ["LANGFUSE_SECRET_KEY"] = LangfuseConstants.SECRET_KEY
|
| 24 |
+
os.environ["LANGFUSE_HOST"] = LangfuseConstants.HOST or "https://us.cloud.langfuse.com"
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class MetadataObservability(BaseModel):
|
| 28 |
+
fullname: str
|
| 29 |
+
task_id: str
|
| 30 |
+
agent: str
|
| 31 |
+
user_id: str
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class BaseAIGenerator:
|
| 35 |
+
def __init__(self,
|
| 36 |
+
task_name: str,
|
| 37 |
+
prompt: ChatPromptTemplate,
|
| 38 |
+
input_llm: Dict,
|
| 39 |
+
metadata_observability: MetadataObservability,
|
| 40 |
+
llm: AzureChatOpenAI = model_5mini | model_4omini,
|
| 41 |
+
):
|
| 42 |
+
self.metadata_observability = metadata_observability
|
| 43 |
+
self.llm = llm
|
| 44 |
+
self.prompt = prompt
|
| 45 |
+
self.input_llm = input_llm
|
| 46 |
+
self.name = task_name
|
| 47 |
+
|
| 48 |
+
def _get_langfuse_client(self):
|
| 49 |
+
try:
|
| 50 |
+
# Environment variables already set at module level
|
| 51 |
+
return get_client()
|
| 52 |
+
except Exception as e:
|
| 53 |
+
logger.warning(f"⚠️ Langfuse unavailable, skipping observability: {e}")
|
| 54 |
+
return None
|
| 55 |
+
|
| 56 |
+
def _get_langfuse_config(self):
|
| 57 |
+
try:
|
| 58 |
+
# Environment variables already set at module level
|
| 59 |
+
handler = CallbackHandler()
|
| 60 |
+
|
| 61 |
+
return {
|
| 62 |
+
"callbacks": [handler],
|
| 63 |
+
"metadata": {
|
| 64 |
+
"langfuse_session_id": self.metadata_observability.task_id,
|
| 65 |
+
"langfuse_user_id": self.metadata_observability.user_id,
|
| 66 |
+
"langfuse_tags": [self.metadata_observability.agent],
|
| 67 |
+
},
|
| 68 |
+
}
|
| 69 |
+
except Exception as e:
|
| 70 |
+
logger.warning(f"⚠️ Langfuse unavailable, skipping observability: {e}")
|
| 71 |
+
return {}
|
| 72 |
+
|
| 73 |
+
@retry(
|
| 74 |
+
reraise=True,
|
| 75 |
+
stop=stop_after_attempt(2),
|
| 76 |
+
wait=wait_exponential(multiplier=1, min=1, max=5),
|
| 77 |
+
retry=retry_if_exception_type(Exception)
|
| 78 |
+
)
|
| 79 |
+
async def _asafe_invoke(self, chain, input_llm, config):
|
| 80 |
+
return await chain.ainvoke(input_llm, config=config)
|
| 81 |
+
|
| 82 |
+
@retry(
|
| 83 |
+
reraise=True,
|
| 84 |
+
stop=stop_after_attempt(2),
|
| 85 |
+
wait=wait_exponential(multiplier=1, min=1, max=5),
|
| 86 |
+
retry=retry_if_exception_type(Exception)
|
| 87 |
+
)
|
| 88 |
+
def _safe_invoke(self, chain, input_llm, config):
|
| 89 |
+
return chain.invoke(input_llm, config=config)
|
| 90 |
+
|
| 91 |
+
@trace_runtime
|
| 92 |
+
async def agenerate(self):
|
| 93 |
+
try:
|
| 94 |
+
config = self._get_langfuse_config()
|
| 95 |
+
chain = self.prompt | self.llm
|
| 96 |
+
langfuse_client = self._get_langfuse_client()
|
| 97 |
+
|
| 98 |
+
if not langfuse_client:
|
| 99 |
+
return await self._asafe_invoke(chain, self.input_llm, config)
|
| 100 |
+
|
| 101 |
+
trace_id = Langfuse.create_trace_id(seed=self.metadata_observability.task_id)
|
| 102 |
+
|
| 103 |
+
with langfuse_client.start_as_current_observation(
|
| 104 |
+
as_type="generation",
|
| 105 |
+
name=self.name,
|
| 106 |
+
trace_context={"trace_id": trace_id},
|
| 107 |
+
metadata=self.metadata_observability,
|
| 108 |
+
) as span:
|
| 109 |
+
with propagate_attributes(
|
| 110 |
+
user_id=self.metadata_observability.user_id,
|
| 111 |
+
session_id=self.metadata_observability.task_id,
|
| 112 |
+
tags=[self.metadata_observability.agent],
|
| 113 |
+
):
|
| 114 |
+
span.update_trace(
|
| 115 |
+
input=self.input_llm,
|
| 116 |
+
)
|
| 117 |
+
|
| 118 |
+
output = await self._asafe_invoke(
|
| 119 |
+
chain=chain,
|
| 120 |
+
input_llm=self.input_llm,
|
| 121 |
+
config=config,
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
span.update_trace(output=output)
|
| 125 |
+
return output
|
| 126 |
+
|
| 127 |
+
except Exception:
|
| 128 |
+
logger.exception("❌ BaseGenerator agenerate error")
|
| 129 |
+
return None
|
| 130 |
+
|
| 131 |
+
@trace_runtime
|
| 132 |
+
def generate(self):
|
| 133 |
+
try:
|
| 134 |
+
config = self._get_langfuse_config()
|
| 135 |
+
chain = self.prompt | self.llm
|
| 136 |
+
langfuse_client = self._get_langfuse_client()
|
| 137 |
+
|
| 138 |
+
if not langfuse_client:
|
| 139 |
+
return self._safe_invoke(chain, self.input_llm, config)
|
| 140 |
+
|
| 141 |
+
trace_id = Langfuse.create_trace_id(seed=self.metadata_observability.task_id)
|
| 142 |
+
|
| 143 |
+
with langfuse_client.start_as_current_observation(
|
| 144 |
+
as_type="generation",
|
| 145 |
+
name=self.name,
|
| 146 |
+
trace_context={"trace_id": trace_id},
|
| 147 |
+
metadata=self.metadata_observability,
|
| 148 |
+
) as span:
|
| 149 |
+
with propagate_attributes(
|
| 150 |
+
user_id=self.metadata_observability.user_id,
|
| 151 |
+
session_id=self.metadata_observability.task_id,
|
| 152 |
+
tags=[self.metadata_observability.agent],
|
| 153 |
+
):
|
| 154 |
+
span.update_trace(
|
| 155 |
+
input=self.input_llm,
|
| 156 |
+
)
|
| 157 |
+
|
| 158 |
+
output = self._safe_invoke(
|
| 159 |
+
chain=chain,
|
| 160 |
+
input_llm=self.input_llm,
|
| 161 |
+
config=config,
|
| 162 |
+
)
|
| 163 |
+
|
| 164 |
+
span.update_trace(output=output)
|
| 165 |
+
return output
|
| 166 |
+
|
| 167 |
+
except Exception:
|
| 168 |
+
logger.exception("❌ BaseGenerator generate error")
|
| 169 |
+
return None
|
services/knowledge/extract_profile.py
CHANGED
|
@@ -5,7 +5,8 @@ from typing import TypedDict, List, Dict, Union
|
|
| 5 |
from langchain_core.prompts import ChatPromptTemplate
|
| 6 |
|
| 7 |
from services.models.data_model import AIProfile, RawProfile, Profiles
|
| 8 |
-
from services.base.BaseGenerator import BaseAIGenerator, MetadataObservability
|
|
|
|
| 9 |
from services.llms.LLM import model_5mini, model_4o_2
|
| 10 |
from utils.decorator import trace_runtime
|
| 11 |
from utils.logger import get_logger
|
|
@@ -16,6 +17,7 @@ logger = get_logger("profile extraction")
|
|
| 16 |
from sqlalchemy.ext.asyncio import AsyncSession
|
| 17 |
from externals.databases.pg_crud import (
|
| 18 |
get_file_by_filename,
|
|
|
|
| 19 |
mark_file_extracted,
|
| 20 |
create_profile,
|
| 21 |
)
|
|
@@ -47,6 +49,9 @@ class KnowledgeExtractService:
|
|
| 47 |
|
| 48 |
if file.is_extracted:
|
| 49 |
logger.info(f"ℹ️ File already extracted: {filename}")
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
# 2️⃣ Download PDF
|
| 52 |
pdf_bytes: bytes = await download_blob_by_filename(
|
|
@@ -133,8 +138,12 @@ class KnowledgeExtractService:
|
|
| 133 |
|
| 134 |
**Instructions**:
|
| 135 |
1. Read the provided CV and extract information needed based on expected output.
|
| 136 |
-
2.
|
| 137 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
""".strip()
|
| 139 |
prompt = ChatPromptTemplate.from_template(extract_one_profile_prompt)
|
| 140 |
input_llm = {
|
|
@@ -144,14 +153,15 @@ class KnowledgeExtractService:
|
|
| 144 |
llm = model_4o_2.with_structured_output(AIProfile)
|
| 145 |
|
| 146 |
gen_ai = BaseAIGenerator(
|
| 147 |
-
task_name=
|
| 148 |
prompt=prompt,
|
| 149 |
input_llm=input_llm,
|
| 150 |
llm=llm,
|
| 151 |
metadata_observability=MetadataObservability(
|
| 152 |
fullname=self.user.full_name,
|
| 153 |
-
task_id=str(
|
| 154 |
agent=self.extract.__name__,
|
|
|
|
| 155 |
)
|
| 156 |
)
|
| 157 |
result = await gen_ai.agenerate()
|
|
|
|
| 5 |
from langchain_core.prompts import ChatPromptTemplate
|
| 6 |
|
| 7 |
from services.models.data_model import AIProfile, RawProfile, Profiles
|
| 8 |
+
# from services.base.BaseGenerator import BaseAIGenerator, MetadataObservability
|
| 9 |
+
from services.base.BaseGenerator_v2 import BaseAIGenerator, MetadataObservability
|
| 10 |
from services.llms.LLM import model_5mini, model_4o_2
|
| 11 |
from utils.decorator import trace_runtime
|
| 12 |
from utils.logger import get_logger
|
|
|
|
| 17 |
from sqlalchemy.ext.asyncio import AsyncSession
|
| 18 |
from externals.databases.pg_crud import (
|
| 19 |
get_file_by_filename,
|
| 20 |
+
get_profile_by_filename,
|
| 21 |
mark_file_extracted,
|
| 22 |
create_profile,
|
| 23 |
)
|
|
|
|
| 49 |
|
| 50 |
if file.is_extracted:
|
| 51 |
logger.info(f"ℹ️ File already extracted: {filename}")
|
| 52 |
+
existing = await get_profile_by_filename(self.db, filename=filename, current_user=self.user)
|
| 53 |
+
if existing:
|
| 54 |
+
return existing
|
| 55 |
|
| 56 |
# 2️⃣ Download PDF
|
| 57 |
pdf_bytes: bytes = await download_blob_by_filename(
|
|
|
|
| 138 |
|
| 139 |
**Instructions**:
|
| 140 |
1. Read the provided CV and extract information needed based on expected output.
|
| 141 |
+
2. Be Careful when to extract information for gpa_edu_1, univ_edu_1, major_edu_1 or gpa_edu_2, univ_edu_2, major_edu_2 or gpa_edu_3, univ_edu_3, major_edu_3
|
| 142 |
+
..._edu_1 is only for bachelor/undergraduate/sarjana degree
|
| 143 |
+
..._edu_2 is only for master/postgraduate degree
|
| 144 |
+
..._edu_3 is only for doctor/phd degree
|
| 145 |
+
3. Reformat the extracted info using correct word spacing.
|
| 146 |
+
4. Do not verbose, just return the final answer.
|
| 147 |
""".strip()
|
| 148 |
prompt = ChatPromptTemplate.from_template(extract_one_profile_prompt)
|
| 149 |
input_llm = {
|
|
|
|
| 153 |
llm = model_4o_2.with_structured_output(AIProfile)
|
| 154 |
|
| 155 |
gen_ai = BaseAIGenerator(
|
| 156 |
+
task_name="extract profile",
|
| 157 |
prompt=prompt,
|
| 158 |
input_llm=input_llm,
|
| 159 |
llm=llm,
|
| 160 |
metadata_observability=MetadataObservability(
|
| 161 |
fullname=self.user.full_name,
|
| 162 |
+
task_id=str(uuid4()),
|
| 163 |
agent=self.extract.__name__,
|
| 164 |
+
user_id=self.user.email,
|
| 165 |
)
|
| 166 |
)
|
| 167 |
result = await gen_ai.agenerate()
|
services/models/data_model.py
CHANGED
|
@@ -32,15 +32,15 @@ class AIProfile(TypedDict):
|
|
| 32 |
fullname: str = Field(description="Fullname of the candidate", default="-")
|
| 33 |
# gender: str = Field(description="Gender of the candidate, if available", default="null")
|
| 34 |
# age: int = Field(description="Age in number")
|
| 35 |
-
gpa_edu_1: float = Field(description="""GPA of candidate's bachelor degree, if exists.""", default=0)
|
| 36 |
univ_edu_1: str = Field(description="""University where candidate take bachelor degree, if exists.""", default="-")
|
| 37 |
major_edu_1: str = Field(description="""Major of candidate's bachelor degree, if exists.""", default="-")
|
| 38 |
|
| 39 |
-
gpa_edu_2: float = Field(description="""GPA of candidate's master degree, if exists.""", default=0)
|
| 40 |
univ_edu_2: str = Field(description="""University where candidate take master degree, if exists.""", default="-")
|
| 41 |
major_edu_2: str = Field(description="""Major of candidate's master degree, if exists.""", default="-")
|
| 42 |
|
| 43 |
-
gpa_edu_3: float = Field(description="""GPA of candidate's doctoral or phd degree, if exists.""", default=0)
|
| 44 |
univ_edu_3: str = Field(description="""University where candidate take doctoral or phd degree, if exists.""", default="-")
|
| 45 |
major_edu_3: str = Field(description="""Major of candidate's doctoral or phd degree, if exists.""", default="-")
|
| 46 |
|
|
|
|
| 32 |
fullname: str = Field(description="Fullname of the candidate", default="-")
|
| 33 |
# gender: str = Field(description="Gender of the candidate, if available", default="null")
|
| 34 |
# age: int = Field(description="Age in number")
|
| 35 |
+
gpa_edu_1: float = Field(description="""GPA of candidate's bachelor degree (same like sarjana, s1, undergradute), if exists.""", default=0)
|
| 36 |
univ_edu_1: str = Field(description="""University where candidate take bachelor degree, if exists.""", default="-")
|
| 37 |
major_edu_1: str = Field(description="""Major of candidate's bachelor degree, if exists.""", default="-")
|
| 38 |
|
| 39 |
+
gpa_edu_2: float = Field(description="""GPA of candidate's master degree (same like master, s2, postgraduate), if exists.""", default=0)
|
| 40 |
univ_edu_2: str = Field(description="""University where candidate take master degree, if exists.""", default="-")
|
| 41 |
major_edu_2: str = Field(description="""Major of candidate's master degree, if exists.""", default="-")
|
| 42 |
|
| 43 |
+
gpa_edu_3: float = Field(description="""GPA of candidate's doctoral or phd degree (same like phd, s3, doctoral), if exists.""", default=0)
|
| 44 |
univ_edu_3: str = Field(description="""University where candidate take doctoral or phd degree, if exists.""", default="-")
|
| 45 |
major_edu_3: str = Field(description="""Major of candidate's doctoral or phd degree, if exists.""", default="-")
|
| 46 |
|
streamlit_app.py
ADDED
|
@@ -0,0 +1,725 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import dotenv
|
| 3 |
+
dotenv.load_dotenv()
|
| 4 |
+
import json
|
| 5 |
+
import requests
|
| 6 |
+
import streamlit as st
|
| 7 |
+
|
| 8 |
+
# ─────────────────────────────────────────────
|
| 9 |
+
# CONFIG
|
| 10 |
+
# ─────────────────────────────────────────────
|
| 11 |
+
BASE_URL = os.environ.get("BACKEND_BASE_URL", "http://localhost:8000")
|
| 12 |
+
|
| 13 |
+
st.set_page_config(
|
| 14 |
+
page_title="Candidate Explorer",
|
| 15 |
+
page_icon="🔍",
|
| 16 |
+
layout="wide",
|
| 17 |
+
initial_sidebar_state="expanded",
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
# ─────────────────────────────────────────────
|
| 21 |
+
# GLOBAL CSS
|
| 22 |
+
# ─────────────────────────────────────────────
|
| 23 |
+
st.markdown(
|
| 24 |
+
"""
|
| 25 |
+
<style>
|
| 26 |
+
/* ── Global ─────────────────────────────── */
|
| 27 |
+
html, body, [class*="css"] {
|
| 28 |
+
font-family: 'Inter', 'Segoe UI', sans-serif;
|
| 29 |
+
color: #333e4a;
|
| 30 |
+
background-color: #ffffff;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
/* ── Sidebar ─────────────────────────────── */
|
| 34 |
+
[data-testid="stSidebar"] {
|
| 35 |
+
background-color: #f4f6ff;
|
| 36 |
+
border-right: 1px solid #c7cef5;
|
| 37 |
+
}
|
| 38 |
+
[data-testid="stSidebar"] h1,
|
| 39 |
+
[data-testid="stSidebar"] h2,
|
| 40 |
+
[data-testid="stSidebar"] h3,
|
| 41 |
+
[data-testid="stSidebar"] label {
|
| 42 |
+
color: #435cdc !important;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
/* ── Buttons ─────────────────────────────── */
|
| 46 |
+
.stButton > button {
|
| 47 |
+
background-color: #435cdc;
|
| 48 |
+
color: #ffffff;
|
| 49 |
+
border: none;
|
| 50 |
+
border-radius: 8px;
|
| 51 |
+
padding: 0.5rem 1.25rem;
|
| 52 |
+
font-weight: 600;
|
| 53 |
+
transition: background-color 0.2s ease;
|
| 54 |
+
}
|
| 55 |
+
.stButton > button:hover {
|
| 56 |
+
background-color: #7b8de7;
|
| 57 |
+
color: #ffffff;
|
| 58 |
+
}
|
| 59 |
+
.stButton > button:focus {
|
| 60 |
+
outline: 2px solid #c7cef5;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
/* ── Tabs ─────────────────────────────────── */
|
| 64 |
+
[data-baseweb="tab-list"] {
|
| 65 |
+
gap: 8px;
|
| 66 |
+
border-bottom: 2px solid #c7cef5;
|
| 67 |
+
}
|
| 68 |
+
[data-baseweb="tab"] {
|
| 69 |
+
border-radius: 8px 8px 0 0;
|
| 70 |
+
padding: 0.5rem 1.25rem;
|
| 71 |
+
font-weight: 600;
|
| 72 |
+
color: #7b8de7;
|
| 73 |
+
background: transparent;
|
| 74 |
+
}
|
| 75 |
+
[aria-selected="true"][data-baseweb="tab"] {
|
| 76 |
+
color: #435cdc !important;
|
| 77 |
+
border-bottom: 3px solid #435cdc !important;
|
| 78 |
+
background: #f4f6ff;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
/* ── Inputs ──────────────────────────────── */
|
| 82 |
+
[data-testid="stTextInput"] input,
|
| 83 |
+
[data-testid="stSelectbox"] select,
|
| 84 |
+
textarea {
|
| 85 |
+
border-radius: 8px !important;
|
| 86 |
+
border: 1.5px solid #c7cef5 !important;
|
| 87 |
+
color: #333e4a !important;
|
| 88 |
+
}
|
| 89 |
+
[data-testid="stTextInput"] input:focus,
|
| 90 |
+
textarea:focus {
|
| 91 |
+
border-color: #435cdc !important;
|
| 92 |
+
box-shadow: 0 0 0 2px #c7cef5;
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
/* ── File uploader ───────────────────────── */
|
| 96 |
+
[data-testid="stFileUploader"] {
|
| 97 |
+
border: 2px dashed #7b8de7;
|
| 98 |
+
border-radius: 10px;
|
| 99 |
+
background: #f4f6ff;
|
| 100 |
+
padding: 1rem;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
/* ── Metric cards ────────────────────────── */
|
| 104 |
+
.scorecard-wrap {
|
| 105 |
+
display: flex;
|
| 106 |
+
gap: 1rem;
|
| 107 |
+
margin-bottom: 1.5rem;
|
| 108 |
+
}
|
| 109 |
+
.scorecard-card {
|
| 110 |
+
flex: 1;
|
| 111 |
+
background: #f4f6ff;
|
| 112 |
+
border: 1.5px solid #c7cef5;
|
| 113 |
+
border-radius: 12px;
|
| 114 |
+
padding: 1.25rem 1.5rem;
|
| 115 |
+
box-shadow: 0 2px 8px rgba(67,92,220,0.07);
|
| 116 |
+
text-align: center;
|
| 117 |
+
}
|
| 118 |
+
.scorecard-card .sc-label {
|
| 119 |
+
font-size: 0.82rem;
|
| 120 |
+
font-weight: 600;
|
| 121 |
+
color: #7b8de7;
|
| 122 |
+
text-transform: uppercase;
|
| 123 |
+
letter-spacing: 0.04em;
|
| 124 |
+
margin-bottom: 0.4rem;
|
| 125 |
+
}
|
| 126 |
+
.scorecard-card .sc-value {
|
| 127 |
+
font-size: 2rem;
|
| 128 |
+
font-weight: 800;
|
| 129 |
+
color: #435cdc;
|
| 130 |
+
line-height: 1.1;
|
| 131 |
+
}
|
| 132 |
+
.scorecard-card .sc-sub {
|
| 133 |
+
font-size: 0.78rem;
|
| 134 |
+
color: #7b8de7;
|
| 135 |
+
margin-top: 0.2rem;
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
/* ── Badge ───────────────────────────────── */
|
| 139 |
+
.badge-success {
|
| 140 |
+
background: #c7cef5; color: #435cdc;
|
| 141 |
+
border-radius: 99px; padding: 2px 10px;
|
| 142 |
+
font-size: 0.78rem; font-weight: 700;
|
| 143 |
+
}
|
| 144 |
+
.badge-warn {
|
| 145 |
+
background: #fff3c4; color: #a07c00;
|
| 146 |
+
border-radius: 99px; padding: 2px 10px;
|
| 147 |
+
font-size: 0.78rem; font-weight: 700;
|
| 148 |
+
}
|
| 149 |
+
.badge-error {
|
| 150 |
+
background: #fde8e8; color: #c0392b;
|
| 151 |
+
border-radius: 99px; padding: 2px 10px;
|
| 152 |
+
font-size: 0.78rem; font-weight: 700;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
/* ── Section header ──────────────────────── */
|
| 156 |
+
.section-title {
|
| 157 |
+
font-size: 1.15rem;
|
| 158 |
+
font-weight: 700;
|
| 159 |
+
color: #435cdc;
|
| 160 |
+
margin-bottom: 0.75rem;
|
| 161 |
+
display: flex;
|
| 162 |
+
align-items: center;
|
| 163 |
+
gap: 0.4rem;
|
| 164 |
+
}
|
| 165 |
+
.section-title::after {
|
| 166 |
+
content: '';
|
| 167 |
+
flex: 1;
|
| 168 |
+
height: 2px;
|
| 169 |
+
background: linear-gradient(90deg, #c7cef5, transparent);
|
| 170 |
+
margin-left: 0.5rem;
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
/* ── Login card ──────────────────────────── */
|
| 174 |
+
.login-card {
|
| 175 |
+
max-width: 420px;
|
| 176 |
+
margin: 4rem auto;
|
| 177 |
+
padding: 2.5rem 2rem;
|
| 178 |
+
background: #ffffff;
|
| 179 |
+
border-radius: 16px;
|
| 180 |
+
box-shadow: 0 4px 32px rgba(67,92,220,0.13);
|
| 181 |
+
border: 1.5px solid #c7cef5;
|
| 182 |
+
}
|
| 183 |
+
.login-card h1 {
|
| 184 |
+
color: #435cdc;
|
| 185 |
+
font-size: 1.8rem;
|
| 186 |
+
font-weight: 800;
|
| 187 |
+
margin-bottom: 0.25rem;
|
| 188 |
+
}
|
| 189 |
+
.login-card p {
|
| 190 |
+
color: #7b8de7;
|
| 191 |
+
font-size: 0.95rem;
|
| 192 |
+
margin-bottom: 1.5rem;
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
/* ── Divider ─────────────────────────────── */
|
| 196 |
+
hr.styled { border: none; border-top: 1.5px solid #c7cef5; margin: 1.2rem 0; }
|
| 197 |
+
|
| 198 |
+
/* ── JSON output ─────────────────────────── */
|
| 199 |
+
.profile-json {
|
| 200 |
+
background: #f4f6ff;
|
| 201 |
+
border-radius: 10px;
|
| 202 |
+
border: 1.5px solid #c7cef5;
|
| 203 |
+
padding: 1rem 1.25rem;
|
| 204 |
+
font-size: 0.85rem;
|
| 205 |
+
color: #333e4a;
|
| 206 |
+
white-space: pre-wrap;
|
| 207 |
+
word-break: break-word;
|
| 208 |
+
max-height: 500px;
|
| 209 |
+
overflow-y: auto;
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
/* ── Table ───────────────────────────────── */
|
| 213 |
+
[data-testid="stDataFrame"] {
|
| 214 |
+
border-radius: 10px;
|
| 215 |
+
overflow: hidden;
|
| 216 |
+
border: 1.5px solid #c7cef5;
|
| 217 |
+
}
|
| 218 |
+
</style>
|
| 219 |
+
""",
|
| 220 |
+
unsafe_allow_html=True,
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
# ─────────────────────────────────────────────
|
| 225 |
+
# SESSION STATE INIT
|
| 226 |
+
# ─────────────────────────────────────────────
|
| 227 |
+
for key, default in [
|
| 228 |
+
("token", None),
|
| 229 |
+
("user", None),
|
| 230 |
+
("upload_results", []),
|
| 231 |
+
("extract_result", None),
|
| 232 |
+
("user_files", []),
|
| 233 |
+
]:
|
| 234 |
+
if key not in st.session_state:
|
| 235 |
+
st.session_state[key] = default
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
# ─────────────────────────────────────────────
|
| 239 |
+
# API HELPERS
|
| 240 |
+
# ─────────────────────────────────────────────
|
| 241 |
+
def _headers():
|
| 242 |
+
return {"Authorization": f"Bearer {st.session_state.token}"}
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def api_login(email: str, password: str):
|
| 246 |
+
"""POST /admin/login — returns (token, error_msg)"""
|
| 247 |
+
try:
|
| 248 |
+
resp = requests.post(
|
| 249 |
+
f"{BASE_URL}/admin/login",
|
| 250 |
+
data={"username": email, "password": password},
|
| 251 |
+
timeout=15,
|
| 252 |
+
)
|
| 253 |
+
if resp.status_code == 200:
|
| 254 |
+
return resp.json().get("access_token"), None
|
| 255 |
+
detail = resp.json().get("detail", resp.text)
|
| 256 |
+
return None, str(detail)
|
| 257 |
+
except requests.exceptions.ConnectionError:
|
| 258 |
+
return None, "Cannot connect to backend. Check BACKEND_BASE_URL."
|
| 259 |
+
except Exception as e:
|
| 260 |
+
return None, str(e)
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
def api_get_me():
|
| 264 |
+
"""GET /admin/me — returns user dict"""
|
| 265 |
+
try:
|
| 266 |
+
resp = requests.get(f"{BASE_URL}/admin/me", headers=_headers(), timeout=10)
|
| 267 |
+
if resp.status_code == 200:
|
| 268 |
+
return resp.json(), None
|
| 269 |
+
return None, resp.json().get("detail", resp.text)
|
| 270 |
+
except Exception as e:
|
| 271 |
+
return None, str(e)
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
def api_get_scorecard():
|
| 275 |
+
"""GET /file/score_card — returns data dict"""
|
| 276 |
+
try:
|
| 277 |
+
resp = requests.get(f"{BASE_URL}/file/score_card", headers=_headers(), timeout=10)
|
| 278 |
+
if resp.status_code == 200:
|
| 279 |
+
return resp.json().get("data", {}), None
|
| 280 |
+
return None, resp.json().get("detail", resp.text)
|
| 281 |
+
except Exception as e:
|
| 282 |
+
return None, str(e)
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
def api_upload_files(uploaded_files):
|
| 286 |
+
"""POST /file/upload — returns list of results"""
|
| 287 |
+
try:
|
| 288 |
+
files = [
|
| 289 |
+
("files", (f.name, f.read(), "application/pdf"))
|
| 290 |
+
for f in uploaded_files
|
| 291 |
+
]
|
| 292 |
+
resp = requests.post(
|
| 293 |
+
f"{BASE_URL}/file/upload",
|
| 294 |
+
headers=_headers(),
|
| 295 |
+
files=files,
|
| 296 |
+
timeout=60,
|
| 297 |
+
)
|
| 298 |
+
if resp.status_code == 201:
|
| 299 |
+
return resp.json().get("files", []), None
|
| 300 |
+
return None, resp.json().get("detail", resp.text)
|
| 301 |
+
except Exception as e:
|
| 302 |
+
return None, str(e)
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
def api_get_user_files(user_id: str):
|
| 306 |
+
"""GET /file/user/{user_id} — returns list of file dicts"""
|
| 307 |
+
try:
|
| 308 |
+
resp = requests.get(
|
| 309 |
+
f"{BASE_URL}/file/user/{user_id}",
|
| 310 |
+
headers=_headers(),
|
| 311 |
+
timeout=10,
|
| 312 |
+
)
|
| 313 |
+
if resp.status_code == 200:
|
| 314 |
+
return resp.json().get("files", []), None
|
| 315 |
+
return None, resp.json().get("detail", resp.text)
|
| 316 |
+
except Exception as e:
|
| 317 |
+
return None, str(e)
|
| 318 |
+
|
| 319 |
+
|
| 320 |
+
def api_extract_profile(filename: str):
|
| 321 |
+
"""POST /profile/extract_profile?filename=... — returns profile dict"""
|
| 322 |
+
try:
|
| 323 |
+
resp = requests.post(
|
| 324 |
+
f"{BASE_URL}/profile/extract_profile",
|
| 325 |
+
headers=_headers(),
|
| 326 |
+
params={"filename": filename},
|
| 327 |
+
timeout=120,
|
| 328 |
+
)
|
| 329 |
+
if resp.status_code == 200:
|
| 330 |
+
return resp.json(), None
|
| 331 |
+
return None, resp.json().get("detail", resp.text)
|
| 332 |
+
except Exception as e:
|
| 333 |
+
return None, str(e)
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
def api_delete_file(filename: str):
|
| 337 |
+
"""DELETE /file/{filename}"""
|
| 338 |
+
try:
|
| 339 |
+
resp = requests.delete(
|
| 340 |
+
f"{BASE_URL}/file/{filename}",
|
| 341 |
+
headers=_headers(),
|
| 342 |
+
timeout=15,
|
| 343 |
+
)
|
| 344 |
+
if resp.status_code == 200:
|
| 345 |
+
return True, None
|
| 346 |
+
return False, resp.json().get("detail", resp.text)
|
| 347 |
+
except Exception as e:
|
| 348 |
+
return False, str(e)
|
| 349 |
+
|
| 350 |
+
|
| 351 |
+
# ─────────────────────────────────────────────
|
| 352 |
+
# UI COMPONENTS
|
| 353 |
+
# ─────────────────────────────────────────────
|
| 354 |
+
def render_scorecard():
|
| 355 |
+
sc, err = api_get_scorecard()
|
| 356 |
+
if err:
|
| 357 |
+
st.warning(f"Could not load scorecard: {err}")
|
| 358 |
+
return
|
| 359 |
+
|
| 360 |
+
total_file = sc.get("total_file", 0)
|
| 361 |
+
total_extracted = sc.get("total_extracted", 0)
|
| 362 |
+
pct = sc.get("percent_extracted", 0)
|
| 363 |
+
# percent_extracted may be a float like 75.0 or string "75%"
|
| 364 |
+
if isinstance(pct, str):
|
| 365 |
+
pct_display = pct
|
| 366 |
+
else:
|
| 367 |
+
pct_display = f"{pct:.1f}%"
|
| 368 |
+
|
| 369 |
+
st.markdown(
|
| 370 |
+
f"""
|
| 371 |
+
<div class="scorecard-wrap">
|
| 372 |
+
<div class="scorecard-card">
|
| 373 |
+
<div class="sc-label">📁 Total CVs Uploaded</div>
|
| 374 |
+
<div class="sc-value">{total_file}</div>
|
| 375 |
+
<div class="sc-sub">files in your workspace</div>
|
| 376 |
+
</div>
|
| 377 |
+
<div class="scorecard-card">
|
| 378 |
+
<div class="sc-label">✅ Profiles Extracted</div>
|
| 379 |
+
<div class="sc-value">{total_extracted}</div>
|
| 380 |
+
<div class="sc-sub">structured profiles</div>
|
| 381 |
+
</div>
|
| 382 |
+
<div class="scorecard-card">
|
| 383 |
+
<div class="sc-label">📊 Extraction Rate</div>
|
| 384 |
+
<div class="sc-value" style="color:#dcc343">{pct_display}</div>
|
| 385 |
+
<div class="sc-sub">of uploaded CVs processed</div>
|
| 386 |
+
</div>
|
| 387 |
+
</div>
|
| 388 |
+
""",
|
| 389 |
+
unsafe_allow_html=True,
|
| 390 |
+
)
|
| 391 |
+
|
| 392 |
+
|
| 393 |
+
def render_sidebar():
|
| 394 |
+
user = st.session_state.user or {}
|
| 395 |
+
with st.sidebar:
|
| 396 |
+
st.markdown(
|
| 397 |
+
f"""
|
| 398 |
+
<div style="text-align:center;padding:1rem 0 0.5rem;">
|
| 399 |
+
<div style="font-size:2.5rem;">👤</div>
|
| 400 |
+
<div style="font-weight:800;font-size:1.1rem;color:#435cdc;">
|
| 401 |
+
{user.get('full_name', 'User')}
|
| 402 |
+
</div>
|
| 403 |
+
<div style="font-size:0.82rem;color:#7b8de7;margin-top:2px;">
|
| 404 |
+
{user.get('email', '')}
|
| 405 |
+
</div>
|
| 406 |
+
<span class="badge-success" style="margin-top:6px;display:inline-block;">
|
| 407 |
+
{user.get('role', 'user').upper()}
|
| 408 |
+
</span>
|
| 409 |
+
</div>
|
| 410 |
+
<hr class="styled">
|
| 411 |
+
""",
|
| 412 |
+
unsafe_allow_html=True,
|
| 413 |
+
)
|
| 414 |
+
|
| 415 |
+
# st.markdown(
|
| 416 |
+
# "<div style='font-size:0.78rem;color:#7b8de7;margin-bottom:4px;'>BACKEND</div>",
|
| 417 |
+
# unsafe_allow_html=True,
|
| 418 |
+
# )
|
| 419 |
+
# st.markdown(
|
| 420 |
+
# f"<code style='font-size:0.75rem;color:#435cdc;'>{BASE_URL}</code>",
|
| 421 |
+
# unsafe_allow_html=True,
|
| 422 |
+
# )
|
| 423 |
+
|
| 424 |
+
# st.markdown("<hr class='styled'>", unsafe_allow_html=True)
|
| 425 |
+
|
| 426 |
+
if st.button("🚪 Logout", use_container_width=True):
|
| 427 |
+
for key in ["token", "user", "upload_results", "extract_result", "user_files"]:
|
| 428 |
+
st.session_state[key] = None if key in ("token", "user", "extract_result") else []
|
| 429 |
+
st.rerun()
|
| 430 |
+
|
| 431 |
+
|
| 432 |
+
# ─────────────────────────────────────────────
|
| 433 |
+
# PAGES
|
| 434 |
+
# ─────────────────────────────────────────────
|
| 435 |
+
def page_login():
|
| 436 |
+
# Center the login card
|
| 437 |
+
_, col, _ = st.columns([1, 1.4, 1])
|
| 438 |
+
with col:
|
| 439 |
+
st.markdown(
|
| 440 |
+
"""
|
| 441 |
+
<div class="login-card">
|
| 442 |
+
<h1>🔍 Candidate Explorer</h1>
|
| 443 |
+
<p>Sign in to manage and analyze candidate CVs.</p>
|
| 444 |
+
</div>
|
| 445 |
+
""",
|
| 446 |
+
unsafe_allow_html=True,
|
| 447 |
+
)
|
| 448 |
+
|
| 449 |
+
with st.form("login_form", clear_on_submit=False):
|
| 450 |
+
st.markdown(
|
| 451 |
+
"<div class='section-title'>Sign In</div>", unsafe_allow_html=True
|
| 452 |
+
)
|
| 453 |
+
email = st.text_input("Email address", placeholder="you@company.com")
|
| 454 |
+
password = st.text_input("Password", type="password", placeholder="••••••••")
|
| 455 |
+
submitted = st.form_submit_button("Sign In →", use_container_width=True)
|
| 456 |
+
|
| 457 |
+
if submitted:
|
| 458 |
+
if not email or not password:
|
| 459 |
+
st.error("Please enter both email and password.")
|
| 460 |
+
else:
|
| 461 |
+
with st.spinner("Signing in…"):
|
| 462 |
+
token, err = api_login(email, password)
|
| 463 |
+
if err:
|
| 464 |
+
st.error(f"Login failed: {err}")
|
| 465 |
+
else:
|
| 466 |
+
st.session_state.token = token
|
| 467 |
+
user, err2 = api_get_me()
|
| 468 |
+
if err2:
|
| 469 |
+
st.session_state.user = {"email": email, "full_name": email, "role": "user"}
|
| 470 |
+
else:
|
| 471 |
+
st.session_state.user = user
|
| 472 |
+
st.rerun()
|
| 473 |
+
|
| 474 |
+
|
| 475 |
+
def page_main():
|
| 476 |
+
render_sidebar()
|
| 477 |
+
|
| 478 |
+
# ── Page header ──────────────────────────────
|
| 479 |
+
st.markdown(
|
| 480 |
+
"<h1 style='color:#435cdc;font-size:1.75rem;font-weight:800;margin-bottom:0.1rem;'>"
|
| 481 |
+
"🔍 Candidate Explorer"
|
| 482 |
+
"</h1>"
|
| 483 |
+
"<p style='color:#7b8de7;margin-top:0;margin-bottom:1.25rem;font-size:0.95rem;'>"
|
| 484 |
+
"Upload CVs, extract candidate profiles, and track your workspace.</p>",
|
| 485 |
+
unsafe_allow_html=True,
|
| 486 |
+
)
|
| 487 |
+
|
| 488 |
+
# ── Scorecard ─────────────────────────────────
|
| 489 |
+
st.markdown("<div class='section-title'>📊 Dashboard Overview</div>", unsafe_allow_html=True)
|
| 490 |
+
render_scorecard()
|
| 491 |
+
|
| 492 |
+
st.markdown("<hr class='styled'>", unsafe_allow_html=True)
|
| 493 |
+
|
| 494 |
+
# ── Tabs ──────────────────────────────────────
|
| 495 |
+
tab_upload, tab_extract = st.tabs(["📁 Upload CV", "🧠 Extract Profile"])
|
| 496 |
+
|
| 497 |
+
# ══════════════════════════════════════════
|
| 498 |
+
# TAB 1 — UPLOAD
|
| 499 |
+
# ══════════════════════════════════════════
|
| 500 |
+
with tab_upload:
|
| 501 |
+
st.markdown("<br>", unsafe_allow_html=True)
|
| 502 |
+
st.markdown(
|
| 503 |
+
"<div class='section-title'>Upload Candidate CVs</div>",
|
| 504 |
+
unsafe_allow_html=True,
|
| 505 |
+
)
|
| 506 |
+
|
| 507 |
+
uploaded = st.file_uploader(
|
| 508 |
+
"Drop PDF files here or click to browse",
|
| 509 |
+
type=["pdf"],
|
| 510 |
+
accept_multiple_files=True,
|
| 511 |
+
help="Only PDF files are accepted.",
|
| 512 |
+
)
|
| 513 |
+
|
| 514 |
+
col_btn, col_info = st.columns([1, 3])
|
| 515 |
+
with col_btn:
|
| 516 |
+
do_upload = st.button("⬆️ Upload", use_container_width=True, disabled=not uploaded)
|
| 517 |
+
|
| 518 |
+
if do_upload and uploaded:
|
| 519 |
+
with st.spinner(f"Uploading {len(uploaded)} file(s)…"):
|
| 520 |
+
results, err = api_upload_files(uploaded)
|
| 521 |
+
if err:
|
| 522 |
+
st.error(f"Upload failed: {err}")
|
| 523 |
+
else:
|
| 524 |
+
st.session_state.upload_results = results
|
| 525 |
+
# Refresh user files list
|
| 526 |
+
user = st.session_state.user or {}
|
| 527 |
+
uid = str(user.get("user_id", ""))
|
| 528 |
+
if uid:
|
| 529 |
+
files, _ = api_get_user_files(uid)
|
| 530 |
+
st.session_state.user_files = files or []
|
| 531 |
+
st.rerun()
|
| 532 |
+
|
| 533 |
+
# ── Results table ──
|
| 534 |
+
if st.session_state.upload_results:
|
| 535 |
+
st.markdown("<hr class='styled'>", unsafe_allow_html=True)
|
| 536 |
+
st.markdown(
|
| 537 |
+
"<div class='section-title'>Upload Results</div>",
|
| 538 |
+
unsafe_allow_html=True,
|
| 539 |
+
)
|
| 540 |
+
for r in st.session_state.upload_results:
|
| 541 |
+
fname = r.get("filename", r.get("name", ""))
|
| 542 |
+
status = r.get("status", "uploaded")
|
| 543 |
+
badge_cls = "badge-success" if "success" in status.lower() or status == "uploaded" else "badge-error"
|
| 544 |
+
st.markdown(
|
| 545 |
+
f"<span class='badge-success'>✓</span> "
|
| 546 |
+
f"<strong style='color:#333e4a;'>{fname}</strong> "
|
| 547 |
+
f"<span class='{badge_cls}'>{status}</span>",
|
| 548 |
+
unsafe_allow_html=True,
|
| 549 |
+
)
|
| 550 |
+
|
| 551 |
+
# ── Existing files ──
|
| 552 |
+
st.markdown("<hr class='styled'>", unsafe_allow_html=True)
|
| 553 |
+
st.markdown(
|
| 554 |
+
"<div class='section-title'>Your Uploaded Files</div>",
|
| 555 |
+
unsafe_allow_html=True,
|
| 556 |
+
)
|
| 557 |
+
|
| 558 |
+
user = st.session_state.user or {}
|
| 559 |
+
uid = str(user.get("user_id", ""))
|
| 560 |
+
|
| 561 |
+
col_refresh, _ = st.columns([1, 5])
|
| 562 |
+
with col_refresh:
|
| 563 |
+
if st.button("🔄 Refresh List", use_container_width=True):
|
| 564 |
+
if uid:
|
| 565 |
+
files, err = api_get_user_files(uid)
|
| 566 |
+
if err:
|
| 567 |
+
st.warning(f"Could not load files: {err}")
|
| 568 |
+
else:
|
| 569 |
+
st.session_state.user_files = files or []
|
| 570 |
+
|
| 571 |
+
if not st.session_state.user_files and uid:
|
| 572 |
+
# Auto-load on first visit
|
| 573 |
+
files, _ = api_get_user_files(uid)
|
| 574 |
+
st.session_state.user_files = files or []
|
| 575 |
+
|
| 576 |
+
if st.session_state.user_files:
|
| 577 |
+
rows = []
|
| 578 |
+
for f in st.session_state.user_files:
|
| 579 |
+
rows.append(
|
| 580 |
+
{
|
| 581 |
+
"Filename": f.get("filename", ""),
|
| 582 |
+
"Type": f.get("file_type", ""),
|
| 583 |
+
"Extracted": "✅" if f.get("is_extracted") else "⏳",
|
| 584 |
+
"Uploaded": str(f.get("uploaded_at", ""))[:19],
|
| 585 |
+
}
|
| 586 |
+
)
|
| 587 |
+
st.dataframe(rows, use_container_width=True, hide_index=True)
|
| 588 |
+
else:
|
| 589 |
+
st.info("No files uploaded yet.")
|
| 590 |
+
|
| 591 |
+
# ══════════════════════════════════════════
|
| 592 |
+
# TAB 2 — EXTRACT PROFILE
|
| 593 |
+
# ══════════════════════════════════════════
|
| 594 |
+
with tab_extract:
|
| 595 |
+
st.markdown("<br>", unsafe_allow_html=True)
|
| 596 |
+
st.markdown(
|
| 597 |
+
"<div class='section-title'>Extract Structured Profile from CV</div>",
|
| 598 |
+
unsafe_allow_html=True,
|
| 599 |
+
)
|
| 600 |
+
|
| 601 |
+
# Load files if needed
|
| 602 |
+
user = st.session_state.user or {}
|
| 603 |
+
uid = str(user.get("user_id", ""))
|
| 604 |
+
if not st.session_state.user_files and uid:
|
| 605 |
+
files, _ = api_get_user_files(uid)
|
| 606 |
+
st.session_state.user_files = files or []
|
| 607 |
+
|
| 608 |
+
file_options = [f.get("filename", "") for f in st.session_state.user_files if f.get("filename")]
|
| 609 |
+
|
| 610 |
+
if not file_options:
|
| 611 |
+
st.info("No CVs found. Upload files first in the **Upload CV** tab.")
|
| 612 |
+
else:
|
| 613 |
+
col_sel, col_ex = st.columns([3, 1])
|
| 614 |
+
with col_sel:
|
| 615 |
+
chosen = st.selectbox(
|
| 616 |
+
"Select a CV file to extract",
|
| 617 |
+
options=file_options,
|
| 618 |
+
help="Choose a PDF you have already uploaded.",
|
| 619 |
+
)
|
| 620 |
+
with col_ex:
|
| 621 |
+
st.markdown("<div style='margin-top:1.72rem;'></div>", unsafe_allow_html=True)
|
| 622 |
+
do_extract = st.button("🧠 Extract", use_container_width=True)
|
| 623 |
+
|
| 624 |
+
if do_extract and chosen:
|
| 625 |
+
with st.spinner(f"Extracting profile from **{chosen}**… this may take a moment."):
|
| 626 |
+
result, err = api_extract_profile(chosen)
|
| 627 |
+
if err:
|
| 628 |
+
st.error(f"Extraction failed: {err}")
|
| 629 |
+
st.session_state.extract_result = None
|
| 630 |
+
else:
|
| 631 |
+
st.session_state.extract_result = result
|
| 632 |
+
# Refresh files to update is_extracted flag
|
| 633 |
+
if uid:
|
| 634 |
+
files, _ = api_get_user_files(uid)
|
| 635 |
+
st.session_state.user_files = files or []
|
| 636 |
+
st.rerun()
|
| 637 |
+
|
| 638 |
+
# ── Display extracted profile ──
|
| 639 |
+
if st.session_state.extract_result:
|
| 640 |
+
st.markdown("<hr class='styled'>", unsafe_allow_html=True)
|
| 641 |
+
result = st.session_state.extract_result
|
| 642 |
+
|
| 643 |
+
# Try to highlight key fields
|
| 644 |
+
fullname = result.get("fullname") or result.get("full_name", "")
|
| 645 |
+
if fullname:
|
| 646 |
+
st.markdown(
|
| 647 |
+
f"<div style='background:#c7cef5;border-radius:10px;padding:0.75rem 1.2rem;"
|
| 648 |
+
f"margin-bottom:1rem;'>"
|
| 649 |
+
f"<span style='color:#435cdc;font-weight:800;font-size:1.1rem;'>👤 {fullname}</span>"
|
| 650 |
+
f"</div>",
|
| 651 |
+
unsafe_allow_html=True,
|
| 652 |
+
)
|
| 653 |
+
|
| 654 |
+
col_l, col_r = st.columns(2)
|
| 655 |
+
|
| 656 |
+
with col_l:
|
| 657 |
+
st.markdown("<div class='section-title'>Education</div>", unsafe_allow_html=True)
|
| 658 |
+
for i in range(1, 4):
|
| 659 |
+
univ = result.get(f"univ_edu_{i}", "")
|
| 660 |
+
major = result.get(f"major_edu_{i}", "")
|
| 661 |
+
gpa = result.get(f"gpa_edu_{i}", "")
|
| 662 |
+
if univ or major:
|
| 663 |
+
gpa_str = f" · GPA {gpa}" if gpa else ""
|
| 664 |
+
st.markdown(
|
| 665 |
+
f"<div style='margin-bottom:0.5rem;padding:0.6rem 1rem;"
|
| 666 |
+
f"background:#f4f6ff;border-radius:8px;border-left:3px solid #435cdc;'>"
|
| 667 |
+
f"<strong style='color:#333e4a;'>{univ or '—'}</strong><br>"
|
| 668 |
+
f"<span style='color:#7b8de7;font-size:0.85rem;'>{major or ''}{gpa_str}</span>"
|
| 669 |
+
f"</div>",
|
| 670 |
+
unsafe_allow_html=True,
|
| 671 |
+
)
|
| 672 |
+
|
| 673 |
+
st.markdown("<div class='section-title' style='margin-top:1rem;'>Experience</div>", unsafe_allow_html=True)
|
| 674 |
+
yoe = result.get("yoe")
|
| 675 |
+
domicile = result.get("domicile", "")
|
| 676 |
+
st.markdown(
|
| 677 |
+
f"<div style='padding:0.6rem 1rem;background:#f4f6ff;border-radius:8px;"
|
| 678 |
+
f"border-left:3px solid #dcc343;'>"
|
| 679 |
+
f"<span style='color:#333e4a;font-weight:600;'>Years of Experience:</span> "
|
| 680 |
+
f"<span style='color:#435cdc;font-weight:800;'>{yoe if yoe is not None else '—'}</span><br>"
|
| 681 |
+
f"<span style='color:#333e4a;font-weight:600;'>Domicile:</span> "
|
| 682 |
+
f"<span style='color:#435cdc;'>{domicile or '—'}</span>"
|
| 683 |
+
f"</div>",
|
| 684 |
+
unsafe_allow_html=True,
|
| 685 |
+
)
|
| 686 |
+
|
| 687 |
+
with col_r:
|
| 688 |
+
def _tag_list(label, items, color="#c7cef5", text_color="#435cdc"):
|
| 689 |
+
if not items:
|
| 690 |
+
return
|
| 691 |
+
st.markdown(
|
| 692 |
+
f"<div class='section-title'>{label}</div>",
|
| 693 |
+
unsafe_allow_html=True,
|
| 694 |
+
)
|
| 695 |
+
tags = "".join(
|
| 696 |
+
f"<span style='background:{color};color:{text_color};border-radius:99px;"
|
| 697 |
+
f"padding:3px 10px;font-size:0.78rem;font-weight:600;margin:2px;display:inline-block;'>"
|
| 698 |
+
f"{t}</span>"
|
| 699 |
+
for t in items
|
| 700 |
+
)
|
| 701 |
+
st.markdown(
|
| 702 |
+
f"<div style='margin-bottom:0.75rem;line-height:2;'>{tags}</div>",
|
| 703 |
+
unsafe_allow_html=True,
|
| 704 |
+
)
|
| 705 |
+
|
| 706 |
+
_tag_list("💻 Hard Skills", result.get("hardskills", []))
|
| 707 |
+
_tag_list("🤝 Soft Skills", result.get("softskills", []), "#f4f6ff", "#333e4a")
|
| 708 |
+
_tag_list("🏆 Certifications", result.get("certifications", []), "#fff3c4", "#a07c00")
|
| 709 |
+
_tag_list("🏢 Business Domains", result.get("business_domain", []), "#c7cef5", "#435cdc")
|
| 710 |
+
|
| 711 |
+
# Raw JSON toggle
|
| 712 |
+
with st.expander("📄 Raw JSON response"):
|
| 713 |
+
st.markdown(
|
| 714 |
+
f"<div class='profile-json'>{json.dumps(result, indent=2, default=str)}</div>",
|
| 715 |
+
unsafe_allow_html=True,
|
| 716 |
+
)
|
| 717 |
+
|
| 718 |
+
|
| 719 |
+
# ─────────────────────────────────────────────
|
| 720 |
+
# ROUTER
|
| 721 |
+
# ─────────────────────────────────────────────
|
| 722 |
+
if st.session_state.token:
|
| 723 |
+
page_main()
|
| 724 |
+
else:
|
| 725 |
+
page_login()
|
utils/decorator.py
CHANGED
|
@@ -5,7 +5,7 @@ import time
|
|
| 5 |
|
| 6 |
from functools import wraps
|
| 7 |
from typing import Callable, Any
|
| 8 |
-
from sqlalchemy.exc import OperationalError, InterfaceError
|
| 9 |
|
| 10 |
def trace_runtime(func: Callable) -> Callable:
|
| 11 |
@wraps(func)
|
|
|
|
| 5 |
|
| 6 |
from functools import wraps
|
| 7 |
from typing import Callable, Any
|
| 8 |
+
from sqlalchemy.exc import OperationalError, InterfaceError, PendingRollbackError
|
| 9 |
|
| 10 |
def trace_runtime(func: Callable) -> Callable:
|
| 11 |
@wraps(func)
|
uv.lock
CHANGED
|
@@ -100,6 +100,22 @@ wheels = [
|
|
| 100 |
{ url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" },
|
| 101 |
]
|
| 102 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
[[package]]
|
| 104 |
name = "annotated-doc"
|
| 105 |
version = "0.0.4"
|
|
@@ -245,6 +261,24 @@ wheels = [
|
|
| 245 |
{ url = "https://files.pythonhosted.org/packages/f5/37/7cd297ff571c4d86371ff024c0e008b37b59e895b28f69444a9b6f94ca1a/bcrypt-3.2.2-cp36-abi3-win_amd64.whl", hash = "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129", size = 29581, upload-time = "2022-05-01T18:05:57.878Z" },
|
| 246 |
]
|
| 247 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
[[package]]
|
| 249 |
name = "certifi"
|
| 250 |
version = "2025.11.12"
|
|
@@ -678,6 +712,30 @@ wheels = [
|
|
| 678 |
{ url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" },
|
| 679 |
]
|
| 680 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 681 |
[[package]]
|
| 682 |
name = "googleapis-common-protos"
|
| 683 |
version = "1.72.0"
|
|
@@ -903,6 +961,33 @@ wheels = [
|
|
| 903 |
{ url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" },
|
| 904 |
]
|
| 905 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 906 |
[[package]]
|
| 907 |
name = "langchain"
|
| 908 |
version = "1.2.0"
|
|
@@ -1226,6 +1311,15 @@ wheels = [
|
|
| 1226 |
{ url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" },
|
| 1227 |
]
|
| 1228 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1229 |
[[package]]
|
| 1230 |
name = "numpy"
|
| 1231 |
version = "2.4.0"
|
|
@@ -1666,6 +1760,42 @@ wheels = [
|
|
| 1666 |
{ url = "https://files.pythonhosted.org/packages/47/08/737aa39c78d705a7ce58248d00eeba0e9fc36be488f9b672b88736fbb1f7/psycopg2-2.9.11-cp314-cp314-win_amd64.whl", hash = "sha256:f10a48acba5fe6e312b891f290b4d2ca595fc9a06850fe53320beac353575578", size = 2803738, upload-time = "2025-10-10T11:10:23.196Z" },
|
| 1667 |
]
|
| 1668 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1669 |
[[package]]
|
| 1670 |
name = "pyasn1"
|
| 1671 |
version = "0.6.1"
|
|
@@ -1784,6 +1914,19 @@ wheels = [
|
|
| 1784 |
{ url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" },
|
| 1785 |
]
|
| 1786 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1787 |
[[package]]
|
| 1788 |
name = "pygments"
|
| 1789 |
version = "2.19.2"
|
|
@@ -1947,6 +2090,19 @@ wheels = [
|
|
| 1947 |
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
|
| 1948 |
]
|
| 1949 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1950 |
[[package]]
|
| 1951 |
name = "regex"
|
| 1952 |
version = "2025.11.3"
|
|
@@ -2118,6 +2274,72 @@ wheels = [
|
|
| 2118 |
{ url = "https://files.pythonhosted.org/packages/79/62/b88e5879512c55b8ee979c666ee6902adc4ed05007226de266410ae27965/rignore-0.7.6-cp314-cp314t-win_arm64.whl", hash = "sha256:b83adabeb3e8cf662cabe1931b83e165b88c526fa6af6b3aa90429686e474896", size = 656035, upload-time = "2025-11-05T21:41:31.13Z" },
|
| 2119 |
]
|
| 2120 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2121 |
[[package]]
|
| 2122 |
name = "rsa"
|
| 2123 |
version = "4.9.1"
|
|
@@ -2173,6 +2395,11 @@ dependencies = [
|
|
| 2173 |
{ name = "uvicorn" },
|
| 2174 |
]
|
| 2175 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2176 |
[package.metadata]
|
| 2177 |
requires-dist = [
|
| 2178 |
{ name = "aiohttp", specifier = ">=3.13.2" },
|
|
@@ -2200,6 +2427,9 @@ requires-dist = [
|
|
| 2200 |
{ name = "uvicorn", specifier = ">=0.40.0" },
|
| 2201 |
]
|
| 2202 |
|
|
|
|
|
|
|
|
|
|
| 2203 |
[[package]]
|
| 2204 |
name = "shellingham"
|
| 2205 |
version = "1.5.4"
|
|
@@ -2218,6 +2448,15 @@ wheels = [
|
|
| 2218 |
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
|
| 2219 |
]
|
| 2220 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2221 |
[[package]]
|
| 2222 |
name = "sniffio"
|
| 2223 |
version = "1.3.1"
|
|
@@ -2268,6 +2507,35 @@ wheels = [
|
|
| 2268 |
{ url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" },
|
| 2269 |
]
|
| 2270 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2271 |
[[package]]
|
| 2272 |
name = "tenacity"
|
| 2273 |
version = "9.1.2"
|
|
@@ -2317,6 +2585,34 @@ wheels = [
|
|
| 2317 |
{ url = "https://files.pythonhosted.org/packages/af/df/c7891ef9d2712ad774777271d39fdef63941ffba0a9d59b7ad1fd2765e57/tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71", size = 920667, upload-time = "2025-10-06T20:22:34.444Z" },
|
| 2318 |
]
|
| 2319 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2320 |
[[package]]
|
| 2321 |
name = "tqdm"
|
| 2322 |
version = "4.67.1"
|
|
@@ -2455,6 +2751,24 @@ wheels = [
|
|
| 2455 |
{ url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" },
|
| 2456 |
]
|
| 2457 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2458 |
[[package]]
|
| 2459 |
name = "watchfiles"
|
| 2460 |
version = "1.1.1"
|
|
|
|
| 100 |
{ url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" },
|
| 101 |
]
|
| 102 |
|
| 103 |
+
[[package]]
|
| 104 |
+
name = "altair"
|
| 105 |
+
version = "6.0.0"
|
| 106 |
+
source = { registry = "https://pypi.org/simple" }
|
| 107 |
+
dependencies = [
|
| 108 |
+
{ name = "jinja2" },
|
| 109 |
+
{ name = "jsonschema" },
|
| 110 |
+
{ name = "narwhals" },
|
| 111 |
+
{ name = "packaging" },
|
| 112 |
+
{ name = "typing-extensions", marker = "python_full_version < '3.15'" },
|
| 113 |
+
]
|
| 114 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f7/c0/184a89bd5feba14ff3c41cfaf1dd8a82c05f5ceedbc92145e17042eb08a4/altair-6.0.0.tar.gz", hash = "sha256:614bf5ecbe2337347b590afb111929aa9c16c9527c4887d96c9bc7f6640756b4", size = 763834, upload-time = "2025-11-12T08:59:11.519Z" }
|
| 115 |
+
wheels = [
|
| 116 |
+
{ url = "https://files.pythonhosted.org/packages/db/33/ef2f2409450ef6daa61459d5de5c08128e7d3edb773fefd0a324d1310238/altair-6.0.0-py3-none-any.whl", hash = "sha256:09ae95b53d5fe5b16987dccc785a7af8588f2dca50de1e7a156efa8a461515f8", size = 795410, upload-time = "2025-11-12T08:59:09.804Z" },
|
| 117 |
+
]
|
| 118 |
+
|
| 119 |
[[package]]
|
| 120 |
name = "annotated-doc"
|
| 121 |
version = "0.0.4"
|
|
|
|
| 261 |
{ url = "https://files.pythonhosted.org/packages/f5/37/7cd297ff571c4d86371ff024c0e008b37b59e895b28f69444a9b6f94ca1a/bcrypt-3.2.2-cp36-abi3-win_amd64.whl", hash = "sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129", size = 29581, upload-time = "2022-05-01T18:05:57.878Z" },
|
| 262 |
]
|
| 263 |
|
| 264 |
+
[[package]]
|
| 265 |
+
name = "blinker"
|
| 266 |
+
version = "1.9.0"
|
| 267 |
+
source = { registry = "https://pypi.org/simple" }
|
| 268 |
+
sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" }
|
| 269 |
+
wheels = [
|
| 270 |
+
{ url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" },
|
| 271 |
+
]
|
| 272 |
+
|
| 273 |
+
[[package]]
|
| 274 |
+
name = "cachetools"
|
| 275 |
+
version = "6.2.6"
|
| 276 |
+
source = { registry = "https://pypi.org/simple" }
|
| 277 |
+
sdist = { url = "https://files.pythonhosted.org/packages/39/91/d9ae9a66b01102a18cd16db0cf4cd54187ffe10f0865cc80071a4104fbb3/cachetools-6.2.6.tar.gz", hash = "sha256:16c33e1f276b9a9c0b49ab5782d901e3ad3de0dd6da9bf9bcd29ac5672f2f9e6", size = 32363, upload-time = "2026-01-27T20:32:59.956Z" }
|
| 278 |
+
wheels = [
|
| 279 |
+
{ url = "https://files.pythonhosted.org/packages/90/45/f458fa2c388e79dd9d8b9b0c99f1d31b568f27388f2fdba7bb66bbc0c6ed/cachetools-6.2.6-py3-none-any.whl", hash = "sha256:8c9717235b3c651603fff0076db52d6acbfd1b338b8ed50256092f7ce9c85bda", size = 11668, upload-time = "2026-01-27T20:32:58.527Z" },
|
| 280 |
+
]
|
| 281 |
+
|
| 282 |
[[package]]
|
| 283 |
name = "certifi"
|
| 284 |
version = "2025.11.12"
|
|
|
|
| 712 |
{ url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" },
|
| 713 |
]
|
| 714 |
|
| 715 |
+
[[package]]
|
| 716 |
+
name = "gitdb"
|
| 717 |
+
version = "4.0.12"
|
| 718 |
+
source = { registry = "https://pypi.org/simple" }
|
| 719 |
+
dependencies = [
|
| 720 |
+
{ name = "smmap" },
|
| 721 |
+
]
|
| 722 |
+
sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" }
|
| 723 |
+
wheels = [
|
| 724 |
+
{ url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" },
|
| 725 |
+
]
|
| 726 |
+
|
| 727 |
+
[[package]]
|
| 728 |
+
name = "gitpython"
|
| 729 |
+
version = "3.1.46"
|
| 730 |
+
source = { registry = "https://pypi.org/simple" }
|
| 731 |
+
dependencies = [
|
| 732 |
+
{ name = "gitdb" },
|
| 733 |
+
]
|
| 734 |
+
sdist = { url = "https://files.pythonhosted.org/packages/df/b5/59d16470a1f0dfe8c793f9ef56fd3826093fc52b3bd96d6b9d6c26c7e27b/gitpython-3.1.46.tar.gz", hash = "sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f", size = 215371, upload-time = "2026-01-01T15:37:32.073Z" }
|
| 735 |
+
wheels = [
|
| 736 |
+
{ url = "https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058", size = 208620, upload-time = "2026-01-01T15:37:30.574Z" },
|
| 737 |
+
]
|
| 738 |
+
|
| 739 |
[[package]]
|
| 740 |
name = "googleapis-common-protos"
|
| 741 |
version = "1.72.0"
|
|
|
|
| 961 |
{ url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" },
|
| 962 |
]
|
| 963 |
|
| 964 |
+
[[package]]
|
| 965 |
+
name = "jsonschema"
|
| 966 |
+
version = "4.26.0"
|
| 967 |
+
source = { registry = "https://pypi.org/simple" }
|
| 968 |
+
dependencies = [
|
| 969 |
+
{ name = "attrs" },
|
| 970 |
+
{ name = "jsonschema-specifications" },
|
| 971 |
+
{ name = "referencing" },
|
| 972 |
+
{ name = "rpds-py" },
|
| 973 |
+
]
|
| 974 |
+
sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" }
|
| 975 |
+
wheels = [
|
| 976 |
+
{ url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" },
|
| 977 |
+
]
|
| 978 |
+
|
| 979 |
+
[[package]]
|
| 980 |
+
name = "jsonschema-specifications"
|
| 981 |
+
version = "2025.9.1"
|
| 982 |
+
source = { registry = "https://pypi.org/simple" }
|
| 983 |
+
dependencies = [
|
| 984 |
+
{ name = "referencing" },
|
| 985 |
+
]
|
| 986 |
+
sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" }
|
| 987 |
+
wheels = [
|
| 988 |
+
{ url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" },
|
| 989 |
+
]
|
| 990 |
+
|
| 991 |
[[package]]
|
| 992 |
name = "langchain"
|
| 993 |
version = "1.2.0"
|
|
|
|
| 1311 |
{ url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" },
|
| 1312 |
]
|
| 1313 |
|
| 1314 |
+
[[package]]
|
| 1315 |
+
name = "narwhals"
|
| 1316 |
+
version = "2.17.0"
|
| 1317 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1318 |
+
sdist = { url = "https://files.pythonhosted.org/packages/75/59/81d0f4cad21484083466f278e6b392addd9f4205b48d45b5c8771670ebf8/narwhals-2.17.0.tar.gz", hash = "sha256:ebd5bc95bcfa2f8e89a8ac09e2765a63055162837208e67b42d6eeb6651d5e67", size = 620306, upload-time = "2026-02-23T09:44:34.142Z" }
|
| 1319 |
+
wheels = [
|
| 1320 |
+
{ url = "https://files.pythonhosted.org/packages/4b/27/20770bd6bf8fbe1e16f848ba21da9df061f38d2e6483952c29d2bb5d1d8b/narwhals-2.17.0-py3-none-any.whl", hash = "sha256:2ac5307b7c2b275a7d66eeda906b8605e3d7a760951e188dcfff86e8ebe083dd", size = 444897, upload-time = "2026-02-23T09:44:32.006Z" },
|
| 1321 |
+
]
|
| 1322 |
+
|
| 1323 |
[[package]]
|
| 1324 |
name = "numpy"
|
| 1325 |
version = "2.4.0"
|
|
|
|
| 1760 |
{ url = "https://files.pythonhosted.org/packages/47/08/737aa39c78d705a7ce58248d00eeba0e9fc36be488f9b672b88736fbb1f7/psycopg2-2.9.11-cp314-cp314-win_amd64.whl", hash = "sha256:f10a48acba5fe6e312b891f290b4d2ca595fc9a06850fe53320beac353575578", size = 2803738, upload-time = "2025-10-10T11:10:23.196Z" },
|
| 1761 |
]
|
| 1762 |
|
| 1763 |
+
[[package]]
|
| 1764 |
+
name = "pyarrow"
|
| 1765 |
+
version = "23.0.1"
|
| 1766 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1767 |
+
sdist = { url = "https://files.pythonhosted.org/packages/88/22/134986a4cc224d593c1afde5494d18ff629393d74cc2eddb176669f234a4/pyarrow-23.0.1.tar.gz", hash = "sha256:b8c5873e33440b2bc2f4a79d2b47017a89c5a24116c055625e6f2ee50523f019", size = 1167336, upload-time = "2026-02-16T10:14:12.39Z" }
|
| 1768 |
+
wheels = [
|
| 1769 |
+
{ url = "https://files.pythonhosted.org/packages/47/10/2cbe4c6f0fb83d2de37249567373d64327a5e4d8db72f486db42875b08f6/pyarrow-23.0.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6b8fda694640b00e8af3c824f99f789e836720aa8c9379fb435d4c4953a756b8", size = 34210066, upload-time = "2026-02-16T10:10:45.487Z" },
|
| 1770 |
+
{ url = "https://files.pythonhosted.org/packages/cb/4f/679fa7e84dadbaca7a65f7cdba8d6c83febbd93ca12fa4adf40ba3b6362b/pyarrow-23.0.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:8ff51b1addc469b9444b7c6f3548e19dc931b172ab234e995a60aea9f6e6025f", size = 35825526, upload-time = "2026-02-16T10:10:52.266Z" },
|
| 1771 |
+
{ url = "https://files.pythonhosted.org/packages/f9/63/d2747d930882c9d661e9398eefc54f15696547b8983aaaf11d4a2e8b5426/pyarrow-23.0.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:71c5be5cbf1e1cb6169d2a0980850bccb558ddc9b747b6206435313c47c37677", size = 44473279, upload-time = "2026-02-16T10:11:01.557Z" },
|
| 1772 |
+
{ url = "https://files.pythonhosted.org/packages/b3/93/10a48b5e238de6d562a411af6467e71e7aedbc9b87f8d3a35f1560ae30fb/pyarrow-23.0.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:9b6f4f17b43bc39d56fec96e53fe89d94bac3eb134137964371b45352d40d0c2", size = 47585798, upload-time = "2026-02-16T10:11:09.401Z" },
|
| 1773 |
+
{ url = "https://files.pythonhosted.org/packages/5c/20/476943001c54ef078dbf9542280e22741219a184a0632862bca4feccd666/pyarrow-23.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fc13fc6c403d1337acab46a2c4346ca6c9dec5780c3c697cf8abfd5e19b6b37", size = 48179446, upload-time = "2026-02-16T10:11:17.781Z" },
|
| 1774 |
+
{ url = "https://files.pythonhosted.org/packages/4b/b6/5dd0c47b335fcd8edba9bfab78ad961bd0fd55ebe53468cc393f45e0be60/pyarrow-23.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5c16ed4f53247fa3ffb12a14d236de4213a4415d127fe9cebed33d51671113e2", size = 50623972, upload-time = "2026-02-16T10:11:26.185Z" },
|
| 1775 |
+
{ url = "https://files.pythonhosted.org/packages/d5/09/a532297c9591a727d67760e2e756b83905dd89adb365a7f6e9c72578bcc1/pyarrow-23.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:cecfb12ef629cf6be0b1887f9f86463b0dd3dc3195ae6224e74006be4736035a", size = 27540749, upload-time = "2026-02-16T10:12:23.297Z" },
|
| 1776 |
+
{ url = "https://files.pythonhosted.org/packages/a5/8e/38749c4b1303e6ae76b3c80618f84861ae0c55dd3c2273842ea6f8258233/pyarrow-23.0.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:29f7f7419a0e30264ea261fdc0e5fe63ce5a6095003db2945d7cd78df391a7e1", size = 34471544, upload-time = "2026-02-16T10:11:32.535Z" },
|
| 1777 |
+
{ url = "https://files.pythonhosted.org/packages/a3/73/f237b2bc8c669212f842bcfd842b04fc8d936bfc9d471630569132dc920d/pyarrow-23.0.1-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:33d648dc25b51fd8055c19e4261e813dfc4d2427f068bcecc8b53d01b81b0500", size = 35949911, upload-time = "2026-02-16T10:11:39.813Z" },
|
| 1778 |
+
{ url = "https://files.pythonhosted.org/packages/0c/86/b912195eee0903b5611bf596833def7d146ab2d301afeb4b722c57ffc966/pyarrow-23.0.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:cd395abf8f91c673dd3589cadc8cc1ee4e8674fa61b2e923c8dd215d9c7d1f41", size = 44520337, upload-time = "2026-02-16T10:11:47.764Z" },
|
| 1779 |
+
{ url = "https://files.pythonhosted.org/packages/69/c2/f2a717fb824f62d0be952ea724b4f6f9372a17eed6f704b5c9526f12f2f1/pyarrow-23.0.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:00be9576d970c31defb5c32eb72ef585bf600ef6d0a82d5eccaae96639cf9d07", size = 47548944, upload-time = "2026-02-16T10:11:56.607Z" },
|
| 1780 |
+
{ url = "https://files.pythonhosted.org/packages/84/a7/90007d476b9f0dc308e3bc57b832d004f848fd6c0da601375d20d92d1519/pyarrow-23.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c2139549494445609f35a5cda4eb94e2c9e4d704ce60a095b342f82460c73a83", size = 48236269, upload-time = "2026-02-16T10:12:04.47Z" },
|
| 1781 |
+
{ url = "https://files.pythonhosted.org/packages/b0/3f/b16fab3e77709856eb6ac328ce35f57a6d4a18462c7ca5186ef31b45e0e0/pyarrow-23.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7044b442f184d84e2351e5084600f0d7343d6117aabcbc1ac78eb1ae11eb4125", size = 50604794, upload-time = "2026-02-16T10:12:11.797Z" },
|
| 1782 |
+
{ url = "https://files.pythonhosted.org/packages/e9/a1/22df0620a9fac31d68397a75465c344e83c3dfe521f7612aea33e27ab6c0/pyarrow-23.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a35581e856a2fafa12f3f54fce4331862b1cfb0bef5758347a858a4aa9d6bae8", size = 27660642, upload-time = "2026-02-16T10:12:17.746Z" },
|
| 1783 |
+
{ url = "https://files.pythonhosted.org/packages/8d/1b/6da9a89583ce7b23ac611f183ae4843cd3a6cf54f079549b0e8c14031e73/pyarrow-23.0.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:5df1161da23636a70838099d4aaa65142777185cc0cdba4037a18cee7d8db9ca", size = 34238755, upload-time = "2026-02-16T10:12:32.819Z" },
|
| 1784 |
+
{ url = "https://files.pythonhosted.org/packages/ae/b5/d58a241fbe324dbaeb8df07be6af8752c846192d78d2272e551098f74e88/pyarrow-23.0.1-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:fa8e51cb04b9f8c9c5ace6bab63af9a1f88d35c0d6cbf53e8c17c098552285e1", size = 35847826, upload-time = "2026-02-16T10:12:38.949Z" },
|
| 1785 |
+
{ url = "https://files.pythonhosted.org/packages/54/a5/8cbc83f04aba433ca7b331b38f39e000efd9f0c7ce47128670e737542996/pyarrow-23.0.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:0b95a3994f015be13c63148fef8832e8a23938128c185ee951c98908a696e0eb", size = 44536859, upload-time = "2026-02-16T10:12:45.467Z" },
|
| 1786 |
+
{ url = "https://files.pythonhosted.org/packages/36/2e/c0f017c405fcdc252dbccafbe05e36b0d0eb1ea9a958f081e01c6972927f/pyarrow-23.0.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:4982d71350b1a6e5cfe1af742c53dfb759b11ce14141870d05d9e540d13bc5d1", size = 47614443, upload-time = "2026-02-16T10:12:55.525Z" },
|
| 1787 |
+
{ url = "https://files.pythonhosted.org/packages/af/6b/2314a78057912f5627afa13ba43809d9d653e6630859618b0fd81a4e0759/pyarrow-23.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c250248f1fe266db627921c89b47b7c06fee0489ad95b04d50353537d74d6886", size = 48232991, upload-time = "2026-02-16T10:13:04.729Z" },
|
| 1788 |
+
{ url = "https://files.pythonhosted.org/packages/40/f2/1bcb1d3be3460832ef3370d621142216e15a2c7c62602a4ea19ec240dd64/pyarrow-23.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f4763b83c11c16e5f4c15601ba6dfa849e20723b46aa2617cb4bffe8768479f", size = 50645077, upload-time = "2026-02-16T10:13:14.147Z" },
|
| 1789 |
+
{ url = "https://files.pythonhosted.org/packages/eb/3f/b1da7b61cd66566a4d4c8383d376c606d1c34a906c3f1cb35c479f59d1aa/pyarrow-23.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:3a4c85ef66c134161987c17b147d6bffdca4566f9a4c1d81a0a01cdf08414ea5", size = 28234271, upload-time = "2026-02-16T10:14:09.397Z" },
|
| 1790 |
+
{ url = "https://files.pythonhosted.org/packages/b5/78/07f67434e910a0f7323269be7bfbf58699bd0c1d080b18a1ab49ba943fe8/pyarrow-23.0.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:17cd28e906c18af486a499422740298c52d7c6795344ea5002a7720b4eadf16d", size = 34488692, upload-time = "2026-02-16T10:13:21.541Z" },
|
| 1791 |
+
{ url = "https://files.pythonhosted.org/packages/50/76/34cf7ae93ece1f740a04910d9f7e80ba166b9b4ab9596a953e9e62b90fe1/pyarrow-23.0.1-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:76e823d0e86b4fb5e1cf4a58d293036e678b5a4b03539be933d3b31f9406859f", size = 35964383, upload-time = "2026-02-16T10:13:28.63Z" },
|
| 1792 |
+
{ url = "https://files.pythonhosted.org/packages/46/90/459b827238936d4244214be7c684e1b366a63f8c78c380807ae25ed92199/pyarrow-23.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a62e1899e3078bf65943078b3ad2a6ddcacf2373bc06379aac61b1e548a75814", size = 44538119, upload-time = "2026-02-16T10:13:35.506Z" },
|
| 1793 |
+
{ url = "https://files.pythonhosted.org/packages/28/a1/93a71ae5881e99d1f9de1d4554a87be37da11cd6b152239fb5bd924fdc64/pyarrow-23.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:df088e8f640c9fae3b1f495b3c64755c4e719091caf250f3a74d095ddf3c836d", size = 47571199, upload-time = "2026-02-16T10:13:42.504Z" },
|
| 1794 |
+
{ url = "https://files.pythonhosted.org/packages/88/a3/d2c462d4ef313521eaf2eff04d204ac60775263f1fb08c374b543f79f610/pyarrow-23.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:46718a220d64677c93bc243af1d44b55998255427588e400677d7192671845c7", size = 48259435, upload-time = "2026-02-16T10:13:49.226Z" },
|
| 1795 |
+
{ url = "https://files.pythonhosted.org/packages/cc/f1/11a544b8c3d38a759eb3fbb022039117fd633e9a7b19e4841cc3da091915/pyarrow-23.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a09f3876e87f48bc2f13583ab551f0379e5dfb83210391e68ace404181a20690", size = 50629149, upload-time = "2026-02-16T10:13:57.238Z" },
|
| 1796 |
+
{ url = "https://files.pythonhosted.org/packages/50/f2/c0e76a0b451ffdf0cf788932e182758eb7558953f4f27f1aff8e2518b653/pyarrow-23.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:527e8d899f14bd15b740cd5a54ad56b7f98044955373a17179d5956ddb93d9ce", size = 28365807, upload-time = "2026-02-16T10:14:03.892Z" },
|
| 1797 |
+
]
|
| 1798 |
+
|
| 1799 |
[[package]]
|
| 1800 |
name = "pyasn1"
|
| 1801 |
version = "0.6.1"
|
|
|
|
| 1914 |
{ url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" },
|
| 1915 |
]
|
| 1916 |
|
| 1917 |
+
[[package]]
|
| 1918 |
+
name = "pydeck"
|
| 1919 |
+
version = "0.9.1"
|
| 1920 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1921 |
+
dependencies = [
|
| 1922 |
+
{ name = "jinja2" },
|
| 1923 |
+
{ name = "numpy" },
|
| 1924 |
+
]
|
| 1925 |
+
sdist = { url = "https://files.pythonhosted.org/packages/a1/ca/40e14e196864a0f61a92abb14d09b3d3da98f94ccb03b49cf51688140dab/pydeck-0.9.1.tar.gz", hash = "sha256:f74475ae637951d63f2ee58326757f8d4f9cd9f2a457cf42950715003e2cb605", size = 3832240, upload-time = "2024-05-10T15:36:21.153Z" }
|
| 1926 |
+
wheels = [
|
| 1927 |
+
{ url = "https://files.pythonhosted.org/packages/ab/4c/b888e6cf58bd9db9c93f40d1c6be8283ff49d88919231afe93a6bcf61626/pydeck-0.9.1-py2.py3-none-any.whl", hash = "sha256:b3f75ba0d273fc917094fa61224f3f6076ca8752b93d46faf3bcfd9f9d59b038", size = 6900403, upload-time = "2024-05-10T15:36:17.36Z" },
|
| 1928 |
+
]
|
| 1929 |
+
|
| 1930 |
[[package]]
|
| 1931 |
name = "pygments"
|
| 1932 |
version = "2.19.2"
|
|
|
|
| 2090 |
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
|
| 2091 |
]
|
| 2092 |
|
| 2093 |
+
[[package]]
|
| 2094 |
+
name = "referencing"
|
| 2095 |
+
version = "0.37.0"
|
| 2096 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2097 |
+
dependencies = [
|
| 2098 |
+
{ name = "attrs" },
|
| 2099 |
+
{ name = "rpds-py" },
|
| 2100 |
+
]
|
| 2101 |
+
sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" }
|
| 2102 |
+
wheels = [
|
| 2103 |
+
{ url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" },
|
| 2104 |
+
]
|
| 2105 |
+
|
| 2106 |
[[package]]
|
| 2107 |
name = "regex"
|
| 2108 |
version = "2025.11.3"
|
|
|
|
| 2274 |
{ url = "https://files.pythonhosted.org/packages/79/62/b88e5879512c55b8ee979c666ee6902adc4ed05007226de266410ae27965/rignore-0.7.6-cp314-cp314t-win_arm64.whl", hash = "sha256:b83adabeb3e8cf662cabe1931b83e165b88c526fa6af6b3aa90429686e474896", size = 656035, upload-time = "2025-11-05T21:41:31.13Z" },
|
| 2275 |
]
|
| 2276 |
|
| 2277 |
+
[[package]]
|
| 2278 |
+
name = "rpds-py"
|
| 2279 |
+
version = "0.30.0"
|
| 2280 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2281 |
+
sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" }
|
| 2282 |
+
wheels = [
|
| 2283 |
+
{ url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" },
|
| 2284 |
+
{ url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" },
|
| 2285 |
+
{ url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" },
|
| 2286 |
+
{ url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" },
|
| 2287 |
+
{ url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" },
|
| 2288 |
+
{ url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" },
|
| 2289 |
+
{ url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" },
|
| 2290 |
+
{ url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" },
|
| 2291 |
+
{ url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" },
|
| 2292 |
+
{ url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" },
|
| 2293 |
+
{ url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" },
|
| 2294 |
+
{ url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" },
|
| 2295 |
+
{ url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" },
|
| 2296 |
+
{ url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" },
|
| 2297 |
+
{ url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" },
|
| 2298 |
+
{ url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" },
|
| 2299 |
+
{ url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" },
|
| 2300 |
+
{ url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" },
|
| 2301 |
+
{ url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" },
|
| 2302 |
+
{ url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" },
|
| 2303 |
+
{ url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" },
|
| 2304 |
+
{ url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" },
|
| 2305 |
+
{ url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" },
|
| 2306 |
+
{ url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" },
|
| 2307 |
+
{ url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" },
|
| 2308 |
+
{ url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" },
|
| 2309 |
+
{ url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" },
|
| 2310 |
+
{ url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" },
|
| 2311 |
+
{ url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" },
|
| 2312 |
+
{ url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" },
|
| 2313 |
+
{ url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" },
|
| 2314 |
+
{ url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" },
|
| 2315 |
+
{ url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" },
|
| 2316 |
+
{ url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" },
|
| 2317 |
+
{ url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" },
|
| 2318 |
+
{ url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" },
|
| 2319 |
+
{ url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" },
|
| 2320 |
+
{ url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" },
|
| 2321 |
+
{ url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" },
|
| 2322 |
+
{ url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" },
|
| 2323 |
+
{ url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" },
|
| 2324 |
+
{ url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" },
|
| 2325 |
+
{ url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" },
|
| 2326 |
+
{ url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" },
|
| 2327 |
+
{ url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" },
|
| 2328 |
+
{ url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" },
|
| 2329 |
+
{ url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" },
|
| 2330 |
+
{ url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" },
|
| 2331 |
+
{ url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" },
|
| 2332 |
+
{ url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" },
|
| 2333 |
+
{ url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" },
|
| 2334 |
+
{ url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" },
|
| 2335 |
+
{ url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" },
|
| 2336 |
+
{ url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" },
|
| 2337 |
+
{ url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" },
|
| 2338 |
+
{ url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" },
|
| 2339 |
+
{ url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" },
|
| 2340 |
+
{ url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" },
|
| 2341 |
+
]
|
| 2342 |
+
|
| 2343 |
[[package]]
|
| 2344 |
name = "rsa"
|
| 2345 |
version = "4.9.1"
|
|
|
|
| 2395 |
{ name = "uvicorn" },
|
| 2396 |
]
|
| 2397 |
|
| 2398 |
+
[package.dev-dependencies]
|
| 2399 |
+
dev = [
|
| 2400 |
+
{ name = "streamlit" },
|
| 2401 |
+
]
|
| 2402 |
+
|
| 2403 |
[package.metadata]
|
| 2404 |
requires-dist = [
|
| 2405 |
{ name = "aiohttp", specifier = ">=3.13.2" },
|
|
|
|
| 2427 |
{ name = "uvicorn", specifier = ">=0.40.0" },
|
| 2428 |
]
|
| 2429 |
|
| 2430 |
+
[package.metadata.requires-dev]
|
| 2431 |
+
dev = [{ name = "streamlit", specifier = ">=1.54.0" }]
|
| 2432 |
+
|
| 2433 |
[[package]]
|
| 2434 |
name = "shellingham"
|
| 2435 |
version = "1.5.4"
|
|
|
|
| 2448 |
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
|
| 2449 |
]
|
| 2450 |
|
| 2451 |
+
[[package]]
|
| 2452 |
+
name = "smmap"
|
| 2453 |
+
version = "5.0.2"
|
| 2454 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2455 |
+
sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" }
|
| 2456 |
+
wheels = [
|
| 2457 |
+
{ url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" },
|
| 2458 |
+
]
|
| 2459 |
+
|
| 2460 |
[[package]]
|
| 2461 |
name = "sniffio"
|
| 2462 |
version = "1.3.1"
|
|
|
|
| 2507 |
{ url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" },
|
| 2508 |
]
|
| 2509 |
|
| 2510 |
+
[[package]]
|
| 2511 |
+
name = "streamlit"
|
| 2512 |
+
version = "1.54.0"
|
| 2513 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2514 |
+
dependencies = [
|
| 2515 |
+
{ name = "altair" },
|
| 2516 |
+
{ name = "blinker" },
|
| 2517 |
+
{ name = "cachetools" },
|
| 2518 |
+
{ name = "click" },
|
| 2519 |
+
{ name = "gitpython" },
|
| 2520 |
+
{ name = "numpy" },
|
| 2521 |
+
{ name = "packaging" },
|
| 2522 |
+
{ name = "pandas" },
|
| 2523 |
+
{ name = "pillow" },
|
| 2524 |
+
{ name = "protobuf" },
|
| 2525 |
+
{ name = "pyarrow" },
|
| 2526 |
+
{ name = "pydeck" },
|
| 2527 |
+
{ name = "requests" },
|
| 2528 |
+
{ name = "tenacity" },
|
| 2529 |
+
{ name = "toml" },
|
| 2530 |
+
{ name = "tornado" },
|
| 2531 |
+
{ name = "typing-extensions" },
|
| 2532 |
+
{ name = "watchdog", marker = "sys_platform != 'darwin'" },
|
| 2533 |
+
]
|
| 2534 |
+
sdist = { url = "https://files.pythonhosted.org/packages/be/66/d887ee80ea85f035baee607c60af024994e17ae9b921277fca9675e76ecf/streamlit-1.54.0.tar.gz", hash = "sha256:09965e6ae7eb0357091725de1ce2a3f7e4be155c2464c505c40a3da77ab69dd8", size = 8662292, upload-time = "2026-02-04T16:37:54.734Z" }
|
| 2535 |
+
wheels = [
|
| 2536 |
+
{ url = "https://files.pythonhosted.org/packages/48/1d/40de1819374b4f0507411a60f4d2de0d620a9b10c817de5925799132b6c9/streamlit-1.54.0-py3-none-any.whl", hash = "sha256:a7b67d6293a9f5f6b4d4c7acdbc4980d7d9f049e78e404125022ecb1712f79fc", size = 9119730, upload-time = "2026-02-04T16:37:52.199Z" },
|
| 2537 |
+
]
|
| 2538 |
+
|
| 2539 |
[[package]]
|
| 2540 |
name = "tenacity"
|
| 2541 |
version = "9.1.2"
|
|
|
|
| 2585 |
{ url = "https://files.pythonhosted.org/packages/af/df/c7891ef9d2712ad774777271d39fdef63941ffba0a9d59b7ad1fd2765e57/tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71", size = 920667, upload-time = "2025-10-06T20:22:34.444Z" },
|
| 2586 |
]
|
| 2587 |
|
| 2588 |
+
[[package]]
|
| 2589 |
+
name = "toml"
|
| 2590 |
+
version = "0.10.2"
|
| 2591 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2592 |
+
sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" }
|
| 2593 |
+
wheels = [
|
| 2594 |
+
{ url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" },
|
| 2595 |
+
]
|
| 2596 |
+
|
| 2597 |
+
[[package]]
|
| 2598 |
+
name = "tornado"
|
| 2599 |
+
version = "6.5.4"
|
| 2600 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2601 |
+
sdist = { url = "https://files.pythonhosted.org/packages/37/1d/0a336abf618272d53f62ebe274f712e213f5a03c0b2339575430b8362ef2/tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7", size = 513632, upload-time = "2025-12-15T19:21:03.836Z" }
|
| 2602 |
+
wheels = [
|
| 2603 |
+
{ url = "https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9", size = 443909, upload-time = "2025-12-15T19:20:48.382Z" },
|
| 2604 |
+
{ url = "https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843", size = 442163, upload-time = "2025-12-15T19:20:49.791Z" },
|
| 2605 |
+
{ url = "https://files.pythonhosted.org/packages/ba/b5/206f82d51e1bfa940ba366a8d2f83904b15942c45a78dd978b599870ab44/tornado-6.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cf66105dc6acb5af613c054955b8137e34a03698aa53272dbda4afe252be17", size = 445746, upload-time = "2025-12-15T19:20:51.491Z" },
|
| 2606 |
+
{ url = "https://files.pythonhosted.org/packages/8e/9d/1a3338e0bd30ada6ad4356c13a0a6c35fbc859063fa7eddb309183364ac1/tornado-6.5.4-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ff0a58b0dc97939d29da29cd624da010e7f804746621c78d14b80238669335", size = 445083, upload-time = "2025-12-15T19:20:52.778Z" },
|
| 2607 |
+
{ url = "https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f", size = 445315, upload-time = "2025-12-15T19:20:53.996Z" },
|
| 2608 |
+
{ url = "https://files.pythonhosted.org/packages/27/07/2273972f69ca63dbc139694a3fc4684edec3ea3f9efabf77ed32483b875c/tornado-6.5.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9c86b1643b33a4cd415f8d0fe53045f913bf07b4a3ef646b735a6a86047dda84", size = 446003, upload-time = "2025-12-15T19:20:56.101Z" },
|
| 2609 |
+
{ url = "https://files.pythonhosted.org/packages/d1/83/41c52e47502bf7260044413b6770d1a48dda2f0246f95ee1384a3cd9c44a/tornado-6.5.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:6eb82872335a53dd063a4f10917b3efd28270b56a33db69009606a0312660a6f", size = 445412, upload-time = "2025-12-15T19:20:57.398Z" },
|
| 2610 |
+
{ url = "https://files.pythonhosted.org/packages/10/c7/bc96917f06cbee182d44735d4ecde9c432e25b84f4c2086143013e7b9e52/tornado-6.5.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6076d5dda368c9328ff41ab5d9dd3608e695e8225d1cd0fd1e006f05da3635a8", size = 445392, upload-time = "2025-12-15T19:20:58.692Z" },
|
| 2611 |
+
{ url = "https://files.pythonhosted.org/packages/0c/1a/d7592328d037d36f2d2462f4bc1fbb383eec9278bc786c1b111cbbd44cfa/tornado-6.5.4-cp39-abi3-win32.whl", hash = "sha256:1768110f2411d5cd281bac0a090f707223ce77fd110424361092859e089b38d1", size = 446481, upload-time = "2025-12-15T19:21:00.008Z" },
|
| 2612 |
+
{ url = "https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl", hash = "sha256:fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc", size = 446886, upload-time = "2025-12-15T19:21:01.287Z" },
|
| 2613 |
+
{ url = "https://files.pythonhosted.org/packages/50/49/8dc3fd90902f70084bd2cd059d576ddb4f8bb44c2c7c0e33a11422acb17e/tornado-6.5.4-cp39-abi3-win_arm64.whl", hash = "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1", size = 445910, upload-time = "2025-12-15T19:21:02.571Z" },
|
| 2614 |
+
]
|
| 2615 |
+
|
| 2616 |
[[package]]
|
| 2617 |
name = "tqdm"
|
| 2618 |
version = "4.67.1"
|
|
|
|
| 2751 |
{ url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" },
|
| 2752 |
]
|
| 2753 |
|
| 2754 |
+
[[package]]
|
| 2755 |
+
name = "watchdog"
|
| 2756 |
+
version = "6.0.0"
|
| 2757 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2758 |
+
sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" }
|
| 2759 |
+
wheels = [
|
| 2760 |
+
{ url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" },
|
| 2761 |
+
{ url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" },
|
| 2762 |
+
{ url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" },
|
| 2763 |
+
{ url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" },
|
| 2764 |
+
{ url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" },
|
| 2765 |
+
{ url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" },
|
| 2766 |
+
{ url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" },
|
| 2767 |
+
{ url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" },
|
| 2768 |
+
{ url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" },
|
| 2769 |
+
{ url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" },
|
| 2770 |
+
]
|
| 2771 |
+
|
| 2772 |
[[package]]
|
| 2773 |
name = "watchfiles"
|
| 2774 |
version = "1.1.1"
|