387 lines
14 KiB
TypeScript
387 lines
14 KiB
TypeScript
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
|
import { IListable } from '@iqser/common-ui';
|
|
import {
|
|
annotationDefaultColorConfig,
|
|
annotationEntityColorConfig,
|
|
AnnotationIconType,
|
|
ChangeType,
|
|
DefaultColors,
|
|
Dictionary,
|
|
Earmark,
|
|
EntityTypes,
|
|
EntryStates,
|
|
FalsePositiveSuperTypes,
|
|
IEntityLogEntry,
|
|
ILegalBasis,
|
|
IPoint,
|
|
IRectangle,
|
|
LogEntryEngine,
|
|
LogEntryEngines,
|
|
LowLevelFilterTypes,
|
|
ManualRedactionType,
|
|
ManualRedactionTypes,
|
|
SuperType,
|
|
SuperTypeMapper,
|
|
SuperTypes,
|
|
} from '@red/domain';
|
|
import { annotationTypesTranslations } from '@translations/annotation-types-translations';
|
|
|
|
interface AnnotationContent {
|
|
translation: string;
|
|
params: { [key: string]: string };
|
|
untranslatedContent: string;
|
|
}
|
|
|
|
export class AnnotationWrapper implements IListable {
|
|
id: string;
|
|
superType: SuperType;
|
|
superTypeLabel: string;
|
|
type: string;
|
|
typeLabel?: string;
|
|
color: string;
|
|
numberOfComments = 0;
|
|
firstTopLeftPoint: IPoint;
|
|
shortContent: string;
|
|
content: AnnotationContent;
|
|
value: string;
|
|
pageNumber: number;
|
|
dictionaryOperation = false;
|
|
positions: IRectangle[] = [];
|
|
legalBasisValue: string;
|
|
// AREA === rectangle
|
|
AREA = false;
|
|
HINT = false;
|
|
IMAGE = false;
|
|
IMAGE_HINT = false;
|
|
section?: string;
|
|
reference: string[] = [];
|
|
imported = false;
|
|
manual = false;
|
|
pending = false;
|
|
textAfter?: string;
|
|
textBefore?: string;
|
|
isChangeLogEntry = false;
|
|
engines: LogEntryEngine[] = [];
|
|
hasBeenResized: boolean;
|
|
hasBeenResizedLocally: boolean;
|
|
hasBeenRecategorized: boolean;
|
|
hasLegalBasisChanged: boolean;
|
|
hasBeenForcedHint: boolean;
|
|
hasBeenForcedRedaction: boolean;
|
|
hasBeenRemovedByManualOverride: boolean;
|
|
isRemoved = false;
|
|
isRemovedLocally = false;
|
|
hiddenInWorkload = false;
|
|
lastManualChange: ManualRedactionType;
|
|
entry: IEntityLogEntry;
|
|
|
|
get isRuleBased() {
|
|
return this.engines.includes(LogEntryEngines.RULE);
|
|
}
|
|
|
|
get isDictBased() {
|
|
return [LogEntryEngines.DICTIONARY, LogEntryEngines.DOSSIER_DICTIONARY].some(engine => this.engines.includes(engine));
|
|
}
|
|
|
|
get isRedactedImageHint() {
|
|
return this.IMAGE_HINT && this.superType === SuperTypes.Redaction;
|
|
}
|
|
|
|
get isSkippedImageHint() {
|
|
return this.IMAGE_HINT && this.superType === SuperTypes.Hint;
|
|
}
|
|
|
|
get searchKey(): string {
|
|
return this.id;
|
|
}
|
|
|
|
get descriptor() {
|
|
return this.isModifyDictionary ? _('dictionary') : _('type');
|
|
}
|
|
|
|
get hasTextAfter() {
|
|
return this.textAfter && this.textAfter.trim().length > 0;
|
|
}
|
|
|
|
get hasTextBefore() {
|
|
return this.textBefore && this.textBefore.trim().length > 0;
|
|
}
|
|
|
|
get canBeMarkedAsFalsePositive() {
|
|
return (
|
|
(this.isRecommendation ||
|
|
this.superType === SuperTypes.Redaction ||
|
|
(this.isSkipped && this.isDictBased) ||
|
|
(this.isRemovedLocally && this.isDictBased)) &&
|
|
!this.isImage &&
|
|
!this.imported &&
|
|
!this.pending &&
|
|
!this.hasBeenResized
|
|
);
|
|
}
|
|
|
|
get isSkipped() {
|
|
return this.superType === SuperTypes.Skipped;
|
|
}
|
|
|
|
get isImage() {
|
|
return this.type?.toLowerCase() === 'image' || this.IMAGE || this.IMAGE_HINT;
|
|
}
|
|
|
|
get isOCR() {
|
|
return this.type?.toLowerCase() === 'ocr';
|
|
}
|
|
|
|
get topLevelFilter() {
|
|
return !LowLevelFilterTypes[this.superType];
|
|
}
|
|
|
|
get filterKey() {
|
|
if (this.isEarmark) {
|
|
return this.color;
|
|
}
|
|
|
|
return this.topLevelFilter ? this.superType : this.superType + this.type;
|
|
}
|
|
|
|
get isFalsePositive() {
|
|
return this.type?.toLowerCase() === 'false_positive' && !!FalsePositiveSuperTypes[this.superType];
|
|
}
|
|
|
|
get isHint() {
|
|
return this.superType === SuperTypes.Hint || this.superType === SuperTypes.ManualHint;
|
|
}
|
|
|
|
get isIgnoredHint() {
|
|
return this.superType === SuperTypes.IgnoredHint;
|
|
}
|
|
|
|
get isEarmark() {
|
|
return this.superType === SuperTypes.TextHighlight;
|
|
}
|
|
|
|
get iconShape(): AnnotationIconType {
|
|
if (this.isRecommendation) {
|
|
return 'hexagon';
|
|
}
|
|
|
|
if (this.HINT) {
|
|
return 'circle';
|
|
}
|
|
|
|
return 'square';
|
|
}
|
|
|
|
get isRedacted() {
|
|
return this.superType === SuperTypes.Redaction || this.superType === SuperTypes.ManualRedaction;
|
|
}
|
|
|
|
get isModifyDictionary() {
|
|
return this.dictionaryOperation;
|
|
}
|
|
|
|
get hasRedactionChanges(): boolean {
|
|
return (
|
|
this.hasBeenResized ||
|
|
this.hasBeenRecategorized ||
|
|
this.hasLegalBasisChanged ||
|
|
this.hasBeenForcedHint ||
|
|
this.hasBeenForcedRedaction ||
|
|
this.hasBeenRemovedByManualOverride
|
|
);
|
|
}
|
|
|
|
get isRecommendation() {
|
|
return this.superType === SuperTypes.Recommendation;
|
|
}
|
|
|
|
get x() {
|
|
return this.firstTopLeftPoint.x;
|
|
}
|
|
|
|
get y() {
|
|
return this.firstTopLeftPoint.y;
|
|
}
|
|
|
|
get legalBasis() {
|
|
return this.legalBasisValue;
|
|
}
|
|
|
|
get width(): number {
|
|
return Math.floor(this.positions[0].width);
|
|
}
|
|
|
|
get height(): number {
|
|
return Math.floor(this.positions[0].height);
|
|
}
|
|
|
|
static fromEarmark(earmark: Earmark) {
|
|
const annotationWrapper = new AnnotationWrapper();
|
|
|
|
annotationWrapper.id = earmark.id;
|
|
annotationWrapper.pageNumber = earmark.positions[0].page;
|
|
annotationWrapper.superType = SuperTypes.TextHighlight;
|
|
annotationWrapper.type = SuperTypes.TextHighlight;
|
|
annotationWrapper.value = 'Imported';
|
|
annotationWrapper.color = earmark.hexColor;
|
|
annotationWrapper.positions = earmark.positions;
|
|
annotationWrapper.firstTopLeftPoint = earmark.positions[0]?.topLeft;
|
|
annotationWrapper.superTypeLabel = annotationTypesTranslations[annotationWrapper.superType];
|
|
|
|
return annotationWrapper;
|
|
}
|
|
|
|
static fromData(
|
|
logEntry: IEntityLogEntry,
|
|
dictionary: Dictionary,
|
|
changeLogType: ChangeType,
|
|
legalBasisList: ILegalBasis[],
|
|
isDocumine: boolean,
|
|
defaultColors: DefaultColors,
|
|
) {
|
|
const annotationWrapper = new AnnotationWrapper();
|
|
|
|
annotationWrapper.pending = logEntry.state === EntryStates.PENDING;
|
|
annotationWrapper.id = logEntry.id + (annotationWrapper.pending ? '-pending' : '');
|
|
annotationWrapper.isChangeLogEntry = logEntry.state === EntryStates.REMOVED || !!changeLogType;
|
|
annotationWrapper.type = logEntry.type;
|
|
annotationWrapper.value = logEntry.value;
|
|
annotationWrapper.firstTopLeftPoint = { x: logEntry.positions[0].rectangle[0], y: logEntry.positions[0].rectangle[1] };
|
|
annotationWrapper.pageNumber = logEntry.positions[0].pageNumber;
|
|
annotationWrapper.positions = logEntry.positions.map(p => ({
|
|
page: p.pageNumber,
|
|
height: p.rectangle[3],
|
|
width: p.rectangle[2],
|
|
topLeft: { x: p.rectangle[0], y: p.rectangle[1] },
|
|
}));
|
|
annotationWrapper.textBefore = logEntry.textBefore;
|
|
annotationWrapper.textAfter = logEntry.textAfter;
|
|
annotationWrapper.dictionaryOperation = logEntry.dictionaryEntry;
|
|
|
|
annotationWrapper.HINT = logEntry.entryType === EntityTypes.HINT;
|
|
annotationWrapper.IMAGE = logEntry.entryType === EntityTypes.IMAGE;
|
|
annotationWrapper.AREA = logEntry.entryType === EntityTypes.AREA;
|
|
annotationWrapper.IMAGE_HINT = logEntry.entryType === EntityTypes.IMAGE_HINT;
|
|
|
|
annotationWrapper.numberOfComments = logEntry.numberOfComments;
|
|
annotationWrapper.imported = logEntry.imported;
|
|
annotationWrapper.legalBasisValue = logEntry.legalBasis;
|
|
annotationWrapper.manual = logEntry.manualChanges?.length > 0;
|
|
annotationWrapper.engines = logEntry.engines ?? [];
|
|
annotationWrapper.section = logEntry.section;
|
|
annotationWrapper.reference = logEntry.reference || [];
|
|
annotationWrapper.hasBeenResized = !!logEntry.manualChanges?.find(c => c.manualRedactionType === ManualRedactionTypes.RESIZE);
|
|
annotationWrapper.hasBeenResizedLocally =
|
|
annotationWrapper.hasBeenResized && annotationWrapper.engines.includes(LogEntryEngines.MANUAL);
|
|
annotationWrapper.hasBeenRecategorized = !!logEntry.manualChanges?.find(
|
|
c => c.manualRedactionType === ManualRedactionTypes.RECATEGORIZE,
|
|
);
|
|
annotationWrapper.hasLegalBasisChanged = !!logEntry.manualChanges?.find(
|
|
c => c.manualRedactionType === ManualRedactionTypes.LEGAL_BASIS_CHANGE,
|
|
);
|
|
annotationWrapper.hasBeenForcedHint =
|
|
!!logEntry.manualChanges?.find(c => c.manualRedactionType === ManualRedactionTypes.FORCE) && annotationWrapper.HINT;
|
|
annotationWrapper.hasBeenForcedRedaction =
|
|
!!logEntry.manualChanges?.find(c => c.manualRedactionType === ManualRedactionTypes.FORCE) && !annotationWrapper.HINT;
|
|
annotationWrapper.hasBeenRemovedByManualOverride = !!logEntry.manualChanges?.find(
|
|
c => c.manualRedactionType === ManualRedactionTypes.REMOVE,
|
|
);
|
|
|
|
const content = this.#createContent(annotationWrapper, logEntry, isDocumine);
|
|
annotationWrapper.shortContent = this.#getShortContent(annotationWrapper, legalBasisList) || content.untranslatedContent;
|
|
annotationWrapper.content = content;
|
|
|
|
const lastRelevantManualChange = logEntry.manualChanges?.at(-1);
|
|
annotationWrapper.lastManualChange = lastRelevantManualChange?.manualRedactionType;
|
|
annotationWrapper.superType = SuperTypeMapper[logEntry.entryType][logEntry.state](logEntry);
|
|
annotationWrapper.superTypeLabel = annotationTypesTranslations[annotationWrapper.superType];
|
|
|
|
annotationWrapper.isRemoved = logEntry.state === EntryStates.REMOVED;
|
|
annotationWrapper.isRemovedLocally =
|
|
lastRelevantManualChange?.manualRedactionType === ManualRedactionTypes.REMOVE &&
|
|
logEntry.engines.includes(LogEntryEngines.MANUAL);
|
|
|
|
annotationWrapper.typeLabel = dictionary?.virtual ? undefined : dictionary?.label;
|
|
|
|
if (annotationWrapper.pending) {
|
|
annotationWrapper.color = defaultColors[annotationDefaultColorConfig.analysis] as string;
|
|
} else {
|
|
const colorKey = annotationEntityColorConfig[annotationWrapper.superType];
|
|
const defaultColor = annotationDefaultColorConfig[annotationWrapper.superType];
|
|
annotationWrapper.color =
|
|
dictionary && !annotationWrapper.isRedactedImageHint
|
|
? (dictionary[colorKey] as string)
|
|
: (defaultColors[defaultColor] as string);
|
|
}
|
|
|
|
annotationWrapper.entry = logEntry;
|
|
|
|
return annotationWrapper;
|
|
}
|
|
|
|
static #createContent(annotationWrapper: AnnotationWrapper, logEntry: IEntityLogEntry, isDocumine: boolean) {
|
|
let untranslatedContent = '';
|
|
const params: { [key: string]: string } = {};
|
|
if (logEntry.matchedRule) {
|
|
params['hasRule'] = 'true';
|
|
params['matchedRule'] = logEntry.matchedRule.replace(/(^[, ]*)|([, ]*$)/g, '');
|
|
params['ruleSymbol'] = isDocumine ? ':' : '';
|
|
|
|
untranslatedContent += `Rule ${logEntry.matchedRule} matched${isDocumine ? ':' : ''} \n\n`;
|
|
}
|
|
|
|
if (logEntry.reason) {
|
|
params['hasReason'] = 'true';
|
|
if (isDocumine && logEntry.reason.slice(-1) === '.') {
|
|
logEntry.reason = logEntry.reason.slice(0, -1);
|
|
}
|
|
if (!params['hasRule']) {
|
|
params['reason'] = logEntry.reason.substring(0, 1).toUpperCase() + logEntry.reason.substring(1);
|
|
} else {
|
|
params['reason'] = logEntry.reason;
|
|
}
|
|
params['reason'] = params['reason'].replace(/(^[, ]*)|([, ]*$)/g, '');
|
|
untranslatedContent += logEntry.reason + '\n\n';
|
|
//remove leading and trailing commas and whitespaces
|
|
untranslatedContent = untranslatedContent.replace(/(^[, ]*)|([, ]*$)/g, '');
|
|
untranslatedContent = untranslatedContent.substring(0, 1).toUpperCase() + untranslatedContent.substring(1);
|
|
}
|
|
|
|
if (annotationWrapper.legalBasis && !isDocumine) {
|
|
params['hasLb'] = 'true';
|
|
params['legalBasis'] = annotationWrapper.legalBasis;
|
|
untranslatedContent += 'Legal basis: ' + annotationWrapper.legalBasis + '\n\n';
|
|
}
|
|
|
|
if (annotationWrapper.hasBeenRemovedByManualOverride) {
|
|
params['hasOverride'] = 'true';
|
|
untranslatedContent += 'Removed by manual override';
|
|
}
|
|
|
|
if (logEntry.section) {
|
|
params['hasSection'] = 'true';
|
|
params['sectionSymbol'] = isDocumine ? '' : ':';
|
|
params['shouldLower'] = untranslatedContent.length.toString();
|
|
params['section'] = logEntry.section;
|
|
let prefix = `In section${isDocumine ? '' : ':'} `;
|
|
if (untranslatedContent.length) {
|
|
prefix = ` ${prefix.toLowerCase()}`;
|
|
}
|
|
untranslatedContent += `${prefix} "${logEntry.section}"`;
|
|
}
|
|
|
|
return { translation: _('annotation-content'), params: params, untranslatedContent: untranslatedContent };
|
|
}
|
|
|
|
static #getShortContent(annotationWrapper: AnnotationWrapper, legalBasisList: ILegalBasis[]) {
|
|
if (annotationWrapper.legalBasis) {
|
|
const lb = legalBasisList.find(lbm => lbm.reason?.toLowerCase().includes(annotationWrapper.legalBasis.toLowerCase()));
|
|
if (lb) {
|
|
return lb.name;
|
|
}
|
|
}
|
|
|
|
return annotationWrapper.legalBasis;
|
|
}
|
|
}
|