Commit inicial - upload de todos os arquivos da pasta
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Single AI Attempt Per Document
|
||||
The system SHALL make at most one AI categorization call per fiscal document per ingestion attempt and SHALL NOT automatically retry a failed AI categorization call.
|
||||
|
||||
#### Scenario: AI categorization call fails
|
||||
- **WHEN** the AI categorization call for a document errors or times out
|
||||
- **THEN** the system does not retry the AI call for that document and proceeds directly to keyword fallback
|
||||
|
||||
### Requirement: Supplier Categorization Cache
|
||||
The system SHALL cache the category determined for a given supplier name (in-memory or persisted) and SHALL reuse the cached category for subsequent documents from the same supplier instead of calling the AI again.
|
||||
|
||||
#### Scenario: Second document from a known supplier
|
||||
- **WHEN** a fiscal document is processed for a supplier name that already has a cached category from a prior categorization
|
||||
- **THEN** the system uses the cached category and does not issue a new AI categorization call
|
||||
|
||||
#### Scenario: Cache miss for a new supplier
|
||||
- **WHEN** a fiscal document is processed for a supplier name with no cached category
|
||||
- **THEN** the system proceeds with the normal AI-then-keyword categorization flow and stores the result in the cache
|
||||
|
||||
### Requirement: Per-Batch AI Call Limit
|
||||
The system SHALL enforce a configurable maximum number of AI categorization calls within a single import batch (or time window). Once the limit is reached, remaining documents in that batch SHALL be categorized using only the keyword fallback, with no further AI calls, until the batch/window resets.
|
||||
|
||||
#### Scenario: Limit reached mid-batch
|
||||
- **WHEN** the number of AI categorization calls in the current batch reaches the configured maximum
|
||||
- **THEN** subsequent documents in the same batch skip the AI call and go directly to keyword fallback (or "Sem categoria" if no keyword matches)
|
||||
|
||||
### Requirement: Configurable Safety Limits
|
||||
The maximum AI calls per batch and cache behavior SHALL be configurable via application configuration/environment variables, not hardcoded in the categorization logic.
|
||||
|
||||
#### Scenario: Operator changes the configured limit
|
||||
- **WHEN** the configured maximum AI calls per batch is changed
|
||||
- **THEN** the categorization flow honors the new limit on the next run without code changes
|
||||
|
||||
### Requirement: Observability of Skipped AI Calls
|
||||
When an AI categorization call is skipped due to the cache or the per-batch limit, the system SHALL log the reason (cache hit or limit reached) so the behavior is observable and auditable.
|
||||
|
||||
#### Scenario: Skip logged
|
||||
- **WHEN** the system skips an AI categorization call because of a cache hit or because the batch limit was reached
|
||||
- **THEN** a log entry is recorded indicating which reason caused the skip and for which document/supplier
|
||||
@@ -0,0 +1,113 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Categoria Table Schema
|
||||
The system SHALL provide a `categoria` table with fields `id`, `categoria`, and `palavra_chave`, used to map keywords to category names as a fallback for automatic classification. The row with `id = 1` is reserved by the system for the "Não Encontrado" category and SHALL always exist; the system SHALL ensure this row is present (creating it if missing) during schema initialization, independent of any other categories the user registers.
|
||||
|
||||
#### Scenario: Table created on schema initialization
|
||||
- **WHEN** the application initializes or migrates the database schema
|
||||
- **THEN** the `categoria` table exists with columns `id`, `categoria`, `palavra_chave`, and a row with `id = 1` and `categoria = "Não Encontrado"` is present
|
||||
|
||||
#### Scenario: Reserved row already present
|
||||
- **WHEN** the application initializes and a `categoria` row with `id = 1` already exists (e.g., the user has customized its `categoria`/`palavra_chave` values)
|
||||
- **THEN** the system does not overwrite or duplicate that row
|
||||
|
||||
### Requirement: Automatic Categorization on Ingestion
|
||||
The system SHALL attempt to determine the category of a fiscal document automatically at the moment it is confirmed/created, without requiring manual user input.
|
||||
|
||||
#### Scenario: New fiscal document confirmed triggers categorization
|
||||
- **WHEN** a detected document is confirmed and a `fiscal_documents` row is created
|
||||
- **THEN** the system runs the categorization flow (AI, then keyword fallback) before finishing the request
|
||||
|
||||
### Requirement: AI-Based Categorization by Supplier Name
|
||||
The system SHALL first attempt to categorize a fiscal document by sending the supplier name to the AI service and requesting a category classification.
|
||||
|
||||
#### Scenario: AI returns a valid category
|
||||
- **WHEN** the AI service returns a recognized, non-empty category for the supplier name
|
||||
- **THEN** the system stores that category on the fiscal document and does not consult the `categoria` keyword table
|
||||
|
||||
#### Scenario: AI call fails or returns no usable category
|
||||
- **WHEN** the AI call raises an error, times out, or returns an empty/unrecognized value
|
||||
- **THEN** the system proceeds to keyword fallback categorization instead of failing the request
|
||||
|
||||
### Requirement: Keyword Fallback Categorization
|
||||
When AI categorization does not produce a valid category, the system SHALL search the `categoria` table for a `palavra_chave` that matches (case-insensitive, substring) the supplier name, and use the corresponding `categoria` value.
|
||||
|
||||
#### Scenario: Keyword match found
|
||||
- **WHEN** AI categorization did not yield a category and a `categoria.palavra_chave` is found as a substring of the supplier name (case-insensitive)
|
||||
- **THEN** the system assigns the matching `categoria.categoria` value to the fiscal document
|
||||
|
||||
#### Scenario: No keyword match found
|
||||
- **WHEN** AI categorization did not yield a category and no `palavra_chave` matches the supplier name
|
||||
- **THEN** the system assigns `categoria_id = 1` ("Não Encontrado") to the fiscal document instead of guessing
|
||||
|
||||
### Requirement: Fallback to Reserved "Não Encontrado" Category
|
||||
When neither AI nor keyword matching determines a category for a fiscal document being processed by the categorization flow, the system SHALL persist that document with `categoria_id = 1` (the reserved "Não Encontrado" category) and SHALL NOT block or fail document processing.
|
||||
|
||||
#### Scenario: No category determined by any method
|
||||
- **WHEN** AI categorization fails/is inconclusive and no keyword match exists for a document going through the categorization flow
|
||||
- **THEN** the fiscal document is saved successfully with `categoria_id = 1` and shown as "Não Encontrado" in the UI
|
||||
|
||||
#### Scenario: Legacy documents predating this change remain distinct from "Não Encontrado"
|
||||
- **WHEN** a `fiscal_documents` row was created before this change and has never been run through the categorization flow
|
||||
- **THEN** its `categoria_id` remains `NULL` and it is shown as "Sem categoria" (distinct from the explicit "Não Encontrado" outcome), until it is reprocessed
|
||||
|
||||
### Requirement: Dashboard Category Breakdown
|
||||
The Dashboard SHALL display expenses aggregated by category (e.g., totals per category) alongside existing period and supplier breakdowns.
|
||||
|
||||
#### Scenario: Dashboard shows category totals
|
||||
- **WHEN** a user opens the Dashboard
|
||||
- **THEN** the page displays total spend grouped by category, including the "Não Encontrado" group (`categoria_id = 1`) for documents the categorization flow could not classify, and a separate "Sem categoria" group for any legacy documents with `categoria_id` still `NULL`
|
||||
|
||||
### Requirement: Dashboard Category Filter
|
||||
The Dashboard SHALL allow the user to filter the displayed expenses by a single selected category.
|
||||
|
||||
#### Scenario: User filters by category
|
||||
- **WHEN** the user selects a category from the Dashboard's category filter
|
||||
- **THEN** all Dashboard figures (KPIs, charts, recent documents) update to reflect only fiscal documents in that category
|
||||
|
||||
#### Scenario: User clears the category filter
|
||||
- **WHEN** the user clears the selected category filter
|
||||
- **THEN** the Dashboard returns to showing all fiscal documents regardless of category
|
||||
|
||||
### Requirement: Categoria Administration List
|
||||
The system SHALL provide an authenticated admin page listing all rows of the `categoria` table (`id`, `categoria`, `palavra_chave`), following the same page layout, table markup, and CSS classes already used by the existing "Documentos" list page.
|
||||
|
||||
#### Scenario: User views the categoria list
|
||||
- **WHEN** an authenticated user navigates to the categorias admin page
|
||||
- **THEN** the system displays all `categoria` rows in a table matching the existing list-page layout (`card`/`table`/`table-scroll` styling), with actions to create, edit, and delete a row
|
||||
|
||||
### Requirement: Categoria Creation
|
||||
The system SHALL allow an authenticated user to create a new `categoria` row (`categoria`, `palavra_chave`) via a form that follows the same structure, validation, and CSRF protection as the existing document create/edit form.
|
||||
|
||||
#### Scenario: User creates a new categoria
|
||||
- **WHEN** an authenticated user submits the "new categoria" form with a non-empty `categoria` and `palavra_chave`
|
||||
- **THEN** the system inserts a new row into the `categoria` table and redirects to the categoria list showing the new entry
|
||||
|
||||
#### Scenario: User submits an invalid categoria form
|
||||
- **WHEN** an authenticated user submits the "new categoria" form with a missing `categoria` or `palavra_chave`
|
||||
- **THEN** the system re-displays the form with a validation error and does not create a row
|
||||
|
||||
### Requirement: Categoria Update
|
||||
The system SHALL allow an authenticated user to edit an existing `categoria` row's `categoria` and `palavra_chave` values, reusing the same form template pattern used for creation (single form, `mode` toggling between new/edit).
|
||||
|
||||
#### Scenario: User edits an existing categoria
|
||||
- **WHEN** an authenticated user submits the edit form for an existing `categoria` row with valid values
|
||||
- **THEN** the system updates that row and redirects to the categoria list reflecting the new values
|
||||
|
||||
### Requirement: Categoria Deletion
|
||||
The system SHALL allow an authenticated user to delete an existing `categoria` row (other than the reserved `id = 1` row) via a CSRF-protected POST action, following the same inline delete-form-with-confirmation pattern used on the existing "Documentos" list page.
|
||||
|
||||
#### Scenario: User deletes a categoria
|
||||
- **WHEN** an authenticated user confirms deletion of a `categoria` row with `id != 1`
|
||||
- **THEN** the system removes that row from the `categoria` table and redirects to the categoria list without it
|
||||
|
||||
#### Scenario: Deleting a categoria referenced by fiscal documents
|
||||
- **WHEN** an authenticated user deletes a `categoria` row that is currently referenced by one or more `fiscal_documents.categoria_id`
|
||||
- **THEN** the system completes the deletion and reassigns those fiscal documents' `categoria_id` to `1` ("Não Encontrado"), without errors or orphaned references
|
||||
|
||||
### Requirement: Reserved Categoria Cannot Be Deleted
|
||||
The system SHALL prevent deletion of the `categoria` row with `id = 1` ("Não Encontrado"), since it is the required fallback target for automatic categorization.
|
||||
|
||||
#### Scenario: User attempts to delete the reserved categoria
|
||||
- **WHEN** an authenticated user attempts to delete the `categoria` row with `id = 1`
|
||||
- **THEN** the system rejects the deletion, shows an error message, and the row remains unchanged
|
||||
Reference in New Issue
Block a user