483 lines
18 KiB
TypeScript
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;
|
|
}
|
|
}
|
|
}
|