M21: add optional organisation identity
This commit is contained in:
@@ -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}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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}",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user