Refactor Explanation
This document explains the current refactored design of the audio-anomaly-detection repo, why each module exists, how inference flows through the system, and exactly how to add new models safely.
1) Refactor Goal
The original API draft had endpoint logic, model loading, feature extraction wiring, and inference behavior in one place. The refactor moved to a modular layout so that:
- API endpoints stay thin and HTTP-focused.
- Model selection is dynamic and config-driven.
- New models can be onboarded mostly via configuration.
- Core inference logic is reusable and testable.
2) Current API Contract
Endpoints
GET /- Health/root message.
GET /models- Returns available model names and default model.
POST /predict-anomaly- Accepts file upload (
.wav,.mp3,.flac,.ogg) and optionalmodel_namequery param.
- Accepts file upload (
Prediction behavior
- If
model_nameis provided, API tries that model. - If omitted, API uses
DEFAULT_MODEL_NAMEfrom env. - If model is unknown, API returns HTTP
400.
3) Project Structure and Responsibilities
main.py
app/
main.py
config.py
dependencies.py
schemas.py
api/
routes.py
services/
errors.py
inference_service.py
registry.py
pipeline.py
features/
feature_extractor.py
tests/
test_registry.py
test_api_routes.py
main.py
Compatibility run entrypoint. Imports app from app.main and runs uvicorn.
app/main.py
Application factory and composition root.
- Configures logging.
- Loads settings.
- Builds
ModelRegistryandInferenceService. - Stores service objects in
app.state.
app/config.py
All environment parsing and config validation.
ModelConfig: per-model config object.AppSettings: top-level app settings.- Supports two config modes:
MODELS_CONFIG_JSON(multi-model)- Legacy single-model env fallback
- Resolves relative artifact paths to absolute paths.
- Ensures default model is present in configured model set.
app/dependencies.py
FastAPI DI adapter to retrieve InferenceService from request.app.state.
app/schemas.py
Response schema contracts.
PredictionResponseModelsResponse
app/api/routes.py
HTTP-facing layer only.
- Validates request shape/file extension.
- Calls service layer.
- Maps service/domain exceptions to HTTP status codes.
- Keeps endpoint logic minimal.
app/services/errors.py
Domain-level exceptions.
UnknownModelErrorInferenceFailureError
app/services/registry.py
Model resolution + lazy pipeline cache.
- Decides which model to use.
- Validates requested model name.
- Builds pipeline only on first request per model.
- Uses lock for thread-safe lazy initialization.
app/services/pipeline.py
Concrete model runtime pipeline (SklearnModelPipeline).
- Feature extraction
- Scaler transform
predict_proba- label mapping
- response payload assembly
app/services/inference_service.py
Inference orchestration from bytes to result.
- Resolves model.
- Decodes audio with librosa.
- Executes model pipeline.
- Adds latency metric.
features/feature_extractor.py
Feature engineering logic retained from existing implementation.
- Silence trim
- pre-emphasis
- MFCC/formants/spectral features
- optional dropped-feature filtering
4) End-to-End Request Flow
- Client sends
POST /predict-anomalywith multipart audio file and optionalmodel_name. - Route validates extension.
- Route reads bytes and calls
InferenceService.predict_from_bytes(...). - Service resolves model name through
ModelRegistry. - Registry returns cached pipeline or builds one lazily.
- Service decodes audio bytes into mono waveform at configured sample rate.
- Pipeline extracts features, scales them, runs classifier probabilities.
- Pipeline returns structured prediction payload.
- Service adds
latency_msand returns response. - Route serializes to
PredictionResponse.
5) Model Configuration
Core env keys
API_TITLEAPI_VERSIONSAMPLE_RATEDEFAULT_MODEL_NAMEMODELS_CONFIG_PATH(preferred)MODELS_CONFIG_JSON
Legacy fallback env keys (when JSON is not set)
MODEL_PATHSCALER_PATHLABEL_ENCODER_PATHDROPPED_FEATURES_PATH
MODELS_CONFIG_PATH (preferred)
Point this to a readable JSON file (for example: config/models.json).
JSON model config shape
Whether read from file or env string, model config is a JSON object keyed by model name. Each value should include:
model_pathscaler_pathlabel_encoder_path(optional)dropped_features_path(optional)feature_extractor(currently supportsdefault)predictor(currently supportssklearn_proba)
6) How to Add a New Model (No Code Changes)
Use this when new model follows current sklearn probability flow.
- Add new artifacts (model/scaler/optional encoder/optional dropped-features) under
models/. - Add model entry in
config/models.json. - Ensure
feature_extractorisdefaultandpredictorissklearn_proba. - Call API with
?model_name=<new_model_name>. - Verify visibility via
GET /models.
7) When Code Changes Are Required for New Models
Case A: Different feature extraction
Expected updates:
- Add new extractor implementation.
- Add branch in
ModelRegistry._build_pipeline(...)byfeature_extractorvalue. - Extend tests for that branch.
Case B: Different preprocessing/transform
Expected updates:
- Add new transform logic in pipeline.
- Add config switch and registry branch.
- Add tests for transform output shape/behavior.
Case C: Non-predict_proba predictor behavior
Expected updates:
- Add new predictor execution path.
- Map output to stable API response schema.
- Add tests for probability and label mapping behavior.
8) Error Handling Strategy
- Unknown
model_name->UnknownModelError-> HTTP400. - Extraction failure (e.g., too-short audio) ->
InferenceFailureError-> HTTP422. - Unsupported extension -> HTTP
400. - Unexpected internal exception -> HTTP
500.
This cleanly separates domain failures from transport-level responses.
9) Modularity Assessment (Current State)
What is modular and healthy:
- Clear boundary between HTTP (
routes.py) and inference orchestration (inference_service.py). - Model loading concerns isolated in
registry.py. - Concrete pipeline isolated in
pipeline.py. - Config parsing and validation centralized in
config.py.
What remains intentionally simple:
- One concrete pipeline class (no unnecessary abstraction layers).
- One prediction endpoint only (
/predict-anomaly) to reduce API surface complexity.
10) Testing Coverage
Current tests
tests/test_registry.py- requested model resolution
- fallback to default model
- unknown model handling
tests/test_api_routes.py- model_name query routing behavior
- unknown model returns 400
Recommended additions
GET /modelsresponse test.- invalid extension returns
400test. - default-model path test via API (no
model_namepassed).
11) Operational Notes
Run app
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
List models
curl http://127.0.0.1:8000/models
Predict
curl -X POST "http://127.0.0.1:8000/predict-anomaly?model_name=<model_name>" \
-F "file=@/path/to/audio.wav"
Run tests
python -m pytest -q
(Use python3 -m pytest -q if python is unavailable in your shell.)
12) Migration Notes
utils/model_handler.pyis legacy and not used by the new route path.- Refactored flow relies on app package modules under
app/. - Existing clients should call
/predict-anomalyonly.
13) Summary
The refactor now provides:
- Dynamic multi-model selection via query + env default.
- Lazy, cached model pipelines.
- Cleaner modular boundaries.
- Simpler, lean architecture (without over-engineered abstractions).
- A clear path to extend behavior when model families diverge.