M21: add optional organisation identity

This commit is contained in:
NuklearRabbit
2026-08-10 15:35:25 +02:00
parent 509cb95110
commit c3f1cfc699
38 changed files with 563 additions and 110 deletions
+15 -13
View File
@@ -39,18 +39,14 @@ def _scenarios(db: Session) -> list[DemoScenarioOut]:
overlap_issue = db.scalar(
select(DataQualityIssue).where(DataQualityIssue.public_ref == "DQ-DEMO-OVERLAP")
)
failed_run = db.scalar(
select(OutboxEvent).where(OutboxEvent.event_id == _FAILED_DEMO_EVENT_ID)
)
failed_run = db.scalar(select(OutboxEvent).where(OutboxEvent.event_id == _FAILED_DEMO_EVENT_ID))
knowledge_health = get_knowledge_provider().health()
# Human copy (title, problem statement, "demonstrates" summary) lives entirely in the
# frontend's demo.json (scenarios.items.<id>.*) so it's available in all three UI
# languages. This service only emits stable identifiers and message codes -- never
# display prose -- per the message_code + params architecture used across the app.
return_ready = bool(
booking and booking.status == "active" and booking.end_odometer_km is None
)
return_ready = bool(booking and booking.status == "active" and booking.end_odometer_km is None)
duplicate_ready = bool(duplicate_issue and duplicate_issue.status == "open")
overlap_ready = bool(overlap_issue and overlap_issue.status == "open")
automation_ready = bool(failed_run and failed_run.delivery_status == "failed")
@@ -65,7 +61,9 @@ def _scenarios(db: Session) -> list[DemoScenarioOut]:
blocked_reason_code=(
None
if return_ready
else "bookingNotFound" if booking is None else "bookingAlreadyProcessed"
else "bookingNotFound"
if booking is None
else "bookingAlreadyProcessed"
),
),
DemoScenarioOut(
@@ -81,7 +79,9 @@ def _scenarios(db: Session) -> list[DemoScenarioOut]:
blocked_reason_code=(
None
if duplicate_ready
else "duplicateIssueNotFound" if duplicate_issue is None else "issueAlreadyResolved"
else "duplicateIssueNotFound"
if duplicate_issue is None
else "issueAlreadyResolved"
),
),
DemoScenarioOut(
@@ -95,7 +95,9 @@ def _scenarios(db: Session) -> list[DemoScenarioOut]:
blocked_reason_code=(
None
if overlap_ready
else "overlapIssueNotFound" if overlap_issue is None else "issueAlreadyResolved"
else "overlapIssueNotFound"
if overlap_issue is None
else "issueAlreadyResolved"
),
),
DemoScenarioOut(
@@ -107,7 +109,9 @@ def _scenarios(db: Session) -> list[DemoScenarioOut]:
blocked_reason_code=(
None
if automation_ready
else "failedEventNotFound" if failed_run is None else "eventAlreadyRecovered"
else "failedEventNotFound"
if failed_run is None
else "eventAlreadyRecovered"
),
),
DemoScenarioOut(
@@ -172,9 +176,7 @@ def scenario_integrity_report(db: Session) -> dict:
overview already use, so this can never drift from what a visitor actually sees."""
scenarios = _scenarios(db)
not_ready = [
{"id": s.id, "reason_code": s.blocked_reason_code}
for s in scenarios
if not s.ready
{"id": s.id, "reason_code": s.blocked_reason_code} for s in scenarios if not s.ready
]
return {"all_ready": len(not_ready) == 0, "not_ready": not_ready}
+1 -2
View File
@@ -141,8 +141,7 @@ def _deliver_one(event_id: uuid.UUID) -> None:
# succeeded.
success = False
error = (
"Unexpected non-JSON-object response from n8n "
f"(status {response.status_code})"
f"Unexpected non-JSON-object response from n8n (status {response.status_code})"
)
error_code = "malformedResponse"
except httpx.HTTPError as exc:
+2 -6
View File
@@ -107,9 +107,7 @@ def derive_n8n_status(db: Session) -> N8nIntegrationStatus:
# procedures_sync_result), the same "the workflow's own callback is the evidence"
# pattern the scheduled scan and error handler already use below.
latest_procedure_sync_at = db.scalar(
select(func.max(AuditEvent.occurred_at)).where(
AuditEvent.action == "n8n_procedures_synced"
)
select(func.max(AuditEvent.occurred_at)).where(AuditEvent.action == "n8n_procedures_synced")
)
# Error handler evidence: registrations posted by the "Fleet Ops — Workflow Error
@@ -240,9 +238,7 @@ def derive_mcp_hub_status(db: Session) -> McpHubIntegrationStatus:
`MCP_HUB_REGISTRATION_ENABLED` flag flipped on. Every `mcp_tool_request` call
already writes an `AuditEvent` (see `app/api/routers/mcp_integrations.py`)."""
total_calls = (
db.scalar(
select(func.count(AuditEvent.id)).where(AuditEvent.action == "mcp_tool_request")
)
db.scalar(select(func.count(AuditEvent.id)).where(AuditEvent.action == "mcp_tool_request"))
or 0
)
latest_call_row = db.execute(
+137 -16
View File
@@ -14,25 +14,146 @@ DEFAULT_LANGUAGE = "en-GB"
STOPWORDS_BY_LANGUAGE: dict[str, set[str]] = {
"en-GB": {
"a", "an", "the", "is", "are", "was", "were", "be", "been", "being",
"to", "of", "in", "on", "at", "for", "and", "or", "but", "if", "then",
"do", "does", "did", "must", "may", "can", "could", "should", "would",
"i", "you", "it", "we", "they", "my", "your", "what", "when", "how",
"with", "without", "this", "that", "these", "those", "not", "no",
"a",
"an",
"the",
"is",
"are",
"was",
"were",
"be",
"been",
"being",
"to",
"of",
"in",
"on",
"at",
"for",
"and",
"or",
"but",
"if",
"then",
"do",
"does",
"did",
"must",
"may",
"can",
"could",
"should",
"would",
"i",
"you",
"it",
"we",
"they",
"my",
"your",
"what",
"when",
"how",
"with",
"without",
"this",
"that",
"these",
"those",
"not",
"no",
},
"nl-BE": {
"een", "de", "het", "is", "zijn", "was", "waren", "worden", "wordt",
"van", "in", "op", "voor", "en", "of", "maar", "als", "dan",
"moet", "mag", "kan", "kunnen", "zou", "zouden",
"ik", "jij", "u", "we", "wij", "zij", "mijn", "jouw", "wat", "wanneer", "hoe",
"met", "zonder", "dit", "dat", "deze", "die", "niet", "geen",
"een",
"de",
"het",
"is",
"zijn",
"was",
"waren",
"worden",
"wordt",
"van",
"in",
"op",
"voor",
"en",
"of",
"maar",
"als",
"dan",
"moet",
"mag",
"kan",
"kunnen",
"zou",
"zouden",
"ik",
"jij",
"u",
"we",
"wij",
"zij",
"mijn",
"jouw",
"wat",
"wanneer",
"hoe",
"met",
"zonder",
"dit",
"dat",
"deze",
"die",
"niet",
"geen",
},
"fr-BE": {
"un", "une", "le", "la", "les", "des", "est", "sont", "était", "être",
"de", "du", "en", "sur", "pour", "et", "ou", "mais", "si", "alors",
"doit", "peut", "peuvent", "pourrait", "devrait",
"je", "tu", "vous", "il", "elle", "nous", "ils", "mon", "votre", "quoi", "quand", "comment",
"avec", "sans", "ce", "cette", "ces", "cela", "pas", "non",
"un",
"une",
"le",
"la",
"les",
"des",
"est",
"sont",
"était",
"être",
"de",
"du",
"en",
"sur",
"pour",
"et",
"ou",
"mais",
"si",
"alors",
"doit",
"peut",
"peuvent",
"pourrait",
"devrait",
"je",
"tu",
"vous",
"il",
"elle",
"nous",
"ils",
"mon",
"votre",
"quoi",
"quand",
"comment",
"avec",
"sans",
"ce",
"cette",
"ces",
"cela",
"pas",
"non",
},
}
@@ -141,7 +262,7 @@ _LOW_CONFIDENCE_TEXT = {
_LEAD_ANSWER_TEMPLATE = {
"en-GB": 'Per "{title}" (v{version}), section "{heading}": {excerpt}',
"nl-BE": 'Volgens "{title}" (v{version}), sectie "{heading}": {excerpt}',
"fr-BE": 'Selon « {title} » (v{version}), section « {heading} » : {excerpt}',
"fr-BE": "Selon « {title} » (v{version}), section « {heading} » : {excerpt}",
}
+3 -1
View File
@@ -10,7 +10,9 @@ SUPPORTED_LANGUAGES = ("nl-BE", "en-GB", "fr-BE")
# Stable across runs (and across which language ships first) so a document's RAGcore
# source_id never changes just because the sync ran on a different day or in a
# different order -- required for RAGcore's upload idempotency to work per document.
_SOURCE_ID_NAMESPACE = uuid.uuid5(uuid.NAMESPACE_URL, "https://mobilityops.internal/knowledge/procedures")
_SOURCE_ID_NAMESPACE = uuid.uuid5(
uuid.NAMESPACE_URL, "https://mobilityops.internal/knowledge/procedures"
)
def parse_frontmatter(raw: str) -> tuple[dict[str, str], str]:
+1 -3
View File
@@ -14,9 +14,7 @@ settings = get_settings()
def is_session_revoked(db: Session, token: str) -> bool:
token_hash = session_token_hash(token)
revoked_id = db.scalar(
select(RevokedSession.id).where(RevokedSession.token_hash == token_hash)
)
revoked_id = db.scalar(select(RevokedSession.id).where(RevokedSession.token_hash == token_hash))
return revoked_id is not None
+4 -12
View File
@@ -175,9 +175,7 @@ def evaluate_vehicle_status(
if facts.has_active_rental and not blocking_reasons:
if current == "rented":
return result(None, RECOMMENDATION_CODE_NO_CONFLICT, safe=False, manual=False)
return result(
"rented", RECOMMENDATION_CODE_ACTIVE_RENTAL, safe=True, manual=False
)
return result("rented", RECOMMENDATION_CODE_ACTIVE_RENTAL, safe=True, manual=False)
if facts.has_active_rental and blocking_reasons:
# Explicitly forbidden shortcut this evaluator must never take: an active
@@ -190,24 +188,18 @@ def evaluate_vehicle_status(
if facts.service_threshold_reached:
if current == "maintenance":
return result(None, RECOMMENDATION_CODE_NO_CONFLICT, safe=False, manual=False)
return result(
"maintenance", RECOMMENDATION_CODE_SERVICE_THRESHOLD, safe=True, manual=False
)
return result("maintenance", RECOMMENDATION_CODE_SERVICE_THRESHOLD, safe=True, manual=False)
if facts.has_booking_conflict:
if current == "blocked":
return result(None, RECOMMENDATION_CODE_NO_CONFLICT, safe=False, manual=False)
return result(
"blocked", RECOMMENDATION_CODE_BOOKING_CONFLICT, safe=True, manual=False
)
return result("blocked", RECOMMENDATION_CODE_BOOKING_CONFLICT, safe=True, manual=False)
# No active rental, no maintenance need, no booking conflict.
if current in ("available", "cleaning", "blocked"):
return result(None, RECOMMENDATION_CODE_NO_CONFLICT, safe=False, manual=False)
if current == "rented":
return result(
"available", RECOMMENDATION_CODE_RENTAL_ENDED, safe=True, manual=False
)
return result("available", RECOMMENDATION_CODE_RENTAL_ENDED, safe=True, manual=False)
if current == "maintenance":
# No positive fact confirms maintenance is actually finished (no completed
# service record is tracked here) -- clearing "maintenance" without such a