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

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;
}
}