Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31486018a5 | ||
|
|
4cfad2e8bc | ||
|
|
1e31e74ad6 |
@@ -1,8 +1,9 @@
|
||||
name: Build and Push
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
@@ -15,9 +16,10 @@ jobs:
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"tag_name": "${{ github.ref_name }}", "name": "Release ${{ github.ref_name }}", "body": "Automated release for ${{ github.ref_name }}"}' \
|
||||
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases"
|
||||
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
needs: release
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
@@ -5,6 +5,24 @@ All notable changes to Library Desk will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.7.3] - 2026-01-07
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Endpoint TTL defaults** - Updated all fetch endpoint defaults to match namespace TTLs (2x refresh interval)
|
||||
|
||||
## [1.7.2] - 2026-01-07
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Volatile TTL doubled** - TTL now 2x refresh interval to survive missed/delayed scheduler runs
|
||||
|
||||
## [1.7.1] - 2026-01-07
|
||||
|
||||
### Fixed
|
||||
|
||||
- **CI workflow** - Updated Gitea Actions to trigger on tag push (matching core-api)
|
||||
|
||||
## [1.7.0] - 2026-01-07
|
||||
|
||||
### Added
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Quick script to check environmental data in Qdrant volatile cache."""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import time
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
async def main():
|
||||
from qdrant_client import QdrantClient
|
||||
from qdrant_client.models import Filter, FieldCondition, MatchValue
|
||||
from src.config import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
qdrant = QdrantClient(url=settings.qdrant_url)
|
||||
|
||||
user = "jpmschweitzer"
|
||||
collection = f"volatile_{user}"
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Volatile Data in Qdrant ({collection})")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
namespaces = ["weather", "air_quality", "forecast", "sun", "news"]
|
||||
|
||||
for ns in namespaces:
|
||||
try:
|
||||
results = qdrant.scroll(
|
||||
collection_name=collection,
|
||||
scroll_filter=Filter(
|
||||
must=[FieldCondition(key="namespace", match=MatchValue(value=ns))]
|
||||
),
|
||||
limit=10,
|
||||
with_payload=True,
|
||||
with_vectors=False,
|
||||
)
|
||||
|
||||
points = results[0]
|
||||
print(f"=== {ns.upper()} ({len(points)} records) ===")
|
||||
|
||||
if not points:
|
||||
print(" (no data)")
|
||||
print()
|
||||
continue
|
||||
|
||||
for point in points:
|
||||
payload = point.payload
|
||||
raw_data = payload.get("raw_data", {})
|
||||
|
||||
if ns == "weather":
|
||||
print(f" Temperature: {raw_data.get('temperature')}°C (feels like {raw_data.get('feels_like')}°C)")
|
||||
print(f" Conditions: {raw_data.get('conditions')}")
|
||||
print(f" Humidity: {raw_data.get('humidity')}%")
|
||||
print(f" Wind: {raw_data.get('wind_speed')} km/h")
|
||||
print(f" UV Index: {raw_data.get('uv_index')}")
|
||||
|
||||
elif ns == "air_quality":
|
||||
print(f" European AQI: {raw_data.get('aqi_european')}")
|
||||
print(f" US AQI: {raw_data.get('aqi_us')}")
|
||||
print(f" PM2.5: {raw_data.get('pm2_5')} µg/m³")
|
||||
print(f" PM10: {raw_data.get('pm10')} µg/m³")
|
||||
print(f" Ozone: {raw_data.get('ozone')} µg/m³")
|
||||
print(f" NO₂: {raw_data.get('nitrogen_dioxide')} µg/m³")
|
||||
|
||||
elif ns == "forecast":
|
||||
daily = raw_data.get("daily", [])
|
||||
for day in daily[:5]:
|
||||
print(f" {day.get('day_name', 'N/A')[:3]}: {day.get('temp_low'):.0f}-{day.get('temp_high'):.0f}°C, {day.get('conditions')}")
|
||||
|
||||
elif ns == "sun":
|
||||
print(f" Sunrise: {raw_data.get('sunrise')}")
|
||||
print(f" Sunset: {raw_data.get('sunset')}")
|
||||
print(f" Daylight: {raw_data.get('daylight_hours', 0):.1f} hours")
|
||||
|
||||
elif ns == "news":
|
||||
headlines = raw_data.get("headlines", [])
|
||||
print(f" Category: {raw_data.get('category', 'general')}")
|
||||
print(f" Headlines ({len(headlines)}):")
|
||||
for item in headlines[:5]:
|
||||
title = item.get("title", "")[:60]
|
||||
source = item.get("source", "")
|
||||
print(f" - [{source}] {title}...")
|
||||
|
||||
# Show TTL info
|
||||
ttl_expiry = payload.get("ttl_expiry")
|
||||
if ttl_expiry:
|
||||
remaining = (ttl_expiry / 1000) - time.time()
|
||||
if remaining > 0:
|
||||
print(f" TTL remaining: {int(remaining)}s ({int(remaining/60)} min)")
|
||||
else:
|
||||
print(f" TTL: EXPIRED")
|
||||
|
||||
print()
|
||||
|
||||
except Exception as e:
|
||||
print(f" Error fetching {ns}: {e}")
|
||||
print()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "library-desk"
|
||||
version = "1.7.0"
|
||||
version = "1.7.3"
|
||||
description = "Coordination service for The Library system - HybridRAG queries, document ingestion, entity extraction, and knowledge consolidation"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
+14
-13
@@ -38,20 +38,21 @@ class VolatileNamespace(str, Enum):
|
||||
|
||||
|
||||
# Default TTLs per namespace (in seconds)
|
||||
# TTL = 2x refresh interval to ensure data survives missed/delayed refreshes
|
||||
NAMESPACE_DEFAULT_TTL: Dict[str, int] = {
|
||||
VolatileNamespace.WEATHER: 3600, # 1 hour - current conditions
|
||||
VolatileNamespace.FORECAST: 43200, # 12 hours - forecast stable longer
|
||||
VolatileNamespace.SUN: 86400, # 24 hours - sun times change daily
|
||||
VolatileNamespace.NEWS: 3600, # 1 hour - news cycles
|
||||
VolatileNamespace.FINANCIAL: 300, # 5 min - markets move fast
|
||||
VolatileNamespace.TRANSIT: 300, # 5 min - schedules update frequently
|
||||
VolatileNamespace.TRAFFIC: 600, # 10 min - traffic patterns
|
||||
VolatileNamespace.AIR_QUALITY: 3600, # 1 hour - air quality stable
|
||||
VolatileNamespace.SPORTS: 60, # 1 min - live scores
|
||||
VolatileNamespace.SOCIAL: 600, # 10 min - social notifications
|
||||
VolatileNamespace.SYSTEM: 60, # 1 min - system health
|
||||
VolatileNamespace.CONTEXT: 3600, # 1 hour - session context
|
||||
VolatileNamespace.CUSTOM: 3600, # 1 hour - default for custom
|
||||
VolatileNamespace.WEATHER: 7200, # 2 hours (hourly refresh)
|
||||
VolatileNamespace.FORECAST: 86400, # 24 hours (12hr refresh)
|
||||
VolatileNamespace.SUN: 172800, # 48 hours (daily refresh)
|
||||
VolatileNamespace.NEWS: 7200, # 2 hours (hourly refresh)
|
||||
VolatileNamespace.FINANCIAL: 600, # 10 min (5 min refresh)
|
||||
VolatileNamespace.TRANSIT: 600, # 10 min (5 min refresh)
|
||||
VolatileNamespace.TRAFFIC: 1200, # 20 min (10 min refresh)
|
||||
VolatileNamespace.AIR_QUALITY: 7200, # 2 hours (hourly refresh)
|
||||
VolatileNamespace.SPORTS: 120, # 2 min (1 min refresh)
|
||||
VolatileNamespace.SOCIAL: 1200, # 20 min (10 min refresh)
|
||||
VolatileNamespace.SYSTEM: 120, # 2 min (1 min refresh)
|
||||
VolatileNamespace.CONTEXT: 7200, # 2 hours - session context
|
||||
VolatileNamespace.CUSTOM: 7200, # 2 hours - default for custom
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -233,7 +233,7 @@ async def store_volatile(
|
||||
async def fetch_weather(
|
||||
city: str,
|
||||
user: str = Query(default=DEFAULT_USER, description="User identifier"),
|
||||
ttl: int = Query(default=3600, ge=60, le=86400, description="TTL in seconds (default 1 hour)"),
|
||||
ttl: int = Query(default=7200, ge=60, le=86400, description="TTL in seconds (default 2 hours)"),
|
||||
qdrant: QdrantDep = None,
|
||||
ollama: OllamaDep = None,
|
||||
api_key: str = Depends(verify_api_key)
|
||||
@@ -275,7 +275,7 @@ async def fetch_forecast(
|
||||
city: str,
|
||||
user: str = Query(default=DEFAULT_USER, description="User identifier"),
|
||||
days: int = Query(default=7, ge=1, le=16, description="Forecast days (1-16)"),
|
||||
ttl: int = Query(default=43200, ge=60, le=604800, description="TTL in seconds (default 12 hours)"),
|
||||
ttl: int = Query(default=86400, ge=60, le=604800, description="TTL in seconds (default 24 hours)"),
|
||||
qdrant: QdrantDep = None,
|
||||
ollama: OllamaDep = None,
|
||||
api_key: str = Depends(verify_api_key)
|
||||
@@ -360,7 +360,7 @@ async def fetch_news(
|
||||
async def fetch_stock(
|
||||
symbol: str,
|
||||
user: str = Query(default=DEFAULT_USER, description="User identifier"),
|
||||
ttl: int = Query(default=300, ge=60, le=3600, description="TTL in seconds"),
|
||||
ttl: int = Query(default=600, ge=60, le=3600, description="TTL in seconds (default 10 min)"),
|
||||
qdrant: QdrantDep = None,
|
||||
ollama: OllamaDep = None,
|
||||
api_key: str = Depends(verify_api_key)
|
||||
@@ -409,7 +409,7 @@ async def fetch_crypto(
|
||||
symbol: str,
|
||||
market: str = Query(default="USD", description="Market currency"),
|
||||
user: str = Query(default=DEFAULT_USER, description="User identifier"),
|
||||
ttl: int = Query(default=300, ge=60, le=3600, description="TTL in seconds"),
|
||||
ttl: int = Query(default=600, ge=60, le=3600, description="TTL in seconds (default 10 min)"),
|
||||
qdrant: QdrantDep = None,
|
||||
ollama: OllamaDep = None,
|
||||
api_key: str = Depends(verify_api_key)
|
||||
@@ -457,7 +457,7 @@ async def fetch_crypto(
|
||||
async def fetch_sun_times(
|
||||
city: str,
|
||||
user: str = Query(default=DEFAULT_USER, description="User identifier"),
|
||||
ttl: int = Query(default=86400, ge=60, le=604800, description="TTL in seconds"),
|
||||
ttl: int = Query(default=172800, ge=60, le=604800, description="TTL in seconds (default 48 hours)"),
|
||||
qdrant: QdrantDep = None,
|
||||
ollama: OllamaDep = None,
|
||||
api_key: str = Depends(verify_api_key)
|
||||
@@ -503,7 +503,7 @@ async def fetch_sun_times(
|
||||
async def fetch_air_quality(
|
||||
city: str,
|
||||
user: str = Query(default=DEFAULT_USER, description="User identifier"),
|
||||
ttl: int = Query(default=3600, ge=60, le=86400, description="TTL in seconds"),
|
||||
ttl: int = Query(default=7200, ge=60, le=86400, description="TTL in seconds (default 2 hours)"),
|
||||
qdrant: QdrantDep = None,
|
||||
ollama: OllamaDep = None,
|
||||
api_key: str = Depends(verify_api_key)
|
||||
@@ -549,8 +549,8 @@ async def fetch_air_quality(
|
||||
async def fetch_environment(
|
||||
city: str,
|
||||
user: str = Query(default=DEFAULT_USER, description="User identifier"),
|
||||
weather_ttl: int = Query(default=3600, ge=60, le=86400, description="Weather TTL in seconds"),
|
||||
air_quality_ttl: int = Query(default=3600, ge=60, le=86400, description="Air quality TTL in seconds"),
|
||||
weather_ttl: int = Query(default=7200, ge=60, le=86400, description="Weather TTL in seconds (default 2 hours)"),
|
||||
air_quality_ttl: int = Query(default=7200, ge=60, le=86400, description="Air quality TTL in seconds (default 2 hours)"),
|
||||
qdrant: QdrantDep = None,
|
||||
ollama: OllamaDep = None,
|
||||
api_key: str = Depends(verify_api_key)
|
||||
|
||||
Reference in New Issue
Block a user