feat: create deployment scripts
This commit is contained in:
parent
78297efe5c
commit
8d5bce4bfb
22 changed files with 2697 additions and 74 deletions
1
CLAUDE.md
Normal file
1
CLAUDE.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
use `uv run python3` inside api/ folder
|
||||||
2
api/.gitignore
vendored
2
api/.gitignore
vendored
|
|
@ -10,3 +10,5 @@ ENV/
|
||||||
|
|
||||||
# NLTK Data (uncomment if you want to download on each deployment)
|
# NLTK Data (uncomment if you want to download on each deployment)
|
||||||
nltk_data/
|
nltk_data/
|
||||||
|
models_cache/
|
||||||
|
genfiles
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,18 @@
|
||||||
|
# Text Salience API
|
||||||
|
|
||||||
|
## Run API
|
||||||
|
```bash
|
||||||
uv run flask --app salience run
|
uv run flask --app salience run
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benchmarks
|
||||||
|
```bash
|
||||||
|
# Generate embeddings
|
||||||
|
uv run python3 benchmarks/generate_embeddings.py
|
||||||
|
|
||||||
|
# Run benchmarks
|
||||||
|
uv run pytest benchmarks/test_bench_cosine_sim.py --benchmark-json=benchmarks/genfiles/benchmark_results.json
|
||||||
|
|
||||||
|
# Visualize results
|
||||||
|
uv run python3 benchmarks/visualize_benchmarks.py benchmarks/genfiles/benchmark_results.json
|
||||||
|
```
|
||||||
|
|
|
||||||
34
api/benchmarks/01-generate_embeddings.py
Normal file
34
api/benchmarks/01-generate_embeddings.py
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
# Add the parent directory to the path so we can import salience
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
parent_dir = os.path.dirname(script_dir)
|
||||||
|
sys.path.insert(0, parent_dir)
|
||||||
|
|
||||||
|
from salience.salience import models, get_sentences
|
||||||
|
|
||||||
|
# Load the transcript
|
||||||
|
transcript_path = os.path.join(parent_dir, 'transcript-1.txt')
|
||||||
|
with open(transcript_path, 'r') as f:
|
||||||
|
source_text = f.read()
|
||||||
|
|
||||||
|
# Get sentences and encode them
|
||||||
|
print("Loading transcript and encoding sentences...")
|
||||||
|
sentences, sentence_ranges = get_sentences(source_text)
|
||||||
|
print(f"Number of sentences: {len(sentences)}")
|
||||||
|
|
||||||
|
# Use the default model for comparison
|
||||||
|
model_name = 'all-mpnet-base-v2'
|
||||||
|
model = models[model_name]
|
||||||
|
vectors = model.encode(sentences)
|
||||||
|
print(f"Vector shape: {vectors.shape}")
|
||||||
|
|
||||||
|
# Save the embeddings to genfiles directory
|
||||||
|
genfiles_dir = os.path.join(script_dir, 'genfiles')
|
||||||
|
os.makedirs(genfiles_dir, exist_ok=True)
|
||||||
|
output_path = os.path.join(genfiles_dir, 'embeddings.npy')
|
||||||
|
np.save(output_path, vectors)
|
||||||
|
print(f"\nEmbeddings saved to: {output_path}")
|
||||||
|
print(f"File size: {os.path.getsize(output_path) / 1024 / 1024:.2f} MB")
|
||||||
113
api/benchmarks/test_bench_cosine_sim.py
Normal file
113
api/benchmarks/test_bench_cosine_sim.py
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
"""
|
||||||
|
Benchmark different cosine similarity implementations using pytest-benchmark.
|
||||||
|
|
||||||
|
First run: python generate_embeddings.py
|
||||||
|
Then run: pytest test_bench_cosine_sim.py --benchmark-json=genfiles/benchmark_results.json
|
||||||
|
To visualize: python visualize_benchmarks.py genfiles/benchmark_results.json
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import numpy as np
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
# Load pre-generated embeddings once for all tests
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
embeddings_path = os.path.join(script_dir, 'genfiles', 'embeddings.npy')
|
||||||
|
vectors = np.load(embeddings_path)
|
||||||
|
|
||||||
|
|
||||||
|
# Original cos_sim function from salience.py
|
||||||
|
def cos_sim_original(a, b):
|
||||||
|
sims = a @ b.T
|
||||||
|
a_norm = np.linalg.norm(a, axis=-1)
|
||||||
|
b_norm = np.linalg.norm(b, axis=-1)
|
||||||
|
a_normalized = (sims.T / a_norm.T).T
|
||||||
|
sims = a_normalized / b_norm
|
||||||
|
return sims
|
||||||
|
|
||||||
|
|
||||||
|
# Nested for loop version
|
||||||
|
def cos_sim_nested_loop(a, b):
|
||||||
|
n = a.shape[0]
|
||||||
|
m = b.shape[0]
|
||||||
|
sims = np.zeros((n, m))
|
||||||
|
|
||||||
|
for i in range(n):
|
||||||
|
for j in range(m):
|
||||||
|
dot_product = np.dot(a[i], b[j])
|
||||||
|
norm_a = np.linalg.norm(a[i])
|
||||||
|
norm_b = np.linalg.norm(b[j])
|
||||||
|
sims[i, j] = dot_product / (norm_a * norm_b)
|
||||||
|
|
||||||
|
return sims
|
||||||
|
|
||||||
|
|
||||||
|
# E*E^T with manual in-place normalization
|
||||||
|
def cos_sim_inplace_norm(a, b):
|
||||||
|
# Compute raw dot products
|
||||||
|
sims = a @ b.T
|
||||||
|
|
||||||
|
# Compute norms once
|
||||||
|
a_norms = np.linalg.norm(a, axis=-1)
|
||||||
|
b_norms = np.linalg.norm(b, axis=-1)
|
||||||
|
|
||||||
|
# Normalize in place
|
||||||
|
for i in range(sims.shape[0]):
|
||||||
|
for j in range(sims.shape[1]):
|
||||||
|
sims[i, j] = sims[i, j] / (a_norms[i] * b_norms[j])
|
||||||
|
|
||||||
|
return sims
|
||||||
|
|
||||||
|
|
||||||
|
# Broadcast division with in-place operations
|
||||||
|
def cos_sim_broadcast_inplace(a, b):
|
||||||
|
# Compute raw dot products
|
||||||
|
sims = a @ b.T
|
||||||
|
|
||||||
|
# Compute norms once
|
||||||
|
a_norms = np.linalg.norm(a, axis=-1, keepdims=True) # shape (n, 1)
|
||||||
|
b_norms = np.linalg.norm(b, axis=-1, keepdims=True) # shape (m, 1)
|
||||||
|
|
||||||
|
# Divide by a_norms (broadcasting across columns)
|
||||||
|
sims /= a_norms
|
||||||
|
# Divide by b_norms.T (broadcasting across rows)
|
||||||
|
sims /= b_norms.T
|
||||||
|
|
||||||
|
return sims
|
||||||
|
|
||||||
|
|
||||||
|
# Verify all implementations produce the same results
|
||||||
|
def test_correctness():
|
||||||
|
"""Verify all implementations produce identical results"""
|
||||||
|
result_original = cos_sim_original(vectors, vectors)
|
||||||
|
result_nested = cos_sim_nested_loop(vectors, vectors)
|
||||||
|
result_inplace = cos_sim_inplace_norm(vectors, vectors)
|
||||||
|
result_broadcast = cos_sim_broadcast_inplace(vectors, vectors)
|
||||||
|
|
||||||
|
assert np.allclose(result_original, result_nested, atol=1e-6)
|
||||||
|
assert np.allclose(result_original, result_inplace, atol=1e-6)
|
||||||
|
assert np.allclose(result_original, result_broadcast, atol=1e-6)
|
||||||
|
|
||||||
|
|
||||||
|
# Benchmark tests
|
||||||
|
def test_bench_original(benchmark):
|
||||||
|
"""Original vectorized implementation"""
|
||||||
|
result = benchmark(cos_sim_original, vectors, vectors)
|
||||||
|
assert result.shape == (vectors.shape[0], vectors.shape[0])
|
||||||
|
|
||||||
|
|
||||||
|
def test_bench_nested_loop(benchmark):
|
||||||
|
"""Nested loop implementation"""
|
||||||
|
result = benchmark(cos_sim_nested_loop, vectors, vectors)
|
||||||
|
assert result.shape == (vectors.shape[0], vectors.shape[0])
|
||||||
|
|
||||||
|
|
||||||
|
def test_bench_inplace_norm(benchmark):
|
||||||
|
"""E*E^T with in-place normalization"""
|
||||||
|
result = benchmark(cos_sim_inplace_norm, vectors, vectors)
|
||||||
|
assert result.shape == (vectors.shape[0], vectors.shape[0])
|
||||||
|
|
||||||
|
|
||||||
|
def test_bench_broadcast_inplace(benchmark):
|
||||||
|
"""Broadcast with in-place operations"""
|
||||||
|
result = benchmark(cos_sim_broadcast_inplace, vectors, vectors)
|
||||||
|
assert result.shape == (vectors.shape[0], vectors.shape[0])
|
||||||
183
api/benchmarks/test_bench_self_cosine_sim.py
Normal file
183
api/benchmarks/test_bench_self_cosine_sim.py
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
"""
|
||||||
|
Benchmark different cosine similarity implementations for SELF-SIMILARITY (A vs A).
|
||||||
|
This specialized version only computes norms once since we're comparing A with itself.
|
||||||
|
|
||||||
|
First run: python generate_embeddings.py
|
||||||
|
Then run: pytest test_bench_self_cosine_sim.py --benchmark-json=genfiles/benchmark_self_results.json
|
||||||
|
To visualize: python visualize_benchmarks.py genfiles/benchmark_self_results.json
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import numpy as np
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
# Load pre-generated embeddings once for all tests
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
embeddings_path = os.path.join(script_dir, 'genfiles', 'embeddings.npy')
|
||||||
|
vectors = np.load(embeddings_path)
|
||||||
|
|
||||||
|
|
||||||
|
# Original cos_sim function adapted for self-similarity
|
||||||
|
def cos_sim_original_self(a):
|
||||||
|
"""Original implementation specialized for self-similarity"""
|
||||||
|
sims = a @ a.T
|
||||||
|
norms = np.linalg.norm(a, axis=-1)
|
||||||
|
a_normalized = (sims.T / norms.T).T
|
||||||
|
sims = a_normalized / norms
|
||||||
|
return sims
|
||||||
|
|
||||||
|
|
||||||
|
# Nested for loop version - PROPERLY IMPLEMENTED (norms calculated once)
|
||||||
|
def cos_sim_nested_loop_self(a):
|
||||||
|
"""Naive nested loop but with norms calculated once using numpy"""
|
||||||
|
n = a.shape[0]
|
||||||
|
sims = np.zeros((n, n))
|
||||||
|
|
||||||
|
# Calculate ALL norms once using vectorized numpy (not in the loop!)
|
||||||
|
norms = np.linalg.norm(a, axis=-1)
|
||||||
|
|
||||||
|
for i in range(n):
|
||||||
|
for j in range(n):
|
||||||
|
dot_product = np.dot(a[i], a[j])
|
||||||
|
sims[i, j] = dot_product / (norms[i] * norms[j])
|
||||||
|
|
||||||
|
return sims
|
||||||
|
|
||||||
|
|
||||||
|
# E*E^T with manual in-place normalization
|
||||||
|
def cos_sim_inplace_norm_self(a):
|
||||||
|
"""In-place normalization specialized for self-similarity"""
|
||||||
|
# Compute raw dot products
|
||||||
|
sims = a @ a.T
|
||||||
|
|
||||||
|
# Compute norms ONCE (not separate a_norms and b_norms)
|
||||||
|
norms = np.linalg.norm(a, axis=-1)
|
||||||
|
|
||||||
|
# Normalize in place
|
||||||
|
for i in range(sims.shape[0]):
|
||||||
|
for j in range(sims.shape[1]):
|
||||||
|
sims[i, j] = sims[i, j] / (norms[i] * norms[j])
|
||||||
|
|
||||||
|
return sims
|
||||||
|
|
||||||
|
|
||||||
|
# Broadcast division with in-place operations
|
||||||
|
def cos_sim_broadcast_inplace_self(a):
|
||||||
|
"""Broadcast in-place specialized for self-similarity"""
|
||||||
|
# Compute raw dot products
|
||||||
|
sims = a @ a.T
|
||||||
|
|
||||||
|
# Compute norms ONCE with keepdims for broadcasting
|
||||||
|
norms = np.linalg.norm(a, axis=-1, keepdims=True) # shape (n, 1)
|
||||||
|
|
||||||
|
# Normalize in-place using broadcasting
|
||||||
|
# Divide by norms (broadcasting across columns)
|
||||||
|
sims /= norms
|
||||||
|
# Divide by norms.T (broadcasting across rows)
|
||||||
|
sims /= norms.T
|
||||||
|
|
||||||
|
return sims
|
||||||
|
|
||||||
|
|
||||||
|
# Broadcast division without in-place operations
|
||||||
|
def cos_sim_broadcast_self(a):
|
||||||
|
"""Broadcast without in-place operations - allocates new matrices"""
|
||||||
|
# Compute raw dot products
|
||||||
|
sims = a @ a.T
|
||||||
|
|
||||||
|
# Compute norms ONCE with keepdims for broadcasting
|
||||||
|
norms = np.linalg.norm(a, axis=-1, keepdims=True) # shape (n, 1)
|
||||||
|
|
||||||
|
# Normalize using broadcasting (creates new matrices)
|
||||||
|
sims = sims / norms
|
||||||
|
sims = sims / norms.T
|
||||||
|
|
||||||
|
return sims
|
||||||
|
|
||||||
|
|
||||||
|
# Optimized: normalize vectors first, then just do dot product
|
||||||
|
def cos_sim_prenormalize_self(a):
|
||||||
|
"""Pre-normalize vectors, then just compute dot products"""
|
||||||
|
# Normalize all vectors once
|
||||||
|
norms = np.linalg.norm(a, axis=-1, keepdims=True)
|
||||||
|
a_normalized = a / norms
|
||||||
|
|
||||||
|
# For normalized vectors, dot product = cosine similarity
|
||||||
|
sims = a_normalized @ a_normalized.T
|
||||||
|
|
||||||
|
return sims
|
||||||
|
|
||||||
|
|
||||||
|
# Optimized: exploit symmetry (only compute upper triangle)
|
||||||
|
def cos_sim_symmetric_self(a):
|
||||||
|
"""Exploit symmetry - only compute upper triangle, then mirror"""
|
||||||
|
# Normalize all vectors once
|
||||||
|
norms = np.linalg.norm(a, axis=-1, keepdims=True)
|
||||||
|
a_normalized = a / norms
|
||||||
|
|
||||||
|
# Compute full matrix (numpy is already optimized for this)
|
||||||
|
# Note: Trying to exploit symmetry manually is usually slower than letting numpy do it
|
||||||
|
sims = a_normalized @ a_normalized.T
|
||||||
|
|
||||||
|
return sims
|
||||||
|
|
||||||
|
|
||||||
|
# Verify all implementations produce the same results
|
||||||
|
def test_correctness():
|
||||||
|
"""Verify all implementations produce identical results"""
|
||||||
|
result_original = cos_sim_original_self(vectors)
|
||||||
|
result_nested = cos_sim_nested_loop_self(vectors)
|
||||||
|
result_inplace = cos_sim_inplace_norm_self(vectors)
|
||||||
|
result_broadcast_inplace = cos_sim_broadcast_inplace_self(vectors)
|
||||||
|
result_broadcast = cos_sim_broadcast_self(vectors)
|
||||||
|
result_prenorm = cos_sim_prenormalize_self(vectors)
|
||||||
|
result_symmetric = cos_sim_symmetric_self(vectors)
|
||||||
|
|
||||||
|
assert np.allclose(result_original, result_nested, atol=1e-6), "Nested loop mismatch"
|
||||||
|
assert np.allclose(result_original, result_inplace, atol=1e-6), "In-place mismatch"
|
||||||
|
assert np.allclose(result_original, result_broadcast_inplace, atol=1e-6), "Broadcast inplace mismatch"
|
||||||
|
assert np.allclose(result_original, result_broadcast, atol=1e-6), "Broadcast mismatch"
|
||||||
|
assert np.allclose(result_original, result_prenorm, atol=1e-6), "Pre-normalize mismatch"
|
||||||
|
assert np.allclose(result_original, result_symmetric, atol=1e-6), "Symmetric mismatch"
|
||||||
|
|
||||||
|
|
||||||
|
# Benchmark tests
|
||||||
|
def test_bench_original_self(benchmark):
|
||||||
|
"""Original implementation (self-similarity)"""
|
||||||
|
result = benchmark(cos_sim_original_self, vectors)
|
||||||
|
assert result.shape == (vectors.shape[0], vectors.shape[0])
|
||||||
|
|
||||||
|
|
||||||
|
def test_bench_nested_loop_self(benchmark):
|
||||||
|
"""Nested loop (properly implemented with norms calculated once)"""
|
||||||
|
result = benchmark(cos_sim_nested_loop_self, vectors)
|
||||||
|
assert result.shape == (vectors.shape[0], vectors.shape[0])
|
||||||
|
|
||||||
|
|
||||||
|
def test_bench_inplace_norm_self(benchmark):
|
||||||
|
"""E*E^T with in-place normalization (self-similarity)"""
|
||||||
|
result = benchmark(cos_sim_inplace_norm_self, vectors)
|
||||||
|
assert result.shape == (vectors.shape[0], vectors.shape[0])
|
||||||
|
|
||||||
|
|
||||||
|
def test_bench_broadcast_inplace_self(benchmark):
|
||||||
|
"""Broadcast with in-place operations (self-similarity)"""
|
||||||
|
result = benchmark(cos_sim_broadcast_inplace_self, vectors)
|
||||||
|
assert result.shape == (vectors.shape[0], vectors.shape[0])
|
||||||
|
|
||||||
|
|
||||||
|
def test_bench_broadcast_self(benchmark):
|
||||||
|
"""Broadcast without in-place operations (self-similarity)"""
|
||||||
|
result = benchmark(cos_sim_broadcast_self, vectors)
|
||||||
|
assert result.shape == (vectors.shape[0], vectors.shape[0])
|
||||||
|
|
||||||
|
|
||||||
|
def test_bench_prenormalize_self(benchmark):
|
||||||
|
"""Pre-normalize vectors first (self-similarity)"""
|
||||||
|
result = benchmark(cos_sim_prenormalize_self, vectors)
|
||||||
|
assert result.shape == (vectors.shape[0], vectors.shape[0])
|
||||||
|
|
||||||
|
|
||||||
|
def test_bench_symmetric_self(benchmark):
|
||||||
|
"""Exploit symmetry (self-similarity)"""
|
||||||
|
result = benchmark(cos_sim_symmetric_self, vectors)
|
||||||
|
assert result.shape == (vectors.shape[0], vectors.shape[0])
|
||||||
198
api/benchmarks/using_timeit.py
Normal file
198
api/benchmarks/using_timeit.py
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
"""
|
||||||
|
Legacy benchmark using timeit library instead of pytest-benchmark.
|
||||||
|
|
||||||
|
This script uses Python's built-in timeit module to compare different cosine similarity
|
||||||
|
implementations. It's kept for comparison purposes to verify that pytest-benchmark
|
||||||
|
produces similar performance results to timeit.
|
||||||
|
|
||||||
|
First run: python 01-generate_embeddings.py
|
||||||
|
Then run: python using_timeit.py
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import timeit
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
# Load pre-generated embeddings
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
embeddings_path = os.path.join(script_dir, 'genfiles', 'embeddings.npy')
|
||||||
|
vectors = np.load(embeddings_path)
|
||||||
|
print(f"Loaded embeddings with shape: {vectors.shape}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Original cos_sim function adapted for self-similarity
|
||||||
|
def cos_sim_original_self(a):
|
||||||
|
"""Original implementation specialized for self-similarity"""
|
||||||
|
sims = a @ a.T
|
||||||
|
norms = np.linalg.norm(a, axis=-1)
|
||||||
|
a_normalized = (sims.T / norms.T).T
|
||||||
|
sims = a_normalized / norms
|
||||||
|
return sims
|
||||||
|
|
||||||
|
# Nested for loop version - PROPERLY IMPLEMENTED (norms calculated once)
|
||||||
|
def cos_sim_nested_loop_self(a):
|
||||||
|
"""Naive nested loop but with norms calculated once using numpy"""
|
||||||
|
n = a.shape[0]
|
||||||
|
sims = np.zeros((n, n))
|
||||||
|
|
||||||
|
# Calculate ALL norms once using vectorized numpy (not in the loop!)
|
||||||
|
norms = np.linalg.norm(a, axis=-1)
|
||||||
|
|
||||||
|
for i in range(n):
|
||||||
|
for j in range(n):
|
||||||
|
dot_product = np.dot(a[i], a[j])
|
||||||
|
sims[i, j] = dot_product / (norms[i] * norms[j])
|
||||||
|
|
||||||
|
return sims
|
||||||
|
|
||||||
|
# E*E^T with manual in-place normalization
|
||||||
|
def cos_sim_inplace_norm_self(a):
|
||||||
|
"""In-place normalization specialized for self-similarity"""
|
||||||
|
# Compute raw dot products
|
||||||
|
sims = a @ a.T
|
||||||
|
|
||||||
|
# Compute norms ONCE (not separate a_norms and b_norms)
|
||||||
|
norms = np.linalg.norm(a, axis=-1)
|
||||||
|
|
||||||
|
# Normalize in place
|
||||||
|
for i in range(sims.shape[0]):
|
||||||
|
for j in range(sims.shape[1]):
|
||||||
|
sims[i, j] = sims[i, j] / (norms[i] * norms[j])
|
||||||
|
|
||||||
|
return sims
|
||||||
|
|
||||||
|
# Broadcast division with in-place operations
|
||||||
|
def cos_sim_broadcast_inplace_self(a):
|
||||||
|
"""Broadcast in-place specialized for self-similarity"""
|
||||||
|
# Compute raw dot products
|
||||||
|
sims = a @ a.T
|
||||||
|
|
||||||
|
# Compute norms ONCE with keepdims for broadcasting
|
||||||
|
norms = np.linalg.norm(a, axis=-1, keepdims=True) # shape (n, 1)
|
||||||
|
|
||||||
|
# Normalize in-place using broadcasting
|
||||||
|
# Divide by norms (broadcasting across columns)
|
||||||
|
sims /= norms
|
||||||
|
# Divide by norms.T (broadcasting across rows)
|
||||||
|
sims /= norms.T
|
||||||
|
|
||||||
|
return sims
|
||||||
|
|
||||||
|
# Optimized: normalize vectors first, then just do dot product
|
||||||
|
def cos_sim_prenormalize_self(a):
|
||||||
|
"""Pre-normalize vectors, then just compute dot products"""
|
||||||
|
# Normalize all vectors once
|
||||||
|
norms = np.linalg.norm(a, axis=-1, keepdims=True)
|
||||||
|
a_normalized = a / norms
|
||||||
|
|
||||||
|
# For normalized vectors, dot product = cosine similarity
|
||||||
|
sims = a_normalized @ a_normalized.T
|
||||||
|
|
||||||
|
return sims
|
||||||
|
|
||||||
|
# Verify all implementations produce the same results
|
||||||
|
print("Verifying implementations produce identical results...")
|
||||||
|
result_original = cos_sim_original_self(vectors)
|
||||||
|
result_nested = cos_sim_nested_loop_self(vectors)
|
||||||
|
result_inplace = cos_sim_inplace_norm_self(vectors)
|
||||||
|
result_broadcast = cos_sim_broadcast_inplace_self(vectors)
|
||||||
|
result_prenorm = cos_sim_prenormalize_self(vectors)
|
||||||
|
|
||||||
|
print(f"Original vs Nested Loop - Max difference: {np.max(np.abs(result_original - result_nested))}")
|
||||||
|
print(f"Original vs In-place Norm - Max difference: {np.max(np.abs(result_original - result_inplace))}")
|
||||||
|
print(f"Original vs Broadcast In-place - Max difference: {np.max(np.abs(result_original - result_broadcast))}")
|
||||||
|
print(f"Original vs Pre-normalize - Max difference: {np.max(np.abs(result_original - result_prenorm))}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Benchmark each implementation
|
||||||
|
print("=" * 60)
|
||||||
|
print("PERFORMANCE BENCHMARK")
|
||||||
|
print("=" * 60)
|
||||||
|
print()
|
||||||
|
|
||||||
|
num_runs = 100
|
||||||
|
|
||||||
|
print(f"Running each implementation {num_runs} times...")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Benchmark original implementation
|
||||||
|
time_original = timeit.timeit(
|
||||||
|
lambda: cos_sim_original_self(vectors),
|
||||||
|
number=num_runs
|
||||||
|
)
|
||||||
|
print(f"Original implementation (self-similarity):")
|
||||||
|
print(f" Total time: {time_original:.4f} seconds")
|
||||||
|
print(f" Average per run: {time_original/num_runs*1000:.4f} ms")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Benchmark nested loop implementation
|
||||||
|
time_nested = timeit.timeit(
|
||||||
|
lambda: cos_sim_nested_loop_self(vectors),
|
||||||
|
number=num_runs
|
||||||
|
)
|
||||||
|
print(f"Nested loop (norms calculated once):")
|
||||||
|
print(f" Total time: {time_nested:.4f} seconds")
|
||||||
|
print(f" Average per run: {time_nested/num_runs*1000:.4f} ms")
|
||||||
|
print(f" Slowdown vs original: {time_nested/time_original:.2f}x")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Benchmark in-place normalization implementation
|
||||||
|
time_inplace = timeit.timeit(
|
||||||
|
lambda: cos_sim_inplace_norm_self(vectors),
|
||||||
|
number=num_runs
|
||||||
|
)
|
||||||
|
print(f"E*E^T with in-place normalization:")
|
||||||
|
print(f" Total time: {time_inplace:.4f} seconds")
|
||||||
|
print(f" Average per run: {time_inplace/num_runs*1000:.4f} ms")
|
||||||
|
print(f" Slowdown vs original: {time_inplace/time_original:.2f}x")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Benchmark broadcast in-place implementation
|
||||||
|
time_broadcast = timeit.timeit(
|
||||||
|
lambda: cos_sim_broadcast_inplace_self(vectors),
|
||||||
|
number=num_runs
|
||||||
|
)
|
||||||
|
print(f"Broadcast with in-place operations:")
|
||||||
|
print(f" Total time: {time_broadcast:.4f} seconds")
|
||||||
|
print(f" Average per run: {time_broadcast/num_runs*1000:.4f} ms")
|
||||||
|
print(f" Speedup vs original: {time_original/time_broadcast:.2f}x")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Benchmark pre-normalize implementation
|
||||||
|
time_prenorm = timeit.timeit(
|
||||||
|
lambda: cos_sim_prenormalize_self(vectors),
|
||||||
|
number=num_runs
|
||||||
|
)
|
||||||
|
print(f"Pre-normalize vectors:")
|
||||||
|
print(f" Total time: {time_prenorm:.4f} seconds")
|
||||||
|
print(f" Average per run: {time_prenorm/num_runs*1000:.4f} ms")
|
||||||
|
print(f" Speedup vs original: {time_original/time_prenorm:.2f}x")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
print("=" * 60)
|
||||||
|
print("SUMMARY")
|
||||||
|
print("=" * 60)
|
||||||
|
fastest = min(time_original, time_nested, time_inplace, time_broadcast, time_prenorm)
|
||||||
|
print(f"Fastest implementation: ", end="")
|
||||||
|
if fastest == time_original:
|
||||||
|
print("Original (self-similarity)")
|
||||||
|
elif fastest == time_nested:
|
||||||
|
print("Nested loop")
|
||||||
|
elif fastest == time_inplace:
|
||||||
|
print("E*E^T with in-place normalization")
|
||||||
|
elif fastest == time_broadcast:
|
||||||
|
print("Broadcast with in-place operations")
|
||||||
|
else:
|
||||||
|
print("Pre-normalize vectors")
|
||||||
|
print()
|
||||||
|
print(f"Performance ranking:")
|
||||||
|
times = [
|
||||||
|
("Original (self-similarity)", time_original),
|
||||||
|
("Nested loop", time_nested),
|
||||||
|
("E*E^T with in-place norm", time_inplace),
|
||||||
|
("Broadcast in-place", time_broadcast),
|
||||||
|
("Pre-normalize", time_prenorm)
|
||||||
|
]
|
||||||
|
times.sort(key=lambda x: x[1])
|
||||||
|
for i, (name, time) in enumerate(times, 1):
|
||||||
|
print(f" {i}. {name}: {time/num_runs*1000:.4f} ms per run ({time/fastest:.2f}x vs fastest)")
|
||||||
150
api/benchmarks/visualize_benchmarks.py
Normal file
150
api/benchmarks/visualize_benchmarks.py
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
"""
|
||||||
|
Visualize pytest-benchmark results with violin plots.
|
||||||
|
|
||||||
|
Usage: python visualize_benchmarks.py benchmark_results.json
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import numpy as np
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import seaborn as sns
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage: python visualize_benchmarks.py benchmark_results.json")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Load benchmark results
|
||||||
|
with open(sys.argv[1], 'r') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
# Extract benchmark data
|
||||||
|
benchmarks = data['benchmarks']
|
||||||
|
|
||||||
|
# Create a list to store all timing data
|
||||||
|
timing_data = []
|
||||||
|
|
||||||
|
for bench in benchmarks:
|
||||||
|
name = bench['name'].replace('test_bench_', '').replace('_', ' ').title()
|
||||||
|
stats = bench['stats']
|
||||||
|
|
||||||
|
# Require actual timing data
|
||||||
|
if 'data' not in stats:
|
||||||
|
print(f"ERROR: No raw timing data found for {name}", file=sys.stderr)
|
||||||
|
print(f"Benchmark must be run with --benchmark-save-data to store raw data", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
times = np.array(stats['data']) * 1000 # Convert to ms
|
||||||
|
|
||||||
|
for iteration, time in enumerate(times):
|
||||||
|
timing_data.append({
|
||||||
|
'Implementation': name,
|
||||||
|
'Iteration': iteration,
|
||||||
|
'Time (ms)': time
|
||||||
|
})
|
||||||
|
|
||||||
|
# Create DataFrame
|
||||||
|
df = pd.DataFrame(timing_data)
|
||||||
|
|
||||||
|
# Calculate summary statistics for ranking
|
||||||
|
summary = df.groupby('Implementation')['Time (ms)'].agg(['mean', 'median', 'std', 'min', 'max'])
|
||||||
|
slowest_mean = summary['mean'].max()
|
||||||
|
summary['Speedup vs Slowest'] = slowest_mean / summary['mean']
|
||||||
|
summary_sorted = summary.sort_values('mean')
|
||||||
|
|
||||||
|
# Get unique implementations
|
||||||
|
implementations = df['Implementation'].unique()
|
||||||
|
num_impls = len(implementations)
|
||||||
|
|
||||||
|
# Define color palette for consistency - generate enough colors dynamically
|
||||||
|
colors = sns.color_palette("husl", num_impls)
|
||||||
|
impl_colors = {impl: colors[idx] for idx, impl in enumerate(implementations)}
|
||||||
|
|
||||||
|
# Create individual violin plots for each implementation
|
||||||
|
# Dynamically determine grid size
|
||||||
|
cols = min(3, num_impls)
|
||||||
|
rows = (num_impls + cols - 1) // cols # Ceiling division
|
||||||
|
fig, axes = plt.subplots(rows, cols, figsize=(7*cols, 5*rows))
|
||||||
|
if num_impls == 1:
|
||||||
|
axes = [axes]
|
||||||
|
else:
|
||||||
|
axes = axes.flatten()
|
||||||
|
|
||||||
|
for idx, impl in enumerate(implementations):
|
||||||
|
impl_data = df[df['Implementation'] == impl]
|
||||||
|
sns.violinplot(data=impl_data, y='Time (ms)', ax=axes[idx], inner='box', color=impl_colors[impl])
|
||||||
|
axes[idx].set_title(f'{impl}', fontsize=12, fontweight='bold')
|
||||||
|
axes[idx].set_ylabel('Time (ms)', fontsize=10)
|
||||||
|
axes[idx].grid(True, alpha=0.3, axis='y')
|
||||||
|
|
||||||
|
# Add mean line
|
||||||
|
mean_val = impl_data['Time (ms)'].mean()
|
||||||
|
axes[idx].axhline(mean_val, color='red', linestyle='--', linewidth=1, alpha=0.7, label=f'Mean: {mean_val:.4f} ms')
|
||||||
|
axes[idx].legend(fontsize=8)
|
||||||
|
|
||||||
|
# Hide any extra empty subplots
|
||||||
|
for idx in range(num_impls, len(axes)):
|
||||||
|
axes[idx].set_visible(False)
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
output_file_individual = sys.argv[1].replace('.json', '_individual.png')
|
||||||
|
plt.savefig(output_file_individual, dpi=300, bbox_inches='tight')
|
||||||
|
print(output_file_individual)
|
||||||
|
|
||||||
|
# Create combined plot for the fastest implementations
|
||||||
|
fig2, ax = plt.subplots(1, 1, figsize=(10, 6))
|
||||||
|
# Pick the top 3 fastest implementations (or fewer if there aren't that many)
|
||||||
|
num_fast = min(3, num_impls)
|
||||||
|
fast_implementations = list(summary_sorted.head(num_fast).index)
|
||||||
|
df_fast = df[df['Implementation'].isin(fast_implementations)]
|
||||||
|
|
||||||
|
# Use the same colors as in individual plots
|
||||||
|
palette = [impl_colors[impl] for impl in fast_implementations]
|
||||||
|
sns.violinplot(data=df_fast, x='Implementation', y='Time (ms)', ax=ax, inner='box', palette=palette)
|
||||||
|
ax.set_title(f'Cosine Similarity: Top {num_fast} Fastest Implementations', fontsize=14, fontweight='bold')
|
||||||
|
ax.set_xlabel('Implementation', fontsize=12)
|
||||||
|
ax.set_ylabel('Time (ms)', fontsize=12)
|
||||||
|
ax.grid(True, alpha=0.3, axis='y')
|
||||||
|
|
||||||
|
# Add mean values as text
|
||||||
|
for impl in fast_implementations:
|
||||||
|
impl_data = df_fast[df_fast['Implementation'] == impl]
|
||||||
|
mean_val = impl_data['Time (ms)'].mean()
|
||||||
|
x_pos = list(fast_implementations).index(impl)
|
||||||
|
ax.text(x_pos, mean_val, f'{mean_val:.4f} ms', ha='center', va='bottom', fontsize=10, fontweight='bold')
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
output_file_combined = sys.argv[1].replace('.json', '_fast_comparison.png')
|
||||||
|
plt.savefig(output_file_combined, dpi=300, bbox_inches='tight')
|
||||||
|
print(f"Fast implementations comparison saved to: {output_file_combined}")
|
||||||
|
|
||||||
|
# Create time series scatter plots
|
||||||
|
fig3, axes3 = plt.subplots(rows, cols, figsize=(7*cols, 5*rows))
|
||||||
|
if num_impls == 1:
|
||||||
|
axes3 = [axes3]
|
||||||
|
else:
|
||||||
|
axes3 = axes3.flatten()
|
||||||
|
|
||||||
|
for idx, impl in enumerate(implementations):
|
||||||
|
impl_data = df[df['Implementation'] == impl].sort_values('Iteration')
|
||||||
|
axes3[idx].scatter(impl_data['Iteration'], impl_data['Time (ms)'], alpha=0.5, s=10, color=impl_colors[impl])
|
||||||
|
axes3[idx].set_title(f'{impl}', fontsize=12, fontweight='bold')
|
||||||
|
axes3[idx].set_xlabel('Iteration', fontsize=10)
|
||||||
|
axes3[idx].set_ylabel('Time (ms)', fontsize=10)
|
||||||
|
axes3[idx].grid(True, alpha=0.3)
|
||||||
|
|
||||||
|
# Add mean line
|
||||||
|
mean_val = impl_data['Time (ms)'].mean()
|
||||||
|
axes3[idx].axhline(mean_val, color='red', linestyle='--', linewidth=1, alpha=0.7, label=f'Mean: {mean_val:.4f} ms')
|
||||||
|
axes3[idx].legend(fontsize=8)
|
||||||
|
|
||||||
|
# Hide any extra empty subplots
|
||||||
|
for idx in range(num_impls, len(axes3)):
|
||||||
|
axes3[idx].set_visible(False)
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
output_file_timeseries = sys.argv[1].replace('.json', '_timeseries.png')
|
||||||
|
plt.savefig(output_file_timeseries, dpi=300, bbox_inches='tight')
|
||||||
|
print(f"Time series scatter plots saved to: {output_file_timeseries}")
|
||||||
|
|
||||||
|
print("\nAll plots generated successfully!")
|
||||||
|
|
@ -11,10 +11,16 @@ requires-python = ">=3.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"flask>=2.3.2,<3.0.0",
|
"flask>=2.3.2,<3.0.0",
|
||||||
"flask-cors>=4.0.0,<5.0.0",
|
"flask-cors>=4.0.0,<5.0.0",
|
||||||
|
"gunicorn>=22.0.0,<23.0.0",
|
||||||
"transformers>=4.30.2,<5.0.0",
|
"transformers>=4.30.2,<5.0.0",
|
||||||
"nltk>=3.8.1,<4.0.0",
|
"nltk>=3.8.1,<4.0.0",
|
||||||
"sentence-transformers>=2.2.2,<3.0.0",
|
"sentence-transformers>=2.2.2,<3.0.0",
|
||||||
"numpy>=1.25.0,<2.0.0",
|
"numpy>=1.25.0,<2.0.0",
|
||||||
|
"pytest>=8.4.2",
|
||||||
|
"pytest-benchmark>=5.2.0",
|
||||||
|
"matplotlib>=3.10.7",
|
||||||
|
"seaborn>=0.13.2",
|
||||||
|
"pandas>=2.3.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import os
|
||||||
# Set NLTK data path to project directory
|
# Set NLTK data path to project directory
|
||||||
PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
NLTK_DATA_DIR = os.path.join(PROJECT_DIR, 'nltk_data')
|
NLTK_DATA_DIR = os.path.join(PROJECT_DIR, 'nltk_data')
|
||||||
|
TRANSFORMERS_CACHE_DIR = os.path.join(PROJECT_DIR, 'models_cache')
|
||||||
|
|
||||||
# Add to NLTK's search path
|
# Add to NLTK's search path
|
||||||
nltk.data.path.insert(0, NLTK_DATA_DIR)
|
nltk.data.path.insert(0, NLTK_DATA_DIR)
|
||||||
|
|
@ -28,12 +29,14 @@ AVAILABLE_MODELS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
# On clustering
|
# On clustering
|
||||||
|
# all-mpnet-base-v2: 40.03
|
||||||
# mixedbread-ai/mxbai-embed-large-v1: 46.71
|
# mixedbread-ai/mxbai-embed-large-v1: 46.71
|
||||||
# gte-large-en-v1.5: 47.95
|
# gte-large-en-v1.5: 47.95
|
||||||
# Qwen/Qwen3-Embedding-0.6B: 52.33
|
# Qwen/Qwen3-Embedding-0.6B: 52.33
|
||||||
# Qwen/Qwen3-Embedding-4B: 57.15
|
# Qwen/Qwen3-Embedding-4B: 57.15
|
||||||
|
|
||||||
# On STS
|
# On STS
|
||||||
|
# all-mpnet-base-v2: 58.17
|
||||||
# gte-large-en-v1.5: 81.43
|
# gte-large-en-v1.5: 81.43
|
||||||
# Qwen/Qwen3-Embedding-0.6B: 76.17
|
# Qwen/Qwen3-Embedding-0.6B: 76.17
|
||||||
# Qwen/Qwen3-Embedding-4B: 80.86
|
# Qwen/Qwen3-Embedding-4B: 80.86
|
||||||
|
|
@ -43,23 +46,22 @@ AVAILABLE_MODELS = {
|
||||||
print("Loading sentence transformer models...")
|
print("Loading sentence transformer models...")
|
||||||
models = {}
|
models = {}
|
||||||
|
|
||||||
models['all-mpnet-base-v2'] = SentenceTransformer('all-mpnet-base-v2')
|
models['all-mpnet-base-v2'] = SentenceTransformer('all-mpnet-base-v2', cache_folder=TRANSFORMERS_CACHE_DIR)
|
||||||
print("Loading Alibaba-NLP/gte-large-en-v1.5")
|
print("Loading Alibaba-NLP/gte-large-en-v1.5")
|
||||||
models['gte-large-en-v1.5'] = SentenceTransformer('Alibaba-NLP/gte-large-en-v1.5', trust_remote_code=True)
|
models['gte-large-en-v1.5'] = SentenceTransformer('Alibaba-NLP/gte-large-en-v1.5', trust_remote_code=True, cache_folder=TRANSFORMERS_CACHE_DIR)
|
||||||
#print("Loading Qwen/Qwen3-Embedding-4B")
|
#print("Loading Qwen/Qwen3-Embedding-4B")
|
||||||
#models['qwen3-embedding-4b'] = SentenceTransformer('Qwen/Qwen3-Embedding-4B', trust_remote_code=True)
|
#models['qwen3-embedding-4b'] = SentenceTransformer('Qwen/Qwen3-Embedding-4B', trust_remote_code=True, cache_folder=TRANSFORMERS_CACHE_DIR)
|
||||||
print("Loading mixedbread-ai/mxbai-embed-large-v1")
|
print("Loading mixedbread-ai/mxbai-embed-large-v1")
|
||||||
models["mxbai-embed-large-v1"] = SentenceTransformer('mixedbread-ai/mxbai-embed-large-v1')
|
models["mxbai-embed-large-v1"] = SentenceTransformer('mixedbread-ai/mxbai-embed-large-v1', cache_folder=TRANSFORMERS_CACHE_DIR)
|
||||||
print("All models loaded!")
|
print("All models loaded!")
|
||||||
|
|
||||||
sent_detector = nltk.data.load('tokenizers/punkt/english.pickle')
|
sent_detector = nltk.data.load('tokenizers/punkt/english.pickle')
|
||||||
|
|
||||||
def cos_sim(a, b):
|
def cos_sim(a):
|
||||||
sims = a @ b.T
|
sims = a @ a.T
|
||||||
a_norm = np.linalg.norm(a, axis=-1)
|
a_norm = np.linalg.norm(a, axis=-1, keepdims=True)
|
||||||
b_norm = np.linalg.norm(b, axis=-1)
|
sims /= a_norm
|
||||||
a_normalized = (sims.T / a_norm.T).T
|
sims /= a_norm.T
|
||||||
sims = a_normalized / b_norm
|
|
||||||
return sims
|
return sims
|
||||||
|
|
||||||
def degree_power(A, k):
|
def degree_power(A, k):
|
||||||
|
|
@ -79,7 +81,7 @@ def get_sentences(source_text):
|
||||||
def text_rank(sentences, model_name='all-mpnet-base-v2'):
|
def text_rank(sentences, model_name='all-mpnet-base-v2'):
|
||||||
model = models[model_name]
|
model = models[model_name]
|
||||||
vectors = model.encode(sentences)
|
vectors = model.encode(sentences)
|
||||||
adjacency = torch.tensor(cos_sim(vectors, vectors)).fill_diagonal_(0.)
|
adjacency = torch.tensor(cos_sim(vectors)).fill_diagonal_(0.)
|
||||||
adjacency[adjacency < 0] = 0
|
adjacency[adjacency < 0] = 0
|
||||||
return normalized_adjacency(adjacency)
|
return normalized_adjacency(adjacency)
|
||||||
|
|
||||||
|
|
|
||||||
499
api/uv.lock
generated
499
api/uv.lock
generated
|
|
@ -117,6 +117,97 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "contourpy"
|
||||||
|
version = "1.3.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "numpy" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cycler"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "filelock"
|
name = "filelock"
|
||||||
version = "3.20.0"
|
version = "3.20.0"
|
||||||
|
|
@ -154,6 +245,55 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/e1/60/e941089faf4f50f2e0231d7f7af69308616a37e99da3ec75df60b8809db7/Flask_Cors-4.0.2-py2.py3-none-any.whl", hash = "sha256:38364faf1a7a5d0a55bd1d2e2f83ee9e359039182f5e6a029557e1f56d92c09a", size = 14467 },
|
{ url = "https://files.pythonhosted.org/packages/e1/60/e941089faf4f50f2e0231d7f7af69308616a37e99da3ec75df60b8809db7/Flask_Cors-4.0.2-py2.py3-none-any.whl", hash = "sha256:38364faf1a7a5d0a55bd1d2e2f83ee9e359039182f5e6a029557e1f56d92c09a", size = 14467 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fonttools"
|
||||||
|
version = "4.60.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/4b/42/97a13e47a1e51a5a7142475bbcf5107fe3a68fc34aef331c897d5fb98ad0/fonttools-4.60.1.tar.gz", hash = "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9", size = 3559823 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ea/85/639aa9bface1537e0fb0f643690672dde0695a5bbbc90736bc571b0b1941/fonttools-4.60.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7b4c32e232a71f63a5d00259ca3d88345ce2a43295bb049d21061f338124246f", size = 2831872 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6b/47/3c63158459c95093be9618794acb1067b3f4d30dcc5c3e8114b70e67a092/fonttools-4.60.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3630e86c484263eaac71d117085d509cbcf7b18f677906824e4bace598fb70d2", size = 2356990 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/dd/1934b537c86fcf99f9761823f1fc37a98fbd54568e8e613f29a90fed95a9/fonttools-4.60.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5c1015318e4fec75dd4943ad5f6a206d9727adf97410d58b7e32ab644a807914", size = 5042189 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d2/d2/9f4e4c4374dd1daa8367784e1bd910f18ba886db1d6b825b12edf6db3edc/fonttools-4.60.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e6c58beb17380f7c2ea181ea11e7db8c0ceb474c9dd45f48e71e2cb577d146a1", size = 4978683 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/c4/0fb2dfd1ecbe9a07954cc13414713ed1eab17b1c0214ef07fc93df234a47/fonttools-4.60.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec3681a0cb34c255d76dd9d865a55f260164adb9fa02628415cdc2d43ee2c05d", size = 5021372 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/d5/495fc7ae2fab20223cc87179a8f50f40f9a6f821f271ba8301ae12bb580f/fonttools-4.60.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f4b5c37a5f40e4d733d3bbaaef082149bee5a5ea3156a785ff64d949bd1353fa", size = 5132562 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bc/fa/021dab618526323c744e0206b3f5c8596a2e7ae9aa38db5948a131123e83/fonttools-4.60.1-cp311-cp311-win32.whl", hash = "sha256:398447f3d8c0c786cbf1209711e79080a40761eb44b27cdafffb48f52bcec258", size = 2230288 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/78/0e1a6d22b427579ea5c8273e1c07def2f325b977faaf60bb7ddc01456cb1/fonttools-4.60.1-cp311-cp311-win_amd64.whl", hash = "sha256:d066ea419f719ed87bc2c99a4a4bfd77c2e5949cb724588b9dd58f3fd90b92bf", size = 2278184 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e3/f7/a10b101b7a6f8836a5adb47f2791f2075d044a6ca123f35985c42edc82d8/fonttools-4.60.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7b0c6d57ab00dae9529f3faf187f2254ea0aa1e04215cf2f1a8ec277c96661bc", size = 2832953 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ed/fe/7bd094b59c926acf2304d2151354ddbeb74b94812f3dc943c231db09cb41/fonttools-4.60.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:839565cbf14645952d933853e8ade66a463684ed6ed6c9345d0faf1f0e868877", size = 2352706 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/ca/4bb48a26ed95a1e7eba175535fe5805887682140ee0a0d10a88e1de84208/fonttools-4.60.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8177ec9676ea6e1793c8a084a90b65a9f778771998eb919d05db6d4b1c0b114c", size = 4923716 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b8/9f/2cb82999f686c1d1ddf06f6ae1a9117a880adbec113611cc9d22b2fdd465/fonttools-4.60.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:996a4d1834524adbb423385d5a629b868ef9d774670856c63c9a0408a3063401", size = 4968175 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/79/be569699e37d166b78e6218f2cde8c550204f2505038cdd83b42edc469b9/fonttools-4.60.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a46b2f450bc79e06ef3b6394f0c68660529ed51692606ad7f953fc2e448bc903", size = 4911031 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/9f/89411cc116effaec5260ad519162f64f9c150e5522a27cbb05eb62d0c05b/fonttools-4.60.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ec722ee589e89a89f5b7574f5c45604030aa6ae24cb2c751e2707193b466fed", size = 5062966 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/62/a1/f888221934b5731d46cb9991c7a71f30cb1f97c0ef5fcf37f8da8fce6c8e/fonttools-4.60.1-cp312-cp312-win32.whl", hash = "sha256:b2cf105cee600d2de04ca3cfa1f74f1127f8455b71dbad02b9da6ec266e116d6", size = 2218750 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/8f/a55b5550cd33cd1028601df41acd057d4be20efa5c958f417b0c0613924d/fonttools-4.60.1-cp312-cp312-win_amd64.whl", hash = "sha256:992775c9fbe2cf794786fa0ffca7f09f564ba3499b8fe9f2f80bd7197db60383", size = 2267026 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/5b/cdd2c612277b7ac7ec8c0c9bc41812c43dc7b2d5f2b0897e15fdf5a1f915/fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f68576bb4bbf6060c7ab047b1574a1ebe5c50a17de62830079967b211059ebb", size = 2825777 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d6/8a/de9cc0540f542963ba5e8f3a1f6ad48fa211badc3177783b9d5cadf79b5d/fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eedacb5c5d22b7097482fa834bda0dafa3d914a4e829ec83cdea2a01f8c813c4", size = 2348080 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2d/8b/371ab3cec97ee3fe1126b3406b7abd60c8fec8975fd79a3c75cdea0c3d83/fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b33a7884fabd72bdf5f910d0cf46be50dce86a0362a65cfc746a4168c67eb96c", size = 4903082 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/05/06b1455e4bc653fcb2117ac3ef5fa3a8a14919b93c60742d04440605d058/fonttools-4.60.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2409d5fb7b55fd70f715e6d34e7a6e4f7511b8ad29a49d6df225ee76da76dd77", size = 4960125 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8e/37/f3b840fcb2666f6cb97038793606bdd83488dca2d0b0fc542ccc20afa668/fonttools-4.60.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c8651e0d4b3bdeda6602b85fdc2abbefc1b41e573ecb37b6779c4ca50753a199", size = 4901454 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/9e/eb76f77e82f8d4a46420aadff12cec6237751b0fb9ef1de373186dcffb5f/fonttools-4.60.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:145daa14bf24824b677b9357c5e44fd8895c2a8f53596e1b9ea3496081dc692c", size = 5044495 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/b3/cede8f8235d42ff7ae891bae8d619d02c8ac9fd0cfc450c5927a6200c70d/fonttools-4.60.1-cp313-cp313-win32.whl", hash = "sha256:2299df884c11162617a66b7c316957d74a18e3758c0274762d2cc87df7bc0272", size = 2217028 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/75/4d/b022c1577807ce8b31ffe055306ec13a866f2337ecee96e75b24b9b753ea/fonttools-4.60.1-cp313-cp313-win_amd64.whl", hash = "sha256:a3db56f153bd4c5c2b619ab02c5db5192e222150ce5a1bc10f16164714bc39ac", size = 2266200 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/83/752ca11c1aa9a899b793a130f2e466b79ea0cf7279c8d79c178fc954a07b/fonttools-4.60.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a884aef09d45ba1206712c7dbda5829562d3fea7726935d3289d343232ecb0d3", size = 2822830 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/57/17/bbeab391100331950a96ce55cfbbff27d781c1b85ebafb4167eae50d9fe3/fonttools-4.60.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8a44788d9d91df72d1a5eac49b31aeb887a5f4aab761b4cffc4196c74907ea85", size = 2345524 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3d/2e/d4831caa96d85a84dd0da1d9f90d81cec081f551e0ea216df684092c6c97/fonttools-4.60.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e852d9dda9f93ad3651ae1e3bb770eac544ec93c3807888798eccddf84596537", size = 4843490 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/13/5e2ea7c7a101b6fc3941be65307ef8df92cbbfa6ec4804032baf1893b434/fonttools-4.60.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:154cb6ee417e417bf5f7c42fe25858c9140c26f647c7347c06f0cc2d47eff003", size = 4944184 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/2b/cf9603551c525b73fc47c52ee0b82a891579a93d9651ed694e4e2cd08bb8/fonttools-4.60.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5664fd1a9ea7f244487ac8f10340c4e37664675e8667d6fee420766e0fb3cf08", size = 4890218 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/2f/933d2352422e25f2376aae74f79eaa882a50fb3bfef3c0d4f50501267101/fonttools-4.60.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:583b7f8e3c49486e4d489ad1deacfb8d5be54a8ef34d6df824f6a171f8511d99", size = 4999324 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/38/99/234594c0391221f66216bc2c886923513b3399a148defaccf81dc3be6560/fonttools-4.60.1-cp314-cp314-win32.whl", hash = "sha256:66929e2ea2810c6533a5184f938502cfdaea4bc3efb7130d8cc02e1c1b4108d6", size = 2220861 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/1d/edb5b23726dde50fc4068e1493e4fc7658eeefcaf75d4c5ffce067d07ae5/fonttools-4.60.1-cp314-cp314-win_amd64.whl", hash = "sha256:f3d5be054c461d6a2268831f04091dc82753176f6ea06dc6047a5e168265a987", size = 2270934 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fb/da/1392aaa2170adc7071fe7f9cfd181a5684a7afcde605aebddf1fb4d76df5/fonttools-4.60.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b6379e7546ba4ae4b18f8ae2b9bc5960936007a1c0e30b342f662577e8bc3299", size = 2894340 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/a7/3b9f16e010d536ce567058b931a20b590d8f3177b2eda09edd92e392375d/fonttools-4.60.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9d0ced62b59e0430b3690dbc5373df1c2aa7585e9a8ce38eff87f0fd993c5b01", size = 2375073 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9b/b5/e9bcf51980f98e59bb5bb7c382a63c6f6cac0eec5f67de6d8f2322382065/fonttools-4.60.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:875cb7764708b3132637f6c5fb385b16eeba0f7ac9fa45a69d35e09b47045801", size = 4849758 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e3/dc/1d2cf7d1cba82264b2f8385db3f5960e3d8ce756b4dc65b700d2c496f7e9/fonttools-4.60.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a184b2ea57b13680ab6d5fbde99ccef152c95c06746cb7718c583abd8f945ccc", size = 5085598 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5d/4d/279e28ba87fb20e0c69baf72b60bbf1c4d873af1476806a7b5f2b7fac1ff/fonttools-4.60.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:026290e4ec76583881763fac284aca67365e0be9f13a7fb137257096114cb3bc", size = 4957603 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/d4/ff19976305e0c05aa3340c805475abb00224c954d3c65e82c0a69633d55d/fonttools-4.60.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0e8817c7d1a0c2eedebf57ef9a9896f3ea23324769a9a2061a80fe8852705ed", size = 4974184 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/63/22/8553ff6166f5cd21cfaa115aaacaa0dc73b91c079a8cfd54a482cbc0f4f5/fonttools-4.60.1-cp314-cp314t-win32.whl", hash = "sha256:1410155d0e764a4615774e5c2c6fc516259fe3eca5882f034eb9bfdbee056259", size = 2282241 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/cb/fa7b4d148e11d5a72761a22e595344133e83a9507a4c231df972e657579b/fonttools-4.60.1-cp314-cp314t-win_amd64.whl", hash = "sha256:022beaea4b73a70295b688f817ddc24ed3e3418b5036ffcd5658141184ef0d0c", size = 2345760 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/93/0dd45cd283c32dea1545151d8c3637b4b8c53cdb3a625aeb2885b184d74d/fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb", size = 1143175 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fsspec"
|
name = "fsspec"
|
||||||
version = "2025.10.0"
|
version = "2025.10.0"
|
||||||
|
|
@ -163,6 +303,18 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/eb/02/a6b21098b1d5d6249b7c5ab69dde30108a71e4e819d4a9778f1de1d5b70d/fsspec-2025.10.0-py3-none-any.whl", hash = "sha256:7c7712353ae7d875407f97715f0e1ffcc21e33d5b24556cb1e090ae9409ec61d", size = 200966 },
|
{ url = "https://files.pythonhosted.org/packages/eb/02/a6b21098b1d5d6249b7c5ab69dde30108a71e4e819d4a9778f1de1d5b70d/fsspec-2025.10.0-py3-none-any.whl", hash = "sha256:7c7712353ae7d875407f97715f0e1ffcc21e33d5b24556cb1e090ae9409ec61d", size = 200966 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gunicorn"
|
||||||
|
version = "22.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "packaging" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/1e/88/e2f93c5738a4c1f56a458fc7a5b1676fc31dcdbb182bef6b40a141c17d66/gunicorn-22.0.0.tar.gz", hash = "sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63", size = 3639760 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/29/97/6d610ae77b5633d24b69c2ff1ac3044e0e565ecbd1ec188f02c45073054c/gunicorn-22.0.0-py3-none-any.whl", hash = "sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9", size = 84443 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hf-xet"
|
name = "hf-xet"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
|
|
@ -220,6 +372,15 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008 },
|
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iniconfig"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itsdangerous"
|
name = "itsdangerous"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
|
|
@ -250,6 +411,96 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241", size = 308396 },
|
{ url = "https://files.pythonhosted.org/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241", size = 308396 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kiwisolver"
|
||||||
|
version = "1.4.9"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markupsafe"
|
name = "markupsafe"
|
||||||
version = "3.0.3"
|
version = "3.0.3"
|
||||||
|
|
@ -324,6 +575,70 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146 },
|
{ url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matplotlib"
|
||||||
|
version = "3.10.7"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "contourpy" },
|
||||||
|
{ name = "cycler" },
|
||||||
|
{ name = "fonttools" },
|
||||||
|
{ name = "kiwisolver" },
|
||||||
|
{ name = "numpy" },
|
||||||
|
{ name = "packaging" },
|
||||||
|
{ name = "pillow" },
|
||||||
|
{ name = "pyparsing" },
|
||||||
|
{ name = "python-dateutil" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ae/e2/d2d5295be2f44c678ebaf3544ba32d20c1f9ef08c49fe47f496180e1db15/matplotlib-3.10.7.tar.gz", hash = "sha256:a06ba7e2a2ef9131c79c49e63dad355d2d878413a0376c1727c8b9335ff731c7", size = 34804865 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fc/bc/0fb489005669127ec13f51be0c6adc074d7cf191075dab1da9fe3b7a3cfc/matplotlib-3.10.7-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:53b492410a6cd66c7a471de6c924f6ede976e963c0f3097a3b7abfadddc67d0a", size = 8257507 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/6a/d42588ad895279ff6708924645b5d2ed54a7fb2dc045c8a804e955aeace1/matplotlib-3.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9749313deb729f08207718d29c86246beb2ea3fdba753595b55901dee5d2fd6", size = 8119565 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/10/b7/4aa196155b4d846bd749cf82aa5a4c300cf55a8b5e0dfa5b722a63c0f8a0/matplotlib-3.10.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2222c7ba2cbde7fe63032769f6eb7e83ab3227f47d997a8453377709b7fe3a5a", size = 8692668 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/e7/664d2b97016f46683a02d854d730cfcf54ff92c1dafa424beebef50f831d/matplotlib-3.10.7-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e91f61a064c92c307c5a9dc8c05dc9f8a68f0a3be199d9a002a0622e13f874a1", size = 9521051 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a8/a3/37aef1404efa615f49b5758a5e0261c16dd88f389bc1861e722620e4a754/matplotlib-3.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f1851eab59ca082c95df5a500106bad73672645625e04538b3ad0f69471ffcc", size = 9576878 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/33/cd/b145f9797126f3f809d177ca378de57c45413c5099c5990de2658760594a/matplotlib-3.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:6516ce375109c60ceec579e699524e9d504cd7578506f01150f7a6bc174a775e", size = 8115142 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2e/39/63bca9d2b78455ed497fcf51a9c71df200a11048f48249038f06447fa947/matplotlib-3.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:b172db79759f5f9bc13ef1c3ef8b9ee7b37b0247f987fbbbdaa15e4f87fd46a9", size = 7992439 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/be/b3/09eb0f7796932826ec20c25b517d568627754f6c6462fca19e12c02f2e12/matplotlib-3.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a0edb7209e21840e8361e91ea84ea676658aa93edd5f8762793dec77a4a6748", size = 8272389 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/11/0b/1ae80ddafb8652fd8046cb5c8460ecc8d4afccb89e2c6d6bec61e04e1eaf/matplotlib-3.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c380371d3c23e0eadf8ebff114445b9f970aff2010198d498d4ab4c3b41eea4f", size = 8128247 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7d/18/95ae2e242d4a5c98bd6e90e36e128d71cf1c7e39b0874feaed3ef782e789/matplotlib-3.10.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d5f256d49fea31f40f166a5e3131235a5d2f4b7f44520b1cf0baf1ce568ccff0", size = 8696996 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7e/3d/5b559efc800bd05cb2033aa85f7e13af51958136a48327f7c261801ff90a/matplotlib-3.10.7-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11ae579ac83cdf3fb72573bb89f70e0534de05266728740d478f0f818983c695", size = 9530153 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/57/eab4a719fd110312d3c220595d63a3c85ec2a39723f0f4e7fa7e6e3f74ba/matplotlib-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4c14b6acd16cddc3569a2d515cfdd81c7a68ac5639b76548cfc1a9e48b20eb65", size = 9593093 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/31/3c/80816f027b3a4a28cd2a0a6ef7f89a2db22310e945cd886ec25bfb399221/matplotlib-3.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:0d8c32b7ea6fb80b1aeff5a2ceb3fb9778e2759e899d9beff75584714afcc5ee", size = 8122771 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/de/77/ef1fc78bfe99999b2675435cc52120887191c566b25017d78beaabef7f2d/matplotlib-3.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:5f3f6d315dcc176ba7ca6e74c7768fb7e4cf566c49cb143f6bc257b62e634ed8", size = 7992812 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/9c/207547916a02c78f6bdd83448d9b21afbc42f6379ed887ecf610984f3b4e/matplotlib-3.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1d9d3713a237970569156cfb4de7533b7c4eacdd61789726f444f96a0d28f57f", size = 8273212 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bc/d0/b3d3338d467d3fc937f0bb7f256711395cae6f78e22cef0656159950adf0/matplotlib-3.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37a1fea41153dd6ee061d21ab69c9cf2cf543160b1b85d89cd3d2e2a7902ca4c", size = 8128713 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/22/ff/6425bf5c20d79aa5b959d1ce9e65f599632345391381c9a104133fe0b171/matplotlib-3.10.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b3c4ea4948d93c9c29dc01c0c23eef66f2101bf75158c291b88de6525c55c3d1", size = 8698527 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d0/7f/ccdca06f4c2e6c7989270ed7829b8679466682f4cfc0f8c9986241c023b6/matplotlib-3.10.7-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22df30ffaa89f6643206cf13877191c63a50e8f800b038bc39bee9d2d4957632", size = 9529690 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b8/95/b80fc2c1f269f21ff3d193ca697358e24408c33ce2b106a7438a45407b63/matplotlib-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b69676845a0a66f9da30e87f48be36734d6748024b525ec4710be40194282c84", size = 9593732 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/b6/23064a96308b9aeceeffa65e96bcde459a2ea4934d311dee20afde7407a0/matplotlib-3.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:744991e0cc863dd669c8dc9136ca4e6e0082be2070b9d793cbd64bec872a6815", size = 8122727 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/a6/2faaf48133b82cf3607759027f82b5c702aa99cdfcefb7f93d6ccf26a424/matplotlib-3.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:fba2974df0bf8ce3c995fa84b79cde38326e0f7b5409e7a3a481c1141340bcf7", size = 7992958 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4a/f0/b018fed0b599bd48d84c08794cb242227fe3341952da102ee9d9682db574/matplotlib-3.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:932c55d1fa7af4423422cb6a492a31cbcbdbe68fd1a9a3f545aa5e7a143b5355", size = 8316849 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b0/b7/bb4f23856197659f275e11a2a164e36e65e9b48ea3e93c4ec25b4f163198/matplotlib-3.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e38c2d581d62ee729a6e144c47a71b3f42fb4187508dbbf4fe71d5612c3433b", size = 8178225 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/62/56/0600609893ff277e6f3ab3c0cef4eafa6e61006c058e84286c467223d4d5/matplotlib-3.10.7-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:786656bb13c237bbcebcd402f65f44dd61ead60ee3deb045af429d889c8dbc67", size = 8711708 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d8/1a/6bfecb0cafe94d6658f2f1af22c43b76cf7a1c2f0dc34ef84cbb6809617e/matplotlib-3.10.7-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09d7945a70ea43bf9248f4b6582734c2fe726723204a76eca233f24cffc7ef67", size = 9541409 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/08/50/95122a407d7f2e446fd865e2388a232a23f2b81934960ea802f3171518e4/matplotlib-3.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d0b181e9fa8daf1d9f2d4c547527b167cb8838fc587deabca7b5c01f97199e84", size = 9594054 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/13/76/75b194a43b81583478a81e78a07da8d9ca6ddf50dd0a2ccabf258059481d/matplotlib-3.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:31963603041634ce1a96053047b40961f7a29eb8f9a62e80cc2c0427aa1d22a2", size = 8200100 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f5/9e/6aefebdc9f8235c12bdeeda44cc0383d89c1e41da2c400caf3ee2073a3ce/matplotlib-3.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:aebed7b50aa6ac698c90f60f854b47e48cd2252b30510e7a1feddaf5a3f72cbf", size = 8042131 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0d/4b/e5bc2c321b6a7e3a75638d937d19ea267c34bd5a90e12bee76c4d7c7a0d9/matplotlib-3.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d883460c43e8c6b173fef244a2341f7f7c0e9725c7fe68306e8e44ed9c8fb100", size = 8273787 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/86/ad/6efae459c56c2fbc404da154e13e3a6039129f3c942b0152624f1c621f05/matplotlib-3.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07124afcf7a6504eafcb8ce94091c5898bbdd351519a1beb5c45f7a38c67e77f", size = 8131348 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/5a/a4284d2958dee4116359cc05d7e19c057e64ece1b4ac986ab0f2f4d52d5a/matplotlib-3.10.7-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c17398b709a6cce3d9fdb1595c33e356d91c098cd9486cb2cc21ea2ea418e715", size = 9533949 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/de/ff/f3781b5057fa3786623ad8976fc9f7b0d02b2f28534751fd5a44240de4cf/matplotlib-3.10.7-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7146d64f561498764561e9cd0ed64fcf582e570fc519e6f521e2d0cfd43365e1", size = 9804247 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/47/5a/993a59facb8444efb0e197bf55f545ee449902dcee86a4dfc580c3b61314/matplotlib-3.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:90ad854c0a435da3104c01e2c6f0028d7e719b690998a2333d7218db80950722", size = 9595497 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0d/a5/77c95aaa9bb32c345cbb49626ad8eb15550cba2e6d4c88081a6c2ac7b08d/matplotlib-3.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:4645fc5d9d20ffa3a39361fcdbcec731382763b623b72627806bf251b6388866", size = 8252732 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/04/45d269b4268d222390d7817dae77b159651909669a34ee9fdee336db5883/matplotlib-3.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:9257be2f2a03415f9105c486d304a321168e61ad450f6153d77c69504ad764bb", size = 8124240 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4b/c7/ca01c607bb827158b439208c153d6f14ddb9fb640768f06f7ca3488ae67b/matplotlib-3.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1e4bbad66c177a8fdfa53972e5ef8be72a5f27e6a607cec0d8579abd0f3102b1", size = 8316938 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/84/d2/5539e66e9f56d2fdec94bb8436f5e449683b4e199bcc897c44fbe3c99e28/matplotlib-3.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d8eb7194b084b12feb19142262165832fc6ee879b945491d1c3d4660748020c4", size = 8178245 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/77/b5/e6ca22901fd3e4fe433a82e583436dd872f6c966fca7e63cf806b40356f8/matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d41379b05528091f00e1728004f9a8d7191260f3862178b88e8fd770206318", size = 9541411 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9e/99/a4524db57cad8fee54b7237239a8f8360bfcfa3170d37c9e71c090c0f409/matplotlib-3.10.7-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a74f79fafb2e177f240579bc83f0b60f82cc47d2f1d260f422a0627207008ca", size = 9803664 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e6/a5/85e2edf76ea0ad4288d174926d9454ea85f3ce5390cc4e6fab196cbf250b/matplotlib-3.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:702590829c30aada1e8cef0568ddbffa77ca747b4d6e36c6d173f66e301f89cc", size = 9594066 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/39/69/9684368a314f6d83fe5c5ad2a4121a3a8e03723d2e5c8ea17b66c1bad0e7/matplotlib-3.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:f79d5de970fc90cd5591f60053aecfce1fcd736e0303d9f0bf86be649fa68fb8", size = 8342832 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/5f/e22e08da14bc1a0894184640d47819d2338b792732e20d292bf86e5ab785/matplotlib-3.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:cb783436e47fcf82064baca52ce748af71725d0352e1d31564cbe9c95df92b9c", size = 8172585 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/58/8f/76d5dc21ac64a49e5498d7f0472c0781dae442dd266a67458baec38288ec/matplotlib-3.10.7-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:15112bcbaef211bd663fa935ec33313b948e214454d949b723998a43357b17b0", size = 8252283 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/27/0d/9c5d4c2317feb31d819e38c9f947c942f42ebd4eb935fc6fd3518a11eaa7/matplotlib-3.10.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d2a959c640cdeecdd2ec3136e8ea0441da59bcaf58d67e9c590740addba2cb68", size = 8116733 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/cc/3fe688ff1355010937713164caacf9ed443675ac48a997bab6ed23b3f7c0/matplotlib-3.10.7-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3886e47f64611046bc1db523a09dd0a0a6bed6081e6f90e13806dd1d1d1b5e91", size = 8693919 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mpmath"
|
name = "mpmath"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
|
@ -524,6 +839,60 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 },
|
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
{ 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 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pillow"
|
name = "pillow"
|
||||||
version = "12.0.0"
|
version = "12.0.0"
|
||||||
|
|
@ -611,6 +980,92 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/95/7e/f896623c3c635a90537ac093c6a618ebe1a90d87206e42309cb5d98a1b9e/pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5", size = 6997850 },
|
{ url = "https://files.pythonhosted.org/packages/95/7e/f896623c3c635a90537ac093c6a618ebe1a90d87206e42309cb5d98a1b9e/pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5", size = 6997850 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pluggy"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "py-cpuinfo"
|
||||||
|
version = "9.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/37/a8/d832f7293ebb21690860d2e01d8115e5ff6f2ae8bbdc953f0eb0fa4bd2c7/py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690", size = 104716 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pygments"
|
||||||
|
version = "2.19.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyparsing"
|
||||||
|
version = "3.2.5"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest"
|
||||||
|
version = "8.4.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
|
{ name = "iniconfig" },
|
||||||
|
{ name = "packaging" },
|
||||||
|
{ name = "pluggy" },
|
||||||
|
{ name = "pygments" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pytest-benchmark"
|
||||||
|
version = "5.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "py-cpuinfo" },
|
||||||
|
{ name = "pytest" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/91/84/84ba011c4b2a44c8fce772be6124821a27cecd0f69b324f24ef4c1172863/pytest_benchmark-5.2.0.tar.gz", hash = "sha256:75731991edf6c807d0699130afbb4ba77d8ce8e3b8314662c340ee8e1db19f43", size = 339143 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b7/c2/57de9aa286a2f6d00c52a7bb4b16dbbfa2a6c80b4a4f0e415c874269a4a6/pytest_benchmark-5.2.0-py3-none-any.whl", hash = "sha256:0631cdf19f6032fc46d6bf9e8d15931d78473228b579a3fd84ca5e2f0e8ee06c", size = 44194 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 }
|
||||||
|
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 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyyaml"
|
name = "pyyaml"
|
||||||
version = "6.0.3"
|
version = "6.0.3"
|
||||||
|
|
@ -802,8 +1257,14 @@ source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "flask" },
|
{ name = "flask" },
|
||||||
{ name = "flask-cors" },
|
{ name = "flask-cors" },
|
||||||
|
{ name = "gunicorn" },
|
||||||
|
{ name = "matplotlib" },
|
||||||
{ name = "nltk" },
|
{ name = "nltk" },
|
||||||
{ name = "numpy" },
|
{ name = "numpy" },
|
||||||
|
{ name = "pandas" },
|
||||||
|
{ name = "pytest" },
|
||||||
|
{ name = "pytest-benchmark" },
|
||||||
|
{ name = "seaborn" },
|
||||||
{ name = "sentence-transformers" },
|
{ name = "sentence-transformers" },
|
||||||
{ name = "transformers" },
|
{ name = "transformers" },
|
||||||
]
|
]
|
||||||
|
|
@ -812,8 +1273,14 @@ dependencies = [
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "flask", specifier = ">=2.3.2,<3.0.0" },
|
{ name = "flask", specifier = ">=2.3.2,<3.0.0" },
|
||||||
{ name = "flask-cors", specifier = ">=4.0.0,<5.0.0" },
|
{ name = "flask-cors", specifier = ">=4.0.0,<5.0.0" },
|
||||||
|
{ name = "gunicorn", specifier = ">=22.0.0,<23.0.0" },
|
||||||
|
{ name = "matplotlib", specifier = ">=3.10.7" },
|
||||||
{ name = "nltk", specifier = ">=3.8.1,<4.0.0" },
|
{ name = "nltk", specifier = ">=3.8.1,<4.0.0" },
|
||||||
{ name = "numpy", specifier = ">=1.25.0,<2.0.0" },
|
{ name = "numpy", specifier = ">=1.25.0,<2.0.0" },
|
||||||
|
{ name = "pandas", specifier = ">=2.3.3" },
|
||||||
|
{ name = "pytest", specifier = ">=8.4.2" },
|
||||||
|
{ name = "pytest-benchmark", specifier = ">=5.2.0" },
|
||||||
|
{ name = "seaborn", specifier = ">=0.13.2" },
|
||||||
{ name = "sentence-transformers", specifier = ">=2.2.2,<3.0.0" },
|
{ name = "sentence-transformers", specifier = ">=2.2.2,<3.0.0" },
|
||||||
{ name = "transformers", specifier = ">=4.30.2,<5.0.0" },
|
{ name = "transformers", specifier = ">=4.30.2,<5.0.0" },
|
||||||
]
|
]
|
||||||
|
|
@ -928,6 +1395,20 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/64/47/a494741db7280eae6dc033510c319e34d42dd41b7ac0c7ead39354d1a2b5/scipy-1.16.3-cp314-cp314t-win_arm64.whl", hash = "sha256:21d9d6b197227a12dcbf9633320a4e34c6b0e51c57268df255a0942983bac562", size = 26464127 },
|
{ url = "https://files.pythonhosted.org/packages/64/47/a494741db7280eae6dc033510c319e34d42dd41b7ac0c7ead39354d1a2b5/scipy-1.16.3-cp314-cp314t-win_arm64.whl", hash = "sha256:21d9d6b197227a12dcbf9633320a4e34c6b0e51c57268df255a0942983bac562", size = 26464127 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "seaborn"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "matplotlib" },
|
||||||
|
{ name = "numpy" },
|
||||||
|
{ name = "pandas" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/86/59/a451d7420a77ab0b98f7affa3a1d78a313d2f7281a57afb1a34bae8ab412/seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7", size = 1457696 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/83/11/00d3c3dfc25ad54e731d91449895a79e4bf2384dc3ac01809010ba88f6d5/seaborn-0.13.2-py3-none-any.whl", hash = "sha256:636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987", size = 294914 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sentence-transformers"
|
name = "sentence-transformers"
|
||||||
version = "2.7.0"
|
version = "2.7.0"
|
||||||
|
|
@ -956,6 +1437,15 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486 },
|
{ url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "six"
|
||||||
|
version = "1.17.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sympy"
|
name = "sympy"
|
||||||
version = "1.14.0"
|
version = "1.14.0"
|
||||||
|
|
@ -1113,6 +1603,15 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614 },
|
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "urllib3"
|
name = "urllib3"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
|
|
|
||||||
33
deploy-models.sh
Executable file
33
deploy-models.sh
Executable file
|
|
@ -0,0 +1,33 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# rsync local models_cache to server shared cache
|
||||||
|
# reports server models not in local (manual cleanup required)
|
||||||
|
|
||||||
|
ssh=deploy-peoplesgrocers-website
|
||||||
|
base=/home/peoplesgrocers
|
||||||
|
project=salience
|
||||||
|
|
||||||
|
ssh $ssh "mkdir -p $base/$project/shared/models_cache"
|
||||||
|
|
||||||
|
test -d api/models_cache || { echo 'no local api/models_cache'; exit 1; }
|
||||||
|
|
||||||
|
echo "local models_cache size:"
|
||||||
|
du -sh api/models_cache
|
||||||
|
|
||||||
|
echo "syncing to $ssh:$base/$project/shared/models_cache/"
|
||||||
|
# do not use compression because these model files are basically random data
|
||||||
|
# the SSH connection is pretty spotting when transferring large files
|
||||||
|
rsync -va --info=progress2 --partial --append-verify api/models_cache/ $ssh:$base/$project/shared/models_cache/
|
||||||
|
|
||||||
|
# local models
|
||||||
|
local=$(cd api/models_cache && find . -maxdepth 2 -name 'models--*' -type d | sed 's|./||' | sort)
|
||||||
|
|
||||||
|
# server models
|
||||||
|
remote=$(ssh $ssh "cd $base/$project/shared/models_cache && find . -maxdepth 2 -name 'models--*' -type d | sed 's|./||'" | sort)
|
||||||
|
|
||||||
|
# report server models not in local
|
||||||
|
echo "checking for unused models on server..."
|
||||||
|
for r in $remote; do
|
||||||
|
echo "$local" | grep -q "^${r}$" || echo "unused: $r"
|
||||||
|
done
|
||||||
301
deploy.sh
Executable file
301
deploy.sh
Executable file
|
|
@ -0,0 +1,301 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# rsync wrapper for unreliable network connections
|
||||||
|
# my home network is spotty and rsync often dies mid-transfer
|
||||||
|
# this lets me manually retry the failed rsync and continue the deploy
|
||||||
|
rsync_retry() {
|
||||||
|
if ! rsync "$@"; then
|
||||||
|
echo ""
|
||||||
|
echo "rsync failed (probably network). run this manually:"
|
||||||
|
echo "rsync $*"
|
||||||
|
echo ""
|
||||||
|
printf "press enter after manual rsync completes to continue..."
|
||||||
|
read _
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deployment topology and zero-downtime process
|
||||||
|
#
|
||||||
|
# Directory structure on remote:
|
||||||
|
# $base/$project/
|
||||||
|
# base_port - starting port number (default 3100)
|
||||||
|
# releases/{stamp}_{hash}/
|
||||||
|
# dist/ - static assets served by nginx
|
||||||
|
# server/ - node server from frontend/server/entrypoint.express.js
|
||||||
|
# api/ - python flask API (salience/, nltk_data/, etc.)
|
||||||
|
# assigned_port - port allocated to frontend for this release
|
||||||
|
# assigned_api_port - port allocated to API for this release
|
||||||
|
# current -> releases/{latest}
|
||||||
|
# systemd/ - unit files per release (frontend + API)
|
||||||
|
#
|
||||||
|
# Zero-downtime deployment:
|
||||||
|
# 1. rsync new release (frontend/dist/ + frontend/server/ + api/)
|
||||||
|
# 2. install dependencies (npm for frontend, uv for API)
|
||||||
|
# 3. allocate two ports (frontend + API via get-next-port.sh)
|
||||||
|
# 4. generate systemd units for new release with unique ports
|
||||||
|
# - frontend: node server
|
||||||
|
# - API: gunicorn with 4 workers running Flask app
|
||||||
|
# 5. start new services, wait for health
|
||||||
|
# 6. update nginx upstream to point to new ports
|
||||||
|
# 7. reload nginx (graceful, no dropped connections)
|
||||||
|
# 8. stop old services
|
||||||
|
# 9. cleanup old releases (keep 3 most recent)
|
||||||
|
#
|
||||||
|
# Port allocation: get-next-port.sh reads base_port and existing
|
||||||
|
# assigned_port files to find first available port.
|
||||||
|
# Each release runs independently until cutover.
|
||||||
|
|
||||||
|
ssh=deploy-peoplesgrocers-website
|
||||||
|
base=/home/peoplesgrocers
|
||||||
|
project=salience
|
||||||
|
nginx_conf=/etc/nginx/sites-available/$project
|
||||||
|
service_listen_address=127.10.143.212
|
||||||
|
local_nginx_snippet="$HOME/src/work/infra/servers/chicago-web01/nginx/snippets/qwik-city-apps/salience.conf"
|
||||||
|
|
||||||
|
test -d frontend/dist || { echo 'no frontend/dist/'; exit 1; }
|
||||||
|
test -d frontend/server || { echo 'no frontend/server/'; exit 1; }
|
||||||
|
test -d .git || { echo 'not a git repo'; exit 1; }
|
||||||
|
git diff-index --quiet HEAD || { echo 'git repo dirty'; exit 1; }
|
||||||
|
|
||||||
|
hash=$(git rev-parse --short=8 HEAD)
|
||||||
|
stamp=$(date +%Y-%b-%d-%a-%I-%M%p | tr 'APM' 'apm')
|
||||||
|
release="${stamp}_${hash}"
|
||||||
|
service_name="${project}-${release}"
|
||||||
|
|
||||||
|
echo "deploying: $project @ $release"
|
||||||
|
printf "continue? [y/n] "
|
||||||
|
read ans
|
||||||
|
test "$ans" = "y" || exit 1
|
||||||
|
|
||||||
|
# prepare remote directories
|
||||||
|
ssh $ssh "mkdir -p $base/$project/{releases,systemd} $base/$project/releases/$release"
|
||||||
|
|
||||||
|
# sync both dist and server
|
||||||
|
echo "syncing dist..."
|
||||||
|
rsync_retry -tvaz frontend/dist/ $ssh:$base/$project/releases/$release/dist/
|
||||||
|
echo "syncing server..."
|
||||||
|
rsync_retry -tvaz frontend/server/ $ssh:$base/$project/releases/$release/server/
|
||||||
|
|
||||||
|
# copy server package.json and install dependencies
|
||||||
|
echo "copying server package.json..."
|
||||||
|
scp frontend/package.json $ssh:$base/$project/releases/$release/package.json
|
||||||
|
echo "installing server dependencies..."
|
||||||
|
ssh $ssh "source ~/.nvm/nvm.sh && cd $base/$project/releases/$release && npm install"
|
||||||
|
|
||||||
|
# sync api directory (exclude benchmarks, include specific files/dirs)
|
||||||
|
echo "syncing api..."
|
||||||
|
rsync_retry -tvaz \
|
||||||
|
--include='salience/' --include='salience/**' \
|
||||||
|
--include='nltk_data/' --include='nltk_data/**' \
|
||||||
|
--include='pyproject.toml' \
|
||||||
|
--include='uv.lock' \
|
||||||
|
--include='transcript.txt' \
|
||||||
|
--include='README.md' \
|
||||||
|
--exclude='*' \
|
||||||
|
api/ $ssh:$base/$project/releases/$release/api/
|
||||||
|
|
||||||
|
# link to shared models cache
|
||||||
|
echo "linking to shared models_cache..."
|
||||||
|
ssh $ssh "mkdir -p $base/$project/shared/models_cache"
|
||||||
|
ssh $ssh "ln -sfn ../../../shared/models_cache $base/$project/releases/$release/api/models_cache"
|
||||||
|
|
||||||
|
echo "installing api dependencies..."
|
||||||
|
ssh $ssh "cd $base/$project/releases/$release/api && ~/.local/bin/uv sync"
|
||||||
|
|
||||||
|
set -x
|
||||||
|
# determine ports for this release (frontend and api)
|
||||||
|
port=$(sh get-next-port.sh)
|
||||||
|
echo "frontend port for this release: $port"
|
||||||
|
api_port=$(sh get-next-port.sh)
|
||||||
|
echo "api port for this release: $api_port"
|
||||||
|
|
||||||
|
# record port assignments
|
||||||
|
ssh $ssh "echo $port > $base/$project/releases/$release/assigned_port"
|
||||||
|
ssh $ssh "echo $api_port > $base/$project/releases/$release/assigned_api_port"
|
||||||
|
|
||||||
|
set +x
|
||||||
|
|
||||||
|
# generate systemd unit file
|
||||||
|
unit_content="[Unit]
|
||||||
|
Description=${project} release ${release}
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=peoplesgrocers
|
||||||
|
WorkingDirectory=$base/$project/releases/$release
|
||||||
|
Environment=\"PORT=$port\"
|
||||||
|
Environment=\"ORIGIN=https://peoplesgrocers.com\"
|
||||||
|
ExecStart=/home/peoplesgrocers/.nvm/versions/node/v24.10.0/bin/node server/entry.express.js
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target"
|
||||||
|
|
||||||
|
echo "$unit_content" | ssh $ssh "cat > $base/$project/systemd/${service_name}.service"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "systemd unit created at: $base/$project/systemd/${service_name}.service"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# generate systemd unit file for API
|
||||||
|
api_service_name="${project}-api-${release}"
|
||||||
|
api_unit_content="[Unit]
|
||||||
|
Description=${project} API release ${release}
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=peoplesgrocers
|
||||||
|
WorkingDirectory=$base/$project/releases/$release/api
|
||||||
|
Environment=\"PORT=$api_port\"
|
||||||
|
ExecStart=$base/$project/releases/$release/api/.venv/bin/gunicorn --bind ${service_listen_address}:$api_port --workers 4 salience:app
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target"
|
||||||
|
|
||||||
|
echo "$api_unit_content" | ssh $ssh "cat > $base/$project/systemd/${api_service_name}.service"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "API systemd unit created at: $base/$project/systemd/${api_service_name}.service"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# find old services
|
||||||
|
old_service=$(ssh $ssh "systemctl list-units --type=service --state=running | grep '^${project}-' | grep -v 'api' | awk '{print \$1}' | head -1" || true)
|
||||||
|
if [ -n "$old_service" ]; then
|
||||||
|
old_port=$(ssh $ssh "systemctl show $old_service --property=Environment" | sed -n 's/.*PORT=\([0-9]*\).*/\1/p')
|
||||||
|
echo "old frontend service: $old_service (port $old_port)"
|
||||||
|
else
|
||||||
|
old_port=""
|
||||||
|
echo "no old frontend service running"
|
||||||
|
fi
|
||||||
|
|
||||||
|
old_api_service=$(ssh $ssh "systemctl list-units --type=service --state=running | grep '^${project}-api-' | awk '{print \$1}' | head -1" || true)
|
||||||
|
if [ -n "$old_api_service" ]; then
|
||||||
|
old_api_port=$(ssh $ssh "systemctl show $old_api_service --property=Environment" | sed -n 's/.*PORT=\([0-9]*\).*/\1/p')
|
||||||
|
echo "old API service: $old_api_service (port $old_api_port)"
|
||||||
|
else
|
||||||
|
old_api_port=""
|
||||||
|
echo "no old API service running"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update local nginx snippet with new port
|
||||||
|
if [ -n "$local_nginx_snippet" ] && [ -f "$local_nginx_snippet" ]; then
|
||||||
|
echo "updating local nginx snippet: $local_nginx_snippet"
|
||||||
|
if [ -n "$old_port" ]; then
|
||||||
|
echo " changing port $old_port -> $port"
|
||||||
|
sed -i.bak "s/${service_listen_address}:${old_port}/${service_listen_address}:${port}/g" "$local_nginx_snippet"
|
||||||
|
else
|
||||||
|
echo " setting port to $port"
|
||||||
|
sed -i.bak "s/${service_listen_address}:[0-9]\{4,5\}/${service_listen_address}:${port}/g" "$local_nginx_snippet"
|
||||||
|
fi
|
||||||
|
rm -f "${local_nginx_snippet}.bak"
|
||||||
|
echo "nginx snippet updated locally"
|
||||||
|
else
|
||||||
|
echo "warning: local_nginx_snippet not set or file not found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "--- run these commands on $ssh ---"
|
||||||
|
echo ""
|
||||||
|
echo "# install and start new services"
|
||||||
|
echo "sudo ln -sf $base/$project/systemd/${service_name}.service /etc/systemd/system/"
|
||||||
|
echo "sudo ln -sf $base/$project/systemd/${api_service_name}.service /etc/systemd/system/"
|
||||||
|
echo "sudo systemctl daemon-reload"
|
||||||
|
echo "sudo systemctl start ${service_name}"
|
||||||
|
echo "sudo systemctl start ${api_service_name}"
|
||||||
|
echo ""
|
||||||
|
echo "# verify services are healthy"
|
||||||
|
echo "sudo systemctl status ${service_name}"
|
||||||
|
echo "sudo systemctl status ${api_service_name}"
|
||||||
|
echo "curl http://${service_listen_address}:$port/"
|
||||||
|
echo "curl http://${service_listen_address}:$api_port/models"
|
||||||
|
echo ""
|
||||||
|
echo "# then deploy your nginx configuration and reload nginx"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ -n "$old_service" ]; then
|
||||||
|
echo "# stop old frontend service"
|
||||||
|
echo "sudo systemctl stop $old_service"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$old_api_service" ]; then
|
||||||
|
echo "# stop old API service"
|
||||||
|
echo "sudo systemctl stop $old_api_service"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "# update current symlink"
|
||||||
|
echo "ln -sfn releases/$release $base/$project/current"
|
||||||
|
echo ""
|
||||||
|
echo "--- end commands ---"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
printf "test health checks? [y/n] "
|
||||||
|
read ans
|
||||||
|
if [ "$ans" = "y" ]; then
|
||||||
|
echo "testing frontend..."
|
||||||
|
ssh $ssh "curl -v http://${service_listen_address}:$port/" || echo "frontend health check failed"
|
||||||
|
echo "testing API..."
|
||||||
|
ssh $ssh "curl -v http://${service_listen_address}:$api_port/models" || echo "API health check failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$old_service" ]; then
|
||||||
|
echo ""
|
||||||
|
printf "stop old frontend service ($old_service)? [y/n] "
|
||||||
|
read ans
|
||||||
|
if [ "$ans" = "y" ]; then
|
||||||
|
ssh $ssh "sudo systemctl stop $old_service"
|
||||||
|
echo "old frontend service stopped"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$old_api_service" ]; then
|
||||||
|
echo ""
|
||||||
|
printf "stop old API service ($old_api_service)? [y/n] "
|
||||||
|
read ans
|
||||||
|
if [ "$ans" = "y" ]; then
|
||||||
|
ssh $ssh "sudo systemctl stop $old_api_service"
|
||||||
|
echo "old API service stopped"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
printf "update current symlink? [y/n] "
|
||||||
|
read ans
|
||||||
|
if [ "$ans" = "y" ]; then
|
||||||
|
ssh $ssh "ln -sfn releases/$release $base/$project/current"
|
||||||
|
echo "current -> $release"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "cleanup old releases (keep 3):"
|
||||||
|
old_releases=$(ssh $ssh "cd $base/$project/releases && ls -t | sed -n '4,\$p'" || true)
|
||||||
|
if [ -n "$old_releases" ]; then
|
||||||
|
echo "$old_releases"
|
||||||
|
printf "remove these? [y/n] "
|
||||||
|
read ans
|
||||||
|
if [ "$ans" = "y" ]; then
|
||||||
|
ssh $ssh "cd $base/$project/releases && ls -t | sed -n '4,\$p' | while read r; do
|
||||||
|
rm -rf \"\$r\"
|
||||||
|
sudo systemctl stop ${project}-\${r} 2>/dev/null || true
|
||||||
|
sudo systemctl stop ${project}-api-\${r} 2>/dev/null || true
|
||||||
|
sudo rm -f /etc/systemd/system/${project}-\${r}.service
|
||||||
|
sudo rm -f /etc/systemd/system/${project}-api-\${r}.service
|
||||||
|
rm -f $base/$project/systemd/${project}-\${r}.service
|
||||||
|
rm -f $base/$project/systemd/${project}-api-\${r}.service
|
||||||
|
done"
|
||||||
|
ssh $ssh "sudo systemctl daemon-reload"
|
||||||
|
echo "cleanup done"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "no old releases to clean"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "done: $release (frontend port $port, API port $api_port)"
|
||||||
|
|
@ -63,3 +63,23 @@ The production build will generate client and server modules by running both cli
|
||||||
```shell
|
```shell
|
||||||
npm run build # or `yarn build`
|
npm run build # or `yarn build`
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Static Site Generator (Node.js)
|
||||||
|
|
||||||
|
Be sure to configure your server to serve very long cache headers for the `build/**/*.js` files.
|
||||||
|
|
||||||
|
Typically you'd set the `Cache-Control` header for those files to `public, max-age=31536000, immutable`.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm run build.server
|
||||||
|
```
|
||||||
|
|
||||||
|
## Express Server
|
||||||
|
|
||||||
|
This app has a minimal [Express server](https://expressjs.com/) implementation. After running a full build, you can preview the build using the command:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run serve
|
||||||
|
```
|
||||||
|
|
||||||
|
Then visit [http://localhost:8080/](http://localhost:8080/)
|
||||||
|
|
|
||||||
15
frontend/adapters/express/vite.config.ts
Normal file
15
frontend/adapters/express/vite.config.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { nodeServerAdapter } from "@builder.io/qwik-city/adapters/node-server/vite";
|
||||||
|
import { extendConfig } from "@builder.io/qwik-city/vite";
|
||||||
|
import baseConfig from "../../vite.config";
|
||||||
|
|
||||||
|
export default extendConfig(baseConfig, () => {
|
||||||
|
return {
|
||||||
|
build: {
|
||||||
|
ssr: true,
|
||||||
|
rollupOptions: {
|
||||||
|
input: ["src/entry.express.tsx", "@qwik-city-plan"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [nodeServerAdapter({ name: "express" })],
|
||||||
|
};
|
||||||
|
});
|
||||||
24
frontend/adapters/static/vite.config.ts
Normal file
24
frontend/adapters/static/vite.config.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { staticAdapter } from "@builder.io/qwik-city/adapters/static/vite";
|
||||||
|
import { extendConfig } from "@builder.io/qwik-city/vite";
|
||||||
|
import baseConfig from "../../vite.config";
|
||||||
|
|
||||||
|
export default extendConfig(baseConfig, () => {
|
||||||
|
return {
|
||||||
|
build: {
|
||||||
|
ssr: true,
|
||||||
|
rollupOptions: {
|
||||||
|
input: ["@qwik-city-plan"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
staticAdapter({
|
||||||
|
origin: "https://peoplesgrocers.com",
|
||||||
|
// Specify which routes to statically generate
|
||||||
|
routes: [
|
||||||
|
"/about",
|
||||||
|
// Add more static routes here as needed
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
});
|
||||||
896
frontend/package-lock.json
generated
896
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -11,6 +11,8 @@
|
||||||
"build": "qwik build",
|
"build": "qwik build",
|
||||||
"build.client": "vite build",
|
"build.client": "vite build",
|
||||||
"build.preview": "vite build --ssr src/entry.preview.tsx",
|
"build.preview": "vite build --ssr src/entry.preview.tsx",
|
||||||
|
"build.server": "qwik check-client src dist && vite build -c adapters/express/vite.config.ts",
|
||||||
|
"build.static": "vite build -c adapters/static/vite.config.ts",
|
||||||
"build.types": "tsc --incremental --noEmit",
|
"build.types": "tsc --incremental --noEmit",
|
||||||
"deploy": "echo 'Run \"npm run qwik add\" to install a server adapter'",
|
"deploy": "echo 'Run \"npm run qwik add\" to install a server adapter'",
|
||||||
"dev": "vite --mode ssr",
|
"dev": "vite --mode ssr",
|
||||||
|
|
@ -19,6 +21,7 @@
|
||||||
"fmt.check": "prettier --check .",
|
"fmt.check": "prettier --check .",
|
||||||
"lint": "eslint \"src/**/*.ts*\"",
|
"lint": "eslint \"src/**/*.ts*\"",
|
||||||
"preview": "qwik build preview && vite preview --open",
|
"preview": "qwik build preview && vite preview --open",
|
||||||
|
"serve": "node server/entry.express",
|
||||||
"start": "vite --open --mode ssr",
|
"start": "vite --open --mode ssr",
|
||||||
"qwik": "qwik"
|
"qwik": "qwik"
|
||||||
},
|
},
|
||||||
|
|
@ -27,7 +30,11 @@
|
||||||
"@builder.io/qwik-city": "^1.17.1",
|
"@builder.io/qwik-city": "^1.17.1",
|
||||||
"@eslint/js": "latest",
|
"@eslint/js": "latest",
|
||||||
"@tailwindcss/vite": "^4.1.16",
|
"@tailwindcss/vite": "^4.1.16",
|
||||||
|
"@types/compression": "^1.7.2",
|
||||||
|
"@types/express": "^4.17.19",
|
||||||
"@types/node": "20.19.0",
|
"@types/node": "20.19.0",
|
||||||
|
"compression": "^1.7.4",
|
||||||
|
"dotenv": "^16.3.2",
|
||||||
"eslint": "9.32.0",
|
"eslint": "9.32.0",
|
||||||
"eslint-plugin-qwik": "^1.17.1",
|
"eslint-plugin-qwik": "^1.17.1",
|
||||||
"globals": "16.4.0",
|
"globals": "16.4.0",
|
||||||
|
|
@ -42,6 +49,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
|
"express": "4.20.0",
|
||||||
"mathjs": "^15.0.0",
|
"mathjs": "^15.0.0",
|
||||||
"prosemirror-commands": "^1.7.1",
|
"prosemirror-commands": "^1.7.1",
|
||||||
"prosemirror-keymap": "^1.2.3",
|
"prosemirror-keymap": "^1.2.3",
|
||||||
|
|
|
||||||
73
frontend/src/entry.express.tsx
Normal file
73
frontend/src/entry.express.tsx
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* WHAT IS THIS FILE?
|
||||||
|
*
|
||||||
|
* It's the entry point for the Express HTTP server when building for production.
|
||||||
|
*
|
||||||
|
* Learn more about Node.js server integrations here:
|
||||||
|
* - https://qwik.dev/docs/deployments/node/
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
createQwikCity,
|
||||||
|
type PlatformNode,
|
||||||
|
} from "@builder.io/qwik-city/middleware/node";
|
||||||
|
import "dotenv/config";
|
||||||
|
import qwikCityPlan from "@qwik-city-plan";
|
||||||
|
import render from "./entry.ssr";
|
||||||
|
import express from "express";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { join } from "node:path";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
type QwikCityPlatform = PlatformNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directories where the static assets are located
|
||||||
|
const distDir = join(fileURLToPath(import.meta.url), "..", "..", "dist");
|
||||||
|
const buildDir = join(distDir, "build");
|
||||||
|
const assetsDir = join(distDir, "assets");
|
||||||
|
|
||||||
|
// Allow for dynamic port
|
||||||
|
const PORT = process.env.PORT ?? 3000;
|
||||||
|
|
||||||
|
// Create the Qwik City Node middleware
|
||||||
|
const { router, notFound } = createQwikCity({
|
||||||
|
render,
|
||||||
|
qwikCityPlan,
|
||||||
|
// getOrigin(req) {
|
||||||
|
// // If deploying under a proxy, you may need to build the origin from the request headers
|
||||||
|
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto
|
||||||
|
// const protocol = req.headers["x-forwarded-proto"] ?? "http";
|
||||||
|
// // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host
|
||||||
|
// const host = req.headers["x-forwarded-host"] ?? req.headers.host;
|
||||||
|
// return `${protocol}://${host}`;
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create the express server
|
||||||
|
// https://expressjs.com/
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
// Enable gzip compression
|
||||||
|
// app.use(compression());
|
||||||
|
|
||||||
|
// Static asset handlers
|
||||||
|
// https://expressjs.com/en/starter/static-files.html
|
||||||
|
app.use(`/build`, express.static(buildDir, { immutable: true, maxAge: "1y" }));
|
||||||
|
app.use(
|
||||||
|
`/assets`,
|
||||||
|
express.static(assetsDir, { immutable: true, maxAge: "1y" }),
|
||||||
|
);
|
||||||
|
app.use(express.static(distDir, { redirect: false }));
|
||||||
|
|
||||||
|
// Use Qwik City's page and endpoint request handler
|
||||||
|
app.use(router);
|
||||||
|
|
||||||
|
// Use Qwik City's 404 handler
|
||||||
|
app.use(notFound);
|
||||||
|
|
||||||
|
// Start the express server
|
||||||
|
app.listen(PORT, () => {
|
||||||
|
/* eslint-disable */
|
||||||
|
console.log(`Server started: http://localhost:${PORT}/`);
|
||||||
|
});
|
||||||
|
|
@ -6,21 +6,49 @@ import { Math } from "~/components/math/math"
|
||||||
|
|
||||||
# How Salience Works
|
# How Salience Works
|
||||||
|
|
||||||
|
A couple of days ago I came across
|
||||||
|
[github.com/mattneary/salience](https://github.com/mattneary/salience) by Matt Neary. I thought it
|
||||||
|
was quite neat how he took sentence embeddings and in just a few lines of code
|
||||||
|
was able to determine the significance of all sentences in a document.
|
||||||
|
|
||||||
|
This post is an outsider's view of how salience works. If you're already working with ML models in Python, this will feel
|
||||||
|
torturously detailed. I wrote this for the rest of us old world programmers: compilers, networking, systems programming looking at
|
||||||
|
C++/Go/Rust, or the poor souls in the frontend Typescript mines.
|
||||||
|
For us refugees of the barbarian past, the tooling and notation can look foreign. I wanted to walk through the math and
|
||||||
|
numpy operations in detail to show what's actually happening with the data.
|
||||||
|
|
||||||
Salience highlights important sentences by treating your document as a graph where sentences that talk about similar things are connected. We then figure out which sentences are most "central" to the document's themes.
|
Salience highlights important sentences by treating your document as a graph where sentences that talk about similar things are connected. We then figure out which sentences are most "central" to the document's themes.
|
||||||
|
|
||||||
## Step 1: Break Text into Sentences
|
## Step 1: Break Text into Sentences
|
||||||
|
|
||||||
We use NLTK's Punkt tokenizer to split text into sentences. This handles tricky cases where simple punctuation splitting fails:
|
The first problem we need to solve is finding the sentences in a document. This is not as easy as splitting on newlines or periods. Consider this example:
|
||||||
|
|
||||||
*"Dr. Smith earned his Ph.D. in 1995."* ← This is **one** sentence, not three!
|
*"Dr. Smith earned his Ph.D. in 1995."* ← This is **one** sentence, not three!
|
||||||
|
|
||||||
## Step 2: Convert Sentences to Embeddings
|
Fortunately, this problem has been adequately solved for decades. We are going to use the **Punkt sentence splitter** (2003) available in the Natural Language Toolkit (NLTK) Python package.
|
||||||
|
|
||||||
Now we have <Math tex="N" /> sentences. We convert each one into a high-dimensional vector that captures its meaning:
|
## Step 2: Apply an Embedding Model
|
||||||
|
|
||||||
<Math display tex="\mathbf{E} = \text{model.encode}(\text{sentences}) \in \mathbb{R}^{N \times D}" />
|
Now we have <Math tex="N" /> sentences. We convert each one into a high-dimensional vector that captures its meaning. For example:
|
||||||
|
|
||||||
This gives us an **embeddings matrix** <Math tex="\mathbf{E}" /> where each row is one sentence:
|
<Math tex="\mathbf{Sentence \space A} = [a_1, a_2, a_3, \ldots, a_D]" display="block" />
|
||||||
|
<Math tex="\mathbf{Sentence \space B} = [b_1, b_2, b_3, \ldots, b_D]" display="block" />
|
||||||
|
<Math tex="\mathbf{Sentence \space C} = [c_1, c_2, c_3, \ldots, c_D]" display="block" />
|
||||||
|
|
||||||
|
## Step 3: Build the Adjacency Matrix
|
||||||
|
|
||||||
|
Now we create a new <Math tex="N \times N" /> adjacency matrix <Math tex="\mathbf{A}" /> that measures how similar each pair of sentences is. For every pair of sentences <Math tex="i" /> and <Math tex="j" />, we need the **cosine similarity**:
|
||||||
|
|
||||||
|
<Math display tex="A_{ij} = \frac{\mathbf{e}_i \cdot \mathbf{e}_j}{\|\mathbf{e}_i\| \|\mathbf{e}_j\|}" />
|
||||||
|
|
||||||
|
Each <Math tex="A_{ij}" /> represents how strongly sentence <Math tex="i" /> is connected to sentence <Math tex="j" />.
|
||||||
|
- <Math tex="A_{ij} = 1" /> means sentences are identical in meaning
|
||||||
|
- <Math tex="A_{ij} = 0" /> means sentences are unrelated
|
||||||
|
- <Math tex="A_{ij} = -1" /> means sentences are opposite in meaning
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
You could work with these embedding vectors one at a time, using two for loops to build the adjacency matrix leetcode style. However, there's a way to delegate the computation to optimized libraries. Instead, organize all embeddings into a single matrix:
|
||||||
|
|
||||||
<Math display tex="\mathbf{E} = \begin{bmatrix} a_1 & a_2 & a_3 & \cdots & a_D \\ b_1 & b_2 & b_3 & \cdots & b_D \\ c_1 & c_2 & c_3 & \cdots & c_D \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ z_1 & z_2 & z_3 & \cdots & z_D \end{bmatrix}" />
|
<Math display tex="\mathbf{E} = \begin{bmatrix} a_1 & a_2 & a_3 & \cdots & a_D \\ b_1 & b_2 & b_3 & \cdots & b_D \\ c_1 & c_2 & c_3 & \cdots & c_D \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ z_1 & z_2 & z_3 & \cdots & z_D \end{bmatrix}" />
|
||||||
|
|
||||||
|
|
@ -29,38 +57,59 @@ Where:
|
||||||
- <Math tex="D" /> = embedding dimension (768 for all-mpnet-base-v2, 1024 for gte-large-en-v1.5)
|
- <Math tex="D" /> = embedding dimension (768 for all-mpnet-base-v2, 1024 for gte-large-en-v1.5)
|
||||||
- Each row represents one sentence in semantic space
|
- Each row represents one sentence in semantic space
|
||||||
|
|
||||||
## Step 3: Build the Adjacency Matrix
|
**Step 3a: Compute all dot products**
|
||||||
|
|
||||||
Now we create a new matrix <Math tex="\mathbf{A}" /> that measures how similar each pair of sentences is. For every pair of sentences <Math tex="i" /> and <Math tex="j" />, we compute:
|
<Math display tex="\mathbf{S} = \mathbf{E} \mathbf{E}^T" />
|
||||||
|
|
||||||
<Math display tex="A_{ij} = \frac{\mathbf{e}_i \cdot \mathbf{e}_j}{\|\mathbf{e}_i\| \|\mathbf{e}_j\|}" />
|
Since <Math tex="\mathbf{E}" /> is <Math tex="N \times D" /> and <Math tex="\mathbf{E}^T" /> is <Math tex="D \times N" />, their product gives us an <Math tex="N \times N" /> matrix where entry <Math tex="S_{ij} = \mathbf{e}_i \cdot \mathbf{e}_j" />.
|
||||||
|
|
||||||
This is the **cosine similarity** between their embedding vectors. It tells us:
|
**Step 3b: Compute the norms and normalize**
|
||||||
- <Math tex="A_{ij} = 1" /> means sentences are identical in meaning
|
|
||||||
- <Math tex="A_{ij} = 0" /> means sentences are unrelated
|
First, compute a vector of norms:
|
||||||
- <Math tex="A_{ij} = -1" /> means sentences are opposite in meaning
|
|
||||||
|
<Math display tex="\mathbf{n} = \begin{bmatrix} \|\mathbf{e}_1\| \\ \|\mathbf{e}_2\| \\ \|\mathbf{e}_3\| \\ \vdots \\ \|\mathbf{e}_N\| \end{bmatrix}" />
|
||||||
|
|
||||||
|
This is an <Math tex="(N, 1)" /> vector where each element is the magnitude of one sentence's embedding. Now we need to visit every single element of <Math tex="\mathbf{S}" /> to make the adjacency matrix <Math tex="A_{ij} = \frac{S_{ij}}{n_i \cdot n_j}" />:
|
||||||
|
|
||||||
|
<Math display tex="\mathbf{A} = \begin{bmatrix} \frac{S_{11}}{n_1 \cdot n_1} & \frac{S_{12}}{n_1 \cdot n_2} & \cdots & \frac{S_{1N}}{n_1 \cdot n_N} \\ \frac{S_{21}}{n_2 \cdot n_1} & \frac{S_{22}}{n_2 \cdot n_2} & \cdots & \frac{S_{2N}}{n_2 \cdot n_N} \\ \vdots & \vdots & \ddots & \vdots \\ \frac{S_{N1}}{n_N \cdot n_1} & \frac{S_{N2}}{n_N \cdot n_2} & \cdots & \frac{S_{NN}}{n_N \cdot n_N} \end{bmatrix}" />
|
||||||
|
|
||||||
|
**Quick benchmark:** For a <Math tex="194 \times 768" /> embeddings matrix (194 sentences):
|
||||||
|
|
||||||
|
- Computing everything in Python for loops: **33.1 ms**
|
||||||
|
- Using <Math tex="\mathbf{E} \mathbf{E}^T" /> for dot products, but element-by-element normalization in Python: **10.9 ms** (saves 22.2 ms)
|
||||||
|
- Using numpy **broadcasting** for normalization too: **0.13 ms**
|
||||||
|
|
||||||
|
Broadcasting is a numpy feature where dividing arrays of different shapes automatically "stretches" the smaller array to match:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def cos_sim(a):
|
||||||
|
sims = a @ a.T
|
||||||
|
norms = np.linalg.norm(a, axis=-1, keepdims=True)
|
||||||
|
sims /= norms # Divides each row i by norm[i]
|
||||||
|
sims /= norms.T # Divides each column j by norm[j]
|
||||||
|
return sims
|
||||||
|
```
|
||||||
|
|
||||||
|
The `keepdims=True` makes `norms` shape <Math tex="(N, 1)" /> instead of <Math tex="(N,)" />, which is crucial—when transposed, <Math tex="(N, 1)" /> becomes <Math tex="(1, N)" />, allowing the broadcasting to work for column-wise division.
|
||||||
|
|
||||||
The result is an <Math tex="N \times N" /> **adjacency matrix** where <Math tex="A_{ij}" /> represents how strongly sentence <Math tex="i" /> is connected to sentence <Math tex="j" />.
|
|
||||||
|
|
||||||
## Step 4: Clean Up the Graph
|
## Step 4: Clean Up the Graph
|
||||||
|
|
||||||
We make two adjustments to the adjacency matrix to get a cleaner graph:
|
We make two adjustments to the adjacency matrix to make our TextRank work:
|
||||||
|
|
||||||
1. **Remove self-loops:** Set diagonal to zero (<Math tex="A_{ii} = 0" />)
|
1. **Remove self-loops:** Set diagonal to zero (<Math tex="A_{ii} = 0" />)
|
||||||
- A sentence shouldn't vote for its own importance
|
|
||||||
|
|
||||||
2. **Remove negative edges:** Set <Math tex="A_{ij} = \max(0, A_{ij})" />
|
2. **Remove negative edges:** Set <Math tex="A_{ij} = \max(0, A_{ij})" />
|
||||||
- Sentences with opposite meanings get disconnected
|
|
||||||
|
|
||||||
**Important assumption:** This assumes your document has a coherent main idea and that sentences are generally on-topic. We're betting that the topic with the most "semantic mass" is the *correct* topic.
|
A sentence shouldn't vote for its own importance. And sentences with opposite meanings get disconnected.
|
||||||
|
|
||||||
**Where this breaks down:**
|
**Important assumption:** This assumes your document has a coherent main idea and that sentences are generally on-topic. We're betting that the topic with the most "semantic mass" is the *correct* topic. This is obviously not true for many documents:
|
||||||
- **Dialectical essays** that deliberately contrast opposing viewpoints
|
|
||||||
- **Documents heavy with quotes** that argue against something
|
|
||||||
- **Debate transcripts** where both sides are equally important
|
|
||||||
- **Critical analysis** that spends significant time explaining a position before refuting it
|
|
||||||
|
|
||||||
For example: "Nuclear power is dangerous. Critics say it causes meltdowns. However, modern reactors are actually very safe."
|
- Dialectical essays that deliberately contrast opposing viewpoints
|
||||||
|
- Documents heavy with quotes that argue against something
|
||||||
|
- Debate transcripts where both sides are equally important
|
||||||
|
- Critical analysis that spends significant time explaining a position before refuting it
|
||||||
|
|
||||||
|
For example: "Nuclear power is dangerous. Critics say it causes meltdowns [...]. However, modern reactors are actually very safe."
|
||||||
|
|
||||||
The algorithm might highlight the criticism because multiple sentences cluster around "danger", even though the document's actual position is pro-nuclear. There's nothing inherent in the math that identifies authorial intent vs. quoted opposition.
|
The algorithm might highlight the criticism because multiple sentences cluster around "danger", even though the document's actual position is pro-nuclear. There's nothing inherent in the math that identifies authorial intent vs. quoted opposition.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import {
|
||||||
component$,
|
component$,
|
||||||
useSignal,
|
useSignal,
|
||||||
useVisibleTask$,
|
useVisibleTask$,
|
||||||
useComputed$,
|
|
||||||
$,
|
$,
|
||||||
noSerialize,
|
noSerialize,
|
||||||
useStore,
|
useStore,
|
||||||
|
|
@ -12,7 +11,7 @@ import {
|
||||||
import { type DocumentHead, routeLoader$ } from "@builder.io/qwik-city";
|
import { type DocumentHead, routeLoader$ } from "@builder.io/qwik-city";
|
||||||
import { EditorState, Plugin, Transaction } from "prosemirror-state";
|
import { EditorState, Plugin, Transaction } from "prosemirror-state";
|
||||||
import { EditorView, Decoration, DecorationSet } from "prosemirror-view";
|
import { EditorView, Decoration, DecorationSet } from "prosemirror-view";
|
||||||
import { Schema, DOMParser, Node as PMNode } from "prosemirror-model";
|
import { Schema, Node as PMNode } from "prosemirror-model";
|
||||||
import { schema as basicSchema } from "prosemirror-schema-basic";
|
import { schema as basicSchema } from "prosemirror-schema-basic";
|
||||||
import { keymap } from "prosemirror-keymap";
|
import { keymap } from "prosemirror-keymap";
|
||||||
import { baseKeymap } from "prosemirror-commands";
|
import { baseKeymap } from "prosemirror-commands";
|
||||||
|
|
@ -123,7 +122,7 @@ export default component$(() => {
|
||||||
const models = useSignal<string[]>([]);
|
const models = useSignal<string[]>([]);
|
||||||
const currentModel = useSignal("all-mpnet-base-v2");
|
const currentModel = useSignal("all-mpnet-base-v2");
|
||||||
const syncState = useSignal<SyncState>("clean");
|
const syncState = useSignal<SyncState>("clean");
|
||||||
const editorView = useSignal<EditorView | null>(null);
|
const editorView = useSignal<NoSerialize<EditorView>>();
|
||||||
const sentenceDecorations = useSignal<SentenceDecoration[]>([]);
|
const sentenceDecorations = useSignal<SentenceDecoration[]>([]);
|
||||||
const salienceData = useSignal<SalienceData | null>(null);
|
const salienceData = useSignal<SalienceData | null>(null);
|
||||||
const debounceTimer = useSignal<number | null>(null);
|
const debounceTimer = useSignal<number | null>(null);
|
||||||
|
|
@ -224,15 +223,20 @@ export default component$(() => {
|
||||||
|
|
||||||
// Parse the initial document text into separate paragraphs
|
// Parse the initial document text into separate paragraphs
|
||||||
// Split by single newlines and preserve blank lines as empty paragraphs
|
// Split by single newlines and preserve blank lines as empty paragraphs
|
||||||
const paragraphs = initialDocument.value
|
const lines = initialDocument.value
|
||||||
.split('\n')
|
.split('\n');
|
||||||
.map(line => {
|
|
||||||
|
const paragraphs: PMNode[] = [];
|
||||||
|
lines.forEach((line) => {
|
||||||
// Create paragraph with text if line has content, otherwise empty paragraph
|
// Create paragraph with text if line has content, otherwise empty paragraph
|
||||||
const content = line.length > 0 ? [salienceSchema.text(line)] : [];
|
const content = line.length > 0 ? [salienceSchema.text(line)] : [];
|
||||||
return salienceSchema.node("paragraph", null, content);
|
//content.push(salienceSchema.node("hard_break"));
|
||||||
|
paragraphs.push(salienceSchema.node("paragraph", null, content));
|
||||||
});
|
});
|
||||||
|
console.log(paragraphs);
|
||||||
|
|
||||||
const initialDoc = salienceSchema.node("doc", null, paragraphs);
|
const initialDoc = salienceSchema.node("doc", null, paragraphs);
|
||||||
|
console.log(initialDoc.textContent);
|
||||||
|
|
||||||
const state = EditorState.create({
|
const state = EditorState.create({
|
||||||
schema: salienceSchema,
|
schema: salienceSchema,
|
||||||
|
|
@ -272,7 +276,7 @@ export default component$(() => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
editorView.value = view;
|
editorView.value = noSerialize(view);
|
||||||
|
|
||||||
fetchSalienceData(
|
fetchSalienceData(
|
||||||
currentModel.value,
|
currentModel.value,
|
||||||
|
|
|
||||||
51
get-next-port.sh
Executable file
51
get-next-port.sh
Executable file
|
|
@ -0,0 +1,51 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# Port allocation for zero-downtime deployments
|
||||||
|
#
|
||||||
|
# Reads remote state to determine next available port.
|
||||||
|
# State files:
|
||||||
|
# $base/$project/base_port - starting port number
|
||||||
|
# $base/$project/releases/*/assigned_port - port for each release
|
||||||
|
#
|
||||||
|
# Algorithm:
|
||||||
|
# 1. read base_port (default 3100)
|
||||||
|
# 2. find all assigned_port files in releases/
|
||||||
|
# 3. return smallest unused port >= base_port
|
||||||
|
|
||||||
|
ssh=deploy-peoplesgrocers-website
|
||||||
|
base=/home/peoplesgrocers
|
||||||
|
project=salience
|
||||||
|
|
||||||
|
ssh $ssh "
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
base=$base
|
||||||
|
project=$project
|
||||||
|
|
||||||
|
test -d \$base/\$project || { echo 'project dir does not exist' >&2; exit 1; }
|
||||||
|
|
||||||
|
# read or initialize base_port
|
||||||
|
if test -f \$base/\$project/base_port; then
|
||||||
|
base_port=\$(cat \$base/\$project/base_port)
|
||||||
|
else
|
||||||
|
base_port=3100
|
||||||
|
echo \$base_port > \$base/\$project/base_port
|
||||||
|
fi
|
||||||
|
|
||||||
|
# find all assigned ports
|
||||||
|
assigned=\$(find \$base/\$project/releases -name assigned_port -exec cat {} \; 2>/dev/null | sort -n || true)
|
||||||
|
|
||||||
|
# find first unused port
|
||||||
|
port=\$base_port
|
||||||
|
while true; do
|
||||||
|
found=0
|
||||||
|
for p in \$assigned; do
|
||||||
|
test \$p -eq \$port && { found=1; break; }
|
||||||
|
done
|
||||||
|
test \$found -eq 0 && break
|
||||||
|
port=\$((port + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo \$port
|
||||||
|
"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue