"""Run existing checks with private saves and trustworthy, compact outcomes."""
from __future__ import annotations

from collections import Counter
import json
import os
from pathlib import Path
import re
import shutil
import subprocess
import sys
import time

from common import (PROJECT, DEV, RUNS, changed_files, digest, executable, git,
                    isolated_environment, new_run, now, read_json, source_state,
                    state_digest, write_json)

RUNNER_VERSION = 1
GODOT = {
    'gameplay': ('smoke_test', 'SMOKE TEST PASSED:', []),
    'visuals': ('bridge_3d_check', 'PONT 3D OK:', []),
    'flow': ('bridge_3d_flow_check', 'PARCOURS 3D OK:', []),
    'debug_progression': ('debug_progression_check', 'DEBUG PROGRESSION: PASSED', []),
    'return': ('restaurant_return_check', 'RESTAURANT RETURN: PASSED', []),
    'restaurant_live': ('restaurant_live_check', 'LIVE RESTAURANT: PASSED', []),
    'sidewalk': ('sidewalk_exit_check', 'SIDEWALK EXIT: PASSED', []),
    'construction': ('construction_lifecycle_check', 'CONSTRUCTION LIFECYCLE: PASSED', []),
    'wander': ('wander_wall_check', 'WANDER WALL CHECK: OK', []),
    'enemy_body': ('enemy_body_check', 'ENEMY BODY CHECK: OK', []),
    'dive_bag': ('dive_bag_check', 'DIVE BAG CHECK: OK', []),
    'salary': ('salary_check', 'SALARY CHECK: OK', []),
    'brigade_order': ('brigade_order_check', 'BRIGADE ORDER CHECK: OK', []),
    'service_gestures': ('service_gestures_check', 'SERVICE GESTURES CHECK: OK', []),
    'key_items': ('key_items_check', 'KEY ITEMS CHECK: OK', []),
    'loot': ('loot_check', 'LOOT CHECK: OK', []),
    'abilities': ('abilities_check', 'ABILITIES CHECK: OK', []),
    'dialogue': ('dialogue_check', 'DIALOGUE CHECK: OK', []),
    'quests': ('quests_check', 'QUESTS CHECK: OK', []),
    'damage_types': ('damage_types_check', 'DAMAGE TYPES CHECK: OK', []),
    'story_tools': ('story_tools_check', 'STORY TOOLS CHECK: OK', []),
    'bob_quest': ('bob_quest_check', 'BOB QUEST CHECK: OK', []),
    'd1_story': ('d1_story_check', 'D1 STORY CHECK: OK', []),
    'fake_ending': ('fake_ending_check', 'FAKE ENDING CHECK: OK', []),
    'intro_comic': ('intro_comic_check', 'INTRO CHECK: OK', []),
    'd2_story': ('d2_story_check', 'D2 STORY CHECK: OK', []),
    'd3_story': ('d3_story_check', 'D3 STORY CHECK: OK', []),
    'dimension_finale': ('dimension_finale_check', 'DIMENSION FINALE CHECK: OK', []),
    'd4_story': ('d4_story_check', 'D4 STORY CHECK: OK', []),
    'input_scheme': ('input_scheme_check', 'INPUT SCHEME CHECK: OK', []),
    'scene_reveal': ('scene_reveal_check', 'SCENE REVEAL CHECK: OK', []),
    'freed_instances': ('freed_instances_check', 'FREED INSTANCES CHECK: OK', []),
    'menu_selection': ('menu_selection_check', 'MENU SELECTION CHECK: OK', []),
    'passages': ('passages_check', 'PASSAGES CHECK: OK', []),
    'network': ('network_check', 'NETWORK CHECK: OK', []),
    'place_transitions': ('place_transitions_check', 'PLACE TRANSITIONS CHECK: OK', []),
    'd2_engine': ('d2_engine_check', 'D2 ENGINE CHECK: OK', []),
    'd3_engine': ('d3_engine_check', 'D3 ENGINE CHECK: OK', []),
    'd4_engine': ('d4_engine_check', 'D4 ENGINE CHECK: OK', []),
    'relief': ('relief_check', 'RELIEF CHECK: OK', []),
    'audio': ('audio_check', 'AUDIO OK:', []),
    'movement': ('movement_program_check', 'MOVEMENT PROGRAM: PASSED', []),
    'stamina': ('room_stamina_check', 'ROOM STAMINA: PASSED', []),
    'contact': ('zero_hit_number_check', 'ZERO HIT NUMBERS: PASSED', []),
    'recipes': ('recipe_fallback_check', 'RECIPE FALLBACK: PASSED', []),
    'boss_bar': ('boss_bar_check', 'BOSS BAR CHECK: OK', []),
    'combat_presentation': ('combat_presentation_check', 'COMBAT PRESENTATION CHECK: OK', []),
    # Ligne 81: l'entree des renforts et des invocations, du maitre au pont.
    'entrance': ('entrance_check', 'ENTRANCE CHECK: OK', []),
    'behavior_effects': ('behavior_effects_check', 'BEHAVIOR EFFECTS CHECK: OK', []),
    # Ligne 71: le banc d'essai des armes, en pas fixe pour aller plus vite que le temps reel.
    'weapons_curve': ('weapons_curve_check', 'WEAPONS CURVE CHECK: OK', ['--fixed-fps', '60']),
    # Ligne 71, etape 9: les capacites d'equipement qui remplacent le dash, par la vraie touche.
    'dash_abilities': ('dash_abilities_check', 'DASH ABILITIES CHECK: OK', ['--fixed-fps', '60']),
    # This old check frees its probe on success rather than quitting the game.
    'persistence': ('upgrade_return_smoke', 'RETOUR: la cuisson a survécu', ['--quit-after', '600']),
}
AREAS = list(GODOT) + ['manager', 'shapes', 'tooling']
ERROR_LINE = re.compile(
    r'^(?:SCRIPT ERROR:|ERROR:|Parse Error:|ECHEC\b|ÉCHEC\b|FAIL(?:ED)?\b|'
    r'SMOKE TEST FAILED|AssertionError|Error:|TypeError:|ReferenceError:|SyntaxError:|'
    r'PHP (?:Fatal|Parse) error:|Fatal error:|Parse error:|Traceback \(most recent call last\):)'
    r'|^.*(?:PONT 3D|PARCOURS 3D) ÉCHEC:', re.MULTILINE)
