--- title: Conversation Data Extraction System sdk: docker emoji: 🗂️ colorFrom: indigo colorTo: purple app_port: 8000 --- # Conversation Data Extraction System A system that extracts structured information from chat conversations: **named entities (NER), topics and sentiment**, then **pseudonymizes** personal data (GDPR) and stores the structured result. ## Project versions This repository is developed incrementally as part of a diploma thesis. Each version is tagged so the progression is visible in the git history. | Version | Tag | Description | |---------|-----|--------------| | V1 | `v1.0` | Single fine-tuned RoBERTa model, `/predict` endpoint, simple SQLite logging with regex anonymization. | | V2 | `v2.0` | Full extraction pipeline: batch `/ingest`, NER, zero-shot topic classification, Presidio-based anonymization, dashboard with stored records and statistics. | | V3 | `v3.0` | Dynamic model selection from the Hugging Face Hub (sentiment, NER and topic classification) at request time, plus XML export of stored records. | | V4 | `v4.0` | Export of stored records in a choice of formats (XML, JSON, CSV) via a single endpoint, picked from a dropdown button on the dashboard. | | V5 | `v5.0` | Per-step model choice (sentiment, NER, topics) switched from radio buttons to a ``)** for each step, listing the suggested models plus a "Custom model" option. The free-text field for typing any other Hugging Face repo id is hidden by default and only appears once "Custom model" is selected in the dropdown. **Security note:** loaded pipelines never use `trust_remote_code=True`, so an arbitrary/untrusted model id supplied by a caller cannot execute custom Python code inside the server process - it is limited to standard `transformers` inference for the given task. An invalid or incompatible model id results in a clean HTTP 400 response instead of crashing the server. ## Batched inference for large `/ingest` batches Since V6, `pipeline.process_batch` (used only by `/ingest`) no longer loops over `process_message` once per message. Instead it calls each processor's batched function - `ner.extract_entities_batch`, `topics.classify_topic_batch`, `sentiment.analyze_sentiment_batch` - once for the whole list of texts, and reassembles the per-message results afterwards. Each batched function passes `batch_size=16` to the underlying `transformers` pipeline so the model itself processes several texts per forward pass instead of one at a time. This matters most for topic classification: zero-shot classification scores every text against every candidate label as a separate NLI pass through `facebook/bart-large-mnli` (~400M parameters), so for the default 8 labels that's 8 passes per message. Batching those calls together is the difference between a 200-message `/ingest` request taking minutes rather than tens of minutes on CPU-only hosting (e.g. the free tier of Hugging Face Spaces). `process_message` (used by `/analyze` and `/predict`, always a single message) is unchanged. ## Background ingest jobs (progress bar + timer) Since V7, `POST /ingest` no longer blocks until the whole batch is processed. It parses/validates the uploaded file synchronously (fast, no model calls), registers a job via `jobs.py` and starts the actual NLP processing in a background thread, returning `{"job_id", "total"}` immediately (HTTP 202). `pipeline.process_batch` processes messages in fixed-size chunks (40 by default, see `pipeline._CHUNK_SIZE`) instead of one call covering the whole batch, and reports cumulative progress via an `on_progress` callback after each chunk - this is what gives `GET /ingest/status/{job_id}` something to report before the whole job is done. The chunk size is a deliberate trade-off: large enough to keep most of V6's batching speedup, small enough to give a handful of progress updates instead of only 0% and 100%. Job state (`status`, `processed`, `total`, timestamps, and the final result or error) lives in an in-memory dict in `jobs.py` - intentionally simple, consistent with the rest of this prototype. A server restart loses in-flight job status; the dashboard treats an unknown `job_id` (HTTP 404) as an error rather than hanging forever. The dashboard polls `/ingest/status/{job_id}` once per second and updates a `` bar (`processed` / `total`) plus a locally-ticking elapsed-time timer (updated every 100ms from the browser's own clock, so it stays smooth between polls). When the job reaches `done` or `error`, polling stops and the final summary/error is shown, same as the old synchronous response. ## Data protection The original message text is **never stored in readable form**. Only a SHA-256 hash (for deduplication) and the anonymized text are persisted. Because the transformation is reversible in principle and re-identification could occur with additional information, the approach is **pseudonymization** under the GDPR (Art. 4(5)); stored data therefore remains personal data and is handled with data minimization in mind. ## Local run ```bash pip install -r requirements.txt python -m spacy download en_core_web_lg uvicorn main:app --reload --port 8000 ``` `requirements-dev.txt` holds extra dependencies (`pandas`, `seqeval`) needed only by the offline helper scripts `prepare_dataset.py` and `evaluate.py` - not required to run the API itself.