Files
query-orchestration/internal/document/text/pageText.go
T
Michael McGuinness 418a2761b3 Merged in feature/mergecell (pull request #142)
Merged Cell

* mergecellv1

* buildimg

* merging

* addfile

* justmergedcell
2025-05-13 12:33:34 +00:00

182 lines
5.5 KiB
Go

package documenttext
import (
"context"
"fmt"
"log/slog"
"strings"
documenttypes "queryorchestration/internal/document/types"
"github.com/aws/aws-sdk-go-v2/service/textract"
"github.com/aws/aws-sdk-go-v2/service/textract/types"
"github.com/google/uuid"
)
func (s *Service) getPageText(ctx context.Context, document documenttypes.File, index int) (string, error) {
res, err := s.GetBasePage(ctx, document, index)
if err != nil {
return "", err
}
var pageBlock *types.Block
for _, block := range res.Blocks {
if block.BlockType == types.BlockTypePage {
pageBlock = &block
break
}
}
if pageBlock == nil {
return "", fmt.Errorf("no page block found for index: %d", index)
}
blockMap := s.getBlockMap(res.Blocks)
lines, specialFeatures, err := s.getPageElements(ctx, document, index, *pageBlock, blockMap)
if err != nil {
return "", err
}
return s.buildPageText(ctx, lines, blockMap, index, specialFeatures)
}
func (s *Service) getPageElements(ctx context.Context, document documenttypes.File, index int, pageBlock types.Block, blockMap map[uuid.UUID]types.Block) ([]uuid.UUID, specialisedFeatures, error) {
directChildren := s.getDirectChildren(pageBlock, blockMap)
hasTable := false
for _, id := range directChildren {
if blockMap[id].BlockType == types.BlockTypeLayoutTable {
hasTable = true
}
}
features := []types.FeatureType{}
if hasTable {
features = append(features, types.FeatureTypeTables)
// features = append(features, types.FeatureTypeForms)
}
// Detecting Checkboxes -
// if has tables
// For a page with no tables, we look for 3 out of 4 of the following conditions
// has_exhibit→ 'exhibit' is on the page
// has_benefit→ one of ['benefit plan', 'benefit plan(s)', 'applicable benefit', 'benefits'] is on the page
// has_plans→ one of ['plan(s):', 'plan(s)/program(s):', 'plans:', 'plan/program:', 'plan(s)', 'plans'] is on the page
// has_services → one of ['services:', 'service:', 'service(s):', 'services', 'service(s)'] is on the page
if len(features) == 0 {
return directChildren, specialisedFeatures{}, nil
}
featuresOut, err := s.getSpecialisedFeatures(ctx, document, index, features)
if err != nil {
return nil, specialisedFeatures{}, err
}
return directChildren, featuresOut, nil
}
type buildParams struct {
numOfSignatures int
currentTable int
}
func (s *Service) buildPageText(ctx context.Context, lines []uuid.UUID, blockMap map[uuid.UUID]types.Block, index int, specialFeatures specialisedFeatures) (string, error) {
var builder strings.Builder
params := buildParams{
numOfSignatures: 0,
currentTable: 0,
}
for _, id := range lines {
err := s.addComponentText(ctx, blockMap[id], blockMap, specialFeatures, &params, &builder)
if err != nil {
return "", err
}
}
for params.currentTable < len(specialFeatures.Tables) {
id := specialFeatures.Tables[params.currentTable]
err := s.addComponentText(ctx, specialFeatures.BlockMap[id], blockMap, specialFeatures, &params, &builder)
if err != nil {
return "", err
}
}
if params.numOfSignatures > 0 {
builder.WriteString(fmt.Sprintf("\nThis page has %d signature.\n", params.numOfSignatures))
}
return builder.String(), nil
}
func (s *Service) addComponentText(ctx context.Context, block types.Block, blockMap map[uuid.UUID]types.Block, specialFeatures specialisedFeatures, params *buildParams, builder *strings.Builder) error {
switch block.BlockType {
case types.BlockTypeLayoutText, types.BlockTypeLayoutTitle, types.BlockTypeLayoutHeader, types.BlockTypeLayoutFooter, types.BlockTypeLayoutSectionHeader, types.BlockTypeLayoutPageNumber, types.BlockTypeLayoutList, types.BlockTypeLayoutFigure, types.BlockTypeLayoutKeyValue:
for _, id := range s.getChildIds(block) {
err := s.addComponentText(ctx, blockMap[id], blockMap, specialFeatures, params, builder)
if err != nil {
return err
}
}
case types.BlockTypeLine:
_, err := builder.WriteString(fmt.Sprintf("%s\n", *block.Text))
if err != nil {
slog.Error("unable to write string", "error", err)
return err
}
case types.BlockTypeWord:
_, err := builder.WriteString(fmt.Sprintf(" %s", *block.Text))
if err != nil {
slog.Error("unable to write string", "error", err)
return err
}
case types.BlockTypeSignature:
params.numOfSignatures++
case types.BlockTypeLayoutTable, types.BlockTypeTable:
if block.BlockType == types.BlockTypeLayoutTable && len(specialFeatures.Tables) <= params.currentTable {
for _, child := range s.getChildIds(block) {
err := s.addComponentText(ctx, blockMap[child], blockMap, specialFeatures, params, builder)
if err != nil {
return err
}
}
params.currentTable++
break
}
tableId := specialFeatures.Tables[params.currentTable]
params.currentTable++
tableBlock := specialFeatures.BlockMap[tableId]
err := s.addTable(ctx, tableBlock, specialFeatures.BlockMap, builder)
if err != nil {
return err
}
default:
slog.Debug("uncaught component", "type", block.BlockType)
}
return nil
}
func (s *Service) GetBasePage(ctx context.Context, doc documenttypes.File, index int) (textract.AnalyzeDocumentOutput, error) {
pageBytes, err := doc.GetPageAsPNG(ctx, index)
if err != nil {
return textract.AnalyzeDocumentOutput{}, err
}
res, err := s.cfg.GetTextractClient().AnalyzeDocument(ctx, &textract.AnalyzeDocumentInput{
Document: &types.Document{
Bytes: pageBytes,
},
FeatureTypes: []types.FeatureType{
types.FeatureTypeLayout,
types.FeatureTypeSignatures,
},
})
if err != nil {
return textract.AnalyzeDocumentOutput{}, err
}
return *res, err
}