Feature: prefer existing tags, types, correspondents, and storage paths in AI suggestions

AI Suggestions previously invented near-duplicate metadata because the classification
prompt had no knowledge of the installation's own taxonomy. This surfaces
a small, ranked, permission-filtered set of existing tags/document
types/correspondents/storage paths - drawn from the document's RAG
neighbors plus its own already-assigned metadata - so the model prefers
reusing what already exists.

The LLM response schema now returns existing_ids (IDs of reused
candidates) separately from new_names (genuinely new suggestions).
Only new_names goes through localization and fuzzy name-matching;
existing_ids is resolved deterministically and never touched by the
localization pass, so exact matches can no longer be silently
corrupted by translation.
This commit is contained in:
stumpylog
2026-08-13 13:00:06 -07:00
parent 2a8579f610
commit ef90c418cd
15 changed files with 2008 additions and 488 deletions
+31
View File
@@ -235,6 +235,37 @@ def permitted_object_ids(
).values_list("id", flat=True)
def visible_object_ids_or_none(
user: User | None,
model: type[Model],
perm: str,
) -> set[int] | None:
"""
Return the set of object IDs of ``model`` that ``user`` may see with
``perm``, or ``None`` meaning "no restriction at all".
``None`` is returned only for an absent user or an *active* superuser.
``permitted_object_ids(None, ...)`` itself means the much narrower "only
unowned rows", which is NOT the same thing as "no user filtering
requested", so that case has to be special-cased before ever calling it.
Every other case is delegated to ``permitted_object_ids`` rather than
re-deciding here, so its ordering is inherited instead of duplicated: a
deactivated superuser must NOT be handed "no restriction", it gets an
empty set (nothing visible), and an unauthenticated user still gets the
unowned rows.
"""
if user is None:
return None
if (
getattr(user, "is_authenticated", False)
and getattr(user, "is_active", False)
and getattr(user, "is_superuser", False)
):
return None
return set(permitted_object_ids(user, model, perm))
def permitted_document_ids(
user: User | None,
*,