cicerone-recommender 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cicerone_recommender-0.6.0/CHANGELOG.md +501 -0
- cicerone_recommender-0.6.0/LICENSE +7 -0
- cicerone_recommender-0.6.0/MANIFEST.in +9 -0
- cicerone_recommender-0.6.0/PKG-INFO +712 -0
- cicerone_recommender-0.6.0/README.md +667 -0
- cicerone_recommender-0.6.0/pyproject.toml +86 -0
- cicerone_recommender-0.6.0/requirements-redis.txt +3 -0
- cicerone_recommender-0.6.0/requirements-sequential.txt +3 -0
- cicerone_recommender-0.6.0/requirements.txt +33 -0
- cicerone_recommender-0.6.0/setup.cfg +4 -0
- cicerone_recommender-0.6.0/src/cicerone/__init__.py +6 -0
- cicerone_recommender-0.6.0/src/cicerone/__main__.py +4 -0
- cicerone_recommender-0.6.0/src/cicerone/artifact.py +240 -0
- cicerone_recommender-0.6.0/src/cicerone/automl.py +367 -0
- cicerone_recommender-0.6.0/src/cicerone/blending.py +336 -0
- cicerone_recommender-0.6.0/src/cicerone/cli.py +215 -0
- cicerone_recommender-0.6.0/src/cicerone/config/__init__.py +105 -0
- cicerone_recommender-0.6.0/src/cicerone/config/constants.py +54 -0
- cicerone_recommender-0.6.0/src/cicerone/config/events.py +126 -0
- cicerone_recommender-0.6.0/src/cicerone/config/load.py +441 -0
- cicerone_recommender-0.6.0/src/cicerone/config/lock_url.py +54 -0
- cicerone_recommender-0.6.0/src/cicerone/config/settings.py +266 -0
- cicerone_recommender-0.6.0/src/cicerone/config/validation.py +94 -0
- cicerone_recommender-0.6.0/src/cicerone/content_fallback.py +293 -0
- cicerone_recommender-0.6.0/src/cicerone/dashboard.py +146 -0
- cicerone_recommender-0.6.0/src/cicerone/dashboard_lookup.py +147 -0
- cicerone_recommender-0.6.0/src/cicerone/dashboard_users.py +27 -0
- cicerone_recommender-0.6.0/src/cicerone/dataset.py +170 -0
- cicerone_recommender-0.6.0/src/cicerone/events/__init__.py +28 -0
- cicerone_recommender-0.6.0/src/cicerone/events/base.py +52 -0
- cicerone_recommender-0.6.0/src/cicerone/events/buffer.py +111 -0
- cicerone_recommender-0.6.0/src/cicerone/events/db.py +372 -0
- cicerone_recommender-0.6.0/src/cicerone/events/ha.py +23 -0
- cicerone_recommender-0.6.0/src/cicerone/events/normalize.py +116 -0
- cicerone_recommender-0.6.0/src/cicerone/events/redis_streams.py +341 -0
- cicerone_recommender-0.6.0/src/cicerone/events/registry.py +43 -0
- cicerone_recommender-0.6.0/src/cicerone/events/s3.py +613 -0
- cicerone_recommender-0.6.0/src/cicerone/events/store.py +220 -0
- cicerone_recommender-0.6.0/src/cicerone/events/updater.py +377 -0
- cicerone_recommender-0.6.0/src/cicerone/events/webhook.py +106 -0
- cicerone_recommender-0.6.0/src/cicerone/events/worker.py +223 -0
- cicerone_recommender-0.6.0/src/cicerone/export_serve_openapi.py +64 -0
- cicerone_recommender-0.6.0/src/cicerone/feature_config.py +229 -0
- cicerone_recommender-0.6.0/src/cicerone/http_auth.py +48 -0
- cicerone_recommender-0.6.0/src/cicerone/ids.py +52 -0
- cicerone_recommender-0.6.0/src/cicerone/io/__init__.py +0 -0
- cicerone_recommender-0.6.0/src/cicerone/io/base.py +103 -0
- cicerone_recommender-0.6.0/src/cicerone/io/dataset_store.py +134 -0
- cicerone_recommender-0.6.0/src/cicerone/io/db_errors.py +19 -0
- cicerone_recommender-0.6.0/src/cicerone/io/db_store.py +232 -0
- cicerone_recommender-0.6.0/src/cicerone/io/factory.py +64 -0
- cicerone_recommender-0.6.0/src/cicerone/io/manifest_reader.py +85 -0
- cicerone_recommender-0.6.0/src/cicerone/io/options.py +122 -0
- cicerone_recommender-0.6.0/src/cicerone/io/recommendation_reader.py +389 -0
- cicerone_recommender-0.6.0/src/cicerone/io/recommendation_schema.py +38 -0
- cicerone_recommender-0.6.0/src/cicerone/io/replace_users.py +32 -0
- cicerone_recommender-0.6.0/src/cicerone/job.py +226 -0
- cicerone_recommender-0.6.0/src/cicerone/locks.py +355 -0
- cicerone_recommender-0.6.0/src/cicerone/manage_dashboard_users.py +81 -0
- cicerone_recommender-0.6.0/src/cicerone/model/__init__.py +50 -0
- cicerone_recommender-0.6.0/src/cicerone/model/combine.py +46 -0
- cicerone_recommender-0.6.0/src/cicerone/model/constants.py +29 -0
- cicerone_recommender-0.6.0/src/cicerone/model/epoch_metrics.py +135 -0
- cicerone_recommender-0.6.0/src/cicerone/model/fit.py +306 -0
- cicerone_recommender-0.6.0/src/cicerone/model/recommend.py +478 -0
- cicerone_recommender-0.6.0/src/cicerone/model/strategies.py +134 -0
- cicerone_recommender-0.6.0/src/cicerone/model_config.py +266 -0
- cicerone_recommender-0.6.0/src/cicerone/packaging.py +117 -0
- cicerone_recommender-0.6.0/src/cicerone/policy.py +367 -0
- cicerone_recommender-0.6.0/src/cicerone/scheduler.py +89 -0
- cicerone_recommender-0.6.0/src/cicerone/serve/__init__.py +21 -0
- cicerone_recommender-0.6.0/src/cicerone/serve/__main__.py +4 -0
- cicerone_recommender-0.6.0/src/cicerone/serve/app.py +468 -0
- cicerone_recommender-0.6.0/src/cicerone/serve/bootstrap_events.py +163 -0
- cicerone_recommender-0.6.0/src/cicerone/serve/code_samples.py +328 -0
- cicerone_recommender-0.6.0/src/cicerone/serve/events_routes.py +155 -0
- cicerone_recommender-0.6.0/src/cicerone/serve/metrics.py +195 -0
- cicerone_recommender-0.6.0/src/cicerone/serve/python_detect.sh +4 -0
- cicerone_recommender-0.6.0/src/cicerone/serve_client.py +92 -0
- cicerone_recommender-0.6.0/src/cicerone/serve_schemas.py +81 -0
- cicerone_recommender-0.6.0/src/cicerone/static/cicerone-icon.svg +37 -0
- cicerone_recommender-0.6.0/src/cicerone/static/cicerone-logo.svg +66 -0
- cicerone_recommender-0.6.0/src/cicerone/static/dashboard.js +64 -0
- cicerone_recommender-0.6.0/src/cicerone/static/tailwind.css +2 -0
- cicerone_recommender-0.6.0/src/cicerone/static/tailwind.input.css +10 -0
- cicerone_recommender-0.6.0/src/cicerone/static/vendor/htmx.min.js +1 -0
- cicerone_recommender-0.6.0/src/cicerone/static/vendor/stimulus.umd.js +2588 -0
- cicerone_recommender-0.6.0/src/cicerone/templates/_recommendations.html +45 -0
- cicerone_recommender-0.6.0/src/cicerone/templates/_status.html +117 -0
- cicerone_recommender-0.6.0/src/cicerone/templates/dashboard.html +79 -0
- cicerone_recommender-0.6.0/src/cicerone/trigger.py +137 -0
- cicerone_recommender-0.6.0/src/cicerone/values.py +50 -0
- cicerone_recommender-0.6.0/src/cicerone_recommender.egg-info/PKG-INFO +712 -0
- cicerone_recommender-0.6.0/src/cicerone_recommender.egg-info/SOURCES.txt +96 -0
- cicerone_recommender-0.6.0/src/cicerone_recommender.egg-info/dependency_links.txt +1 -0
- cicerone_recommender-0.6.0/src/cicerone_recommender.egg-info/entry_points.txt +2 -0
- cicerone_recommender-0.6.0/src/cicerone_recommender.egg-info/requires.txt +23 -0
- cicerone_recommender-0.6.0/src/cicerone_recommender.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.6.0] - 2026-08-20
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Docs site homepage: latest dated CHANGELOG release and a GitHub **what changed**
|
|
14
|
+
link to that section.
|
|
15
|
+
- Dashboard user lookup: inspect a `user_id`'s current precomputed top-K
|
|
16
|
+
(rank, item, score, source, optional category) from the job output store,
|
|
17
|
+
with cold-start fallback, on the Basic-Auth status page.
|
|
18
|
+
`GET /dashboard?user_id=` fills the lookup on load.
|
|
19
|
+
- PyPI distribution `cicerone-recommender` (`import cicerone`; the name
|
|
20
|
+
`cicerone` is taken). Wheel includes compiled dashboard CSS. A GitHub
|
|
21
|
+
Release publishes via trusted publishing (`.github/workflows/publish.yml`).
|
|
22
|
+
- `cicerone` CLI (`start`/`job`/`serve`/`dashboard`/`scheduler`/`users` /
|
|
23
|
+
`export-openapi`) with `--config` for a TOML path, plus `--log-level` /
|
|
24
|
+
`--log-format` (or `CICERONE_LOG_LEVEL` / `CICERONE_LOG_FORMAT`). Runtime
|
|
25
|
+
image pip-installs the wheel; entrypoint is `cicerone start`.
|
|
26
|
+
|
|
27
|
+
- Optional project-site articles at `/articles/` (static Markdown under
|
|
28
|
+
`website/src/content/docs/articles/`). No nav, RSS, or index until a
|
|
29
|
+
published post exists. Article pages use IBM Plex Serif and a ~65ch
|
|
30
|
+
measure. Brand accents invert for dark theme. Listing keeps an h1;
|
|
31
|
+
posts use `description` for meta. Website-only PRs skip Docker lint/test
|
|
32
|
+
jobs; the `ci` job still succeeds.
|
|
33
|
+
|
|
34
|
+
- Optional **sequential** strategy (`SASRecModel` / `BERT4RecModel`) via
|
|
35
|
+
`[model.sequential]` (`architecture = "sasrec"` or `"bert4rec"`). Requires
|
|
36
|
+
`cicerone-recommender[sequential]` / `requirements-sequential.txt`
|
|
37
|
+
(`rectools[torch]`); serve mode never imports torch. AutoML drops it from
|
|
38
|
+
the candidate pool when the extra is missing or median distinct items/user
|
|
39
|
+
is below `[job.sequential].min_median_interactions` (default 5), and logs
|
|
40
|
+
the skip.
|
|
41
|
+
|
|
42
|
+
- Incremental events horizontal HA: leader-only apply lease
|
|
43
|
+
(`{lock_key}:events:apply`) when `events.ha = true` with
|
|
44
|
+
`job.trigger.lock_backend` postgres/redis. Fan-out sources acquire the
|
|
45
|
+
lease only when a micro-batch is ready. Metrics:
|
|
46
|
+
`cicerone_events_lock_total`, `cicerone_events_leader`,
|
|
47
|
+
`cicerone_events_apply_busy_total`.
|
|
48
|
+
|
|
49
|
+
- Redis Streams EventSource (`events.kind = "redis_streams"`): consumer-group
|
|
50
|
+
poll via `XREADGROUP` / `XACK`, idle PEL recovery with `XAUTOCLAIM`, and
|
|
51
|
+
stream entry id fallback when `event_id` is omitted. Requires
|
|
52
|
+
`cicerone-recommender[redis]` / `requirements-redis.txt` (same optional
|
|
53
|
+
`redis` pin as the lock backend).
|
|
54
|
+
- User-scoped incremental write-through: load/replace only affected users
|
|
55
|
+
(plus `__cold_start__`) via `OutputSink.replace_recommendations_for_users`
|
|
56
|
+
(returns post-write distinct user count) instead of full-frame overwrite.
|
|
57
|
+
Updater keeps an LRU-bounded per-user cache (default 2048); dataset
|
|
58
|
+
`count_recommendation_users` projects only `user_id` from parquet, and
|
|
59
|
+
`load_recommendations_for_users` uses parquet `filters` for `user_id` when
|
|
60
|
+
the engine supports predicate pushdown.
|
|
61
|
+
- Incremental events Prometheus metrics on serve `/metrics` (source lag /
|
|
62
|
+
connected, flush counters, last success timestamp, tick errors) and an
|
|
63
|
+
incremental-events panel on the Basic-Auth dashboard (from manifests).
|
|
64
|
+
- Incremental events between full retrains: internal `EventSource` surface,
|
|
65
|
+
webhook `POST /events`, micro-batch buffer/worker, and write-through
|
|
66
|
+
updater for popular/latest slices (`[events]` config). Operator guide:
|
|
67
|
+
`docs/incremental-events.md`. Webhook `occurred_at` requires an explicit
|
|
68
|
+
timezone (`Z` / offset) or Unix epoch seconds (UTC).
|
|
69
|
+
- DB event source (`events.kind = "db"`): watermark poll over
|
|
70
|
+
`events_table` / `events_query`, durable optional `watermark_path`,
|
|
71
|
+
watermark advances only on successful flush ack.
|
|
72
|
+
- S3-compatible event source (`events.kind = "s3"`), R2-first: list/marker
|
|
73
|
+
poll via the same `build_s3_client` / `endpoint_url` options as dataset
|
|
74
|
+
I/O; optional AWS-only SQS mode (rejected with `endpoint_url`). JSON
|
|
75
|
+
object/array payloads; ack advances marker or deletes the SQS message.
|
|
76
|
+
|
|
77
|
+
### Changed
|
|
78
|
+
|
|
79
|
+
- Docs: `docs/how-it-works.md` (pipeline, strategies, papers); incremental
|
|
80
|
+
events operator guide; tutorial webhook step; site sidebar/homepage
|
|
81
|
+
cards; OpenAPI `x-codeSamples` for `POST /events`; PyPI extras
|
|
82
|
+
(`cicerone-recommender[sequential]` / `[redis]`) next to `requirements-*.txt`;
|
|
83
|
+
example TOML / OpenAPI regenerate command for pip hosts; missing-package
|
|
84
|
+
errors name the PyPI extras; operator ingest recap lives in
|
|
85
|
+
`docs/incremental-events.md` (other pages link it).
|
|
86
|
+
- Parse article `draft` from YAML frontmatter; article layout CSS keys off
|
|
87
|
+
`data-cicerone-articles` rather than starlight-blog class names.
|
|
88
|
+
- Share the articles URL prefix between the Starlight plugin and layout
|
|
89
|
+
classifier; `robots.txt` allows the site and disallows `/pagefind/`.
|
|
90
|
+
- Article plugin gating passes `{ production }` explicitly; layout kind
|
|
91
|
+
matches starlight-blog listing routes and ignores a missing route id.
|
|
92
|
+
- Drop the Starlight “Edit page” footer; site content is edited in git.
|
|
93
|
+
- Docker `package` stage validates the wheel via `python -m cicerone.packaging`
|
|
94
|
+
(selects `cicerone_recommender-<version>` including PEP 440 local versions
|
|
95
|
+
and numeric wheel build tags).
|
|
96
|
+
|
|
97
|
+
- README and docs-site dashboard screenshot include the user recommendation lookup.
|
|
98
|
+
- Docs site copies `docs/images/` into `website/public/images/docs/` at build time.
|
|
99
|
+
- Dashboard lookup form is labeled, results are announced, and job-run
|
|
100
|
+
tables expose captions / column headers; helper text contrast is higher.
|
|
101
|
+
- Dashboard inspector k is `min(job.top_k, dashboard.lookup_k)` (default 20).
|
|
102
|
+
|
|
103
|
+
- Bump `pyarrow` 25.0.0 → 25.0.1 (#85), `SQLAlchemy` 2.0.51 → 2.0.52 (#86),
|
|
104
|
+
`uvicorn` 0.52.1 → 0.52.3 (#87), `ruff` 0.16.2 → 0.16.3 (#88).
|
|
105
|
+
- Bump GitHub Actions Pages deploy helpers: `actions/upload-pages-artifact`
|
|
106
|
+
v3 → v5 (Dependabot #79), `actions/deploy-pages` v4 → v5 (#80).
|
|
107
|
+
- Bump `fastapi` 0.140.13 → 0.141.1 (Dependabot #69).
|
|
108
|
+
- Dependabot: ignore `numpy` major bumps (Python 3.11 CI) and `boto3>=1.43.57`
|
|
109
|
+
(aiobotocore botocore pin).
|
|
110
|
+
- Project docs site (not part of the runtime product): Starlight under
|
|
111
|
+
`website/`, synced from `docs/`, published at [cicerone.dev](https://cicerone.dev).
|
|
112
|
+
|
|
113
|
+
### Fixed
|
|
114
|
+
|
|
115
|
+
- OpenAPI `POST /events` curl sample JSON-encodes `USER_ID` with `python3`
|
|
116
|
+
(falls back to `python`, then `jq`) and errors if none is on PATH.
|
|
117
|
+
`examples/serve/curl_examples.sh` sources the same
|
|
118
|
+
`src/cicerone/serve/python_detect.sh` snippet the OpenAPI samples embed.
|
|
119
|
+
- Docker test image includes `examples/serve/` so CI can read `curl_examples.sh`.
|
|
120
|
+
- Tutorial webhook step starts serve with `cicerone --config … serve` (same as
|
|
121
|
+
the HTTP API step).
|
|
122
|
+
- `cicerone users` with a config path requires enabled `dashboard.users_path`
|
|
123
|
+
(or an explicit `--users-path`); the error names the loaded config and the
|
|
124
|
+
dashboard settings that were resolved.
|
|
125
|
+
|
|
126
|
+
- Dashboard still starts if the recommendation store cannot be opened (lookup disabled).
|
|
127
|
+
- Dashboard lookup errors show a generic message; details stay in the logs.
|
|
128
|
+
- Dashboard lookup URL updates keep the hash fragment.
|
|
129
|
+
- Dashboard lookup disables the Look up button during the htmx request.
|
|
130
|
+
- Postgres `is_locked()` logs and re-raises probe failures instead of
|
|
131
|
+
treating a dead database as “lock free”; `owned()` logs before fail-closed.
|
|
132
|
+
- S3 EventSource `nack` returns events to the local pending queue (and
|
|
133
|
+
extends SQS visibility) instead of dropping the batch. SQS HA lock-busy
|
|
134
|
+
nacks can retry immediately; list-mode array payloads no longer lose
|
|
135
|
+
sibling events when one id is nacked.
|
|
136
|
+
- Event worker ack/nack bookkeeping: buffer duplicates are acked (not left
|
|
137
|
+
in-flight), capacity overflow is nacked for redelivery, and stop drains
|
|
138
|
+
the buffer once before closing the source.
|
|
139
|
+
- DB event source poll uses `(occurred_at, event_id)` cursor/order when an
|
|
140
|
+
`event_id` column exists so same-timestamp pages cannot skip rows.
|
|
141
|
+
|
|
142
|
+
## [0.5.1] - 2026-08-12
|
|
143
|
+
|
|
144
|
+
### Added
|
|
145
|
+
|
|
146
|
+
- Serve OpenAPI / ReDoc ``x-codeSamples`` (Ruby, Python, JavaScript, Shell)
|
|
147
|
+
for `/health` and `/recommendations/{user_id}`.
|
|
148
|
+
|
|
149
|
+
### Fixed
|
|
150
|
+
|
|
151
|
+
- Redis lock `release()` joins the refresher (≤250ms) and ignores in-flight
|
|
152
|
+
refresh failures after an intentional stop, avoiding a `_mark_lost` race.
|
|
153
|
+
- Retrain Prometheus labels use the real trigger source (`cron`, `s3-poll`,
|
|
154
|
+
`webhook`, …) instead of collapsing non-webhook to `poll`.
|
|
155
|
+
- Serve fails closed when `features.toml` cannot be loaded (no silent disable
|
|
156
|
+
of availability filters).
|
|
157
|
+
- Input poller treats local `stat` errors like S3 failures (log and continue).
|
|
158
|
+
|
|
159
|
+
### Changed
|
|
160
|
+
|
|
161
|
+
- Blending warm path uses per-user / shared latest rankings without building
|
|
162
|
+
Cartesian U×K latest frames; `latest_by_user` keys are normalized to `str`.
|
|
163
|
+
- Serve caches `generated_at` with the refresh loop; cold-start prefers the
|
|
164
|
+
per-user index / refresh-time fallback; category filters use a refresh-time
|
|
165
|
+
`category → item_id` map.
|
|
166
|
+
- Content-fallback fit avoids `iterrows`; recommend uses vectorized top-K.
|
|
167
|
+
- Event caps apply in one sort across all capped event types.
|
|
168
|
+
- Shared helpers: `cicerone.values` (`is_missing` / `as_list`),
|
|
169
|
+
`io.options.read_parquet` / `validate_storage_options`, and
|
|
170
|
+
`MISSING_TABLE_ERRORS`.
|
|
171
|
+
- `cicerone.serve` package `__all__` exports only the public API.
|
|
172
|
+
|
|
173
|
+
## [0.5.0] - 2026-08-11
|
|
174
|
+
|
|
175
|
+
### Added
|
|
176
|
+
|
|
177
|
+
- Serve-mode Prometheus metrics at `GET /metrics` (`prometheus-client`):
|
|
178
|
+
request volume/latency, cache hit/miss/age/refresh, recommendation
|
|
179
|
+
source tiers, retrain-trigger counts, and `cicerone_up`. Configured via
|
|
180
|
+
`[serve].metrics_enabled` (default `true`) and optional
|
|
181
|
+
`[serve].metrics_token` (`X-Metrics-Token` header; empty = open endpoint).
|
|
182
|
+
Per-replica registry; no multiprocess mode.
|
|
183
|
+
- Optional `[model.<strategy>]` TOML tables for RecTools `model_from_config`
|
|
184
|
+
(`collaborative`, `item_based`, `popular`, `latest`).
|
|
185
|
+
- Optional scheduler lock backends (`[job.trigger].lock_backend`:
|
|
186
|
+
`in_process` default, `postgres`, `redis`) for multi-replica mutual
|
|
187
|
+
exclusion; manifest records `lock_backend`. Redis is an optional
|
|
188
|
+
install (`requirements-redis.txt`). Optional `lock_key` /
|
|
189
|
+
`lock_ttl_seconds` that namespace shared Redis/Postgres lock stores.
|
|
190
|
+
Redis lock TTL is refreshed while held so long runs stay exclusive.
|
|
191
|
+
|
|
192
|
+
### Changed
|
|
193
|
+
|
|
194
|
+
- Strategies built via RecTools `model_from_config` / `get_config`.
|
|
195
|
+
- Artifacts schema **v3**: RecTools `model.save` / `load_model` in a zip
|
|
196
|
+
envelope; `content_fallback` still pickle. Schema v2 no longer loadable.
|
|
197
|
+
`created_at` is stored as ISO-8601 in `meta.json` and loaded as `datetime`.
|
|
198
|
+
- Split `model` / `config` into packages; import paths unchanged. Cross-module
|
|
199
|
+
helpers are public (`_` = module-local only). Tests follow the same split
|
|
200
|
+
(`test_model_*.py`, `test_config_*.py`).
|
|
201
|
+
- Docs: architecture test/module map, tutorial `[model.*]` knobs, README /
|
|
202
|
+
CONTRIBUTING test layout notes; serve `/metrics` in README, architecture,
|
|
203
|
+
tutorial, and `examples/serve/`.
|
|
204
|
+
- `cicerone.serve` is a package (`serve/app.py`, `serve/metrics.py`);
|
|
205
|
+
`python -m cicerone.serve` and `from cicerone.serve import create_app`
|
|
206
|
+
unchanged.
|
|
207
|
+
- Shared defaults: item-based `K` from `DEFAULT_ITEM_BASED_K_NEIGHBORS`;
|
|
208
|
+
latest window from `model_config.LATEST_WINDOW_DAYS` (re-exported by
|
|
209
|
+
`model.constants`).
|
|
210
|
+
|
|
211
|
+
### Renamed config keys (legacy still accepted)
|
|
212
|
+
|
|
213
|
+
- `job.item_based.k_neighbors` → `model.item_based.model.K`. Conflicting
|
|
214
|
+
values raise `ConfigError`.
|
|
215
|
+
|
|
216
|
+
## [0.4.1] - 2026-08-07
|
|
217
|
+
|
|
218
|
+
### Added
|
|
219
|
+
|
|
220
|
+
- Nested settings surfaces on `Settings`: `serve`, `trigger`, `dashboard`, and
|
|
221
|
+
`automl` dataclasses (flat `settings.serve_host`-style accessors remain as
|
|
222
|
+
compatibility properties). Invalid config knobs raise `ConfigError`
|
|
223
|
+
(`ValueError` subclass); missing files / unset `${ENV}` still raise
|
|
224
|
+
`RuntimeError`.
|
|
225
|
+
- Manifest field `partial_outputs` when a sink write fails after some outputs
|
|
226
|
+
were already persisted (success is set only after all writes succeed).
|
|
227
|
+
- `[[boosts]]` accepted as an alias for `[[boost]]` in `features.toml`
|
|
228
|
+
(defining both is an error).
|
|
229
|
+
- I/O factory kind registry; `configure_item_filters` on the
|
|
230
|
+
`RecommendationReader` protocol; frozen `ModelArtifact`.
|
|
231
|
+
|
|
232
|
+
### Changed
|
|
233
|
+
|
|
234
|
+
- Event caps keep the **most recent** N events per `(user, item, event_type)`
|
|
235
|
+
(sorted by `occurred_at` descending before `cumcount`).
|
|
236
|
+
- Aggregated `(user, item)` pairs with non-positive weight are **dropped**
|
|
237
|
+
instead of being floored to `1e-3` (negative review sums no longer become
|
|
238
|
+
weak positive LightFM signals).
|
|
239
|
+
- Serve cold-start heuristic is deterministic across dataset and DB backends:
|
|
240
|
+
prefer `popular_fallback`, then `latest`, then lexicographic `user_id`;
|
|
241
|
+
DB path picks one user then fetches that user's top-K (no `LIMIT k*20`
|
|
242
|
+
under-fill).
|
|
243
|
+
- `recommend_with_models` split into cohort → recommend → combine → boost
|
|
244
|
+
phases; blending uses per-user indexes and optional shared latest rankings;
|
|
245
|
+
serve dataset reader indexes recommendations by `user_id` at refresh time.
|
|
246
|
+
- ProcessPool strategy now initializes LightFM with `num_threads=1` inside
|
|
247
|
+
workers to avoid CPU oversubscription.
|
|
248
|
+
|
|
249
|
+
### Fixed
|
|
250
|
+
|
|
251
|
+
- `item_true` eligibility no longer treats non-empty strings such as
|
|
252
|
+
`"false"` / `"0"` as true (`astype(bool)`); only explicit truthy tokens
|
|
253
|
+
/ non-zero numerics / bools pass.
|
|
254
|
+
- DB `_clear_table_for_replace` catches the same missing-table errors as the
|
|
255
|
+
rest of the DB store (including SQLite `OperationalError` on `TRUNCATE`).
|
|
256
|
+
|
|
257
|
+
## [0.4.0] - 2026-08-06
|
|
258
|
+
|
|
259
|
+
### Added
|
|
260
|
+
|
|
261
|
+
- **Content cold-item fallback** (`content_fallback` strategy): recommends
|
|
262
|
+
zero-interaction items by one-hot cosine similarity over configured
|
|
263
|
+
`item_features` against each warm user's history. Gated by
|
|
264
|
+
`[job.content_fallback].enabled` (default off); when enabled, auto-inserted
|
|
265
|
+
before the first non-personalized strategy. Independent of `item_based`.
|
|
266
|
+
Source label: `content_fallback`. Requires `scikit-learn`.
|
|
267
|
+
- **Weighted multi-source blending** (`[blending]` in `config/features.toml`):
|
|
268
|
+
replaces the binary personalized-vs-`popular_fallback` choice with a
|
|
269
|
+
gradual per-user mix of `personalized`, `popular`, and date-based
|
|
270
|
+
`latest` (publication/`occurred_at`-style columns on `items`). A
|
|
271
|
+
configurable sigmoid or linear curve maps interaction count →
|
|
272
|
+
personalized weight; the remainder is split by `popular_share`. When no
|
|
273
|
+
usable date column exists, `latest` is disabled and its weight moves to
|
|
274
|
+
`popular`. Combined rows use `source = "blended"`. Availability /
|
|
275
|
+
eligibility still filter every source before the blend. A shared
|
|
276
|
+
`__cold_start__` row set is written for serve-mode fallback.
|
|
277
|
+
- **Serve read contract** aligned with a Gorse-style lookup (without
|
|
278
|
+
adopting Gorse infra): `GET /recommendations/{user_id}` accepts
|
|
279
|
+
`limit` / `k`, `category`, and `exclude_unavailable`, returns
|
|
280
|
+
`{generated_at, user_id, fallback, items:[{item_id,rank,score,source}]}`
|
|
281
|
+
plus an `X-Generated-At` header from the last run manifest, and falls
|
|
282
|
+
back to the precomputed cold-start list for unknown users (not a bare
|
|
283
|
+
404). Items are snapshotted to the output store
|
|
284
|
+
(`items_snapshot.parquet` / `recommendation_items`) so filters stay on
|
|
285
|
+
the configured output without loading ML deps. Documented in the README
|
|
286
|
+
Serve section alongside the existing Dashboard style.
|
|
287
|
+
- **Serve OpenAPI contract**: response models for `/health` and
|
|
288
|
+
`/recommendations/{user_id}` so FastAPI's `/openapi.json`, `/docs`, and
|
|
289
|
+
`/redoc` document the real JSON shape (including `X-Generated-At`). A
|
|
290
|
+
checked-in schema at `docs/openapi/serve.openapi.json` can be regenerated
|
|
291
|
+
with `python -m cicerone.export_serve_openapi`.
|
|
292
|
+
- **Thin serve clients**: `cicerone.serve_client.ServeClient` (stdlib
|
|
293
|
+
`urllib`, typed via `serve_schemas`) plus copy-paste examples under
|
|
294
|
+
`examples/serve/` (Python, Node `fetch`, curl).
|
|
295
|
+
|
|
296
|
+
### Changed
|
|
297
|
+
|
|
298
|
+
- **Default model chain** is now `["collaborative", "item_based", "popular"]`
|
|
299
|
+
(was `["collaborative", "popular"]`), so sparse warm users get item-KNN
|
|
300
|
+
backfill before raw popularity.
|
|
301
|
+
- **`[job.item_based].k_neighbors`** configures `TFIDFRecommender(K=…)`
|
|
302
|
+
(default `20`).
|
|
303
|
+
- Serve JSON response is an object (with `generated_at` / `items`) rather
|
|
304
|
+
than a bare list; `k` remains accepted as an alias for `limit`.
|
|
305
|
+
- Docs: architecture data-flow covers the three combine paths (priority /
|
|
306
|
+
RRF / `[blending]`) and the items snapshot write; tutorial gains a
|
|
307
|
+
blending walkthrough and an updated serve-API section (response shape,
|
|
308
|
+
`limit` / `category` / cold-start fallback, OpenAPI `/docs`,
|
|
309
|
+
`examples/serve/` clients).
|
|
310
|
+
- Blending correctness: date-based `latest` is ranked per eligibility
|
|
311
|
+
cohort (no cross-cohort allowlist union); `__cold_start__` uses the
|
|
312
|
+
global item-scoped allowlist; multi-personalized strategies collapse to
|
|
313
|
+
best rank before RRF; sigmoid maps `n=0 → 0`; strategy `latest` is
|
|
314
|
+
skipped while blending is on; serve heuristic fallback never reuses
|
|
315
|
+
warm `blended` rows.
|
|
316
|
+
- `cicerone.serve.main` imports I/O factory helpers lazily so OpenAPI export
|
|
317
|
+
and `create_app` do not require dataset/DB backend imports at module load.
|
|
318
|
+
- Serve OpenAPI `info.version` follows `cicerone.__version__` (single source
|
|
319
|
+
of truth with the changelog / release tag).
|
|
320
|
+
- `config.make_settings(**overrides)` is the shared Settings factory for
|
|
321
|
+
tests and OpenAPI schema export (replacing duplicated default blocks).
|
|
322
|
+
|
|
323
|
+
## [0.3.2] - 2026-08-04
|
|
324
|
+
|
|
325
|
+
### Added
|
|
326
|
+
|
|
327
|
+
- Optional per-epoch LightFM training metrics (`[job].log_epoch_metrics`,
|
|
328
|
+
default `false`; interval via `[job].epoch_metrics_every`, default `5`):
|
|
329
|
+
when enabled, the collaborative strategy fits via `fit_partial` one epoch
|
|
330
|
+
at a time and logs in-sample Precision@K/Recall@K over a seeded random
|
|
331
|
+
user sample. Tunables (`epoch_metrics_max_users`, regression/plateau
|
|
332
|
+
thresholds) live on `EpochMetricsSettings`. Significant regression or late
|
|
333
|
+
plateau across logged epochs emits a WARN. Off by default so scheduled
|
|
334
|
+
batch runs stay unchanged.
|
|
335
|
+
- Top-K ID-mapping regression test: synthetic catalog with sparse external
|
|
336
|
+
IDs that must not collide with rectools' dense internal indices, asserting
|
|
337
|
+
no duplicate items per user and no seen items in personalized rows.
|
|
338
|
+
|
|
339
|
+
### Changed
|
|
340
|
+
|
|
341
|
+
- Documented that per-strategy top-K extraction is rectools-native
|
|
342
|
+
(`ModelBase.recommend()` + Dataset id maps) and that AutoML eval metrics
|
|
343
|
+
already use `rectools.metrics.calc_metrics` (MAP/NDCG/Recall) — no custom
|
|
344
|
+
metric implementations.
|
|
345
|
+
|
|
346
|
+
## [0.3.1] - 2026-08-04
|
|
347
|
+
|
|
348
|
+
### Changed
|
|
349
|
+
|
|
350
|
+
- README gains a top-level **Features** list (batch, strategies, AutoML,
|
|
351
|
+
policies, serve, trigger, dashboard, artifacts).
|
|
352
|
+
|
|
353
|
+
- Priority combine fills top-K from earlier strategies first (stable
|
|
354
|
+
priority + rank sort) instead of interleaving by per-strategy rank.
|
|
355
|
+
- `[job].max_workers` drives ProcessPool parallelism for AutoML folds and
|
|
356
|
+
strategy fitting (default `1` / sequential; set `>1` to opt in).
|
|
357
|
+
- AutoML `primary_metric` matches an exact name or a single `NAME@k`;
|
|
358
|
+
ambiguous `NAME@k` sets are rejected (no order-dependent first match).
|
|
359
|
+
- Shared I/O helpers: `object_key`, `sql_identifier`, S3 not-found checks.
|
|
360
|
+
- Model-artifact DB writes use portable SQLAlchemy `LargeBinary` /
|
|
361
|
+
timezone-aware `DateTime` and clear via `DELETE` (dialect-agnostic).
|
|
362
|
+
- Config validates `top_k`, `half_life_days`, feature column types, and
|
|
363
|
+
`model_weights` keys against `job.models`.
|
|
364
|
+
- Batch job builds artifacts in memory and writes outputs only after
|
|
365
|
+
successful compute, reducing partial failed-run publishes.
|
|
366
|
+
|
|
367
|
+
### Fixed
|
|
368
|
+
|
|
369
|
+
- Optional dataset `users`/`items` reads only treat missing files / S3
|
|
370
|
+
not-found as absent; other errors propagate.
|
|
371
|
+
|
|
372
|
+
## [0.3.0] - 2026-08-03
|
|
373
|
+
|
|
374
|
+
### Added
|
|
375
|
+
|
|
376
|
+
- **Business policy layer** (`config/features.toml` → `[[eligibility]]` /
|
|
377
|
+
`[[boost]]`): declarative hard filters (region/nationality, market match,
|
|
378
|
+
category allowlists) and soft ranking boosts (paying producers, plan
|
|
379
|
+
tiers, numeric lifts). Applied at batch recommend time via
|
|
380
|
+
`cicerone.policy`; serve mode stays a lookup of already-policy-aware rows.
|
|
381
|
+
`item_availability_filters` remains sugar for global `item_true` rules.
|
|
382
|
+
User-scoped eligibility groups users into cohorts whose allowed-item set
|
|
383
|
+
is computed once and reused across strategies; missing `item_column`
|
|
384
|
+
warnings are deduplicated per `(kind, rule, column)`. When boosts are
|
|
385
|
+
configured, candidates are over-fetched (`boost_overfetch_factor` ×
|
|
386
|
+
`top_k`, default 3 — tunable in `features.toml`) before score multipliers
|
|
387
|
+
so a commercially boosted item just outside the raw top-K can still enter
|
|
388
|
+
the final list. Cohorts whose eligibility excludes every item get an empty
|
|
389
|
+
allowlist (no silent catalog fallback) and are skipped.
|
|
390
|
+
- Optional **model artifact** (`[job].save_model_artifact`): the batch job
|
|
391
|
+
can write a versioned, portable fitted-model bundle
|
|
392
|
+
(`model.artifact` / `model_artifacts` table) alongside recommendations.
|
|
393
|
+
Load and recommend without re-fitting via `cicerone.artifact`. Serve mode
|
|
394
|
+
still reads precomputed rows only — this does not add live inference.
|
|
395
|
+
Artifacts persist the `users` frame (schema version **2**) so
|
|
396
|
+
`recommend_from_artifact` can re-apply user-scoped eligibility offline.
|
|
397
|
+
- Optional `postgres` service in `docker-compose.yml` (Postgres 16, compose
|
|
398
|
+
profile `db`) for local `kind = "db"` input/output. First boot creates
|
|
399
|
+
`cicerone` (app/tutorial) and `cicerone_test` (pytest) databases.
|
|
400
|
+
`INPUT_DATABASE_URL` / `OUTPUT_DATABASE_URL` stay unset unless provided
|
|
401
|
+
via `.env`.
|
|
402
|
+
- System-style end-to-end test (`tests/test_system_db.py`) against a real
|
|
403
|
+
Postgres: seeds shared conftest fixtures → `job.run` (db in/out + model
|
|
404
|
+
artifact) → verify recommendations, manifest, artifact blob, and the
|
|
405
|
+
serve/dashboard DB readers. Schema reset is module-scoped, reflects via
|
|
406
|
+
SQLAlchemy metadata, and is gated by a test DB name check plus
|
|
407
|
+
`ALLOW_SCHEMA_RESET_FOR_TESTS=1`.
|
|
408
|
+
- User-facing documentation for model artifacts and business policies:
|
|
409
|
+
README sections, tutorial §8–9, architecture notes, annotated recipes in
|
|
410
|
+
`config/features.toml`, and `model_artifact_table` in the commented db
|
|
411
|
+
config example.
|
|
412
|
+
|
|
413
|
+
### Changed
|
|
414
|
+
|
|
415
|
+
- `model.train_and_recommend` is split into `fit_strategies` +
|
|
416
|
+
`recommend_with_models` so fitted weights can be reused (AutoML cache,
|
|
417
|
+
model artifacts) without a second fit.
|
|
418
|
+
- Tutorial database section uses
|
|
419
|
+
`docker compose --profile db up -d postgres` instead of an ad-hoc
|
|
420
|
+
`docker run` Postgres container.
|
|
421
|
+
- Local pytest guidance points `TEST_DATABASE_URL` at `cicerone_test` (with
|
|
422
|
+
`ALLOW_SCHEMA_RESET_FOR_TESTS=1`) so tests do not wipe app data.
|
|
423
|
+
|
|
424
|
+
### Fixed
|
|
425
|
+
|
|
426
|
+
- Model-artifact DB writes use `CREATE TABLE IF NOT EXISTS` + `TRUNCATE` +
|
|
427
|
+
`INSERT` (no `DROP`/`CREATE` race under concurrent jobs).
|
|
428
|
+
- `model_artifact_table` is validated as a simple SQL identifier before
|
|
429
|
+
interpolation.
|
|
430
|
+
- `cicerone.artifact` documents that pickle loads are trusted-internal-only
|
|
431
|
+
(never user-controlled payloads; not on the serve path).
|
|
432
|
+
|
|
433
|
+
## [0.2.1] - 2026-07-29
|
|
434
|
+
|
|
435
|
+
### Added
|
|
436
|
+
|
|
437
|
+
- AutoML candidate backtesting can now evaluate time-based folds in
|
|
438
|
+
parallel: `evaluate_candidates(..., max_workers=1)` runs folds through a
|
|
439
|
+
`ProcessPoolExecutor` when `max_workers > 1`, instead of sequentially
|
|
440
|
+
(opt-in; default behavior/performance is unchanged).
|
|
441
|
+
- Strategy fitting in `train_and_recommend(..., max_workers=1)` can
|
|
442
|
+
likewise fit independent, not-yet-cached strategies via a
|
|
443
|
+
`ProcessPoolExecutor` when `max_workers > 1` (opt-in; default is
|
|
444
|
+
unchanged).
|
|
445
|
+
|
|
446
|
+
### Changed
|
|
447
|
+
|
|
448
|
+
- The job's input reads (events, users, items) now run concurrently via a
|
|
449
|
+
`ThreadPoolExecutor` instead of sequentially, speeding up runs against
|
|
450
|
+
network-backed (S3) input sources.
|
|
451
|
+
|
|
452
|
+
## [0.2.0] - 2026-07-29
|
|
453
|
+
|
|
454
|
+
### Added
|
|
455
|
+
|
|
456
|
+
- **Serve mode**: an optional lightweight, read-only HTTP API
|
|
457
|
+
(`[job].mode = "serve"`) that exposes precomputed recommendations for a
|
|
458
|
+
given user without any model loaded in the request path. Protected by a
|
|
459
|
+
bearer token (`[serve].auth_token` / `SERVE_AUTH_TOKEN`).
|
|
460
|
+
- **Event-driven retrain trigger**: an opt-in webhook
|
|
461
|
+
(`[job.trigger].enabled = true`) that lets an external system request an
|
|
462
|
+
immediate retrain instead of waiting for the next scheduled run, with an
|
|
463
|
+
optional S3-poll mode and a debounce guard against duplicate/overlapping
|
|
464
|
+
triggers. Protected by a bearer token (`[job.trigger].auth_token` /
|
|
465
|
+
`TRIGGER_AUTH_TOKEN`).
|
|
466
|
+
- **Dashboard**: a small, standalone read-only status page
|
|
467
|
+
(`[dashboard].enabled = true`) showing recent job run history and errors,
|
|
468
|
+
independent of `[job].mode`. Protected by HTTP Basic Auth against a set of
|
|
469
|
+
named users managed via the new `manage_dashboard_users` CLI. Built with
|
|
470
|
+
htmx + Stimulus + Tailwind, polling a `/partials/status` endpoint.
|
|
471
|
+
- `docs/tutorial.md`: new walkthroughs for serve mode, the retrain trigger,
|
|
472
|
+
and the dashboard.
|
|
473
|
+
|
|
474
|
+
### Changed
|
|
475
|
+
|
|
476
|
+
- README's Security section corrected: previously claimed the container
|
|
477
|
+
"accepts no inbound connections" unconditionally; now documents that the
|
|
478
|
+
serve API, trigger webhook, and dashboard each expose one port only when
|
|
479
|
+
explicitly enabled, and describes how each is protected.
|
|
480
|
+
- `.env.example` now documents `SERVE_AUTH_TOKEN` and `TRIGGER_AUTH_TOKEN`.
|
|
481
|
+
- `config/cicerone.toml` now includes a commented `[dashboard]` example
|
|
482
|
+
alongside the existing `[serve]` and `[job.trigger]` examples.
|
|
483
|
+
|
|
484
|
+
## [0.1.0] - 2026-07-22
|
|
485
|
+
|
|
486
|
+
### Added
|
|
487
|
+
|
|
488
|
+
- Initial batch recommender job: reads interaction events, trains a hybrid
|
|
489
|
+
[rectools](https://github.com/MobileTeleSystems/RecTools) + LightFM model,
|
|
490
|
+
and writes top-K recommendations per user.
|
|
491
|
+
- Pluggable input/output backends: parquet files (`dataset`, S3-compatible
|
|
492
|
+
or local disk) and SQLAlchemy-backed database tables (`db`).
|
|
493
|
+
- Pluggable multi-model strategy registry (`collaborative`, `item_based`,
|
|
494
|
+
`popular`, `latest`) with weighted reciprocal rank fusion across models.
|
|
495
|
+
- AutoML mode to automatically pick a model strategy.
|
|
496
|
+
- Generic, TOML-based configuration (`config/cicerone.toml`,
|
|
497
|
+
`config/features.toml`) with `${ENV_VAR}` secret interpolation.
|
|
498
|
+
- Scheduler for running the job on a cron schedule.
|
|
499
|
+
- Getting-started tutorial (`docs/tutorial.md`) and architecture overview
|
|
500
|
+
(`docs/architecture.md`).
|
|
501
|
+
- CI: ruff lint/format, mypy, pip-audit, CodeQL, Dependabot.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
----------------------------------------------------------------------------
|
|
2
|
+
"THE BEER-WARE LICENSE" (Revision 42):
|
|
3
|
+
<ngw@torbido.co> wrote this project. As long as you retain this notice you
|
|
4
|
+
can do whatever you want with this stuff. If we meet some day, and you think
|
|
5
|
+
this stuff is worth it, you can buy me a beer in return — or, even better,
|
|
6
|
+
buy me one straight from Torbido (https://torbido.it).
|
|
7
|
+
----------------------------------------------------------------------------
|