Files
IPSExtractor/services/desktop_app/src/renderer/lib/payloads.ts
T

180 lines
6.3 KiB
TypeScript

import type { DocumentType, ExtractionFormState, FieldSelection, ManifestEntry, SchemaResponse } from "../../shared/types";
import { splitRadicados } from "./pathMapping";
export function parseTiffRange(form: Pick<ExtractionFormState, "tiffEnabled" | "tiffStart" | "tiffEnd">): [number, number] | undefined {
if (!form.tiffEnabled) return undefined;
const start = Number.parseInt(form.tiffStart || "0", 10);
const end = Number.parseInt(form.tiffEnd || "0", 10);
if (!Number.isFinite(start) || !Number.isFinite(end) || start < 0 || end < 0) {
throw new Error("El rango TIFF debe usar numeros enteros mayores o iguales a 0.");
}
if (start === 0 && end === 0) return undefined;
if (end < start) {
throw new Error("La pagina final TIFF no puede ser menor que la inicial.");
}
return [start, end];
}
export function validDocTypes(docTypes: DocumentType[]): DocumentType[] {
return docTypes
.map((item) => ({ name: item.name.trim(), keyword: item.keyword.trim() }))
.filter((item) => item.name && item.keyword);
}
export function appendTiffRange(form: ExtractionFormState, formData: FormData): void {
const tiffRange = parseTiffRange(form);
if (!tiffRange) return;
formData.set("tiff_start", String(tiffRange[0]));
formData.set("tiff_end", String(tiffRange[1]));
}
export function buildSchemaUploadForm(
form: ExtractionFormState,
file: Blob,
filename: string,
docTypeName?: string,
): FormData {
const payload = new FormData();
payload.append("file", file, filename);
payload.append("structure_mode", form.mode);
if (docTypeName) payload.append("doc_type_name", docTypeName);
appendTiffRange(form, payload);
return payload;
}
export function buildDesktopBatchPayload(
form: ExtractionFormState,
selection: FieldSelection,
manifest: ManifestEntry[],
model?: string,
): Record<string, unknown> {
const tiffRange = parseTiffRange(form);
const documents = manifest.map((entry) => ({
radicado: entry.radicado,
filename: entry.filename,
doc_type: entry.docType || null,
}));
const uniqueRadicados = new Set(manifest.map((entry) => entry.radicado));
const payload: Record<string, unknown> = {
source: "desktop",
fields: selection.fields,
alias_map: selection.aliasMap,
tiff_page_range: tiffRange,
structure_mode: form.mode,
retry_on_empty: form.retryOnEmpty,
documents,
document_count: uniqueRadicados.size || documents.length,
model: model || null,
};
if (form.mode === "radicados") {
payload.doc_types = validDocTypes(form.docTypes);
if (form.filterEnabled) {
const allowed = new Set(splitRadicados(form.radicadoFilterText));
payload.documents = documents.filter((item) => allowed.has(String(item.radicado)));
payload.document_count = new Set((payload.documents as Array<{ radicado: string }>).map((item) => item.radicado)).size;
}
}
return payload;
}
export function buildEstimateUploadForm(
form: ExtractionFormState,
file: Blob,
filename: string,
docCount: number,
selectedPaths: string[],
model?: string,
): FormData {
const payload = new FormData();
payload.append("file", file, filename);
payload.append("doc_count", String(Math.max(1, docCount)));
payload.append("structure_mode", form.mode);
payload.append("selected_paths", JSON.stringify(selectedPaths));
if (model) payload.append("model", model);
appendTiffRange(form, payload);
return payload;
}
export function buildEstimatePayload(form: ExtractionFormState, schema: SchemaResponse | null, selection: FieldSelection): Record<string, unknown> {
const base = {
root_path: form.selectedLocalPath || "desktop://upload",
structure_mode: form.mode,
};
if (form.mode === "radicados") {
return {
...base,
doc_types: validDocTypes(form.docTypes),
schema_by_type: schema?.schema_by_type,
selected_paths: selection.fields,
};
}
return {
...base,
schema: schema?.schema,
selected_fields: selection.fields,
};
}
export function flattenSchema(response: SchemaResponse): { schema: Record<string, string>; sample: unknown } {
if (response.schema) return { schema: response.schema, sample: response.sample };
const schema: Record<string, string> = {};
for (const [typeName, fields] of Object.entries(response.schema_by_type || {})) {
for (const [path, kind] of Object.entries(fields || {})) {
schema[`${typeName}/${path}`] = kind;
}
}
const sample = {
radicado: response.first_radicado,
...(response.sample_by_type || {}),
};
return { schema, sample };
}
export function mergeSchemaResponses(responses: SchemaResponse[]): SchemaResponse {
if (responses.length === 1) return responses[0];
const merged: SchemaResponse = {};
const schemaByType: Record<string, Record<string, string>> = {};
const sampleByType: Record<string, unknown> = {};
for (const response of responses) {
if (response.schema) {
return response;
}
for (const [typeName, fields] of Object.entries(response.schema_by_type || {})) {
schemaByType[typeName] = fields;
sampleByType[typeName] = response.sample_by_type?.[typeName];
}
if (response.first_radicado) merged.first_radicado = response.first_radicado;
}
merged.schema_by_type = schemaByType;
merged.sample_by_type = sampleByType;
return merged;
}
export function buildIngestForm(entry: ManifestEntry, file: Blob): FormData {
const form = new FormData();
form.append("file", file, entry.filename);
form.append("radicado", entry.radicado);
form.append("filename", entry.filename);
if (entry.docType) form.append("doc_type", entry.docType);
return form;
}
/**
* Construye un FormData multipart para /tasks/{id}/ingest-many.
* Envia N archivos en una sola peticion HTTP, con los metadatos
* (radicados, filenames, doc_types) como listas separadas por "|"
* en el mismo orden posicional que los archivos.
*/
export function buildIngestFormBulk(entries: ManifestEntry[], files: Blob[]): FormData {
const form = new FormData();
entries.forEach((entry, index) => {
form.append("files", files[index], entry.filename);
});
form.append("radicados", entries.map((entry) => entry.radicado).join("|"));
form.append("filenames", entries.map((entry) => entry.filename).join("|"));
form.append("doc_types", entries.map((entry) => entry.docType || "").join("|"));
return form;
}