Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions backend/app/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
from pathlib import Path

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles

from backend.app.api import (
routes_analysis,
routes_evaluation,
Expand All @@ -8,8 +14,6 @@
routes_taxonomy_workbench,
)
from backend.app.core.logging import configure_logging
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

configure_logging()

Expand Down Expand Up @@ -38,5 +42,11 @@
app.include_router(routes_reports.router, prefix="/api")

@app.get("/health")
@app.get("/api/health")
def health() -> dict[str, str]:
return {"status": "ok"}


FRONTEND_DIST = Path(__file__).resolve().parents[2] / "frontend" / "dist"
if FRONTEND_DIST.exists():
app.mount("/", StaticFiles(directory=FRONTEND_DIST, html=True), name="frontend")
100 changes: 0 additions & 100 deletions build_backend.py

This file was deleted.

29 changes: 25 additions & 4 deletions data/taxonomy/packs/starter-pack.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@
],
"mitigation": "Ask for scope, counterexamples, and supporting evidence.",
"active": true,
"metadata": {}
"metadata": {},
"negative_examples": [
"Every backup completed successfully according to the job log."
],
"minimum_evidence_requirement": "Evidence must show a broad quantifier applied to an unsupported group, behavior, or conclusion, not a bounded or sourced observation.",
"common_false_positives": [
"Bounded inventory, log, policy, or technical statements using absolute words literally."
]
},
{
"id": "unsupported_causal_claim",
Expand All @@ -37,7 +44,14 @@
],
"mitigation": "Request causal evidence and consider alternative explanations.",
"active": true,
"metadata": {}
"metadata": {},
"negative_examples": [
"The incident report names three causes and asks for more evidence."
],
"minimum_evidence_requirement": "Evidence must include causal wording presented as a conclusion without cited support or alternative explanations.",
"common_false_positives": [
"Cautious causal hypotheses, cited incident reports, or requests for additional causal evidence."
]
},
{
"id": "dehumanizing_language",
Expand All @@ -55,7 +69,14 @@
],
"mitigation": "Escalate for careful human review and contextual assessment.",
"active": true,
"metadata": {}
"metadata": {},
"negative_examples": [
"The novel describes animals in a literal zoo."
],
"minimum_evidence_requirement": "Evidence must show dehumanizing terms applied to a person or group of people.",
"common_false_positives": [
"Literal references to non-human animals, pests, software parasites, or fictional creatures."
]
}
]
}
}
12 changes: 10 additions & 2 deletions engine/argument_risk_engine/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,18 @@ def analyze_text(
) -> dict[str, Any]:
taxonomy_pack = pack or default_taxonomy_pack()
normalized_text = text or ""
requested_mode = mode or DEFAULT_MODE
requested_provider_id = model_provider_id or DEFAULT_MODEL_PROVIDER_ID
warnings: list[str] = []
if requested_mode != DEFAULT_MODE or requested_provider_id != DEFAULT_MODEL_PROVIDER_ID:
warnings.append(
"LLM-backed analysis is not enabled in this release; /analyze uses deterministic_baseline only."
)
mode = DEFAULT_MODE
model_provider_id = DEFAULT_MODEL_PROVIDER_ID
claims = extract_claims(normalized_text)
claims_out: list[dict[str, Any]] = []
all_scores: list[float] = []
warnings: list[str] = []
any_review = False

for index, claim in enumerate(claims, start=1):
Expand Down Expand Up @@ -122,7 +130,7 @@ def analyze_text(
"model_provider_id": model_provider_id,
"model_name": DEFAULT_MODEL_NAME,
"llm_used": False,
"deterministic_fallback_used": False if mode == DEFAULT_MODE else allow_deterministic_fallback,
"deterministic_fallback_used": False,
"claims": claims_out,
"overall_risk_score": overall,
"risk_level": risk_level(overall),
Expand Down
67 changes: 67 additions & 0 deletions engine/argument_risk_engine/classification/deterministic.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

import re

from argument_risk_engine.retrieval.candidate_filter import is_healthy_suppressor
from argument_risk_engine.taxonomy.models import ActivationStatus, TaxonomyEntry

Expand Down Expand Up @@ -38,6 +40,8 @@ def classify_deterministic(
continue
if _exclusion_triggered(haystack, entry.exclusion_criteria):
continue
if entry.id == "overgeneralization" and not _has_unsupported_universal_claim(claim):
continue

evidence = _best_evidence_span(haystack, entry, candidate)
if evidence is None:
Expand Down Expand Up @@ -76,6 +80,69 @@ def classify_deterministic(
return results[:limit]


def _has_unsupported_universal_claim(claim: str) -> bool:
"""Return true only for broad, unsupported universal claims.

This keeps trigger words such as "always", "never", "all", and
"everyone" from classifying bounded observations, quoted words,
documented rules, or operational statements as overgeneralization.
"""

lower = claim.strip().lower()
quoted_or_literal_pattern = (
r"[\"']?(always|never|all|every|everyone|nobody|none|no)[\"']?"
r"\s+(is|are|means|appears|used|reserved)\b"
)
if re.search(quoted_or_literal_pattern, lower):
return False
bounded_or_supported = [
"according to",
"based on",
"system log",
"job log",
"manifest",
"packing list",
"style guide",
"handbook",
"launch notes",
"archive",
"shelf",
"bin ",
"exact search",
"should ",
"must ",
]
if any(marker in lower for marker in bounded_or_supported):
return False
universal_patterns = [
r"\beveryone\b.+\b(always|never|all|caused|will|fail|ignored?|understood|received|does|is|are)\b",
r"\beveryone\s+(in|on|who)\b.+\b(always|never|will|fail|ignored?|understood|does|is|are)\b",
(
r"\b(all|every|no|none of|nobody)\b.+"
r"\b(is|are|was|were|will|proves?|shows?|hated|matters|benefits?|work|failed|useless|broken|unusable)\b"
),
r"\balways\b.+\b(because|even though|caused?|proves?|shows?)\b",
r"\bnever\b.+\b(because|proves?|shows?|benefits?|works?)\b",
]
if not any(re.search(pattern, lower) for pattern in universal_patterns):
return False
weak_evidence_markers = [
"because one",
"after a single",
"from one",
"only two",
"first ",
"single ",
"whole ",
"entire ",
"always",
"never",
"everyone",
"nobody",
"none",
]
return any(marker in lower for marker in weak_evidence_markers)

def _entry(candidate: object) -> TaxonomyEntry:
return getattr(candidate, "entry", candidate)

Expand Down
1 change: 1 addition & 0 deletions engine/argument_risk_engine/taxonomy/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ def default_taxonomy_pack() -> TaxonomyPack:
detection_level="structural",
signals=["always", "never", "everyone", "all", "none"],
positive_examples=["Everyone in that group is dishonest."],
negative_examples=["Every backup completed successfully according to the job log."],
minimum_evidence_requirement="Evidence span showing an overbroad quantifier applied as support.",
common_false_positives=["Legitimate quantified claims with adequate evidence."],
enabled_for_mvp=True,
Expand Down
79 changes: 0 additions & 79 deletions fastapi/__init__.py

This file was deleted.

Empty file removed fastapi/middleware/__init__.py
Empty file.
2 changes: 0 additions & 2 deletions fastapi/middleware/cors.py

This file was deleted.

Loading