# /user/bin/env python3
# -*- coding: utf-8 -*-
"""Sample class."""

import re
import json
import pprint as pp
from datetime import datetime
from packaging import version

import logging
import numpy as np
import pandas as pd

from lc18report_dev.info import Info
from lc18report_dev.config import Config
from lc18report_dev.knowledge_database import KnowledgeDataBase
from lc18report_dev.template import Template
from lc18report_dev.base import base
from lc18report_dev.exceptions import DuplicatedPcError
from lc18report_dev.exceptions import GeneFormatError
from lc18report_dev.exceptions import NoPcError
from lc18report_dev.exceptions import GenesNotSetError
from lc18report_dev.exceptions import PropMismatchError
from lc18report_dev.exceptions import NoKdbRecordFoundError

logger = logging.getLogger(__name__)


class Sample:
    """Class for sample data and related functions."""

    _ERR_CODE = {
        0: 'success',
        1: 'low coverage',
        2: 'wrong gene format',
        3: 'no kdb record',
        4: 'yuedong missing id',
        5: 'yuedong missing previous record',
        6: 'wrong kdb format',
        7: 'no pc found',
        8: 'duplicated pc found',
        99: 'unknow error',
    }
    genes = []
    _EN_MODE_TEXT = {
        '野生型': {True: 'wild type', False: '野生型'},
        '突变：': {True: 'Variant: ', False: '突变：'},
        '未检出': {True: 'No variant', False: '未检出'},
        '合格': {True: 'Pass', False: '合格'},
        '风险': {True: 'Risk', False: '风险'},
        'I级变异': {True: 'Tier I', False: 'I级变异'},
        'II级变异': {True: 'Tier II', False: 'II级变异'},
        'III级变异': {True: 'Tier III', False: 'III级变异'},
    }

    def __init__(self, lib_id):
        """Sample init function.

        Args:
            lib_id (str): sample library id
        """
        self.report_type: str = ''
        self.test_num: int = 0
        self.lib_id: str = lib_id
        self.id: str = lib_id
        self.resident_id: str = ''
        self.name: str = ''
        self.gender: str = ''
        self.age: int = 0
        self.clinical: str = ''
        self.pathol: str = ''
        self.stage: str = ''
        self.pre_test: str = ''
        self.history: str = ''
        self.med_history: str = ''
        self.type: str = ''
        self.smpl_dt: str = ''
        self.recv_dt: str = ''
        self.rprt_dt: str = ''
        self.q30: float = 0.0
        self.aver_depth: float = 0.0
        self.target_reads: float = 0.0
        self.cov_pos: float = 0.0
        self.cov_t1_t2: float = 0.0
        self.dna_total: float = 0.0
        self.lib_total: float = 0.0
        self.err_code: int = 0
        self.kdb_file: str = ''
        self.base_info_file: str = ''
        self.template_version: str = ''
        self.yuedong_template_version: str = ''
        self.plasma_volume: float = 0.0
        self.variants: list = []
        self.fusions: list = []
        self.sample_source: str = ''
        self.institution: str = ''
        self.en_mode: bool = False
        self.ct_ml: float = 0.0
        self.vm_ml: float = 0.0

    def __repr__(self) -> str:
        return f'<Sample: {self.lib_id}>'

    def __str__(self) -> str:
        return pp.pformat(self.get_sample_dict(), indent=4)

    def __lt__(self, other) -> bool:
        return self.rprt_dt < other.rprt_dt

    @classmethod
    def set_config(cls, config: Config) -> None:
        """Set report config to get two info file locations.

        Args:
            config (Config): report config object

        """
        if config.genes is None:
            logger.error('Genes is not set.')
            raise GenesNotSetError()
        cls.genes = config.genes

    def set_info(self, info: Info) -> None:
        if info is None:
            logger.error('Info is not set.')
            raise ValueError()
        self._set_sample_info(info)
        self._set_lab_info(info)

    def set_versions(
            self,
            template_version: str,
            yuedong_template_version: str,
            en_mode: bool = False) -> None:
        self.template_version = template_version
        self.yuedong_template_version = yuedong_template_version
        self.en_mode = en_mode

    def set_kdb_file(self, kdb_file: str) -> None:
        self.kdb_file = kdb_file
        logger.info(f"Sample.set_kdb_file: {self.kdb_file}")

    def set_base_file(self, base_info_file: str,record_xlsx:str) -> None:
        self.base_info_file = base_info_file
        self.record_xlsx=record_xlsx

    def set_error(self, err_code: int) -> None:
        self.err_code = err_code

    def set_kdb_format_error(self) -> None:
        self.set_error(4)

    def set_unknown_error(self) -> None:
        self.set_error(99)

    def _set_properties(
            self, q30, aver_depth, target_reads, cov_pos, cov_t1_t2):
        if self.q30 != 0.0 and self.q30 != q30:
            logger.error(f'({self.lib_id}): Mismathcing q30.')
            raise PropMismatchError()
        self.q30 = q30
        if self.aver_depth != 0.0 and self.aver_depth != aver_depth:
            logger.error(
                f'({self.lib_id}): Mismathcing aver_depth.')
            raise PropMismatchError()
        self.aver_depth = aver_depth
        if self.target_reads != 0.0 and self.target_reads != target_reads:
            logger.error(
                f'({self.lib_id}): Mismathcing target_reads.')
            raise PropMismatchError()
        self.target_reads = target_reads
        if self.cov_pos != 0.0 and self.cov_pos != cov_pos:
            logger.error(
                f'({self.lib_id}): Mismathcing cov_pos.')
            raise PropMismatchError()
        self.cov_pos = cov_pos
        self._set_coverage(cov_t1_t2)

    def _set_coverage(self, cov_t1_t2):
        if self.cov_t1_t2 != 0.0 and self.cov_t1_t2 != cov_t1_t2:
            logger.error(
                f'({self.lib_id}): Mismathcing cov_t1_t2.')
            raise PropMismatchError()
        self.cov_t1_t2 = cov_t1_t2

    def _set_report_type(self, product_name: str, product_note: str) -> None:
        '''根据产品名称判断报告类型，只支持lc、lc-yuedong'''
        if '肺癌' in product_name:
            self.report_type = 'lc'
            if '阅动' in product_name:
                self.report_type = 'lc-yuedong'

    def _set_sample_info(self, info: Info) -> None:
        id_head_values = [('文库编号', self.lib_id)]
        matched = re.match(r'^Lib(\d+)\D*$', self.lib_id)
        if matched:
            id_head_values.append(('样本编号', 'A' + matched.group(1)))
            id_head_values.append(('样本编号', 'BD' + matched.group(1)))
        sample_info = info.get_sample_info(id_head_values).to_dict()
        if not sample_info:
            return

        def _parse_date(raw_date: str) -> str:
            if not raw_date.strip():
                return ''
            datetime_formats = [r'%Y.%m.%d', r'%m/%d/%Y', r'%Y-%m-%d %H:%M:%S']
            for datetime_format in datetime_formats:
                try:
                    date_obj = datetime.strptime(raw_date, datetime_format)
                    return date_obj.strftime(r'%Y/%m/%d')
                except Exception:
                    pass
            logger.warning(f'({raw_date}): Wrong date format.')
            return ''

        def _parse_str(raw) -> str:
            if pd.isnull(raw):
                return ''
            if not (raw):
                return ''
            return str(raw)

        def _get_info(info: dict, *names) -> str:
            for name in names:
                if name in info:
                    return _parse_str(info[name])
            return ''

        try:
            product_name = _get_info(sample_info, '检测项目1')
            product_note = _get_info(sample_info, '检测项目备注')
        except KeyError:
            product_name = '肺癌18基因检测'
            product_note = 'NA'
        self.id = _get_info(sample_info, '样本编号')
        self._set_report_type(product_name, product_note)
        self.resident_id = _get_info(sample_info, '受检者身份证号')
        self.name = _get_info(sample_info, '样本名称')
        self.gender = _get_info(sample_info, '性别')
        self.age = sample_info['年龄']
        self.clinical = _get_info(sample_info, '临床诊断')
        self.pathol = _get_info(sample_info, '病理诊断', '病理分型')
        self.stage = _get_info(sample_info, '肿瘤分期')
        self.pre_test = _get_info(sample_info, '既往基因检测结果')
        #, '简短病史及家族史'
        self.history = _get_info(sample_info, '药物治疗史')
        self.type = _get_info(sample_info, '样本类型')
        self.smpl_dt = _parse_date(_get_info(sample_info, '采集时间'))
        self.recv_dt = _parse_date(_get_info(sample_info, '收样日期'))
        self.rprt_dt = datetime.today().strftime(r'%Y/%m/%d')

    def _set_lab_info(self, info: Info) -> None:
        id_head_values = [('文库编号', self.lib_id)]
        matched = re.match(r'^Lib(\d+)\D*$', self.lib_id)
        if matched:
            id_head_values.append(('样本编号', 'A' + matched.group(1)))
            id_head_values.append(('样本编号', 'BD' + matched.group(1)))
        lab_info = info.get_lab_info(id_head_values).to_dict()
        if not lab_info:
            return
        self.dna_total = float(lab_info['DNA 总量'])
        self.lib_total = float(lab_info['文库总量'])
        self.plasma_volume = float(lab_info[r'提取cfDNA血浆体积（mL）'])

    def _add_fusion(self, cds):
        # self.fusions.append({'cds': cds})
        genes = self.genes
        fusion = {
            'gene': '',
            'exon': '',
            'cds': cds.strip(),
        }

        try:
            matched = re.match(
                r'^(\w+)\s+exon\s+(\d+)\s+\-+\s+(\w+)\s+exon\s+(\d+)\(?.*;\w+$',
                fusion['cds']
            )
            gene1 = matched.group(1)
            exon1 = matched.group(2)
            gene2 = matched.group(3)
            exon2 = matched.group(4)
        except Exception:
            self.err_code = 2
            logger.error(
                f'({self.lib_id}): Fusion gene format error found for: '
                f'{fusion["cds"]}')
            raise GeneFormatError()

        if gene1.lower() in genes:
            fusion['gene'] = gene1
            fusion['exon'] = 'exon' + exon1
            fusion['gene2'] = gene2
            fusion['exon2'] = exon2
        if gene2.lower() in genes:
            fusion['gene'] = gene2
            fusion['exon'] = 'exon' + exon2
            fusion['gene2'] = gene1
            fusion['exon2'] = exon1

        fusion['cds'] = f'{gene1}-{gene2}({gene1[0]}{exon1}:{gene2[0]}{exon2})'

        self.fusions.append(fusion)

    def _add_variant(self, gene_transcript, exon, cds, aa, vaf):
        gene = ''
        try:
            matched = re.match(r'^(\w+)(\(NM\_\d+\))?$', gene_transcript)
            if matched:
                gene = matched.group(1)
        except Exception:
            self.err_code = 2
            logger.error(
                f'({self.lib_id}): Variant gene format error found for: '
                f'{gene_transcript}')
            raise GeneFormatError()
        variant = {
            'gene': gene,
            'gene_transcript': gene_transcript,
            'exon': exon,
            'cds': cds,
            'aa': aa,
            'vaf': vaf
        }
        self.variants.append(variant)

    def get_sample_dict(self) -> dict:
        """Get sample content as dict.

        Raises:
            LowCovError: sample coverage too low

        Returns:
            dict: sample content as dict
        """

        out_dict = {}
        for attr, value in self.__dict__.items():
            out_dict[attr] = value

        return out_dict

    def get_report_content(self, force_mode=False, en_mode=False) -> dict:
        """Get report content of sample.

        Raises:
            LowCovError: sample coverage too low

        Returns:
            dict: report content
        """
        brk = Template.BRK
        output_dict = {}
        output_dict['err_code'] = self.err_code
        output_dict['template_version'] = self.template_version

        # easy sample info
        easy_attr_names = [
            'id', 'lib_id', 'resident_id', 'name', 'gender', 'age', 'clinical',
            'pathol', 'stage', 'pre_test', 'history', 'type', 'sample_source',
            'institution'
        ]
        for attr_name in easy_attr_names:
            attr = getattr(self, attr_name)
            if not attr or attr is None or attr != attr:
                output_dict[attr_name] = ''
            else:
                output_dict[attr_name] = str(attr)

        diagnose = []
        if self.pathol:
            diagnose.append(self.pathol)
        if self.clinical:
            diagnose.append(self.clinical)
        output_dict['diagnose'] = '-'.join(diagnose)

        # dates
        output_dict['today'] = datetime.today().strftime(r'%Y/%m/%d')
        date_attr_names = ['smpl_dt', 'recv_dt', 'rprt_dt']
        for attr_name in date_attr_names:
            output_dict[attr_name] = getattr(self, attr_name)
        # gene table info
        vaf_index=[]
        # avg_vaf=0.0
        # max_vaf=0.0
        genes = self.genes
        for gene in genes:
            exon_list = []
            result_list = []
            vaf_list = []
            merged_list = []
            for variant in self.variants:
                if variant['gene'].lower() != gene:
                    continue
                exon = variant['exon'].strip()
                result = variant['cds'].strip()
                vaf = '> 80%'
                if variant['vaf'] <= 80:
                    vaf = '%.2f' % variant['vaf'] + '%'
                if result and variant['aa'].strip():
                    exon += brk
                    result += brk + variant['aa'].strip()
                    vaf += brk
                else:
                    result += variant['aa'].strip()
                exon_list.append(exon)
                result_list.append(result)
                vaf_list.append(vaf)
                vaf_index.append(variant['vaf'])
                merged_list.append(brk.join([exon, result, vaf]))
            for fusion in self.fusions:
                if fusion['gene'].lower() != gene:
                    continue
                exon = fusion['exon']
                result = fusion['cds']
                vaf = 'N/A'
                exon_list.append(exon)
                result_list.append(result)
                vaf_list.append(vaf)
                merged_list.append(brk.join([exon, result]))

            exon = brk.join(exon_list)
            exon = '#_red_#' + exon if exon else 'N/A'
            output_dict[f'{gene}_exon'] = exon
            result = brk.join(result_list)
            result = '#_red_#' + \
                result if result else self._EN_MODE_TEXT['野生型'][en_mode]
            output_dict[f'{gene}_result'] = result
            vaf = brk.join(vaf_list)
            vaf = '#_red_#' + vaf if vaf else 'N/A'
            output_dict[f'{gene}_vaf'] = vaf
            merged = self._EN_MODE_TEXT['突变：'][en_mode] + \
                brk + (brk * 2).join(merged_list)
            merged = '#_red_#' + \
                merged if merged else self._EN_MODE_TEXT['未检出'][en_mode]
            output_dict[f'{gene}_merged'] = merged

        # qc info
        output_dict['dna_total'] = '%.0f' % self.dna_total + 'ng'
        output_dict['lib_total'] = '%.0f' % self.lib_total + 'ng'
        output_dict['aver_depth'] = '%.0f' % self.aver_depth + 'X'
        output_dict['cov_pos'] = '%.0f' % self.cov_pos + '%'
        output_dict['cov_t1_t2'] = '%.0f' % self.cov_t1_t2 + '%'
        output_dict['q30'] = '%.0f' % self.q30 + '%'
        output_dict['hGe_ml'] = 'N/A' if pd.isnull(
            self.hGe) else '%.0f' % self.hGe
        output_dict['ct_ml'] = '0' if pd.isnull(
            self.ct_ml) or base.ctdna_ml(self.ct_ml)==0 else base.ctdna_ml(self.ct_ml)
        output_dict['vm_ml'] = '0' if pd.isnull(
            self.vm_ml) or base.ctdna_ml(self.vm_ml)==0 else base.ctdna_ml(self.vm_ml)

        qc_pass = True
        if self.dna_total < 20:
            output_dict['dna_total'] = '#_red_#' + output_dict['dna_total']
            qc_pass = False
        if self.lib_total < 50:
            output_dict['lib_total'] = '#_red_#' + output_dict['lib_total']
            qc_pass = False
        if self.aver_depth < 1000:
            output_dict['aver_depth'] = '#_red_#' + output_dict['aver_depth']
            qc_pass = False
        if self.cov_pos < 100:
            output_dict['cov_pos'] = '#_red_#' + output_dict['cov_pos']
            qc_pass = False
        if self.cov_t1_t2 < 100:
            output_dict['cov_t1_t2'] = '#_red_#' + output_dict['cov_t1_t2']
            qc_pass = False
        if self.q30 < 80:
            output_dict['q30'] = '#_red_#' + output_dict['q30']
            qc_pass = False
        if pd.isnull(self.hGe):
            output_dict['hGe_ml'] = '#_red_#' + output_dict['hGe_ml']

        output_dict['qc_result'] = self._EN_MODE_TEXT['合格'][en_mode] if qc_pass else '#_red_#' + \
            self._EN_MODE_TEXT['风险'][en_mode]

        # result_tables
        kdb = KnowledgeDataBase(kdb_file=self.kdb_file)
        matched_records = []
        unmatched_records = []
        level_1_table = []
        level_2_table = []
        level_3_table = []
        level_1_multi_table = []
        level_2_multi_table = []
        level_3_multi_table = []
        pos_genes = set()
        meds = set()

        for variant in self.variants:
            record = kdb.get_record(
                variant['gene'], variant['aa'], variant['cds'])
            if record is None:
                if not force_mode:
                    self.err_code = 3
                    raise NoKdbRecordFoundError()
                unmatched_records.append(variant)
            else:
                record=record.copy()
                record['vaf'] = variant['vaf']
                record['var'] = [
                    variant['gene'],
                    variant['exon'],
                    variant['cds'],
                    variant['aa']
                ]
                matched_records.append(record)

        for fusion in self.fusions:
            record = kdb.get_fusion_record(fusion['gene'], fusion['gene2'])
            if record is None:
                if not force_mode:
                    self.err_code = 3
                    raise NoKdbRecordFoundError()
                unmatched_records.append({
                    'gene': fusion['gene'],
                    'exon': fusion['exon'],
                    'variant': fusion['cds'],
                    'vaf': fusion['vaf'],
                })
            else:
                record['vaf'] = 'N/A'
                record['var'] = [
                    fusion['gene'],
                    fusion['exon'],
                    fusion['cds'],
                ]
                matched_records.append(record)

        for record in matched_records:
            var = record['var']
            try:
                vaf = '> 80%'
                if record['vaf'] <= 80:
                    vaf = '%.2f' % record['vaf'] + '%'
            except TypeError:
                vaf = record['vaf']
            level = record['等级'].strip()
            sensitive = record['提示敏感']
            resistance = record['提示耐药']
            description = record['变异解析'].strip()
            if level == 'I':
                level_1_table.append(
                    [var, vaf, sensitive, resistance, description])
                level_1_multi_table.append(
                    {
                        'variant': brk.join(var),
                        'vaf': vaf,
                        'sensitive': brk.join(sensitive),
                        'resistance': brk.join(resistance),
                        'description': description,
                    }
                )
                # add level 1 meds.
                meds.update(record['提示敏感'])
                meds.update(record['提示耐药'])
            elif level == 'II':
                level_2_table.append(
                    [var, vaf, sensitive, resistance, description])
                level_2_multi_table.append(
                    {
                        'variant': brk.join(var),
                        'vaf': vaf,
                        'sensitive': brk.join(sensitive),
                        'resistance': brk.join(resistance),
                        'description': description,
                    }
                )
            elif level == 'III':
                gene = record['var'][0]
                exon = record['var'][1]
                level_3_table.append(
                    [var, vaf, sensitive, resistance, description])
                level_3_multi_table.append(
                    {
                        'gene': gene,
                        'exon': exon,
                        'variant': brk.join([record['var'][2],record['var'][3]]),
                        'vaf': vaf
                    }
                )
            else:
                raise Exception()

            # gene tables
            pos_genes.add(record['基因'])


        # clean up meds.
        if '无' in meds:
            meds.remove('无')
        if '/' in meds:
            meds.remove('/')
        # loop through copy of meds and remove non-A level meds.
        for med in list(meds):
            med_clean = med.replace(
                '（', '(').replace('）', ')').replace('，', ',')
            med_level_matched = re.match(
                r'^[^\(]+\((\s*.+\s*,)?\s*[Aa]\d?\s*\)\s*$', med_clean)
            if med_level_matched is None:
                meds.remove(med)

        output_dict['tables'] = {
            self._EN_MODE_TEXT['I级变异'][en_mode]: level_1_table,
            self._EN_MODE_TEXT['II级变异'][en_mode]: level_2_table,
            self._EN_MODE_TEXT['III级变异'][en_mode]: level_3_table
        }

        for record in unmatched_records:
            gene = record['gene']
            exon = record['exon']
            var = record['cds'] + brk + record['aa']
            try:
                vaf = '> 80%'
                if record['vaf'] <= 80:
                    vaf = '%.2f' % record['vaf'] + '%'
            except TypeError:
                vaf = record['vaf']
            level_3_multi_table.append({
                'gene': gene,
                'exon': exon,
                'variant': var,
                'vaf': vaf,
            })

        output_dict['multi_tables'] = {
            self._EN_MODE_TEXT['I级变异'][en_mode]: level_1_multi_table,
            self._EN_MODE_TEXT['II级变异'][en_mode]: level_2_multi_table,
            self._EN_MODE_TEXT['III级变异'][en_mode]: level_3_multi_table
        }

        gene_tables = {gene: kdb.get_gene(gene) for gene in pos_genes}
        output_dict['gene_tables'] = gene_tables

        for record in matched_records:
            for med in list(meds):
                for resistance in record['提示耐药']:
                    if resistance == med:
                        meds.remove(med)
         # remove duplicates.
        meds = {med.replace('（', '(').split('(')[0] for med in meds}
        med_tables = {med: kdb.get_med(med) for med in meds}
        output_dict['med_tables'] = med_tables

        return output_dict

    def get_report_json(self, compact=True, force_mode=False, en_mode=False) -> str:
        report_content = self.get_report_content(
            force_mode=force_mode, en_mode=en_mode)
        report_json: str
        if compact:
            report_json = json.dumps(report_content, separators=(',', ':'))
        else:
            report_json = json.dumps(report_content)
        return report_json

    def to_log(self):
        """Get log output of sample.

        Returns:
            str: log output of sample
        """
        result_text = self._ERR_CODE[self.err_code]
        return f'{self.lib_id}\t{self.cov_t1_t2}\t{result_text}'

    @staticmethod
    def parse_output_xlsx(self,pipeline_output_xlsx):
        """Parse xlsx file and get all sample data into sample instances.

        Args:
            pipeline_output_xlsx (str): xlsx file path

        Returns:
            list: list of sample instance
        """
        sample_dict = {}
        pc_coverage = np.nan
        pipeline_output_df = pd.read_excel(pipeline_output_xlsx, dtype=str)
        pipeline_output_df = pipeline_output_df.rename(
            columns={
                'Q30_trim(%)': 'q30',
                'coverageT1plusT2(%)': 'coverage',
                'CDS': 'cds',
                'AA': 'aa',
                'VAF': 'vaf',
            })

        try:
            cov_pos = float(
                pipeline_output_df.query('sample.str.startswith("PC")').iloc[0]["coverage"]
            )
        except Exception:
            cov_pos = 0.0

        pc_sample_df = pipeline_output_df.query('sample.str.startswith("PC")')
        unique_pc_counts = pc_sample_df['sample'].unique()
        if unique_pc_counts.size >= 2:
            raise DuplicatedPcError()
        if unique_pc_counts.size == 0:
            raise NoPcError()
        pc_coverage = float(pc_sample_df['coverage'].iloc[0])

        # MARK 普通样本编号，必须使用Lib开头
        sample_output_df = pipeline_output_df.query('sample.str.startswith("Lib")')
        for r in sample_output_df.itertuples():
            lib_id = r.sample.strip() if r.sample == r.sample else ''
            gene_transcript = r.gene.strip() if r.gene == r.gene else ''
            exon = r.exon.strip() if r.exon == r.exon else ''
            cds = r.cds.strip() if r.cds == r.cds else ''
            aa = r.aa.strip() if r.aa == r.aa else ''
            if '?' in aa:
                aa=''
            vaf = float(r.vaf) * 100
            q30 = float(r.q30)
            aver_depth = float(r.aver_seq_depth)
            target_reads = float(r.Target_reads)
            cov_t1_t2 = pc_coverage
            if lib_id not in sample_dict.keys():
                sample_dict[lib_id] = Sample(lib_id)

            sample_dict[lib_id]._set_properties(
                q30,
                aver_depth,
                target_reads,
                cov_pos,
                cov_t1_t2,
            )


            if not cds or cds is None or cds != cds:
                continue

            if cds and not gene_transcript:
                sample_dict[lib_id]._add_fusion(cds)
                continue
            b=False
            if self.var_gather :
                for lib in self.var_gather:
                    if lib['sample_id'] is lib_id:
                        if isinstance(lib['vaf'],list):
                            lib['vaf'].append(vaf)
                        else:
                            lib['vaf']=[lib['vaf'],vaf]
                        b=True
                        break
            if not b :
                sample={
                    'sample_id' : lib_id,
                    'vaf':vaf
                }
                self.var_gather.append(sample)
            sample_dict[lib_id]._add_variant(
                gene_transcript,
                exon,
                cds,
                aa,
                vaf,
            )

        return list(sample_dict.values())

    @staticmethod
    def report_json_to_content(report_json: str) -> dict:
        return json.loads(report_json)

    @property
    def _is_pre_multi_table_template(self) -> bool:
        current_version = version.parse(self.template_version)
        pre_version = version.parse('1.0.0')
        return current_version < pre_version

    @property
    def hGe(self) -> float:
        try:
            return (self.dna_total * 1000 / 3.3) / self.plasma_volume
        except ZeroDivisionError:
            return np.NaN
        except TypeError:
            return np.NaN

    @staticmethod
    def from_dict(sample_dict: dict, sample_type: str = 'lc-yuedong'):
        """Create sample instance from dict.

        Args:
            sample_dict (dict): dict version of sample content
        """

        sample = Sample(sample_dict['lib_id'])
        for attr, value in sample_dict.items():
            setattr(sample, attr, value)
        sample.report_type = sample_type

        return sample

    def has_residential_id(self) -> bool:
        if self.resident_id is None:
            return False
        if not self.resident_id.strip():
            return False
        return True

    @property
    def report_type_zh(self) -> str:
        type2zh = {
            'lc': '肺癌18基因报告',
            'lc-yuedong': '肺癌阅动动态监测报告',
        }
        return type2zh[self.report_type]

    def is_lc(self):
        return self.report_type.startswith('lc')

    def _reset_rprt_dt(self):
        self.rprt_dt = datetime.today().strftime(r'%Y/%m/%d')
