red-ui/apps/red-ui/src/app/models/file/annotation.wrapper.ts

483 lines
18 KiB
TypeScript

import { annotationTypesTranslations } from '../../translations/annotation-types-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { IComment, IManualChange, ImportedRedaction, IPoint, IRectangle, LogEntryStatus, ManualRedactionType } from '@red/domain';
import { RedactionLogEntry } from '@models/file/redaction-log.entry';
import {
FalsePositiveSuperTypes,
LowLevelFilterTypes,
SuggestionAddSuperTypes,
SuggestionsSuperTypes,
SuperType,
SuperTypes,
} from '@models/file/super-types';
import { List } from '@iqser/common-ui';
export class AnnotationWrapper implements Record<string, unknown> {
[x: string]: unknown;
superType: SuperType;
typeValue: string;
recategorizationType: string;
color: string;
comments: IComment[] = [];
firstTopLeftPoint: IPoint;
annotationId: string;
shortContent: string;
content: string;
value: string;
typeLabel: string;
pageNumber: number;
hint: boolean;
redaction: boolean;
status: string;
dictionaryOperation: boolean;
positions: IRectangle[];
recommendationType: string;
legalBasisValue: string;
legalBasisChangeValue?: string;
resizing = false;
rectangle?: boolean;
section?: string;
reference: List;
imported?: boolean;
image?: boolean;
manual?: boolean;
hidden?: boolean;
pending?: boolean;
hintDictionary?: boolean;
textAfter?: string;
textBefore?: string;
isChangeLogEntry?: boolean;
changeLogType?: 'ADDED' | 'REMOVED' | 'CHANGED';
engines?: string[];
hasBeenResized: boolean;
hasBeenRecategorized: boolean;
hasLegalBasisChanged: boolean;
hasBeenForcedHint: boolean;
hasBeenForcedRedaction: boolean;
hasBeenRemovedByManualOverride: boolean;
get isChangeLogRemoved() {
return this.changeLogType === 'REMOVED';
}
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.isImage;
}
get isSuperTypeBasedColor() {
return this.isSkipped || this.isSuggestion || this.isDeclinedSuggestion || this.isIgnoredHint;
}
get isSkipped() {
return this.superType === SuperTypes.Skipped;
}
get isImage() {
return this.type?.toLowerCase() === 'image' || this.image;
}
get isOCR() {
return this.type?.toLowerCase() === 'ocr';
}
get type() {
return this.recategorizationType || this.typeValue;
}
get topLevelFilter() {
return !LowLevelFilterTypes[this.superType];
}
get filterKey() {
if (this.isHighlight) {
return this.color;
}
return this.topLevelFilter ? this.superType : this.superType + this.type;
}
get isFalsePositive() {
return this.type?.toLowerCase() === 'false_positive' && !!FalsePositiveSuperTypes[this.superType];
}
get isDeclinedSuggestion() {
return this.superType === SuperTypes.DeclinedSuggestion;
}
get isApproved() {
return this.status === 'APPROVED';
}
get isHint() {
return this.superType === SuperTypes.Hint;
}
get isHighlight() {
return this.superType === SuperTypes.TextHighlight;
}
get isIgnoredHint() {
return this.superType === SuperTypes.IgnoredHint;
}
get isRedacted() {
return this.superType === SuperTypes.Redaction || this.superType === SuperTypes.ManualRedaction;
}
get isSuggestion() {
return !!SuggestionsSuperTypes[this.superType];
}
get isSuggestionResize() {
return this.superType === SuperTypes.SuggestionResize;
}
get isSuggestionRecategorizeImage() {
return this.superType === SuperTypes.SuggestionRecategorizeImage;
}
get isSuggestionAdd() {
return !!SuggestionAddSuperTypes[this.superType];
}
get isSuggestionRemove() {
return this.superType === SuperTypes.SuggestionRemove || this.superType === SuperTypes.SuggestionRemoveDictionary;
}
get isSuggestionLegalBasisChange() {
return this.superType === SuperTypes.SuggestionChangeLegalBasis;
}
get isModifyDictionary() {
return this.dictionaryOperation;
}
get hasRedactionChanges(): boolean {
return (
this.hasBeenResized ||
this.hasBeenRecategorized ||
this.hasLegalBasisChanged ||
this.hasBeenForcedHint ||
this.hasBeenForcedRedaction ||
this.hasBeenRemovedByManualOverride
);
}
get isConvertedRecommendation() {
return this.isRecommendation && this.superType === SuperTypes.SuggestionAddDictionary;
}
get isRecommendation() {
return this.superType === SuperTypes.Recommendation;
}
get id() {
return this.annotationId;
}
get x() {
return this.firstTopLeftPoint.x;
}
get y() {
return this.firstTopLeftPoint.y;
}
get legalBasis() {
return this.legalBasisChangeValue || this.legalBasisValue;
}
get width(): number {
return Math.floor(this.positions[0].width);
}
get height(): number {
return Math.floor(this.positions[0].height);
}
get previewAnnotation() {
return (
this.isRedacted ||
this.isSuggestionAdd ||
this.isSuggestionResize ||
this.isSuggestionLegalBasisChange ||
this.isSuggestionRecategorizeImage
);
}
static fromHighlight(color: string, entry: ImportedRedaction) {
const annotationWrapper = new AnnotationWrapper();
annotationWrapper.annotationId = entry.id;
annotationWrapper.pageNumber = entry.positions[0].page;
annotationWrapper.superType = SuperTypes.TextHighlight;
annotationWrapper.typeValue = SuperTypes.TextHighlight;
annotationWrapper.value = 'Imported';
annotationWrapper.color = color;
annotationWrapper.positions = entry.positions;
annotationWrapper.firstTopLeftPoint = entry.positions[0]?.topLeft;
annotationWrapper.typeLabel = annotationTypesTranslations[annotationWrapper.superType];
return annotationWrapper;
}
static fromData(redactionLogEntry?: RedactionLogEntry) {
const annotationWrapper = new AnnotationWrapper();
annotationWrapper.annotationId = redactionLogEntry.id;
annotationWrapper.isChangeLogEntry = redactionLogEntry.isChangeLogEntry;
annotationWrapper.changeLogType = redactionLogEntry.changeLogType;
annotationWrapper.redaction = redactionLogEntry.redacted;
annotationWrapper.hint = redactionLogEntry.hint;
annotationWrapper.typeValue = redactionLogEntry.type;
annotationWrapper.value = redactionLogEntry.value;
annotationWrapper.firstTopLeftPoint = redactionLogEntry.positions[0]?.topLeft;
annotationWrapper.pageNumber = redactionLogEntry.positions[0]?.page;
annotationWrapper.positions = redactionLogEntry.positions;
annotationWrapper.textBefore = redactionLogEntry.textBefore;
annotationWrapper.textAfter = redactionLogEntry.textAfter;
annotationWrapper.dictionaryOperation = redactionLogEntry.dictionaryEntry;
annotationWrapper.image = redactionLogEntry.image;
annotationWrapper.imported = redactionLogEntry.imported;
annotationWrapper.legalBasisValue = redactionLogEntry.legalBasis;
annotationWrapper.comments = redactionLogEntry.comments || [];
annotationWrapper.manual = redactionLogEntry.manualChanges?.length > 0;
annotationWrapper.engines = redactionLogEntry.engines;
annotationWrapper.section = redactionLogEntry.section;
annotationWrapper.reference = redactionLogEntry.reference || [];
annotationWrapper.rectangle = redactionLogEntry.rectangle;
annotationWrapper.hintDictionary = redactionLogEntry.hintDictionary;
annotationWrapper.hasBeenResized = !!redactionLogEntry.manualChanges?.find(
c => c.manualRedactionType === ManualRedactionType.RESIZE && c.annotationStatus === LogEntryStatus.APPROVED,
);
annotationWrapper.hasBeenRecategorized = !!redactionLogEntry.manualChanges?.find(
c => c.manualRedactionType === ManualRedactionType.RECATEGORIZE && c.annotationStatus === LogEntryStatus.APPROVED,
);
annotationWrapper.hasLegalBasisChanged = !!redactionLogEntry.manualChanges?.find(
c => c.manualRedactionType === ManualRedactionType.LEGAL_BASIS_CHANGE && c.annotationStatus === LogEntryStatus.APPROVED,
);
annotationWrapper.hasBeenForcedHint = !!redactionLogEntry.manualChanges?.find(
c => c.manualRedactionType === ManualRedactionType.FORCE_HINT && c.annotationStatus === LogEntryStatus.APPROVED,
);
annotationWrapper.hasBeenForcedRedaction = !!redactionLogEntry.manualChanges?.find(
c => c.manualRedactionType === ManualRedactionType.FORCE_REDACT && c.annotationStatus === LogEntryStatus.APPROVED,
);
annotationWrapper.hasBeenRemovedByManualOverride = !!redactionLogEntry.manualChanges?.find(
c => c.manualRedactionType === ManualRedactionType.REMOVE_LOCALLY && c.annotationStatus === LogEntryStatus.APPROVED,
);
this._createContent(annotationWrapper, redactionLogEntry);
this._setSuperType(annotationWrapper, redactionLogEntry);
this._handleRecommendations(annotationWrapper, redactionLogEntry);
annotationWrapper.typeLabel = annotationTypesTranslations[annotationWrapper.superType];
return annotationWrapper;
}
private static _handleRecommendations(annotationWrapper: AnnotationWrapper, redactionLogEntry: RedactionLogEntry) {
if (annotationWrapper.superType === SuperTypes.Recommendation) {
annotationWrapper.recommendationType = redactionLogEntry.type.substr('recommendation_'.length);
}
}
private static _setSuperType(annotationWrapper: AnnotationWrapper, redactionLogEntryWrapper: RedactionLogEntry) {
if (redactionLogEntryWrapper.recommendation && !redactionLogEntryWrapper.redacted) {
annotationWrapper.superType = SuperTypes.Recommendation;
return;
}
if (redactionLogEntryWrapper.manualChanges?.length) {
const lastManualChange = redactionLogEntryWrapper.manualChanges[redactionLogEntryWrapper.manualChanges.length - 1];
annotationWrapper.pending = !lastManualChange.processed;
annotationWrapper.superType = AnnotationWrapper._selectSuperType(
redactionLogEntryWrapper,
lastManualChange,
annotationWrapper.hintDictionary,
);
if (lastManualChange.annotationStatus === LogEntryStatus.REQUESTED) {
annotationWrapper.recategorizationType = lastManualChange.propertyChanges.type;
}
} else {
if (redactionLogEntryWrapper.recommendation) {
annotationWrapper.superType = SuperTypes.Recommendation;
} else if (redactionLogEntryWrapper.redacted) {
annotationWrapper.superType = SuperTypes.Redaction;
} else if (redactionLogEntryWrapper.hint) {
annotationWrapper.superType = SuperTypes.Hint;
} else {
annotationWrapper.superType = SuperTypes.Skipped;
}
}
}
private static _createContent(annotationWrapper: AnnotationWrapper, entry: RedactionLogEntry) {
let content = '';
if (entry.matchedRule) {
content += `Rule ${entry.matchedRule} matched \n\n`;
}
if (entry.reason) {
content += entry.reason + '\n\n';
//remove leading and trailing commas and whitespaces
content = content.replace(/^[, ]*|[, ]*$/g, '');
content = content.substring(0, 1).toUpperCase() + content.substring(1);
}
if (annotationWrapper.legalBasis) {
content += 'Legal basis: ' + annotationWrapper.legalBasis + '\n\n';
}
if (entry.section) {
content += 'In section: "' + entry.section + '"';
}
annotationWrapper.shortContent = this._getShortContent(annotationWrapper, entry) || content;
annotationWrapper.content = content;
}
private static _getShortContent(annotationWrapper: AnnotationWrapper, entry: RedactionLogEntry) {
if (annotationWrapper.legalBasis) {
const lb = entry.legalBasisList?.find(lbm => lbm.reason.toLowerCase().includes(annotationWrapper.legalBasis.toLowerCase()));
if (lb) {
return lb.name;
}
}
return annotationWrapper.legalBasis;
}
private static _selectSuperType(
redactionLogEntry: RedactionLogEntry,
lastManualChange: IManualChange,
isHintDictionary: boolean,
): SuperType {
switch (lastManualChange.manualRedactionType) {
case ManualRedactionType.ADD_LOCALLY:
switch (lastManualChange.annotationStatus) {
case LogEntryStatus.APPROVED:
return SuperTypes.ManualRedaction;
case LogEntryStatus.DECLINED:
return SuperTypes.DeclinedSuggestion;
case LogEntryStatus.REQUESTED:
return SuperTypes.SuggestionAdd;
}
break;
case ManualRedactionType.ADD_TO_DICTIONARY:
switch (lastManualChange.annotationStatus) {
case LogEntryStatus.APPROVED:
return isHintDictionary ? SuperTypes.Hint : SuperTypes.Redaction;
case LogEntryStatus.DECLINED:
return SuperTypes.DeclinedSuggestion;
case LogEntryStatus.REQUESTED:
return SuperTypes.SuggestionAddDictionary;
}
break;
case ManualRedactionType.REMOVE_LOCALLY:
switch (lastManualChange.annotationStatus) {
case LogEntryStatus.APPROVED:
return isHintDictionary ? SuperTypes.IgnoredHint : SuperTypes.Skipped;
case LogEntryStatus.DECLINED:
return isHintDictionary ? SuperTypes.Hint : redactionLogEntry.redacted ? SuperTypes.Redaction : SuperTypes.Skipped;
case LogEntryStatus.REQUESTED:
return SuperTypes.SuggestionRemove;
}
break;
case ManualRedactionType.REMOVE_FROM_DICTIONARY:
switch (lastManualChange.annotationStatus) {
case LogEntryStatus.APPROVED:
return SuperTypes.Skipped;
case LogEntryStatus.DECLINED:
return SuperTypes.Redaction;
case LogEntryStatus.REQUESTED:
return SuperTypes.SuggestionRemoveDictionary;
}
break;
case ManualRedactionType.FORCE_REDACT:
switch (lastManualChange.annotationStatus) {
case LogEntryStatus.APPROVED:
return SuperTypes.Redaction;
case LogEntryStatus.DECLINED:
return SuperTypes.Skipped;
case LogEntryStatus.REQUESTED:
return SuperTypes.SuggestionForceRedaction;
}
break;
case ManualRedactionType.FORCE_HINT:
switch (lastManualChange.annotationStatus) {
case LogEntryStatus.APPROVED:
return SuperTypes.Hint;
case LogEntryStatus.DECLINED:
return SuperTypes.IgnoredHint;
case LogEntryStatus.REQUESTED:
return SuperTypes.SuggestionForceHint;
}
break;
case ManualRedactionType.RECATEGORIZE:
switch (lastManualChange.annotationStatus) {
case LogEntryStatus.APPROVED:
case LogEntryStatus.DECLINED: {
if (redactionLogEntry.recommendation) {
return SuperTypes.Recommendation;
} else if (redactionLogEntry.redacted) {
return SuperTypes.Redaction;
} else if (redactionLogEntry.hint) {
return SuperTypes.Hint;
} else {
return SuperTypes.Skipped;
}
}
case LogEntryStatus.REQUESTED:
return SuperTypes.SuggestionRecategorizeImage;
}
break;
case ManualRedactionType.LEGAL_BASIS_CHANGE:
switch (lastManualChange.annotationStatus) {
case LogEntryStatus.APPROVED:
case LogEntryStatus.DECLINED:
return redactionLogEntry.type === 'manual' ? SuperTypes.ManualRedaction : SuperTypes.Redaction;
case LogEntryStatus.REQUESTED:
return SuperTypes.SuggestionChangeLegalBasis;
}
break;
case ManualRedactionType.RESIZE:
switch (lastManualChange.annotationStatus) {
case LogEntryStatus.APPROVED:
case LogEntryStatus.DECLINED:
if (redactionLogEntry.recommendation) {
return SuperTypes.Recommendation;
} else if (redactionLogEntry.redacted) {
return redactionLogEntry.type === 'manual' ? SuperTypes.ManualRedaction : SuperTypes.Redaction;
} else if (redactionLogEntry.hint) {
return SuperTypes.Hint;
} else {
return SuperTypes.Skipped;
}
case LogEntryStatus.REQUESTED:
return SuperTypes.SuggestionResize;
}
break;
}
}
}