ANSI = re.compile(r'\x1b\[[0-9;]*m')


def outcome(output, code, timed_out, success=None):
    output = ANSI.sub('', output)
    errors = [line.strip() for line in output.splitlines() if ERROR_LINE.search(line.strip())]
    if timed_out:
        errors.append('RUNNER: timeout; check did not finish')
    if code != 0:
        errors.append(f'RUNNER: exit code {code}')
    if success and success not in output:
        errors.append('RUNNER: required completion marker missing')
    return ('FAIL' if errors else 'PASS'), errors


def execute(command, environment, log, timeout, success=None):
    start = time.monotonic()
    timed_out = False
    # File output avoids pipe deadlocks and keeps large logs out of agent context.
    with Path(log).open('wb') as stream:
        process = subprocess.Popen(command, cwd=PROJECT, env=environment,
                                   stdout=stream, stderr=subprocess.STDOUT)
        try:
            code = process.wait(timeout=timeout)
        except subprocess.TimeoutExpired:
            timed_out = True
            if os.name == 'nt':
                subprocess.run(['taskkill', '/PID', str(process.pid), '/T', '/F'],
                               stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            else:
                process.kill()
            code = process.wait()
    output = Path(log).read_text(encoding='utf-8', errors='replace')
    status, errors = outcome(output, code, timed_out, success)
    return {'status': status, 'exit_code': code, 'timed_out': timed_out,
            'seconds': round(time.monotonic() - start, 2), 'errors': errors,
            'log': str(Path(log).relative_to(PROJECT)), 'command': command}


def select_checks(areas, full=False, match=None):
    areas = AREAS if full else areas
    selected = []
    for area in dict.fromkeys(areas):
        if area in GODOT:
            scene, success, extra = GODOT[area]
            selected.append({'id': area, 'kind': 'godot', 'scene': scene,
                             'success': success, 'extra': extra})
            if area == 'flow' and 'return' not in areas:
                selected.append({'id': 'return', 'kind': 'godot',
                                 'scene': 'restaurant_return_check',
                                 'success': 'RESTAURANT RETURN: PASSED', 'extra': []})
            if area == 'gameplay':
                if 'construction' not in areas:
                    selected.append({'id': 'construction', 'kind': 'godot',
                                     'scene': 'construction_lifecycle_check',
                                     'success': 'CONSTRUCTION LIFECYCLE: PASSED', 'extra': []})
                if 'sidewalk' not in areas:
                    selected.append({'id': 'sidewalk', 'kind': 'godot',
                                     'scene': 'sidewalk_exit_check',
                                     'success': 'SIDEWALK EXIT: PASSED', 'extra': []})
                if 'restaurant_live' not in areas:
                    selected.append({'id': 'restaurant_live', 'kind': 'godot',
                                     'scene': 'restaurant_live_check',
                                     'success': 'LIVE RESTAURANT: PASSED', 'extra': []})
                if 'wander' not in areas:
                    selected.append({'id': 'wander', 'kind': 'godot',
                                     'scene': 'wander_wall_check',
                                     'success': 'WANDER WALL CHECK: OK', 'extra': []})
                if 'enemy_body' not in areas:
                    selected.append({'id': 'enemy_body', 'kind': 'godot',
                                     'scene': 'enemy_body_check',
                                     'success': 'ENEMY BODY CHECK: OK', 'extra': []})
                if 'dive_bag' not in areas:
                    selected.append({'id': 'dive_bag', 'kind': 'godot',
                                     'scene': 'dive_bag_check',
                                     'success': 'DIVE BAG CHECK: OK', 'extra': []})
                if 'salary' not in areas:
                    selected.append({'id': 'salary', 'kind': 'godot',
                                     'scene': 'salary_check',
                                     'success': 'SALARY CHECK: OK', 'extra': []})
                if 'brigade_order' not in areas:
                    selected.append({'id': 'brigade_order', 'kind': 'godot',
                                     'scene': 'brigade_order_check',
                                     'success': 'BRIGADE ORDER CHECK: OK', 'extra': []})
                if 'service_gestures' not in areas:
                    selected.append({'id': 'service_gestures', 'kind': 'godot',
                                     'scene': 'service_gestures_check',
                                     'success': 'SERVICE GESTURES CHECK: OK', 'extra': []})
                if 'key_items' not in areas:
                    selected.append({'id': 'key_items', 'kind': 'godot',
                                     'scene': 'key_items_check',
                                     'success': 'KEY ITEMS CHECK: OK', 'extra': []})
                if 'loot' not in areas:
                    selected.append({'id': 'loot', 'kind': 'godot',
                                     'scene': 'loot_check',
                                     'success': 'LOOT CHECK: OK', 'extra': []})
                if 'abilities' not in areas:
                    selected.append({'id': 'abilities', 'kind': 'godot',
                                     'scene': 'abilities_check',
                                     'success': 'ABILITIES CHECK: OK', 'extra': []})
                if 'dialogue' not in areas:
                    selected.append({'id': 'dialogue', 'kind': 'godot',
                                     'scene': 'dialogue_check',
                                     'success': 'DIALOGUE CHECK: OK', 'extra': []})
                if 'quests' not in areas:
                    selected.append({'id': 'quests', 'kind': 'godot',
                                     'scene': 'quests_check',
                                     'success': 'QUESTS CHECK: OK', 'extra': []})
                if 'damage_types' not in areas:
                    selected.append({'id': 'damage_types', 'kind': 'godot',
                                     'scene': 'damage_types_check',
                                     'success': 'DAMAGE TYPES CHECK: OK', 'extra': []})
                if 'story_tools' not in areas:
                    selected.append({'id': 'story_tools', 'kind': 'godot',
                                     'scene': 'story_tools_check',
                                     'success': 'STORY TOOLS CHECK: OK', 'extra': []})
                if 'bob_quest' not in areas:
                    selected.append({'id': 'bob_quest', 'kind': 'godot',
                                     'scene': 'bob_quest_check',
                                     'success': 'BOB QUEST CHECK: OK', 'extra': []})
                if 'passages' not in areas:
                    selected.append({'id': 'passages', 'kind': 'godot',
                                     'scene': 'passages_check',
                                     'success': 'PASSAGES CHECK: OK', 'extra': []})
                if 'network' not in areas:
                    selected.append({'id': 'network', 'kind': 'godot',
                                     'scene': 'network_check',
                                     'success': 'NETWORK CHECK: OK', 'extra': []})
                if 'place_transitions' not in areas:
                    selected.append({'id': 'place_transitions', 'kind': 'godot',
                                     'scene': 'place_transitions_check',
                                     'success': 'PLACE TRANSITIONS CHECK: OK', 'extra': []})
                if 'd2_engine' not in areas:
                    selected.append({'id': 'd2_engine', 'kind': 'godot',
                                     'scene': 'd2_engine_check',
                                     'success': 'D2 ENGINE CHECK: OK', 'extra': []})
                if 'd3_engine' not in areas:
                    selected.append({'id': 'd3_engine', 'kind': 'godot',
                                     'scene': 'd3_engine_check',
                                     'success': 'D3 ENGINE CHECK: OK', 'extra': []})
                if 'd4_engine' not in areas:
                    selected.append({'id': 'd4_engine', 'kind': 'godot',
                                     'scene': 'd4_engine_check',
                                     'success': 'D4 ENGINE CHECK: OK', 'extra': []})
                if 'relief' not in areas:
                    selected.append({'id': 'relief', 'kind': 'godot',
                                     'scene': 'relief_check',
                                     'success': 'RELIEF CHECK: OK', 'extra': []})
                if 'entrance' not in areas:
                    selected.append({'id': 'entrance', 'kind': 'godot',
                                     'scene': 'entrance_check',
                                     'success': 'ENTRANCE CHECK: OK', 'extra': []})
                # Le smoke test porte 51 echecs connus: une garantie neuve y
                # serait invisible, noyee dans une ligne de base qu'on ne lit
                # plus. Le souffle a donc sa propre scene, ou un echec est un
                # echec.
                selected.append({'id': 'explosion', 'kind': 'godot',
                                 'scene': 'explosion_check', 'success': 'EXPLOSION OK: PASSED',
                                 'extra': []})
                # Cette scene existait sans jamais tourner automatiquement: la
                # visee, l'ancrage et le choix de cible n'etaient donc gardes
                # que par la main qui pensait a la lancer.
                selected.append({'id': 'targeting', 'kind': 'godot',
                                 'scene': 'attack_targeting_check',
                                 'success': 'ATTACK TARGETING: PASSED', 'extra': []})
                # L'orchestrateur de phases s'eprouve SANS donjon: on lui pousse
                # des deltas et on lit la phase demandee. Boucles infinies,
                # seuils de PV et grands pas de temps se testent ainsi en une
                # seconde et demie, la ou un vrai combat en prendrait vingt.
                selected.append({'id': 'encounter', 'kind': 'godot',
                                 'scene': 'encounter_check',
                                 'success': 'RENCONTRE OK: PASSED', 'extra': []})
                # La console du bac a sable n'etait couverte par RIEN: ni parse,
                # ni execution. Une faute de frappe dans un de ses onglets ne se
                # voyait qu'en ouvrant le bac a sable en jeu, donc jamais par
                # celui qui venait de l'ecrire.
                selected.append({'id': 'sandbox_console', 'kind': 'godot',
                                 'scene': 'sandbox_console_check',
                                 'success': 'BAC A SABLE OK: PASSED', 'extra': []})
                # Les simplifications du restaurant (23/09/2026). Le smoke eprouve
                # l'ancienne mecanique interrupteurs baisses; celle-ci eprouve la
                # nouvelle, celle que le joueur rencontre.
                selected.append({'id': 'restaurant_simplification', 'kind': 'godot',
                                 'scene': 'restaurant_simplification_check',
                                 'success': 'SIMPLIFICATION OK: PASSED', 'extra': []})
                # Ce que le HUD dit de l'urgence: barres colorees, vie critique,
                # ardoise coloree par patience, poison qui reste en plongee.
                selected.append({'id': 'hud_feedback', 'kind': 'godot',
                                 'scene': 'hud_feedback_check',
                                 'success': 'HUD OK: PASSED', 'extra': []})
                # La refonte des employes (23/09/2026): une seule classe, des
                # affinites et des vitesses par tache, des competences passives.
                # Le smoke garde ses 45 echecs connus; ici, un echec est un echec.
                selected.append({'id': 'employees', 'kind': 'godot',
                                 'scene': 'employee_revolution_check',
                                 'success': 'EMPLOYES OK: PASSED', 'extra': []})
                # La salle sans le joueur et le savoir du heros (23/09/2026):
                # clients decus hors ecran, alerte de reputation, fermeture par
                # la brigade, niveau des plats servi de la main seulement.
                selected.append({'id': 'restaurant_absence', 'kind': 'godot',
                                 'scene': 'restaurant_absence_check',
                                 'success': 'ABSENCE OK: PASSED', 'extra': []})
            if area == 'audio':
                # Un evenement configure mais qu'aucun script n'emet est une
                # panne SILENCIEUSE: la page de config le montre rempli, la
                # publication l'accepte, et le jeu se tait. La scene Godot ne
                # peut pas le voir — seule une relecture croisee du code et des
                # donnees l'attrape. Elle accompagne donc la zone audio.
                selected.append({'id': 'audio_wiring', 'kind': 'python',
                                 'path': 'tools/audio_wiring_audit.py',
                                 'success': 'MUET (configure, jamais emis) : 0'})
        elif area == 'manager':
            for path in sorted((PROJECT / 'tools/game-data-manager/tests').glob('*')):
                # This directory also holds migrations and seed_ui_texts.js,
                # which write the live master/draft. They are not checks.
                if not path.stem.endswith('_test'):
                    continue
                if path.suffix not in {'.js', '.php'} or (match and match not in path.name):
                    continue
                selected.append({'id': path.stem, 'kind': 'node' if path.suffix == '.js' else 'php',
                                 'path': path.relative_to(PROJECT).as_posix()})
        elif area == 'shapes':
            selected.append({'id': 'shapes', 'kind': 'php',
                             'path': 'tools/shapes-3d-manager/tests/mirror_test.php'})
        elif area == 'tooling':
            selected.append({'id': 'tooling', 'kind': 'python',
                             'path': 'tools/dev/test_dev.py', 'success': '\nOK'})
    if not selected:
        raise ValueError('No checks matched; this is not a successful validation')
    return selected


def fixture_digest(path):
    if not path:
        return 'empty-user-directory-v1'
    folder = Path(path).resolve()
    if not folder.is_dir():
        raise ValueError('Fixture must be a directory containing a user:// layout')
    records = {}
    for item in sorted(folder.rglob('*')):
        if item.is_symlink():
            raise ValueError('Fixtures must not contain symbolic links')
        if item.is_file():
            records[item.relative_to(folder).as_posix()] = digest(item.read_bytes())
    return state_digest(records)


def prepare_godot(run, environment, fixture, timeout):
    if os.name != 'nt':
        raise ValueError('Godot save isolation is verified on Windows only; configure a platform adapter first')
    godot = executable('godot')
    environment['BIGEGG_DEV_SCENE'] = ''
    probe = execute([godot, '--headless', '--path', str(PROJECT),
                     '--script', str(DEV / 'run_scene.gd')], environment,
                    run / 'isolation.log', min(timeout, 30), 'DEV ISOLATION OK')
    if probe['status'] != 'PASS':
        raise ValueError(f'Save isolation probe failed; see {probe["log"]}')
    text = (run / 'isolation.log').read_text(encoding='utf-8', errors='replace')
    match = re.search(r'^DEV_USER_DIR: (.+)$', text, re.MULTILINE)
    if not match:
        raise ValueError('Isolation probe returned no user directory')
    user = Path(match.group(1).strip()).resolve()
    if not user.is_relative_to(run.resolve()):
        raise ValueError('Godot user directory escaped the isolated run')
    # The probe can initialize settings. Remove only its verified private data.
    if user.exists():
        shutil.rmtree(user)
    user.mkdir(parents=True)
    if fixture:
        shutil.copytree(Path(fixture), user, dirs_exist_ok=True)
    return godot, user


def compare_reports(baseline, report):
    if baseline['conditions'] != report['conditions']:
        return {'comparable': False, 'reason': 'Selection, seed, fixture, tools or runner changed'}
    def records(value):
        return Counter((entry['id'], error) for entry in value['checks'] for error in entry['errors'])
    old, new = records(baseline), records(report)
    def describe(items):
        return [{'check': check, 'error': error, 'count': count}
                for (check, error), count in sorted(items.items())]
    return {'comparable': True, 'new': describe(new - old), 'resolved': describe(old - new),
            'unchanged_count': sum((old & new).values())}


def run_checks(args):
    selected = select_checks(args.area or [], args.full, args.match)
    if args.plan:
        print(json.dumps({'checks': [item['id'] for item in selected],
                          'count': len(selected)}, ensure_ascii=False))
        return 0
    run = new_run('checks')
    before = source_state()
    versions = {}
    for kind in sorted({item['kind'] for item in selected}):
        try:
            program = sys.executable if kind == 'python' else executable(kind)
            version = subprocess.run([program, '--version'], capture_output=True,
                                     text=True, errors='replace', timeout=10)
            versions[kind] = {'path': program, 'version': (version.stdout or version.stderr).splitlines()[0]}
        except (ValueError, OSError, subprocess.TimeoutExpired, IndexError) as error:
            versions[kind] = {'unavailable': str(error)}
    report = {'created_at': now(), 'git_head': git('rev-parse', 'HEAD', check=False).stdout.strip(),
              'source_digest': state_digest(before), 'sources': before,
              'conditions': {'runner': RUNNER_VERSION,
                             'runner_code': {name: digest((DEV / name).read_bytes())
                                             for name in ['common.py', 'checks.py', 'run_scene.gd']},
                             'checks': selected, 'seed': args.seed,
                             'fixture': fixture_digest(args.fixture), 'tools': versions}, 'checks': []}
    for item in selected:
        check_dir = run / item['id']
        check_dir.mkdir()
        environment = isolated_environment(check_dir)
        environment['BIGEGG_DEV_SEED'] = str(args.seed)
        try:
            if item['kind'] == 'godot':
                godot, user = prepare_godot(check_dir, environment, args.fixture, args.timeout)
                environment['BIGEGG_DEV_SCENE'] = 'res://scenes/main/' + item['scene'] + '.tscn'
                command = [godot, '--headless', '--path', str(PROJECT), '--script',
                           str(DEV / 'run_scene.gd'), *item['extra']]
            elif item['kind'] == 'python':
                command = [sys.executable, str(PROJECT / item['path'])]
            else:
                command = [executable(item['kind'])]
                if item['kind'] == 'php':
                    command.append('-n')
                command.append(str(PROJECT / item['path']))
            result = execute(command, environment, check_dir / 'output.log', args.timeout, item.get('success'))
        except (ValueError, OSError, subprocess.SubprocessError) as error:
            result = {'status': 'BLOCKED', 'seconds': 0, 'errors': [str(error)], 'log': None}
        result['id'] = item['id']
        # Stable error identities, even when an error mentions its private run path.
        result['errors'] = [error.replace(str(check_dir), '<RUN>').replace(check_dir.as_posix(), '<RUN>')
                            for error in result['errors']]
        report['checks'].append(result)
        print(f'{result["status"]:7} {item["id"]}: {result["seconds"]}s, {len(result["errors"])} error record(s)', flush=True)
        for error in result['errors'][:3]:
            print('  ' + error[:240], flush=True)
        write_json(run / 'report.json', report)
    after = source_state()
    report['inputs_changed_during_run'] = changed_files(before, after)
    statuses = [item['status'] for item in report['checks']]
    report['status'] = ('BLOCKED' if report['inputs_changed_during_run'] or 'BLOCKED' in statuses
                        else 'FAIL' if 'FAIL' in statuses else 'PASS')
    if args.baseline:
        report['comparison'] = compare_reports(read_json(args.baseline), report)
        comparison = report['comparison']
        if comparison['comparable']:
            print(f'Baseline: {len(comparison["new"])} new, {len(comparison["resolved"])} resolved, '
                  f'{comparison["unchanged_count"]} unchanged error record(s). Status remains {report["status"]}.')
        else:
            print('Baseline not comparable: ' + comparison['reason'])
    write_json(run / 'report.json', report)
    write_json(RUNS / 'latest.json', {'report': str((run / 'report.json').relative_to(PROJECT))})
    if report['inputs_changed_during_run']:
        print('Inputs changed during run: ' + ', '.join(report['inputs_changed_during_run'][:8]))
    print(f'{report["status"]}: {len(selected)} check(s). Report: {run.relative_to(PROJECT).as_posix()}/report.json')
    return {'PASS': 0, 'FAIL': 1, 'BLOCKED': 2}[report['status']]
