Compare commits
2 Commits
523a59f864
...
ab4ee4b34a
| Author | SHA1 | Date | |
|---|---|---|---|
| ab4ee4b34a | |||
| 8cc2bc9087 |
226
Stage1_Theme_Discovery.py
Normal file
226
Stage1_Theme_Discovery.py
Normal file
@@ -0,0 +1,226 @@
|
||||
import marimo
|
||||
|
||||
__generated_with = "0.18.1"
|
||||
app = marimo.App(width="medium")
|
||||
|
||||
|
||||
@app.cell
|
||||
def _():
|
||||
import marimo as mo
|
||||
import json
|
||||
import pandas as pd
|
||||
import re
|
||||
from pathlib import Path
|
||||
from utils import connect_qumo_ollama, load_srt
|
||||
|
||||
# Configuration
|
||||
VM_NAME = 'hiperf-gpu'
|
||||
MODEL = 'llama3.3:70b'
|
||||
TRANSCRIPT_DIR = Path("data/transcripts")
|
||||
OUTPUT_FILE = Path("master_codebook.json")
|
||||
|
||||
client = connect_qumo_ollama(VM_NAME)
|
||||
return (
|
||||
MODEL,
|
||||
OUTPUT_FILE,
|
||||
TRANSCRIPT_DIR,
|
||||
client,
|
||||
json,
|
||||
load_srt,
|
||||
mo,
|
||||
pd,
|
||||
re,
|
||||
)
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(mo):
|
||||
mo.md(r"""
|
||||
# Stage 1: Theme Discovery
|
||||
|
||||
**Goal:** Identify recurring themes across a sample of interviews.
|
||||
|
||||
1. **Select Transcripts:** Choose 4-5 representative interviews.
|
||||
2. **Extract Topics:** The AI will analyze each transcript to find key topics.
|
||||
3. **Synthesize Themes:** Topics are grouped into a Master Codebook.
|
||||
4. **Refine & Save:** Edit the definitions and save the `master_codebook.json`.
|
||||
""")
|
||||
return
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(TRANSCRIPT_DIR, mo):
|
||||
# File Selection
|
||||
srt_files = list(TRANSCRIPT_DIR.glob("*.srt"))
|
||||
file_options = {f.name: str(f) for f in srt_files}
|
||||
|
||||
file_selector = mo.ui.multiselect(
|
||||
options=file_options,
|
||||
label="Select Transcripts (Recommended: 4-5)",
|
||||
full_width=True
|
||||
)
|
||||
file_selector
|
||||
return (file_selector,)
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(file_selector, mo):
|
||||
mo.md(f"**Selected:** {len(file_selector.value)} files")
|
||||
return
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(mo):
|
||||
start_discovery_btn = mo.ui.run_button(label="Start Discovery Process")
|
||||
start_discovery_btn
|
||||
return (start_discovery_btn,)
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(
|
||||
MODEL,
|
||||
client,
|
||||
file_selector,
|
||||
json,
|
||||
load_srt,
|
||||
mo,
|
||||
re,
|
||||
start_discovery_btn,
|
||||
):
|
||||
# Map Phase: Extract Topics per Transcript
|
||||
extracted_topics = []
|
||||
status_callout = mo.md("")
|
||||
|
||||
if start_discovery_btn.value and file_selector.value:
|
||||
with mo.status.spinner("Analyzing transcripts...") as _spinner:
|
||||
for filepath in file_selector.value:
|
||||
_transcript = load_srt(filepath)
|
||||
|
||||
# Truncate for discovery if too long (optional, but good for speed)
|
||||
# Using first 15k chars usually gives enough context for high-level themes
|
||||
_context = _transcript[:15000]
|
||||
|
||||
_prompt = f"""
|
||||
Analyze this interview transcript and list the top 5-7 key topics or themes discussed.
|
||||
Focus on: Brand voice, Customer experience, Design systems, and AI.
|
||||
|
||||
Return ONLY a JSON list of strings. Example: ["Inconsistent Tone", "Mobile Latency", "AI Trust"]
|
||||
|
||||
Transcript:
|
||||
{_context}...
|
||||
"""
|
||||
|
||||
try:
|
||||
_response = client.generate(model=MODEL, prompt=_prompt)
|
||||
# Find JSON list in response
|
||||
_match = re.search(r'\[.*\]', _response.response, re.DOTALL)
|
||||
if _match:
|
||||
_topics = json.loads(_match.group(0))
|
||||
extracted_topics.extend(_topics)
|
||||
except Exception as e:
|
||||
print(f"Error processing {filepath}: {e}")
|
||||
|
||||
status_callout = mo.callout(
|
||||
f"✅ Extracted {len(extracted_topics)} raw topics from {len(file_selector.value)} files.",
|
||||
kind="success"
|
||||
)
|
||||
elif start_discovery_btn.value:
|
||||
status_callout = mo.callout("Please select at least one file.", kind="warn")
|
||||
|
||||
status_callout
|
||||
return (extracted_topics,)
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(MODEL, client, extracted_topics, json, mo, re, start_discovery_btn):
|
||||
# Reduce Phase: Synthesize Themes
|
||||
suggested_themes = []
|
||||
|
||||
if start_discovery_btn.value and extracted_topics:
|
||||
with mo.status.spinner("Synthesizing Master Codebook...") as _spinner:
|
||||
_topics_str = ", ".join(extracted_topics)
|
||||
|
||||
_synthesis_prompt = f"""
|
||||
You are a qualitative data architect.
|
||||
|
||||
I have a list of raw topics extracted from multiple interviews:
|
||||
[{_topics_str}]
|
||||
|
||||
Task:
|
||||
1. Group these into 5-8 distinct, high-level Themes.
|
||||
2. Create a definition for each theme.
|
||||
3. Assign a hex color code to each.
|
||||
4. ALWAYS include a theme named "Other" for miscellaneous insights.
|
||||
|
||||
Return a JSON object with this structure:
|
||||
[
|
||||
{{"Theme": "Theme Name", "Definition": "Description...", "Color": "#HEXCODE"}},
|
||||
...
|
||||
]
|
||||
"""
|
||||
|
||||
_response = client.generate(model=MODEL, prompt=_synthesis_prompt)
|
||||
|
||||
_match = re.search(r'\[.*\]', _response.response, re.DOTALL)
|
||||
if _match:
|
||||
try:
|
||||
suggested_themes = json.loads(_match.group(0))
|
||||
except:
|
||||
suggested_themes = [{"Theme": "Error parsing JSON", "Definition": _response.response, "Color": "#000000"}]
|
||||
|
||||
return (suggested_themes,)
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(mo, pd, suggested_themes):
|
||||
# Interactive Editor
|
||||
|
||||
# Default empty structure if nothing generated yet
|
||||
_initial_data = suggested_themes if suggested_themes else [
|
||||
{"Theme": "Example Theme", "Definition": "Description here...", "Color": "#CCCCCC"}
|
||||
]
|
||||
|
||||
df_themes = pd.DataFrame(_initial_data)
|
||||
|
||||
theme_editor = mo.ui.data_editor(
|
||||
df_themes,
|
||||
label="Master Codebook Editor",
|
||||
column_config={
|
||||
"Color": mo.ui.column.color_picker(label="Color")
|
||||
},
|
||||
num_rows="dynamic" # Allow adding/removing rows
|
||||
)
|
||||
|
||||
mo.vstack([
|
||||
mo.md("### Review & Refine Codebook"),
|
||||
mo.md("Edit the themes below. You can add rows, change colors, or refine definitions."),
|
||||
theme_editor
|
||||
])
|
||||
return (theme_editor,)
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(OUTPUT_FILE, json, mo, theme_editor):
|
||||
save_btn = mo.ui.run_button(label="Save Master Codebook")
|
||||
|
||||
save_message = mo.md("")
|
||||
|
||||
if save_btn.value:
|
||||
_final_df = theme_editor.value
|
||||
# Convert to list of dicts
|
||||
_codebook = _final_df.to_dict(orient="records")
|
||||
|
||||
with open(OUTPUT_FILE, "w") as f:
|
||||
json.dump(_codebook, f, indent=2)
|
||||
|
||||
save_message = mo.callout(f"✅ Saved {len(_codebook)} themes to `{OUTPUT_FILE}`", kind="success")
|
||||
|
||||
mo.vstack([
|
||||
save_btn,
|
||||
save_message
|
||||
])
|
||||
return
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
120
Taguette-Preprocess.py
Normal file
120
Taguette-Preprocess.py
Normal file
@@ -0,0 +1,120 @@
|
||||
import marimo
|
||||
|
||||
__generated_with = "0.18.0"
|
||||
app = marimo.App(width="medium")
|
||||
|
||||
|
||||
@app.cell
|
||||
def _():
|
||||
import marimo as mo
|
||||
import pandas as pd
|
||||
from pathlib import Path
|
||||
return Path, mo, pd
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(Path):
|
||||
INPUT_DIR = Path("data/transcripts/raw")
|
||||
OUTPUT_DIR = Path("data/transcripts/clean")
|
||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
return INPUT_DIR, OUTPUT_DIR
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(INPUT_DIR, mo):
|
||||
csv_files = list(INPUT_DIR.glob("*.csv"))
|
||||
file_options = {f.stem: str(f) for f in csv_files}
|
||||
|
||||
file_dropdown = mo.ui.dropdown(
|
||||
options=file_options,
|
||||
label="Select CSV Transcript",
|
||||
full_width=True
|
||||
)
|
||||
file_dropdown
|
||||
return (file_dropdown,)
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(file_dropdown, mo, pd):
|
||||
def csv_to_markdown(df):
|
||||
"""Convert transcript DataFrame to markdown, merging consecutive same-speaker turns."""
|
||||
lines = [f"# Interview Transcript\n"]
|
||||
|
||||
# Track previous speaker to detect when speaker changes
|
||||
prev_speaker = None
|
||||
# Accumulate text from consecutive turns by same speaker
|
||||
merged_text = []
|
||||
|
||||
for _, row in df.iterrows():
|
||||
speaker = row["Speaker"]
|
||||
text = str(row["Transcript"]).strip()
|
||||
|
||||
if speaker == prev_speaker:
|
||||
# Same speaker continues — append text to current block
|
||||
merged_text.append(text)
|
||||
else:
|
||||
# New speaker detected — flush previous speaker's block
|
||||
if prev_speaker is not None:
|
||||
# Format: **Speaker**: text-part-1\ntext-part-2 + blank line
|
||||
lines.append(f"**{prev_speaker}**: {'\n'.join(merged_text)}\n\n")
|
||||
|
||||
# Start new block for current speaker
|
||||
prev_speaker = speaker
|
||||
merged_text = [text]
|
||||
|
||||
# Flush final speaker's block
|
||||
if prev_speaker is not None:
|
||||
lines.append(f"**{prev_speaker}**: {'\n'.join(merged_text)}\n\n")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
# Preview
|
||||
preview = mo.md("")
|
||||
if file_dropdown.value:
|
||||
df = pd.read_csv(file_dropdown.value)
|
||||
md_content = csv_to_markdown(df)
|
||||
preview = mo.md(md_content)
|
||||
|
||||
preview
|
||||
return (csv_to_markdown,)
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(mo):
|
||||
convert_btn = mo.ui.run_button(label="Convert to Markdown")
|
||||
convert_btn
|
||||
return (convert_btn,)
|
||||
|
||||
|
||||
@app.cell
|
||||
def _(OUTPUT_DIR, Path, convert_btn, csv_to_markdown, file_dropdown, mo, pd):
|
||||
result = mo.md("")
|
||||
|
||||
if convert_btn.value and file_dropdown.value:
|
||||
_df = pd.read_csv(file_dropdown.value)
|
||||
_md = csv_to_markdown(_df)
|
||||
_out_path = OUTPUT_DIR / (Path(file_dropdown.value).stem + ".md")
|
||||
_out_path.write_text(_md)
|
||||
result = mo.callout(f"✅ Saved to `{_out_path}`", kind="success")
|
||||
|
||||
result
|
||||
return
|
||||
|
||||
|
||||
@app.cell(hide_code=True)
|
||||
def _(mo):
|
||||
mo.md(r"""
|
||||
# Taguette
|
||||
|
||||
Upload and process using taguette: http://taguette.tail44fa00.ts.net/
|
||||
""")
|
||||
return
|
||||
|
||||
|
||||
@app.cell
|
||||
def _():
|
||||
return
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
@@ -8,6 +8,7 @@ dependencies = [
|
||||
"marimo>=0.18.0",
|
||||
"numpy>=2.3.5",
|
||||
"ollama>=0.6.1",
|
||||
"pandas>=2.3.3",
|
||||
"pyzmq>=27.1.0",
|
||||
"requests>=2.32.5",
|
||||
"taguette>=1.5.1",
|
||||
|
||||
8
utils.py
8
utils.py
@@ -76,11 +76,15 @@ def connect_qumo_ollama(vm_name: str ='ollama-lite') -> Client:
|
||||
client = Client(
|
||||
host=QUMO_OLLAMA_URL
|
||||
)
|
||||
except requests.ConnectionError:
|
||||
print(f"Failed to reach {QUMO_OLLAMA_URL}. Check that the VM is running and Tailscale is up")
|
||||
|
||||
print(f"Connection succesful. WebUI available at: http://{vm_name}.tail44fa00.ts.net:3000\nAvailable models:")
|
||||
for m in client.list().models:
|
||||
print(f" - '{m.model}' ")
|
||||
return client
|
||||
|
||||
except requests.ConnectionError:
|
||||
pass
|
||||
|
||||
print(f"Failed to reach {QUMO_OLLAMA_URL}. Check that the VM is running and Tailscale is up")
|
||||
return None
|
||||
|
||||
|
||||
79
uv.lock
generated
79
uv.lock
generated
@@ -353,6 +353,7 @@ dependencies = [
|
||||
{ name = "marimo" },
|
||||
{ name = "numpy" },
|
||||
{ name = "ollama" },
|
||||
{ name = "pandas" },
|
||||
{ name = "pyzmq" },
|
||||
{ name = "requests" },
|
||||
{ name = "taguette" },
|
||||
@@ -363,6 +364,7 @@ requires-dist = [
|
||||
{ name = "marimo", specifier = ">=0.18.0" },
|
||||
{ name = "numpy", specifier = ">=2.3.5" },
|
||||
{ name = "ollama", specifier = ">=0.6.1" },
|
||||
{ name = "pandas", specifier = ">=2.3.3" },
|
||||
{ name = "pyzmq", specifier = ">=27.1.0" },
|
||||
{ name = "requests", specifier = ">=2.32.5" },
|
||||
{ name = "taguette", specifier = ">=1.5.1" },
|
||||
@@ -706,6 +708,53 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pandas"
|
||||
version = "2.3.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
{ name = "python-dateutil" },
|
||||
{ name = "pytz" },
|
||||
{ name = "tzdata" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parso"
|
||||
version = "0.8.5"
|
||||
@@ -880,6 +929,27 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/40/b2d7b9fdccc63e48ae4dbd363b6b89eb7ac346ea49ed667bb71f92af3021/pymdown_extensions-10.17.1-py3-none-any.whl", hash = "sha256:1f160209c82eecbb5d8a0d8f89a4d9bd6bdcbde9a8537761844cfc57ad5cd8a6", size = 266310, upload-time = "2025-11-11T21:44:56.809Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.9.0.post0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2025.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0.3"
|
||||
@@ -1159,6 +1229,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2025.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.5.0"
|
||||
|
||||
Reference in New Issue
Block a user